2 axis-group-interface.cc -- implement Axis_group_interface
4 source file of the GNU LilyPond music typesetter
6 (c) 2000--2009 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"
21 #include "skyline-pair.hh"
27 Axis_group_interface::add_element (Grob *me, Grob *e)
29 SCM axes = me->get_property ("axes");
30 if (!scm_is_pair (axes))
31 programming_error ("axes should be nonempty");
33 for (SCM ax = axes; scm_is_pair (ax); ax = scm_cdr (ax))
35 Axis a = (Axis) scm_to_int (scm_car (ax));
37 if (!e->get_parent (a))
38 e->set_parent (me, a);
40 e->set_object ((a == X_AXIS)
41 ? ly_symbol2scm ("axis-group-parent-X")
42 : ly_symbol2scm ("axis-group-parent-Y"),
46 /* must be ordered, because Align_interface also uses
47 Axis_group_interface */
48 Pointer_group_interface::add_grob (me, ly_symbol2scm ("elements"), e);
52 Axis_group_interface::has_axis (Grob *me, Axis a)
54 SCM axes = me->get_property ("axes");
56 return (SCM_BOOL_F != scm_memq (scm_from_int (a), axes));
60 Axis_group_interface::relative_group_extent (vector<Grob*> const &elts,
64 for (vsize i = 0; i < elts.size (); i++)
67 if (!to_boolean (se->get_property ("cross-staff")))
69 Interval dims = se->extent (common, a);
70 if (!dims.is_empty ())
78 Axis_group_interface::cached_pure_height (Grob *me, int start, int end)
80 SCM adjacent_pure_heights = me->get_property ("adjacent-pure-heights");
82 if (!scm_is_pair (adjacent_pure_heights)
83 || !scm_is_vector (scm_cdr (adjacent_pure_heights)))
84 return Interval (0, 0);
86 return combine_pure_heights (me, scm_cdr (adjacent_pure_heights), start, end);
90 Axis_group_interface::begin_of_line_pure_height (Grob *me, int start)
92 SCM adjacent_pure_heights = me->get_property ("adjacent-pure-heights");
94 if (!scm_is_pair (adjacent_pure_heights)
95 || !scm_is_vector (scm_car (adjacent_pure_heights)))
96 return Interval (0, 0);
98 return combine_pure_heights (me, scm_car (adjacent_pure_heights), start, start+1);
102 Axis_group_interface::combine_pure_heights (Grob *me, SCM measure_extents, int start, int end)
104 Paper_score *ps = get_root_system (me)->paper_score ();
105 vector<vsize> breaks = ps->get_break_indices ();
106 vector<Grob*> cols = ps->get_columns ();
109 for (vsize i = 0; i + 1 < breaks.size (); i++)
111 int r = Paper_column::get_rank (cols[breaks[i]]);
116 ext.unite (ly_scm2interval (scm_c_vector_ref (measure_extents, i)));
122 // adjacent-pure-heights is a pair of vectors, each of which has one element
123 // for every measure in the score. The first vector stores, for each measure,
124 // the combined height of the elements that are present only when the bar
125 // is at the beginning of a line. The second vector stores, for each measure,
126 // the combined height of the elements that are present only when the bar
127 // is not at the beginning of a line.
129 MAKE_SCHEME_CALLBACK (Axis_group_interface, adjacent_pure_heights, 1)
131 Axis_group_interface::adjacent_pure_heights (SCM smob)
133 Grob *me = unsmob_grob (smob);
135 Grob *common = calc_pure_elts_and_common (me);
136 extract_grob_set (me, "pure-relevant-items", items);
137 extract_grob_set (me, "pure-relevant-spanners", spanners);
139 Paper_score *ps = get_root_system (me)->paper_score ();
140 vector<vsize> breaks = ps->get_break_indices ();
141 vector<Grob*> cols = ps->get_columns ();
143 SCM begin_line_heights = scm_c_make_vector (breaks.size () - 1, SCM_EOL);
144 SCM mid_line_heights = scm_c_make_vector (breaks.size () - 1, SCM_EOL);
147 for (vsize i = 0; i + 1 < breaks.size (); i++)
149 int start = Paper_column::get_rank (cols[breaks[i]]);
150 int end = Paper_column::get_rank (cols[breaks[i+1]]);
151 Interval begin_line_iv;
152 Interval mid_line_iv;
154 for (vsize j = it_index; j < items.size (); j++)
156 Item *it = dynamic_cast<Item*> (items[j]);
157 int rank = it->get_column ()->get_rank ();
159 if (rank <= end && it->pure_is_visible (start, end)
160 && !to_boolean (it->get_property ("cross-staff")))
162 Interval dims = items[j]->pure_height (common, start, end);
163 Interval &target_iv = start == it->get_column ()->get_rank () ? begin_line_iv : mid_line_iv;
165 if (!dims.is_empty ())
166 target_iv.unite (dims);
175 for (vsize j = 0; j < spanners.size (); j++)
177 Interval_t<int> rank_span = spanners[j]->spanned_rank_interval ();
178 if (rank_span[LEFT] <= end && rank_span[RIGHT] >= start
179 && !to_boolean (spanners[j]->get_property ("cross-staff")))
181 Interval dims = spanners[j]->pure_height (common, start, end);
183 if (!dims.is_empty ())
184 mid_line_iv.unite (dims);
188 scm_vector_set_x (begin_line_heights, scm_from_int (i), ly_interval2scm (begin_line_iv));
189 scm_vector_set_x (mid_line_heights, scm_from_int (i), ly_interval2scm (mid_line_iv));
191 return scm_cons (begin_line_heights, mid_line_heights);
195 Axis_group_interface::relative_pure_height (Grob *me, int start, int end)
197 /* It saves a _lot_ of time if we assume a VerticalAxisGroup is additive
198 (ie. height (i, k) = max (height (i, j) height (j, k)) for all i <= j <= k).
199 Unfortunately, it isn't always true, particularly if there is a
200 VerticalAlignment somewhere in the descendants.
202 Apart from PianoStaff, which has a fixed VerticalAlignment so it doesn't
203 count, the only VerticalAlignment comes from Score. This makes it
204 reasonably safe to assume that if our parent is a VerticalAlignment,
205 we can assume additivity and cache things nicely. */
206 Grob *p = me->get_parent (Y_AXIS);
207 if (p && Align_interface::has_interface (p))
208 return Axis_group_interface::cached_pure_height (me, start, end);
210 Grob *common = calc_pure_elts_and_common (me);
211 extract_grob_set (me, "pure-relevant-items", items);
212 extract_grob_set (me, "pure-relevant-spanners", spanners);
216 for (vsize i = 0; i < items.size (); i++)
218 Item *it = dynamic_cast<Item*> (items[i]);
219 int rank = it->get_column ()->get_rank ();
223 else if (rank >= start && it->pure_is_visible (start, end)
224 && !to_boolean (it->get_property ("cross-staff")))
226 Interval dims = it->pure_height (common, start, end);
227 if (!dims.is_empty ())
232 for (vsize i = 0; i < spanners.size (); i++)
234 Interval_t<int> rank_span = spanners[i]->spanned_rank_interval ();
235 if (rank_span[LEFT] <= end && rank_span[RIGHT] >= start
236 && !to_boolean (spanners[i]->get_property ("cross-staff")))
238 Interval dims = spanners[i]->pure_height (common, start, end);
239 if (!dims.is_empty ())
246 MAKE_SCHEME_CALLBACK (Axis_group_interface, width, 1);
248 Axis_group_interface::width (SCM smob)
250 Grob *me = unsmob_grob (smob);
251 return generic_group_extent (me, X_AXIS);
254 MAKE_SCHEME_CALLBACK (Axis_group_interface, height, 1);
256 Axis_group_interface::height (SCM smob)
258 Grob *me = unsmob_grob (smob);
259 return generic_group_extent (me, Y_AXIS);
262 MAKE_SCHEME_CALLBACK (Axis_group_interface, pure_height, 3);
264 Axis_group_interface::pure_height (SCM smob, SCM start_scm, SCM end_scm)
266 int start = robust_scm2int (start_scm, 0);
267 int end = robust_scm2int (end_scm, INT_MAX);
268 Grob *me = unsmob_grob (smob);
270 /* Maybe we are in the second pass of a two-pass spacing run. In that
271 case, the Y-extent of a system is already given to us */
272 System *system = dynamic_cast<System*> (me);
275 SCM line_break_details = system->column (start)->get_property ("line-break-system-details");
276 SCM system_y_extent = scm_assq (ly_symbol2scm ("system-Y-extent"), line_break_details);
277 if (scm_is_pair (system_y_extent))
278 return scm_cdr (system_y_extent);
281 return ly_interval2scm (pure_group_height (me, start, end));
284 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_skylines, 1);
286 Axis_group_interface::calc_skylines (SCM smob)
288 Grob *me = unsmob_grob (smob);
289 extract_grob_set (me, "elements", elts);
290 Skyline_pair skylines = skyline_spacing (me, elts);
292 return skylines.smobbed_copy ();
295 /* whereas calc_skylines calculates skylines for axis-groups with a lot of
296 visible children, combine_skylines is designed for axis-groups whose only
297 children are other axis-groups (ie. VerticalAlignment). Rather than
298 calculating all the skylines from scratch, we just merge the skylines
301 MAKE_SCHEME_CALLBACK (Axis_group_interface, combine_skylines, 1);
303 Axis_group_interface::combine_skylines (SCM smob)
305 Grob *me = unsmob_grob (smob);
306 extract_grob_set (me, "elements", elements);
307 Grob *y_common = common_refpoint_of_array (elements, me, Y_AXIS);
308 Grob *x_common = common_refpoint_of_array (elements, me, X_AXIS);
311 programming_error ("combining skylines that don't belong to me");
314 for (vsize i = 0; i < elements.size (); i++)
316 SCM skyline_scm = elements[i]->get_property ("vertical-skylines");
317 if (Skyline_pair::unsmob (skyline_scm))
319 Real offset = elements[i]->relative_coordinate (y_common, Y_AXIS);
320 Skyline_pair other = *Skyline_pair::unsmob (skyline_scm);
321 other.raise (offset);
322 other.shift (elements[i]->relative_coordinate (x_common, X_AXIS));
326 return ret.smobbed_copy ();
330 Axis_group_interface::generic_group_extent (Grob *me, Axis a)
332 /* trigger the callback to do skyline-spacing on the children */
334 (void) me->get_property ("vertical-skylines");
336 extract_grob_set (me, "elements", elts);
337 Grob *common = common_refpoint_of_array (elts, me, a);
339 Real my_coord = me->relative_coordinate (common, a);
340 Interval r (relative_group_extent (elts, common, a));
342 return ly_interval2scm (r - my_coord);
345 /* This is like generic_group_extent, but it only counts the grobs that
346 are children of some other axis-group. This is uncached; if it becomes
347 commonly used, it may be necessary to cache it somehow. */
349 Axis_group_interface::staff_extent (Grob *me, Grob *refp, Axis ext_a, Grob *staff, Axis parent_a)
351 extract_grob_set (me, "elements", elts);
352 vector<Grob*> new_elts;
354 for (vsize i = 0; i < elts.size (); i++)
355 if (elts[i]->common_refpoint (staff, parent_a) == staff)
356 new_elts.push_back (elts[i]);
358 return relative_group_extent (new_elts, refp, ext_a);
363 Axis_group_interface::calc_pure_elts_and_common (Grob *me)
365 if (Grob *c = unsmob_grob (me->get_object ("pure-Y-common")))
368 extract_grob_set (me, "elements", elts);
370 vector<Grob*> relevant_items;
371 vector<Grob*> relevant_spanners;
372 SCM pure_relevant_p = ly_lily_module_constant ("pure-relevant?");
374 for (vsize i = 0; i < elts.size (); i++)
376 if (to_boolean (scm_apply_1 (pure_relevant_p, elts[i]->self_scm (), SCM_EOL)))
378 if (dynamic_cast<Item*> (elts[i]))
379 relevant_items.push_back (elts[i]);
380 else if (dynamic_cast<Spanner*> (elts[i]))
381 relevant_spanners.push_back (elts[i]);
385 Item *it = dynamic_cast<Item*> (elts[i]);
390 Item *piece = it->find_prebroken_piece (d);
391 if (piece && to_boolean (scm_apply_1 (pure_relevant_p, piece->self_scm (), SCM_EOL)))
392 relevant_items.push_back (piece);
394 while (flip (&d) != LEFT);
396 vector_sort (relevant_items, Item::less);
398 Grob *common = common_refpoint_of_array (relevant_items, me, Y_AXIS);
399 common = common_refpoint_of_array (relevant_spanners, common, Y_AXIS);
401 me->set_object ("pure-Y-common", common->self_scm ());
403 SCM items_scm = Grob_array::make_array ();
404 SCM spanners_scm = Grob_array::make_array ();
406 unsmob_grob_array (items_scm)->set_array (relevant_items);
407 unsmob_grob_array (spanners_scm)->set_array (relevant_spanners);
408 me->set_object ("pure-relevant-items", items_scm);
409 me->set_object ("pure-relevant-spanners", spanners_scm);
415 Axis_group_interface::calc_common (Grob *me, Axis axis)
417 extract_grob_set (me, "elements", elts);
418 Grob *common = common_refpoint_of_array (elts, me, axis);
421 me->programming_error ("No common parent found in calc_common axis.");
425 return common->self_scm ();
429 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_x_common, 1);
431 Axis_group_interface::calc_x_common (SCM grob)
433 return calc_common (unsmob_grob (grob), X_AXIS);
436 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_y_common, 1);
438 Axis_group_interface::calc_y_common (SCM grob)
440 return calc_common (unsmob_grob (grob), Y_AXIS);
444 Axis_group_interface::pure_group_height (Grob *me, int start, int end)
446 Grob *common = calc_pure_elts_and_common (me);
448 Real my_coord = me->relative_coordinate (common, Y_AXIS);
449 Interval r (relative_pure_height (me, start, end));
455 Axis_group_interface::get_children (Grob *me, vector<Grob*> *found)
457 found->push_back (me);
459 if (!has_interface (me))
462 extract_grob_set (me, "elements", elements);
463 for (vsize i = 0; i < elements.size (); i++)
465 Grob *e = elements[i];
466 Axis_group_interface::get_children (e, found);
471 staff_priority_less (Grob * const &g1, Grob * const &g2)
473 Real priority_1 = robust_scm2double (g1->get_property ("outside-staff-priority"), -infinity_f);
474 Real priority_2 = robust_scm2double (g2->get_property ("outside-staff-priority"), -infinity_f);
476 if (priority_1 < priority_2)
478 else if (priority_1 > priority_2)
481 /* if neither grob has an outside-staff priority, the ordering will have no
482 effect -- we just need to choose a consistent ordering. We do this to
483 avoid the side-effect of calculating extents. */
484 if (isinf (priority_1))
487 /* if there is no preference in staff priority, choose the left-most one */
488 Grob *common = g1->common_refpoint (g2, X_AXIS);
489 Real start_1 = g1->extent (common, X_AXIS)[LEFT];
490 Real start_2 = g2->extent (common, X_AXIS)[LEFT];
491 return start_1 < start_2;
495 add_boxes (Grob *me, Grob *x_common, Grob *y_common, vector<Box> *const boxes, Skyline_pair *skylines)
497 /* if a child has skylines, use them instead of the extent box */
498 if (Skyline_pair *pair = Skyline_pair::unsmob (me->get_property ("vertical-skylines")))
500 Skyline_pair s = *pair;
501 s.shift (me->relative_coordinate (x_common, X_AXIS));
502 s.raise (me->relative_coordinate (y_common, Y_AXIS));
505 else if (Grob_array *elements = unsmob_grob_array (me->get_object ("elements")))
507 for (vsize i = 0; i < elements->size (); i++)
508 add_boxes (elements->grob (i), x_common, y_common, boxes, skylines);
510 else if (!scm_is_number (me->get_property ("outside-staff-priority"))
511 && !to_boolean (me->get_property ("cross-staff")))
513 boxes->push_back (Box (me->extent (x_common, X_AXIS),
514 me->extent (y_common, Y_AXIS)));
518 /* We want to avoid situations like this:
526 The point is that "still more text" should be positioned under
527 "more text". In order to achieve this, we place the grobs in several
528 passes. We keep track of the right-most horizontal position that has been
529 affected by the current pass so far (actually we keep track of 2
530 positions, one for above the staff, one for below).
532 In each pass, we loop through the unplaced grobs from left to right.
533 If the grob doesn't overlap the right-most affected position, we place it
534 (and then update the right-most affected position to point to the right
535 edge of the just-placed grob). Otherwise, we skip it until the next pass.
538 add_grobs_of_one_priority (Skyline_pair *const skylines,
539 vector<Grob*> elements,
544 Drul_array<Real> last_affected_position;
547 while (!elements.empty ())
549 last_affected_position[UP] = -infinity_f;
550 last_affected_position[DOWN] = -infinity_f;
552 for (vsize i = elements.size (); i--;)
554 Direction dir = get_grob_direction (elements[i]);
557 warning (_ ("an outside-staff object should have a direction, defaulting to up"));
561 Box b (elements[i]->extent (x_common, X_AXIS),
562 elements[i]->extent (y_common, Y_AXIS));
563 SCM horizon_padding_scm = elements[i]->get_property ("outside-staff-horizontal-padding");
564 Real horizon_padding = robust_scm2double (horizon_padding_scm, 0.0);
566 if (b[X_AXIS][LEFT] - 2*horizon_padding < last_affected_position[dir])
569 if (!b[X_AXIS].is_empty () && !b[Y_AXIS].is_empty ())
573 Skyline other = Skyline (boxes, horizon_padding, X_AXIS, -dir);
574 Real padding = robust_scm2double (elements[i]->get_property ("outside-staff-padding"), 0.5);
575 Real dist = (*skylines)[dir].distance (other) + padding;
579 b.translate (Offset (0, dir*dist));
580 elements[i]->translate_axis (dir*dist, Y_AXIS);
582 (*skylines)[dir].insert (b, 0, X_AXIS);
583 elements[i]->set_property ("outside-staff-priority", SCM_BOOL_F);
584 last_affected_position[dir] = b[X_AXIS][RIGHT];
588 Ugh: quadratic. --hwn
590 elements.erase (elements.begin () + i);
595 // TODO: it is tricky to correctly handle skyline placement of cross-staff grobs.
596 // For example, cross-staff beams cannot be formatted until the distance between
597 // staves is known and therefore any grobs that depend on the beam cannot be placed
598 // until the skylines are known. On the other hand, the distance between staves should
599 // really depend on position of the cross-staff grobs that lie between them.
600 // Currently, we just leave cross-staff grobs out of the
601 // skyline altogether, but this could mean that staves are placed so close together
602 // that there is no room for the cross-staff grob. It also means, of course, that
603 // we don't get the benefits of skyline placement for cross-staff grobs.
605 Axis_group_interface::skyline_spacing (Grob *me, vector<Grob*> elements)
607 /* For grobs with an outside-staff-priority, the sorting function might
608 call extent and cause suicide. This breaks the contract that is required
609 for the STL sort function. To avoid this, we make sure that any suicides
610 are triggered beforehand.
612 for (vsize i = 0; i < elements.size (); i++)
613 if (scm_is_number (elements[i]->get_property ("outside-staff-priority")))
614 elements[i]->extent (elements[i], X_AXIS);
616 vector_sort (elements, staff_priority_less);
617 Grob *x_common = common_refpoint_of_array (elements, me, X_AXIS);
618 Grob *y_common = common_refpoint_of_array (elements, me, Y_AXIS);
620 assert (y_common == me);
625 Skyline_pair skylines;
626 for (i = 0; i < elements.size ()
627 && !scm_is_number (elements[i]->get_property ("outside-staff-priority")); i++)
628 if (!to_boolean (elements[i]->get_property ("cross-staff")))
629 add_boxes (elements[i], x_common, y_common, &boxes, &skylines);
631 SCM padding_scm = me->get_property ("skyline-horizontal-padding");
632 Real padding = robust_scm2double (padding_scm, 0.1);
633 skylines.merge (Skyline_pair (boxes, padding, X_AXIS));
634 for (; i < elements.size (); i++)
636 if (to_boolean (elements[i]->get_property ("cross-staff")))
639 SCM priority = elements[i]->get_property ("outside-staff-priority");
640 vector<Grob*> current_elts;
641 current_elts.push_back (elements[i]);
642 while (i + 1 < elements.size ()
643 && scm_eq_p (elements[i+1]->get_property ("outside-staff-priority"), priority))
644 current_elts.push_back (elements[++i]);
646 add_grobs_of_one_priority (&skylines, current_elts, x_common, y_common);
648 skylines.shift (-me->relative_coordinate (x_common, X_AXIS));
652 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_max_stretch, 1)
654 Axis_group_interface::calc_max_stretch (SCM smob)
656 Grob *me = unsmob_grob (smob);
658 extract_grob_set (me, "elements", elts);
660 for (vsize i = 0; i < elts.size (); i++)
661 if (Axis_group_interface::has_interface (elts[i]))
662 ret += robust_scm2double (elts[i]->get_property ("max-stretch"), 0.0);
664 return scm_from_double (ret);
667 MAKE_SCHEME_CALLBACK (Axis_group_interface, print, 1)
669 Axis_group_interface::print (SCM smob)
674 Grob *me = unsmob_grob (smob);
676 if (Skyline_pair *s = Skyline_pair::unsmob (me->get_property ("vertical-skylines")))
678 ret.add_stencil (Lookup::points_to_line_stencil (0.1, (*s)[UP].to_points (X_AXIS))
679 .in_color (255, 0, 255));
680 ret.add_stencil (Lookup::points_to_line_stencil (0.1, (*s)[DOWN].to_points (X_AXIS))
681 .in_color (0, 255, 255));
683 return ret.smobbed_copy ();
686 ADD_INTERFACE (Axis_group_interface,
687 "An object that groups other layout objects.",
692 "adjacent-pure-heights "
695 "keep-fixed-while-stretching "
699 "pure-relevant-items "
700 "pure-relevant-spanners "