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 "grob-array.hh"
14 #include "hara-kiri-group-spanner.hh"
15 #include "international.hh"
17 #include "paper-column.hh"
18 #include "paper-score.hh"
19 #include "pointer-group-interface.hh"
20 #include "separation-item.hh"
21 #include "skyline-pair.hh"
22 #include "staff-grouper-interface.hh"
28 Axis_group_interface::add_element (Grob *me, Grob *e)
30 SCM axes = me->get_property ("axes");
31 if (!scm_is_pair (axes))
32 programming_error ("axes should be nonempty");
34 for (SCM ax = axes; scm_is_pair (ax); ax = scm_cdr (ax))
36 Axis a = (Axis) scm_to_int (scm_car (ax));
38 if (!e->get_parent (a))
39 e->set_parent (me, a);
41 e->set_object ((a == X_AXIS)
42 ? ly_symbol2scm ("axis-group-parent-X")
43 : ly_symbol2scm ("axis-group-parent-Y"),
47 /* must be ordered, because Align_interface also uses
48 Axis_group_interface */
49 Pointer_group_interface::add_grob (me, ly_symbol2scm ("elements"), e);
53 Axis_group_interface::has_axis (Grob *me, Axis a)
55 SCM axes = me->get_property ("axes");
57 return (SCM_BOOL_F != scm_memq (scm_from_int (a), axes));
61 Axis_group_interface::relative_group_extent (vector<Grob*> const &elts,
65 for (vsize i = 0; i < elts.size (); i++)
68 if (!to_boolean (se->get_property ("cross-staff")))
70 Interval dims = se->extent (common, a);
71 if (!dims.is_empty ())
79 Axis_group_interface::cached_pure_height (Grob *me, int start, int end)
81 SCM adjacent_pure_heights = me->get_property ("adjacent-pure-heights");
83 if (!scm_is_pair (adjacent_pure_heights)
84 || !scm_is_vector (scm_cdr (adjacent_pure_heights)))
85 return Interval (0, 0);
87 return combine_pure_heights (me, scm_cdr (adjacent_pure_heights), start, end);
91 Axis_group_interface::begin_of_line_pure_height (Grob *me, int start)
93 SCM adjacent_pure_heights = me->get_property ("adjacent-pure-heights");
95 if (!scm_is_pair (adjacent_pure_heights)
96 || !scm_is_vector (scm_car (adjacent_pure_heights)))
97 return Interval (0, 0);
99 return combine_pure_heights (me, scm_car (adjacent_pure_heights), start, start+1);
103 Axis_group_interface::combine_pure_heights (Grob *me, SCM measure_extents, int start, int end)
105 Paper_score *ps = get_root_system (me)->paper_score ();
106 vector<vsize> breaks = ps->get_break_indices ();
107 vector<Grob*> cols = ps->get_columns ();
110 for (vsize i = 0; i + 1 < breaks.size (); i++)
112 int r = Paper_column::get_rank (cols[breaks[i]]);
117 ext.unite (ly_scm2interval (scm_c_vector_ref (measure_extents, i)));
123 // adjacent-pure-heights is a pair of vectors, each of which has one element
124 // for every measure in the score. The first vector stores, for each measure,
125 // the combined height of the elements that are present only when the bar
126 // is at the beginning of a line. The second vector stores, for each measure,
127 // the combined height of the elements that are present only when the bar
128 // is not at the beginning of a line.
130 MAKE_SCHEME_CALLBACK (Axis_group_interface, adjacent_pure_heights, 1)
132 Axis_group_interface::adjacent_pure_heights (SCM smob)
134 Grob *me = unsmob_grob (smob);
136 Grob *common = calc_pure_elts_and_common (me);
137 extract_grob_set (me, "pure-relevant-items", items);
138 extract_grob_set (me, "pure-relevant-spanners", spanners);
140 Paper_score *ps = get_root_system (me)->paper_score ();
141 vector<vsize> breaks = ps->get_break_indices ();
142 vector<Grob*> cols = ps->get_columns ();
144 SCM begin_line_heights = scm_c_make_vector (breaks.size () - 1, SCM_EOL);
145 SCM mid_line_heights = scm_c_make_vector (breaks.size () - 1, SCM_EOL);
148 for (vsize i = 0; i + 1 < breaks.size (); i++)
150 int start = Paper_column::get_rank (cols[breaks[i]]);
151 int end = Paper_column::get_rank (cols[breaks[i+1]]);
152 Interval begin_line_iv;
153 Interval mid_line_iv;
155 for (vsize j = it_index; j < items.size (); j++)
157 Item *it = dynamic_cast<Item*> (items[j]);
158 int rank = it->get_column ()->get_rank ();
160 if (rank <= end && it->pure_is_visible (start, end)
161 && !to_boolean (it->get_property ("cross-staff")))
163 Interval dims = items[j]->pure_height (common, start, end);
164 Interval &target_iv = start == it->get_column ()->get_rank () ? begin_line_iv : mid_line_iv;
166 if (!dims.is_empty ())
167 target_iv.unite (dims);
176 for (vsize j = 0; j < spanners.size (); j++)
178 Interval_t<int> rank_span = spanners[j]->spanned_rank_interval ();
179 if (rank_span[LEFT] <= end && rank_span[RIGHT] >= start
180 && !to_boolean (spanners[j]->get_property ("cross-staff")))
182 Interval dims = spanners[j]->pure_height (common, start, end);
184 if (!dims.is_empty ())
185 mid_line_iv.unite (dims);
189 scm_vector_set_x (begin_line_heights, scm_from_int (i), ly_interval2scm (begin_line_iv));
190 scm_vector_set_x (mid_line_heights, scm_from_int (i), ly_interval2scm (mid_line_iv));
192 return scm_cons (begin_line_heights, mid_line_heights);
196 Axis_group_interface::relative_pure_height (Grob *me, int start, int end)
198 /* It saves a _lot_ of time if we assume a VerticalAxisGroup is additive
199 (ie. height (i, k) = max (height (i, j) height (j, k)) for all i <= j <= k).
200 Unfortunately, it isn't always true, particularly if there is a
201 VerticalAlignment somewhere in the descendants.
203 Apart from PianoStaff, which has a fixed VerticalAlignment so it doesn't
204 count, the only VerticalAlignment comes from Score. This makes it
205 reasonably safe to assume that if our parent is a VerticalAlignment,
206 we can assume additivity and cache things nicely. */
207 Grob *p = me->get_parent (Y_AXIS);
208 if (p && Align_interface::has_interface (p))
209 return Axis_group_interface::cached_pure_height (me, start, end);
211 Grob *common = calc_pure_elts_and_common (me);
212 extract_grob_set (me, "pure-relevant-items", items);
213 extract_grob_set (me, "pure-relevant-spanners", spanners);
217 for (vsize i = 0; i < items.size (); i++)
219 Item *it = dynamic_cast<Item*> (items[i]);
220 int rank = it->get_column ()->get_rank ();
224 else if (rank >= start && it->pure_is_visible (start, end)
225 && !to_boolean (it->get_property ("cross-staff")))
227 Interval dims = it->pure_height (common, start, end);
228 if (!dims.is_empty ())
233 for (vsize i = 0; i < spanners.size (); i++)
235 Interval_t<int> rank_span = spanners[i]->spanned_rank_interval ();
236 if (rank_span[LEFT] <= end && rank_span[RIGHT] >= start
237 && !to_boolean (spanners[i]->get_property ("cross-staff")))
239 Interval dims = spanners[i]->pure_height (common, start, end);
240 if (!dims.is_empty ())
247 MAKE_SCHEME_CALLBACK (Axis_group_interface, width, 1);
249 Axis_group_interface::width (SCM smob)
251 Grob *me = unsmob_grob (smob);
252 return generic_group_extent (me, X_AXIS);
255 MAKE_SCHEME_CALLBACK (Axis_group_interface, height, 1);
257 Axis_group_interface::height (SCM smob)
259 Grob *me = unsmob_grob (smob);
260 return generic_group_extent (me, Y_AXIS);
263 MAKE_SCHEME_CALLBACK (Axis_group_interface, pure_height, 3);
265 Axis_group_interface::pure_height (SCM smob, SCM start_scm, SCM end_scm)
267 int start = robust_scm2int (start_scm, 0);
268 int end = robust_scm2int (end_scm, INT_MAX);
269 Grob *me = unsmob_grob (smob);
271 /* Maybe we are in the second pass of a two-pass spacing run. In that
272 case, the Y-extent of a system is already given to us */
273 System *system = dynamic_cast<System*> (me);
276 SCM line_break_details = system->column (start)->get_property ("line-break-system-details");
277 SCM system_y_extent = scm_assq (ly_symbol2scm ("system-Y-extent"), line_break_details);
278 if (scm_is_pair (system_y_extent))
279 return scm_cdr (system_y_extent);
282 return ly_interval2scm (pure_group_height (me, start, end));
285 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_skylines, 1);
287 Axis_group_interface::calc_skylines (SCM smob)
289 Grob *me = unsmob_grob (smob);
290 extract_grob_set (me, "elements", elts);
291 Skyline_pair skylines = skyline_spacing (me, elts);
293 return skylines.smobbed_copy ();
296 /* whereas calc_skylines calculates skylines for axis-groups with a lot of
297 visible children, combine_skylines is designed for axis-groups whose only
298 children are other axis-groups (ie. VerticalAlignment). Rather than
299 calculating all the skylines from scratch, we just merge the skylines
302 MAKE_SCHEME_CALLBACK (Axis_group_interface, combine_skylines, 1);
304 Axis_group_interface::combine_skylines (SCM smob)
306 Grob *me = unsmob_grob (smob);
307 extract_grob_set (me, "elements", elements);
308 Grob *y_common = common_refpoint_of_array (elements, me, Y_AXIS);
309 Grob *x_common = common_refpoint_of_array (elements, me, X_AXIS);
312 programming_error ("combining skylines that don't belong to me");
315 for (vsize i = 0; i < elements.size (); i++)
317 SCM skyline_scm = elements[i]->get_property ("vertical-skylines");
318 if (Skyline_pair::unsmob (skyline_scm))
320 Real offset = elements[i]->relative_coordinate (y_common, Y_AXIS);
321 Skyline_pair other = *Skyline_pair::unsmob (skyline_scm);
322 other.raise (offset);
323 other.shift (elements[i]->relative_coordinate (x_common, X_AXIS));
327 return ret.smobbed_copy ();
331 Axis_group_interface::generic_group_extent (Grob *me, Axis a)
333 /* trigger the callback to do skyline-spacing on the children */
335 (void) me->get_property ("vertical-skylines");
337 extract_grob_set (me, "elements", elts);
338 Grob *common = common_refpoint_of_array (elts, me, a);
340 Real my_coord = me->relative_coordinate (common, a);
341 Interval r (relative_group_extent (elts, common, a));
343 return ly_interval2scm (r - my_coord);
346 /* This is like generic_group_extent, but it only counts the grobs that
347 are children of some other axis-group. This is uncached; if it becomes
348 commonly used, it may be necessary to cache it somehow. */
350 Axis_group_interface::staff_extent (Grob *me, Grob *refp, Axis ext_a, Grob *staff, Axis parent_a)
352 extract_grob_set (me, "elements", elts);
353 vector<Grob*> new_elts;
355 for (vsize i = 0; i < elts.size (); i++)
356 if (elts[i]->common_refpoint (staff, parent_a) == staff)
357 new_elts.push_back (elts[i]);
359 return relative_group_extent (new_elts, refp, ext_a);
364 Axis_group_interface::calc_pure_elts_and_common (Grob *me)
366 if (Grob *c = unsmob_grob (me->get_object ("pure-Y-common")))
369 extract_grob_set (me, "elements", elts);
371 vector<Grob*> relevant_items;
372 vector<Grob*> relevant_spanners;
373 SCM pure_relevant_p = ly_lily_module_constant ("pure-relevant?");
375 for (vsize i = 0; i < elts.size (); i++)
377 if (to_boolean (scm_apply_1 (pure_relevant_p, elts[i]->self_scm (), SCM_EOL)))
379 if (dynamic_cast<Item*> (elts[i]))
380 relevant_items.push_back (elts[i]);
381 else if (dynamic_cast<Spanner*> (elts[i]))
382 relevant_spanners.push_back (elts[i]);
386 Item *it = dynamic_cast<Item*> (elts[i]);
391 Item *piece = it->find_prebroken_piece (d);
392 if (piece && to_boolean (scm_apply_1 (pure_relevant_p, piece->self_scm (), SCM_EOL)))
393 relevant_items.push_back (piece);
395 while (flip (&d) != LEFT);
397 vector_sort (relevant_items, Item::less);
399 Grob *common = common_refpoint_of_array (relevant_items, me, Y_AXIS);
400 common = common_refpoint_of_array (relevant_spanners, common, Y_AXIS);
402 me->set_object ("pure-Y-common", common->self_scm ());
404 SCM items_scm = Grob_array::make_array ();
405 SCM spanners_scm = Grob_array::make_array ();
407 unsmob_grob_array (items_scm)->set_array (relevant_items);
408 unsmob_grob_array (spanners_scm)->set_array (relevant_spanners);
409 me->set_object ("pure-relevant-items", items_scm);
410 me->set_object ("pure-relevant-spanners", spanners_scm);
416 Axis_group_interface::calc_common (Grob *me, Axis axis)
418 extract_grob_set (me, "elements", elts);
419 Grob *common = common_refpoint_of_array (elts, me, axis);
422 me->programming_error ("No common parent found in calc_common axis.");
426 return common->self_scm ();
430 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_x_common, 1);
432 Axis_group_interface::calc_x_common (SCM grob)
434 return calc_common (unsmob_grob (grob), X_AXIS);
437 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_y_common, 1);
439 Axis_group_interface::calc_y_common (SCM grob)
441 return calc_common (unsmob_grob (grob), Y_AXIS);
445 Axis_group_interface::pure_group_height (Grob *me, int start, int end)
447 Grob *common = calc_pure_elts_and_common (me);
449 Real my_coord = me->relative_coordinate (common, Y_AXIS);
450 Interval r (relative_pure_height (me, start, end));
456 Axis_group_interface::get_children (Grob *me, vector<Grob*> *found)
458 found->push_back (me);
460 if (!has_interface (me))
463 extract_grob_set (me, "elements", elements);
464 for (vsize i = 0; i < elements.size (); i++)
466 Grob *e = elements[i];
467 Axis_group_interface::get_children (e, found);
472 staff_priority_less (Grob * const &g1, Grob * const &g2)
474 Real priority_1 = robust_scm2double (g1->get_property ("outside-staff-priority"), -infinity_f);
475 Real priority_2 = robust_scm2double (g2->get_property ("outside-staff-priority"), -infinity_f);
477 if (priority_1 < priority_2)
479 else if (priority_1 > priority_2)
482 /* if neither grob has an outside-staff priority, the ordering will have no
483 effect -- we just need to choose a consistent ordering. We do this to
484 avoid the side-effect of calculating extents. */
485 if (isinf (priority_1))
488 /* if there is no preference in staff priority, choose the left-most one */
489 Grob *common = g1->common_refpoint (g2, X_AXIS);
490 Real start_1 = g1->extent (common, X_AXIS)[LEFT];
491 Real start_2 = g2->extent (common, X_AXIS)[LEFT];
492 return start_1 < start_2;
496 add_boxes (Grob *me, Grob *x_common, Grob *y_common, vector<Box> *const boxes, Skyline_pair *skylines)
498 /* if a child has skylines, use them instead of the extent box */
499 if (Skyline_pair *pair = Skyline_pair::unsmob (me->get_property ("vertical-skylines")))
501 Skyline_pair s = *pair;
502 s.shift (me->relative_coordinate (x_common, X_AXIS));
503 s.raise (me->relative_coordinate (y_common, Y_AXIS));
506 else if (Grob_array *elements = unsmob_grob_array (me->get_object ("elements")))
508 for (vsize i = 0; i < elements->size (); i++)
509 add_boxes (elements->grob (i), x_common, y_common, boxes, skylines);
511 else if (!scm_is_number (me->get_property ("outside-staff-priority"))
512 && !to_boolean (me->get_property ("cross-staff")))
514 boxes->push_back (Box (me->extent (x_common, X_AXIS),
515 me->extent (y_common, Y_AXIS)));
519 /* We want to avoid situations like this:
527 The point is that "still more text" should be positioned under
528 "more text". In order to achieve this, we place the grobs in several
529 passes. We keep track of the right-most horizontal position that has been
530 affected by the current pass so far (actually we keep track of 2
531 positions, one for above the staff, one for below).
533 In each pass, we loop through the unplaced grobs from left to right.
534 If the grob doesn't overlap the right-most affected position, we place it
535 (and then update the right-most affected position to point to the right
536 edge of the just-placed grob). Otherwise, we skip it until the next pass.
539 add_grobs_of_one_priority (Skyline_pair *const skylines,
540 vector<Grob*> elements,
545 Drul_array<Real> last_affected_position;
548 while (!elements.empty ())
550 last_affected_position[UP] = -infinity_f;
551 last_affected_position[DOWN] = -infinity_f;
553 for (vsize i = elements.size (); i--;)
555 Direction dir = get_grob_direction (elements[i]);
558 warning (_ ("an outside-staff object should have a direction, defaulting to up"));
562 Box b (elements[i]->extent (x_common, X_AXIS),
563 elements[i]->extent (y_common, Y_AXIS));
564 SCM horizon_padding_scm = elements[i]->get_property ("outside-staff-horizontal-padding");
565 Real horizon_padding = robust_scm2double (horizon_padding_scm, 0.0);
567 if (b[X_AXIS][LEFT] - 2*horizon_padding < last_affected_position[dir])
570 if (!b[X_AXIS].is_empty () && !b[Y_AXIS].is_empty ())
574 Skyline other = Skyline (boxes, horizon_padding, X_AXIS, -dir);
575 Real padding = robust_scm2double (elements[i]->get_property ("outside-staff-padding"), 0.5);
576 Real dist = (*skylines)[dir].distance (other) + padding;
580 b.translate (Offset (0, dir*dist));
581 elements[i]->translate_axis (dir*dist, Y_AXIS);
583 skylines->insert (b, 0, X_AXIS);
584 elements[i]->set_property ("outside-staff-priority", SCM_BOOL_F);
585 last_affected_position[dir] = b[X_AXIS][RIGHT];
589 Ugh: quadratic. --hwn
591 elements.erase (elements.begin () + i);
596 // TODO: it is tricky to correctly handle skyline placement of cross-staff grobs.
597 // For example, cross-staff beams cannot be formatted until the distance between
598 // staves is known and therefore any grobs that depend on the beam cannot be placed
599 // until the skylines are known. On the other hand, the distance between staves should
600 // really depend on position of the cross-staff grobs that lie between them.
601 // Currently, we just leave cross-staff grobs out of the
602 // skyline altogether, but this could mean that staves are placed so close together
603 // that there is no room for the cross-staff grob. It also means, of course, that
604 // we don't get the benefits of skyline placement for cross-staff grobs.
606 Axis_group_interface::skyline_spacing (Grob *me, vector<Grob*> elements)
608 /* For grobs with an outside-staff-priority, the sorting function might
609 call extent and cause suicide. This breaks the contract that is required
610 for the STL sort function. To avoid this, we make sure that any suicides
611 are triggered beforehand.
613 for (vsize i = 0; i < elements.size (); i++)
614 if (scm_is_number (elements[i]->get_property ("outside-staff-priority")))
615 elements[i]->extent (elements[i], X_AXIS);
617 vector_sort (elements, staff_priority_less);
618 Grob *x_common = common_refpoint_of_array (elements, me, X_AXIS);
619 Grob *y_common = common_refpoint_of_array (elements, me, Y_AXIS);
621 assert (y_common == me);
626 Skyline_pair skylines;
627 for (i = 0; i < elements.size ()
628 && !scm_is_number (elements[i]->get_property ("outside-staff-priority")); i++)
629 if (!to_boolean (elements[i]->get_property ("cross-staff")))
630 add_boxes (elements[i], x_common, y_common, &boxes, &skylines);
632 SCM padding_scm = me->get_property ("skyline-horizontal-padding");
633 Real padding = robust_scm2double (padding_scm, 0.1);
634 skylines.merge (Skyline_pair (boxes, padding, X_AXIS));
635 for (; i < elements.size (); i++)
637 if (to_boolean (elements[i]->get_property ("cross-staff")))
640 SCM priority = elements[i]->get_property ("outside-staff-priority");
641 vector<Grob*> current_elts;
642 current_elts.push_back (elements[i]);
643 while (i + 1 < elements.size ()
644 && scm_eq_p (elements[i+1]->get_property ("outside-staff-priority"), priority))
646 if (!to_boolean (elements[i+1]->get_property ("cross-staff")))
647 current_elts.push_back (elements[i+1]);
651 add_grobs_of_one_priority (&skylines, current_elts, x_common, y_common);
653 skylines.shift (-me->relative_coordinate (x_common, X_AXIS));
657 MAKE_SCHEME_CALLBACK (Axis_group_interface, print, 1)
659 Axis_group_interface::print (SCM smob)
664 Grob *me = unsmob_grob (smob);
666 if (Skyline_pair *s = Skyline_pair::unsmob (me->get_property ("vertical-skylines")))
668 ret.add_stencil (Lookup::points_to_line_stencil (0.1, (*s)[UP].to_points (X_AXIS))
669 .in_color (255, 0, 255));
670 ret.add_stencil (Lookup::points_to_line_stencil (0.1, (*s)[DOWN].to_points (X_AXIS))
671 .in_color (0, 255, 255));
673 return ret.smobbed_copy ();
676 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_next_staff_spacing, 1)
678 Axis_group_interface::calc_next_staff_spacing (SCM smob)
680 Grob *me = unsmob_grob (smob);
681 Grob *grouper = unsmob_grob (me->get_object ("staff-grouper"));
685 Grob *last_in_group = Staff_grouper_interface::get_last_grob (grouper);
686 if (me == last_in_group)
687 return grouper->get_property ("after-last-staff-spacing");
689 return grouper->get_property ("between-staff-spacing");
691 return me->get_property ("default-next-staff-spacing");
694 ADD_INTERFACE (Axis_group_interface,
695 "An object that groups other layout objects.",
697 // TODO: some of these properties are specific to
698 // VerticalAxisGroup. We should split off a
699 // vertical-axis-group-interface.
703 "adjacent-pure-heights "
705 "default-next-staff-spacing "
707 "inter-loose-line-spacing "
708 "inter-staff-spacing "
709 "keep-fixed-while-stretching "
711 "next-staff-spacing "
714 "pure-relevant-items "
715 "pure-relevant-spanners "