2 axis-group-interface.cc -- implement Axis_group_interface
4 source file of the GNU LilyPond music typesetter
6 (c) 2000--2007 Han-Wen Nienhuys <hanwen@xs4all.nl>
9 #include "axis-group-interface.hh"
11 #include "align-interface.hh"
12 #include "directional-element-interface.hh"
13 #include "pointer-group-interface.hh"
14 #include "grob-array.hh"
15 #include "hara-kiri-group-spanner.hh"
16 #include "international.hh"
18 #include "paper-column.hh"
19 #include "paper-score.hh"
20 #include "separation-item.hh"
26 Axis_group_interface::add_element (Grob *me, Grob *e)
28 SCM axes = me->get_property ("axes");
29 if (!scm_is_pair (axes))
30 programming_error ("axes should be nonempty");
32 for (SCM ax = axes; scm_is_pair (ax); ax = scm_cdr (ax))
34 Axis a = (Axis) scm_to_int (scm_car (ax));
36 if (!e->get_parent (a))
37 e->set_parent (me, a);
39 e->set_object ((a == X_AXIS)
40 ? ly_symbol2scm ("axis-group-parent-X")
41 : ly_symbol2scm ("axis-group-parent-Y"),
45 /* must be ordered, because Align_interface also uses
46 Axis_group_interface */
47 Pointer_group_interface::add_grob (me, ly_symbol2scm ("elements"), e);
51 Axis_group_interface::has_axis (Grob *me, Axis a)
53 SCM axes = me->get_property ("axes");
55 return (SCM_BOOL_F != scm_memq (scm_from_int (a), axes));
59 Axis_group_interface::relative_group_extent (vector<Grob*> const &elts,
63 for (vsize i = 0; i < elts.size (); i++)
66 if (!to_boolean (se->get_property ("cross-staff")))
68 Interval dims = se->extent (common, a);
69 if (!dims.is_empty ())
78 FIXME: pure extent handling has a lot of ad-hoc caching.
79 This should be done with grob property callbacks.
85 Axis_group_interface::cached_pure_height (Grob *me, int start, int end)
87 Paper_score *ps = get_root_system (me)->paper_score ();
88 vector<vsize> breaks = ps->get_break_indices ();
89 vector<Grob*> cols = ps->get_columns ();
91 SCM extents = me->get_property ("adjacent-pure-heights");
93 if (!scm_is_vector (extents))
94 return Interval (0, 0);
97 for (vsize i = 0; i + 1 < breaks.size (); i++)
99 int r = Paper_column::get_rank (cols[breaks[i]]);
104 ext.unite (ly_scm2interval (scm_c_vector_ref (extents, i)));
110 MAKE_SCHEME_CALLBACK (Axis_group_interface, adjacent_pure_heights, 1)
112 Axis_group_interface::adjacent_pure_heights (SCM smob)
114 Grob *me = unsmob_grob (smob);
116 Grob *common = calc_pure_elts_and_common (me);
117 extract_grob_set (me, "pure-relevant-items", items);
118 extract_grob_set (me, "pure-relevant-spanners", spanners);
120 Paper_score *ps = get_root_system (me)->paper_score ();
121 vector<vsize> breaks = ps->get_break_indices ();
122 vector<Grob*> cols = ps->get_columns ();
124 SCM ret = scm_c_make_vector (breaks.size () - 1, SCM_EOL);
126 for (vsize i = 0; i + 1 < breaks.size (); i++)
128 int start = Paper_column::get_rank (cols[breaks[i]]);
129 int end = Paper_column::get_rank (cols[breaks[i+1]]);
132 for (vsize j = it_index; j < items.size (); j++)
134 Item *it = dynamic_cast<Item*> (items[j]);
135 int rank = it->get_column ()->get_rank ();
137 if (rank <= end && it->pure_is_visible (start, end)
138 && !to_boolean (it->get_property ("cross-staff")))
140 Interval dims = items[j]->pure_height (common, start, end);
141 if (!dims.is_empty ())
151 for (vsize j = 0; j < spanners.size (); j++)
153 Interval_t<int> rank_span = spanners[j]->spanned_rank_interval ();
154 if (rank_span[LEFT] <= end && rank_span[RIGHT] >= start
155 && !to_boolean (spanners[j]->get_property ("cross-staff")))
157 Interval dims = spanners[j]->pure_height (common, start, end);
158 if (!dims.is_empty ())
163 scm_vector_set_x (ret, scm_from_int (i), ly_interval2scm (iv));
169 Axis_group_interface::relative_pure_height (Grob *me, int start, int end)
171 /* It saves a _lot_ of time if we assume a VerticalAxisGroup is additive
172 (ie. height (i, k) = max (height (i, j) height (j, k)) for all i <= j <= k).
173 Unfortunately, it isn't always true, particularly if there is a
174 VerticalAlignment somewhere in the descendants.
176 Apart from PianoStaff, which has a fixed VerticalAlignment so it doesn't
177 count, the only VerticalAlignment comes from Score. This makes it
178 reasonably safe to assume that if our parent is a VerticalAlignment,
179 we can assume additivity and cache things nicely. */
180 Grob *p = me->get_parent (Y_AXIS);
181 if (p && Align_interface::has_interface (p))
182 return Axis_group_interface::cached_pure_height (me, start, end);
184 Grob *common = calc_pure_elts_and_common (me);
185 extract_grob_set (me, "pure-relevant-items", items);
186 extract_grob_set (me, "pure-relevant-spanners", spanners);
190 for (vsize i = 0; i < items.size (); i++)
192 Item *it = dynamic_cast<Item*> (items[i]);
193 int rank = it->get_column ()->get_rank ();
197 else if (rank >= start && it->pure_is_visible (start, end)
198 && !to_boolean (it->get_property ("cross-staff")))
200 Interval dims = it->pure_height (common, start, end);
201 if (!dims.is_empty ())
206 for (vsize i = 0; i < spanners.size (); i++)
208 Interval_t<int> rank_span = spanners[i]->spanned_rank_interval ();
209 if (rank_span[LEFT] <= end && rank_span[RIGHT] >= start
210 && !to_boolean (spanners[i]->get_property ("cross-staff")))
212 Interval dims = spanners[i]->pure_height (common, start, end);
213 if (!dims.is_empty ())
220 MAKE_SCHEME_CALLBACK (Axis_group_interface, width, 1);
222 Axis_group_interface::width (SCM smob)
224 Grob *me = unsmob_grob (smob);
225 return generic_group_extent (me, X_AXIS);
228 MAKE_SCHEME_CALLBACK (Axis_group_interface, height, 1);
230 Axis_group_interface::height (SCM smob)
232 Grob *me = unsmob_grob (smob);
233 return generic_group_extent (me, Y_AXIS);
236 MAKE_SCHEME_CALLBACK (Axis_group_interface, pure_height, 3);
238 Axis_group_interface::pure_height (SCM smob, SCM start_scm, SCM end_scm)
240 int start = robust_scm2int (start_scm, 0);
241 int end = robust_scm2int (end_scm, INT_MAX);
242 Grob *me = unsmob_grob (smob);
244 /* Maybe we are in the second pass of a two-pass spacing run. In that
245 case, the Y-extent of a system is already given to us */
246 System *system = dynamic_cast<System*> (me);
249 SCM line_break_details = system->column (start)->get_property ("line-break-system-details");
250 SCM system_y_extent = scm_assq (ly_symbol2scm ("system-Y-extent"), line_break_details);
251 if (scm_is_pair (system_y_extent))
252 return scm_cdr (system_y_extent);
255 return ly_interval2scm (pure_group_height (me, start, end));
258 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_skylines, 1);
260 Axis_group_interface::calc_skylines (SCM smob)
262 Grob *me = unsmob_grob (smob);
263 extract_grob_set (me, "elements", elts);
264 Skyline_pair skylines = skyline_spacing (me, elts);
266 return skylines.smobbed_copy ();
269 /* whereas calc_skylines calculates skylines for axis-groups with a lot of
270 visible children, combine_skylines is designed for axis-groups whose only
271 children are other axis-groups (ie. VerticalAlignment). Rather than
272 calculating all the skylines from scratch, we just merge the skylines
275 MAKE_SCHEME_CALLBACK (Axis_group_interface, combine_skylines, 1);
277 Axis_group_interface::combine_skylines (SCM smob)
279 Grob *me = unsmob_grob (smob);
280 extract_grob_set (me, "elements", elements);
281 Grob *y_common = common_refpoint_of_array (elements, me, Y_AXIS);
282 Grob *x_common = common_refpoint_of_array (elements, me, X_AXIS);
285 programming_error ("combining skylines that don't belong to me");
288 for (vsize i = 0; i < elements.size (); i++)
290 SCM skyline_scm = elements[i]->get_property ("vertical-skylines");
291 if (Skyline_pair::unsmob (skyline_scm))
293 Real offset = elements[i]->relative_coordinate (y_common, Y_AXIS);
294 Skyline_pair other = *Skyline_pair::unsmob (skyline_scm);
295 other.raise (offset);
296 other.shift (elements[i]->relative_coordinate (x_common, X_AXIS));
300 return ret.smobbed_copy ();
304 Axis_group_interface::generic_group_extent (Grob *me, Axis a)
306 /* trigger the callback to do skyline-spacing on the children */
308 (void) me->get_property ("vertical-skylines");
310 extract_grob_set (me, "elements", elts);
311 Grob *common = common_refpoint_of_array (elts, me, a);
313 Real my_coord = me->relative_coordinate (common, a);
314 Interval r (relative_group_extent (elts, common, a));
316 return ly_interval2scm (r - my_coord);
319 /* This is like generic_group_extent, but it only counts the grobs that
320 are children of some other axis-group. This is uncached; if it becomes
321 commonly used, it may be necessary to cache it somehow. */
323 Axis_group_interface::staff_extent (Grob *me, Grob *refp, Axis ext_a, Grob *staff, Axis parent_a)
325 extract_grob_set (me, "elements", elts);
326 vector<Grob*> new_elts;
328 for (vsize i = 0; i < elts.size (); i++)
329 if (elts[i]->common_refpoint (staff, parent_a) == staff)
330 new_elts.push_back (elts[i]);
332 return relative_group_extent (new_elts, refp, ext_a);
337 Axis_group_interface::calc_pure_elts_and_common (Grob *me)
339 if (Grob *c = unsmob_grob (me->get_object ("pure-Y-common")))
342 extract_grob_set (me, "elements", elts);
344 vector<Grob*> relevant_items;
345 vector<Grob*> relevant_spanners;
346 SCM pure_relevant_p = ly_lily_module_constant ("pure-relevant?");
348 for (vsize i = 0; i < elts.size (); i++)
350 if (to_boolean (scm_apply_1 (pure_relevant_p, elts[i]->self_scm (), SCM_EOL)))
352 if (dynamic_cast<Item*> (elts[i]))
353 relevant_items.push_back (elts[i]);
354 else if (dynamic_cast<Spanner*> (elts[i]))
355 relevant_spanners.push_back (elts[i]);
359 Item *it = dynamic_cast<Item*> (elts[i]);
364 Item *piece = it->find_prebroken_piece (d);
365 if (piece && to_boolean (scm_apply_1 (pure_relevant_p, piece->self_scm (), SCM_EOL)))
366 relevant_items.push_back (piece);
368 while (flip (&d) != LEFT);
370 vector_sort (relevant_items, Item::less);
372 Grob *common = common_refpoint_of_array (relevant_items, me, Y_AXIS);
373 common = common_refpoint_of_array (relevant_spanners, common, Y_AXIS);
375 me->set_object ("pure-Y-common", common->self_scm ());
377 SCM items_scm = Grob_array::make_array ();
378 SCM spanners_scm = Grob_array::make_array ();
380 unsmob_grob_array (items_scm)->set_array (relevant_items);
381 unsmob_grob_array (spanners_scm)->set_array (relevant_spanners);
382 me->set_object ("pure-relevant-items", items_scm);
383 me->set_object ("pure-relevant-spanners", spanners_scm);
389 Axis_group_interface::calc_common (Grob *me, Axis axis)
391 extract_grob_set (me, "elements", elts);
392 Grob *common = common_refpoint_of_array (elts, me, axis);
395 me->programming_error ("No common parent found in calc_common axis.");
399 return common->self_scm ();
403 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_x_common, 1);
405 Axis_group_interface::calc_x_common (SCM grob)
407 return calc_common (unsmob_grob (grob), X_AXIS);
410 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_y_common, 1);
412 Axis_group_interface::calc_y_common (SCM grob)
414 return calc_common (unsmob_grob (grob), Y_AXIS);
418 Axis_group_interface::pure_group_height (Grob *me, int start, int end)
420 Grob *common = calc_pure_elts_and_common (me);
422 Real my_coord = me->relative_coordinate (common, Y_AXIS);
423 Interval r (relative_pure_height (me, start, end));
429 Axis_group_interface::get_children (Grob *me, vector<Grob*> *found)
431 found->push_back (me);
433 if (!has_interface (me))
436 extract_grob_set (me, "elements", elements);
437 for (vsize i = 0; i < elements.size (); i++)
439 Grob *e = elements[i];
440 Axis_group_interface::get_children (e, found);
445 staff_priority_less (Grob * const &g1, Grob * const &g2)
447 Real priority_1 = robust_scm2double (g1->get_property ("outside-staff-priority"), -infinity_f);
448 Real priority_2 = robust_scm2double (g2->get_property ("outside-staff-priority"), -infinity_f);
450 if (priority_1 < priority_2)
452 else if (priority_1 > priority_2)
455 /* if neither grob has an outside-staff priority, the ordering will have no
456 effect -- we just need to choose a consistent ordering. We do this to
457 avoid the side-effect of calculating extents. */
458 if (isinf (priority_1))
461 /* if there is no preference in staff priority, choose the left-most one */
462 Grob *common = g1->common_refpoint (g2, X_AXIS);
463 Real start_1 = g1->extent (common, X_AXIS)[LEFT];
464 Real start_2 = g2->extent (common, X_AXIS)[LEFT];
465 return start_1 < start_2;
469 add_boxes (Grob *me, Grob *x_common, Grob *y_common, vector<Box> *const boxes, Skyline_pair *skylines)
471 /* if a child has skylines, use them instead of the extent box */
472 if (Skyline_pair *pair = Skyline_pair::unsmob (me->get_property ("vertical-skylines")))
474 Skyline_pair s = *pair;
475 s.shift (me->relative_coordinate (x_common, X_AXIS));
476 s.raise (me->relative_coordinate (y_common, Y_AXIS));
479 else if (Grob_array *elements = unsmob_grob_array (me->get_object ("elements")))
481 for (vsize i = 0; i < elements->size (); i++)
482 add_boxes (elements->grob (i), x_common, y_common, boxes, skylines);
484 else if (!scm_is_number (me->get_property ("outside-staff-priority"))
485 && !to_boolean (me->get_property ("cross-staff")))
487 boxes->push_back (Box (me->extent (x_common, X_AXIS),
488 me->extent (y_common, Y_AXIS)));
492 /* We want to avoid situations like this:
500 The point is that "still more text" should be positioned under
501 "more text". In order to achieve this, we place the grobs in several
502 passes. We keep track of the right-most horizontal position that has been
503 affected by the current pass so far (actually we keep track of 2
504 positions, one for above the staff, one for below).
506 In each pass, we loop through the unplaced grobs from left to right.
507 If the grob doesn't overlap the right-most affected position, we place it
508 (and then update the right-most affected position to point to the right
509 edge of the just-placed grob). Otherwise, we skip it until the next pass.
512 add_grobs_of_one_priority (Skyline_pair *const skylines,
513 vector<Grob*> elements,
518 Drul_array<Real> last_affected_position;
521 while (!elements.empty ())
523 last_affected_position[UP] = -infinity_f;
524 last_affected_position[DOWN] = -infinity_f;
526 for (vsize i = elements.size (); i--;)
528 Direction dir = get_grob_direction (elements[i]);
531 warning (_ ("an outside-staff object should have a direction, defaulting to up"));
535 Box b (elements[i]->extent (x_common, X_AXIS),
536 elements[i]->extent (y_common, Y_AXIS));
537 SCM horizon_padding_scm = elements[i]->get_property ("outside-staff-horizontal-padding");
538 Real horizon_padding = robust_scm2double (horizon_padding_scm, 0.0);
540 if (b[X_AXIS][LEFT] - 2*horizon_padding < last_affected_position[dir])
543 if (!b[X_AXIS].is_empty () && !b[Y_AXIS].is_empty ())
547 Skyline other = Skyline (boxes, horizon_padding, X_AXIS, -dir);
548 Real padding = robust_scm2double (elements[i]->get_property ("outside-staff-padding"), 0.5);
549 Real dist = (*skylines)[dir].distance (other) + padding;
553 b.translate (Offset (0, dir*dist));
554 elements[i]->translate_axis (dir*dist, Y_AXIS);
556 (*skylines)[dir].insert (b, 0, X_AXIS);
557 elements[i]->set_property ("outside-staff-priority", SCM_BOOL_F);
558 last_affected_position[dir] = b[X_AXIS][RIGHT];
562 Ugh: quadratic. --hwn
564 elements.erase (elements.begin () + i);
570 Axis_group_interface::skyline_spacing (Grob *me, vector<Grob*> elements)
572 vector_sort (elements, staff_priority_less);
573 Grob *x_common = common_refpoint_of_array (elements, me, X_AXIS);
574 Grob *y_common = common_refpoint_of_array (elements, me, Y_AXIS);
576 assert (y_common == me);
581 Skyline_pair skylines;
582 for (i = 0; i < elements.size ()
583 && !scm_is_number (elements[i]->get_property ("outside-staff-priority")); i++)
584 add_boxes (elements[i], x_common, y_common, &boxes, &skylines);
586 SCM padding_scm = me->get_property ("skyline-horizontal-padding");
587 Real padding = robust_scm2double (padding_scm, 0.1);
588 skylines.merge (Skyline_pair (boxes, padding, X_AXIS));
589 for (; i < elements.size (); i++)
591 SCM priority = elements[i]->get_property ("outside-staff-priority");
592 vector<Grob*> current_elts;
593 current_elts.push_back (elements[i]);
594 while (i + 1 < elements.size ()
595 && scm_eq_p (elements[i+1]->get_property ("outside-staff-priority"), priority))
596 current_elts.push_back (elements[++i]);
598 add_grobs_of_one_priority (&skylines, current_elts, x_common, y_common);
600 skylines.shift (-me->relative_coordinate (x_common, X_AXIS));
604 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_max_stretch, 1)
606 Axis_group_interface::calc_max_stretch (SCM smob)
608 Grob *me = unsmob_grob (smob);
610 extract_grob_set (me, "elements", elts);
612 for (vsize i = 0; i < elts.size (); i++)
613 if (Axis_group_interface::has_interface (elts[i]))
614 ret += robust_scm2double (elts[i]->get_property ("max-stretch"), 0.0);
616 return scm_from_double (ret);
619 extern bool debug_skylines;
620 MAKE_SCHEME_CALLBACK (Axis_group_interface, print, 1)
622 Axis_group_interface::print (SCM smob)
627 Grob *me = unsmob_grob (smob);
629 if (Skyline_pair *s = Skyline_pair::unsmob (me->get_property ("vertical-skylines")))
631 ret.add_stencil (Lookup::points_to_line_stencil (0.1, (*s)[UP].to_points (X_AXIS)).in_color (255, 0, 255));
632 ret.add_stencil (Lookup::points_to_line_stencil (0.1, (*s)[DOWN].to_points (X_AXIS)).in_color (0, 255, 255));
634 return ret.smobbed_copy ();
637 ADD_INTERFACE (Axis_group_interface,
638 "An object that groups other layout objects.",
643 "adjacent-pure-heights "
646 "keep-fixed-while-stretching "
650 "pure-relevant-items "
651 "pure-relevant-spanners "