2 This file is part of LilyPond, the GNU music typesetter.
4 Copyright (C) 2000--2015 Han-Wen Nienhuys <hanwen@xs4all.nl>
6 LilyPond is free software: you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation, either version 3 of the License, or
9 (at your option) any later version.
11 LilyPond is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with LilyPond. If not, see <http://www.gnu.org/licenses/>.
20 #include "axis-group-interface.hh"
24 #include "align-interface.hh"
25 #include "directional-element-interface.hh"
26 #include "grob-array.hh"
27 #include "hara-kiri-group-spanner.hh"
28 #include "international.hh"
29 #include "interval-set.hh"
31 #include "paper-column.hh"
32 #include "paper-score.hh"
33 #include "pointer-group-interface.hh"
34 #include "separation-item.hh"
35 #include "skyline-pair.hh"
36 #include "staff-grouper-interface.hh"
41 #include "unpure-pure-container.hh"
44 pure_staff_priority_less (Grob *const &g1, Grob *const &g2);
46 Real Axis_group_interface::default_outside_staff_padding_ = 0.46;
49 Axis_group_interface::get_default_outside_staff_padding ()
51 return default_outside_staff_padding_;
55 Axis_group_interface::add_element (Grob *me, Grob *e)
57 SCM axes = me->get_property ("axes");
58 if (!scm_is_pair (axes))
59 programming_error ("axes should be nonempty");
61 for (SCM ax = axes; scm_is_pair (ax); ax = scm_cdr (ax))
63 Axis a = (Axis) scm_to_int (scm_car (ax));
65 if (!e->get_parent (a))
66 e->set_parent (me, a);
68 e->set_object ((a == X_AXIS)
69 ? ly_symbol2scm ("axis-group-parent-X")
70 : ly_symbol2scm ("axis-group-parent-Y"),
74 /* must be ordered, because Align_interface also uses
75 Axis_group_interface */
76 Pointer_group_interface::add_grob (me, ly_symbol2scm ("elements"), e);
80 Axis_group_interface::has_axis (Grob *me, Axis a)
82 SCM axes = me->get_property ("axes");
84 return (SCM_BOOL_F != scm_memq (scm_from_int (a), axes));
88 Axis_group_interface::relative_group_extent (vector<Grob *> const &elts,
91 return relative_maybe_bound_group_extent (elts, common, a, false);
95 Axis_group_interface::relative_maybe_bound_group_extent (vector<Grob *> const &elts,
96 Grob *common, Axis a, bool bound)
99 for (vsize i = 0; i < elts.size (); i++)
102 if (!to_boolean (se->get_property ("cross-staff")))
104 Interval dims = (bound && has_interface (se)
105 ? generic_bound_extent (se, common, a)
106 : se->extent (common, a));
107 if (!dims.is_empty ())
115 Axis_group_interface::generic_bound_extent (Grob *me, Grob *common, Axis a)
117 /* trigger the callback to do skyline-spacing on the children */
119 (void) me->get_property ("vertical-skylines");
121 extract_grob_set (me, "elements", elts);
122 vector<Grob *> new_elts;
124 SCM interfaces = me->get_property ("bound-alignment-interfaces");
126 for (vsize i = 0; i < elts.size (); i++)
127 for (SCM l = interfaces; scm_is_pair (l); l = scm_cdr (l))
128 if (elts[i]->internal_has_interface (scm_car (l)))
129 new_elts.push_back (elts[i]);
131 if (!new_elts.size ())
132 return robust_relative_extent (me, common, a);
135 common = common_refpoint_of_array (new_elts, me, a);
137 return relative_maybe_bound_group_extent (new_elts, common, a, true);
141 Axis_group_interface::sum_partial_pure_heights (Grob *me, int start, int end)
143 Interval iv = begin_of_line_pure_height (me, start);
144 iv.unite (rest_of_line_pure_height (me, start, end));
150 Axis_group_interface::part_of_line_pure_height (Grob *me, bool begin, int start, int end)
152 Spanner *sp = dynamic_cast<Spanner *> (me);
154 return Interval (0, 0);
155 SCM cache_symbol = begin
156 ? ly_symbol2scm ("begin-of-line-pure-height")
157 : ly_symbol2scm ("rest-of-line-pure-height");
158 SCM cached = sp->get_cached_pure_property (cache_symbol, start, end);
159 if (scm_is_pair (cached))
160 return robust_scm2interval (cached, Interval (0, 0));
162 SCM adjacent_pure_heights = me->get_property ("adjacent-pure-heights");
165 if (!scm_is_pair (adjacent_pure_heights))
166 ret = Interval (0, 0);
169 SCM these_pure_heights = begin
170 ? scm_car (adjacent_pure_heights)
171 : scm_cdr (adjacent_pure_heights);
173 if (scm_is_vector (these_pure_heights))
174 ret = combine_pure_heights (me, these_pure_heights, start, end);
176 ret = Interval (0, 0);
179 sp->cache_pure_property (cache_symbol, start, end, ly_interval2scm (ret));
184 Axis_group_interface::begin_of_line_pure_height (Grob *me, int start)
186 return part_of_line_pure_height (me, true, start, start + 1);
190 Axis_group_interface::rest_of_line_pure_height (Grob *me, int start, int end)
192 return part_of_line_pure_height (me, false, start, end);
196 Axis_group_interface::combine_pure_heights (Grob *me, SCM measure_extents, int start, int end)
198 Paper_score *ps = get_root_system (me)->paper_score ();
199 vector<vsize> breaks = ps->get_break_indices ();
200 vector<Grob *> cols = ps->get_columns ();
203 for (vsize i = 0; i + 1 < breaks.size (); i++)
205 int r = Paper_column::get_rank (cols[breaks[i]]);
210 ext.unite (ly_scm2interval (scm_c_vector_ref (measure_extents, i)));
216 // adjacent-pure-heights is a pair of vectors, each of which has one element
217 // for every measure in the score. The first vector stores, for each measure,
218 // the combined height of the elements that are present only when the bar
219 // is at the beginning of a line. The second vector stores, for each measure,
220 // the combined height of the elements that are present only when the bar
221 // is not at the beginning of a line.
222 MAKE_SCHEME_CALLBACK (Axis_group_interface, adjacent_pure_heights, 1)
224 Axis_group_interface::adjacent_pure_heights (SCM smob)
226 Grob *me = unsmob<Grob> (smob);
228 Grob *common = unsmob<Grob> (me->get_object ("pure-Y-common"));
229 extract_grob_set (me, "pure-relevant-grobs", elts);
231 Paper_score *ps = get_root_system (me)->paper_score ();
232 vector<vsize> ranks = ps->get_break_ranks ();
234 vector<Interval> begin_line_heights;
235 vector<Interval> mid_line_heights;
236 vector<Interval> begin_line_staff_heights;
237 vector<Interval> mid_line_staff_heights;
238 begin_line_heights.resize (ranks.size () - 1);
239 mid_line_heights.resize (ranks.size () - 1);
241 for (vsize i = 0; i < elts.size (); ++i)
245 if (to_boolean (g->get_property ("cross-staff")))
251 bool outside_staff = scm_is_number (g->get_property ("outside-staff-priority"));
252 Real padding = robust_scm2double (g->get_property ("outside-staff-padding"), get_default_outside_staff_padding ());
254 // When we encounter the first outside-staff grob, make a copy
255 // of the current heights to use as an estimate for the staff heights.
256 // Note that the outside-staff approximation that we use here doesn't
257 // consider any collisions that might occur between outside-staff grobs,
258 // but only the fact that outside-staff grobs may need to be raised above
260 if (outside_staff && begin_line_staff_heights.empty ())
262 begin_line_staff_heights = begin_line_heights;
263 mid_line_staff_heights = mid_line_heights;
266 // TODO: consider a pure version of get_grob_direction?
267 Direction d = to_dir (g->get_property_data ("direction"));
268 d = (d == CENTER) ? UP : d;
270 Interval_t<int> rank_span = g->spanned_rank_interval ();
271 vsize first_break = lower_bound (ranks, (vsize)rank_span[LEFT], less<vsize> ());
272 if (first_break > 0 && ranks[first_break] >= (vsize)rank_span[LEFT])
275 for (vsize j = first_break; j + 1 < ranks.size () && (int)ranks[j] <= rank_span[RIGHT]; ++j)
277 int start = ranks[j];
278 int end = ranks[j + 1];
280 // Take grobs that are visible with respect to a slightly longer line.
281 // Otherwise, we will never include grobs at breakpoints which aren't
282 // end-of-line-visible.
283 int visibility_end = j + 2 < ranks.size () ? ranks[j + 2] : end;
285 if (g->pure_is_visible (start, visibility_end))
287 Interval dims = g->pure_height (common, start, end);
288 if (!dims.is_empty ())
290 if (rank_span[LEFT] <= start)
293 begin_line_heights[j].unite (begin_line_staff_heights[j].union_disjoint (dims, padding, d));
295 begin_line_heights[j].unite (dims);
297 if (rank_span[RIGHT] > start)
300 mid_line_heights[j].unite (mid_line_staff_heights[j].union_disjoint (dims, padding, d));
302 mid_line_heights[j].unite (dims);
309 // Convert begin_line_heights and min_line_heights to SCM.
310 SCM begin_scm = scm_c_make_vector (ranks.size () - 1, SCM_EOL);
311 SCM mid_scm = scm_c_make_vector (ranks.size () - 1, SCM_EOL);
312 for (vsize i = 0; i < begin_line_heights.size (); ++i)
314 scm_vector_set_x (begin_scm, scm_from_int (i), ly_interval2scm (begin_line_heights[i]));
315 scm_vector_set_x (mid_scm, scm_from_int (i), ly_interval2scm (mid_line_heights[i]));
318 return scm_cons (begin_scm, mid_scm);
322 Axis_group_interface::relative_pure_height (Grob *me, int start, int end)
324 /* It saves a _lot_ of time if we assume a VerticalAxisGroup is additive
325 (ie. height (i, k) = max (height (i, j) height (j, k)) for all i <= j <= k).
326 Unfortunately, it isn't always true, particularly if there is a
327 VerticalAlignment somewhere in the descendants.
329 Usually, the only VerticalAlignment comes from Score. This makes it
330 reasonably safe to assume that if our parent is a VerticalAlignment,
331 we can assume additivity and cache things nicely. */
332 Grob *p = me->get_parent (Y_AXIS);
333 if (p && Align_interface::has_interface (p))
334 return Axis_group_interface::sum_partial_pure_heights (me, start, end);
336 Grob *common = unsmob<Grob> (me->get_object ("pure-Y-common"));
337 extract_grob_set (me, "pure-relevant-grobs", elts);
340 for (vsize i = 0; i < elts.size (); i++)
343 Interval_t<int> rank_span = g->spanned_rank_interval ();
344 if (rank_span[LEFT] <= end && rank_span[RIGHT] >= start
345 && g->pure_is_visible (start, end)
346 && !(to_boolean (g->get_property ("cross-staff"))
347 && Stem::has_interface (g)))
349 Interval dims = g->pure_height (common, start, end);
350 if (!dims.is_empty ())
357 MAKE_SCHEME_CALLBACK (Axis_group_interface, width, 1);
359 Axis_group_interface::width (SCM smob)
361 Grob *me = unsmob<Grob> (smob);
362 return generic_group_extent (me, X_AXIS);
365 MAKE_SCHEME_CALLBACK (Axis_group_interface, height, 1);
367 Axis_group_interface::height (SCM smob)
369 Grob *me = unsmob<Grob> (smob);
370 return generic_group_extent (me, Y_AXIS);
373 MAKE_SCHEME_CALLBACK (Axis_group_interface, pure_height, 3);
375 Axis_group_interface::pure_height (SCM smob, SCM start_scm, SCM end_scm)
377 int start = robust_scm2int (start_scm, 0);
378 int end = robust_scm2int (end_scm, INT_MAX);
379 Grob *me = unsmob<Grob> (smob);
381 /* Maybe we are in the second pass of a two-pass spacing run. In that
382 case, the Y-extent of a system is already given to us */
383 System *system = dynamic_cast<System *> (me);
386 SCM line_break_details = system->column (start)->get_property ("line-break-system-details");
387 SCM system_y_extent = scm_assq (ly_symbol2scm ("system-Y-extent"), line_break_details);
388 if (scm_is_pair (system_y_extent))
389 return scm_cdr (system_y_extent);
392 return ly_interval2scm (pure_group_height (me, start, end));
395 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_skylines, 1);
397 Axis_group_interface::calc_skylines (SCM smob)
399 Grob *me = unsmob<Grob> (smob);
400 Skyline_pair skylines = skyline_spacing (me);
401 return skylines.smobbed_copy ();
404 /* whereas calc_skylines calculates skylines for axis-groups with a lot of
405 visible children, combine_skylines is designed for axis-groups whose only
406 children are other axis-groups (ie. VerticalAlignment). Rather than
407 calculating all the skylines from scratch, we just merge the skylines
410 MAKE_SCHEME_CALLBACK (Axis_group_interface, combine_skylines, 1);
412 Axis_group_interface::combine_skylines (SCM smob)
414 Grob *me = unsmob<Grob> (smob);
415 extract_grob_set (me, "elements", elements);
416 Grob *y_common = common_refpoint_of_array (elements, me, Y_AXIS);
417 Grob *x_common = common_refpoint_of_array (elements, me, X_AXIS);
420 programming_error ("combining skylines that don't belong to me");
423 for (vsize i = 0; i < elements.size (); i++)
425 SCM skyline_scm = elements[i]->get_property ("vertical-skylines");
426 if (unsmob<Skyline_pair> (skyline_scm))
428 Real offset = elements[i]->relative_coordinate (y_common, Y_AXIS);
429 Skyline_pair other = *unsmob<Skyline_pair> (skyline_scm);
430 other.raise (offset);
431 other.shift (elements[i]->relative_coordinate (x_common, X_AXIS));
435 return ret.smobbed_copy ();
439 Axis_group_interface::generic_group_extent (Grob *me, Axis a)
441 extract_grob_set (me, "elements", elts);
443 /* trigger the callback to do skyline-spacing on the children */
445 for (vsize i = 0; i < elts.size (); i++)
446 if (!(Stem::has_interface (elts[i])
447 && to_boolean (elts[i]->get_property ("cross-staff"))))
448 (void) elts[i]->get_property ("vertical-skylines");
450 Grob *common = common_refpoint_of_array (elts, me, a);
452 Real my_coord = me->relative_coordinate (common, a);
453 Interval r (relative_group_extent (elts, common, a));
455 return ly_interval2scm (r - my_coord);
458 /* This is like generic_group_extent, but it only counts the grobs that
459 are children of some other axis-group. This is uncached; if it becomes
460 commonly used, it may be necessary to cache it somehow. */
462 Axis_group_interface::staff_extent (Grob *me, Grob *refp, Axis ext_a, Grob *staff, Axis parent_a)
464 extract_grob_set (me, "elements", elts);
465 vector<Grob *> new_elts;
467 for (vsize i = 0; i < elts.size (); i++)
468 if (elts[i]->common_refpoint (staff, parent_a) == staff)
469 new_elts.push_back (elts[i]);
471 return relative_group_extent (new_elts, refp, ext_a);
474 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_pure_relevant_grobs, 1);
476 Axis_group_interface::calc_pure_relevant_grobs (SCM smob)
478 Grob *me = unsmob<Grob> (smob);
479 /* TODO: Filter out elements that belong to a different Axis_group,
481 << \new Staff=A { c'1~ \change Staff=B c'}
482 \new Staff=B { \clef bass R1 R } >>
483 because thier location relative to this Axis_group is not known before
484 page layout. For now, we need to trap this case in calc_pure_y_common.
486 return internal_calc_pure_relevant_grobs (me, "elements");
490 Axis_group_interface::internal_calc_pure_relevant_grobs (Grob *me, const string &grob_set_name)
492 extract_grob_set (me, grob_set_name.c_str (), elts);
494 vector<Grob *> relevant_grobs;
496 for (vsize i = 0; i < elts.size (); i++)
498 if (elts[i] && elts[i]->is_live ())
499 relevant_grobs.push_back (elts[i]);
501 TODO (mikesol): it is probably bad that we're reading prebroken
502 pieces from potentially suicided elements. This behavior
503 has been in current master since at least 2.16.
505 We need to fully suicide all Items, meaning that their
506 prebroken pieces should not be accessible, which means that
507 Item::handle_prebroken_dependencies should only be called
508 AFTER this list is composed. The list composition function
509 should probably not check for suicided items or NULL pointers
510 but leave that to the various methods that use it.
512 if (Item *it = dynamic_cast<Item *> (elts[i]))
514 for (LEFT_and_RIGHT (d))
516 Item *piece = it->find_prebroken_piece (d);
517 if (piece && piece->is_live ())
518 relevant_grobs.push_back (piece);
523 vector_sort (relevant_grobs, pure_staff_priority_less);
524 SCM grobs_scm = Grob_array::make_array ();
525 unsmob<Grob_array> (grobs_scm)->set_array (relevant_grobs);
530 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_pure_y_common, 1);
532 Axis_group_interface::calc_pure_y_common (SCM smob)
534 Grob *me = unsmob<Grob> (smob);
536 extract_grob_set (me, "pure-relevant-grobs", elts);
537 Grob *common = common_refpoint_of_array (elts, me, Y_AXIS);
538 if (common != me && Align_interface::has_interface (common))
540 me->programming_error("My pure_y_common is a VerticalAlignment,"
541 " which might contain several staves.");
546 me->programming_error ("No common parent found in calc_pure_y_common.");
550 return common->self_scm ();
554 Axis_group_interface::calc_common (Grob *me, Axis axis)
556 extract_grob_set (me, "elements", elts);
557 Grob *common = common_refpoint_of_array (elts, me, axis);
560 me->programming_error ("No common parent found in calc_common axis.");
564 return common->self_scm ();
567 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_x_common, 1);
569 Axis_group_interface::calc_x_common (SCM grob)
571 return calc_common (unsmob<Grob> (grob), X_AXIS);
574 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_y_common, 1);
576 Axis_group_interface::calc_y_common (SCM grob)
578 return calc_common (unsmob<Grob> (grob), Y_AXIS);
582 Axis_group_interface::pure_group_height (Grob *me, int start, int end)
584 Grob *common = unsmob<Grob> (me->get_object ("pure-Y-common"));
588 programming_error ("no pure Y common refpoint");
591 Real my_coord = me->pure_relative_y_coordinate (common, start, end);
592 Interval r (relative_pure_height (me, start, end));
598 Axis_group_interface::get_children (Grob *me, vector<Grob *> *found)
600 found->push_back (me);
602 if (!has_interface (me))
605 extract_grob_set (me, "elements", elements);
606 for (vsize i = 0; i < elements.size (); i++)
608 Grob *e = elements[i];
609 Axis_group_interface::get_children (e, found);
614 staff_priority_less (Grob *const &g1, Grob *const &g2)
616 Real priority_1 = robust_scm2double (g1->get_property ("outside-staff-priority"), -infinity_f);
617 Real priority_2 = robust_scm2double (g2->get_property ("outside-staff-priority"), -infinity_f);
619 if (priority_1 < priority_2)
621 else if (priority_1 > priority_2)
624 /* if neither grob has an outside-staff priority, the ordering will have no
625 effect -- we just need to choose a consistent ordering. We do this to
626 avoid the side-effect of calculating extents. */
627 if (isinf (priority_1))
630 /* if there is no preference in staff priority, choose the left-most one */
631 Grob *common = g1->common_refpoint (g2, X_AXIS);
632 Real start_1 = g1->extent (common, X_AXIS)[LEFT];
633 Real start_2 = g2->extent (common, X_AXIS)[LEFT];
634 return start_1 < start_2;
638 pure_staff_priority_less (Grob *const &g1, Grob *const &g2)
640 Real priority_1 = robust_scm2double (g1->get_property ("outside-staff-priority"), -infinity_f);
641 Real priority_2 = robust_scm2double (g2->get_property ("outside-staff-priority"), -infinity_f);
643 return priority_1 < priority_2;
647 add_interior_skylines (Grob *me, Grob *x_common, Grob *y_common, vector<Skyline_pair> *skylines)
649 if (Grob_array *elements = unsmob<Grob_array> (me->get_object ("elements")))
651 for (vsize i = 0; i < elements->size (); i++)
652 add_interior_skylines (elements->grob (i), x_common, y_common, skylines);
654 else if (!scm_is_number (me->get_property ("outside-staff-priority"))
655 && !to_boolean (me->get_property ("cross-staff")))
657 Skyline_pair *maybe_pair = unsmob<Skyline_pair> (me->get_property ("vertical-skylines"));
660 if (maybe_pair->is_empty ())
662 skylines->push_back (Skyline_pair (*maybe_pair));
663 skylines->back ().shift (me->relative_coordinate (x_common, X_AXIS));
664 skylines->back ().raise (me->relative_coordinate (y_common, Y_AXIS));
668 // Raises the grob elt (whose skylines are given by h_skyline
669 // and v_skyline) so that it doesn't intersect with staff_skyline,
670 // or with anything in other_h_skylines and other_v_skylines.
672 avoid_outside_staff_collisions (Grob *elt,
673 Skyline_pair *v_skyline,
675 Real horizon_padding,
676 vector<Skyline_pair> const &other_v_skylines,
677 vector<Real> const &other_padding,
678 vector<Real> const &other_horizon_padding,
681 assert (other_v_skylines.size () == other_padding.size ());
682 assert (other_v_skylines.size () == other_horizon_padding.size ());
683 vector<Interval> forbidden_intervals;
684 for (vsize j = 0; j < other_v_skylines.size (); j++)
686 Skyline_pair const &v_other = other_v_skylines[j];
687 Real pad = max (padding, other_padding[j]);
688 Real horizon_pad = max (horizon_padding, other_horizon_padding[j]);
690 // We need to push elt up by at least this much to be above v_other.
691 Real up = (*v_skyline)[DOWN].distance (v_other[UP], horizon_pad) + pad;
692 // We need to push elt down by at least this much to be below v_other.
693 Real down = (*v_skyline)[UP].distance (v_other[DOWN], horizon_pad) + pad;
695 forbidden_intervals.push_back (Interval (-down, up));
698 Interval_set allowed_shifts
699 = Interval_set::interval_union (forbidden_intervals).complement ();
700 Real move = allowed_shifts.nearest_point (0, dir);
701 v_skyline->raise (move);
702 elt->translate_axis (move, Y_AXIS);
706 valid_outside_staff_placement_directive (Grob *me)
708 SCM directive = me->get_property ("outside-staff-placement-directive");
710 if (scm_is_eq (directive, ly_symbol2scm ("left-to-right-greedy"))
711 || scm_is_eq (directive, ly_symbol2scm ("left-to-right-polite"))
712 || scm_is_eq (directive, ly_symbol2scm ("right-to-left-greedy"))
713 || scm_is_eq (directive, ly_symbol2scm ("right-to-left-polite")))
716 me->warning (_f ("\"%s\" is not a valid outside-staff-placement-directive",
717 robust_symbol2string (directive, "").c_str ()));
719 return ly_symbol2scm ("left-to-right-polite");
722 // Shifts the grobs in elements to ensure that they (and any
723 // connected riders) don't collide with the staff skylines
724 // or anything in all_X_skylines. Afterwards, the skylines
725 // of the grobs in elements will be added to all_v_skylines.
727 add_grobs_of_one_priority (Grob *me,
728 Drul_array<vector<Skyline_pair> > *all_v_skylines,
729 Drul_array<vector<Real> > *all_paddings,
730 Drul_array<vector<Real> > *all_horizon_paddings,
731 vector<Grob *> elements,
734 multimap<Grob *, Grob *> const &riders)
738 = valid_outside_staff_placement_directive (me);
740 bool l2r = (scm_is_eq (directive, ly_symbol2scm ("left-to-right-greedy"))
741 || scm_is_eq (directive, ly_symbol2scm ("left-to-right-polite")));
743 bool polite = (scm_is_eq (directive, ly_symbol2scm ("left-to-right-polite"))
744 || scm_is_eq (directive, ly_symbol2scm ("right-to-left-polite")));
747 vector<Skyline_pair> skylines_to_merge;
749 // We want to avoid situations like this:
753 // -------------------
755 // -------------------
757 // The point is that "still more text" should be positioned under
758 // "more text". In order to achieve this, we place the grobs in several
759 // passes. We keep track of the right-most horizontal position that has been
760 // affected by the current pass so far (actually we keep track of 2
761 // positions, one for above the staff, one for below).
763 // In each pass, we loop through the unplaced grobs from left to right.
764 // If the grob doesn't overlap the right-most affected position, we place it
765 // (and then update the right-most affected position to point to the right
766 // edge of the just-placed grob). Otherwise, we skip it until the next pass.
767 while (!elements.empty ())
769 Drul_array<Real> last_end (-infinity_f, -infinity_f);
770 vector<Grob *> skipped_elements;
771 for (vsize i = l2r ? 0 : elements.size ();
772 l2r ? i < elements.size () : i--;
775 Grob *elt = elements[i];
777 = robust_scm2double (elt->get_property ("outside-staff-padding"),
779 ::get_default_outside_staff_padding ());
781 = robust_scm2double (elt->get_property ("outside-staff-horizontal-padding"), 0.0);
782 Interval x_extent = elt->extent (x_common, X_AXIS);
783 x_extent.widen (horizon_padding);
785 Direction dir = get_grob_direction (elt);
788 warning (_ ("an outside-staff object should have a direction, defaulting to up"));
792 if (x_extent[LEFT] <= last_end[dir] && polite)
794 skipped_elements.push_back (elt);
797 last_end[dir] = x_extent[RIGHT];
799 Skyline_pair *v_orig = unsmob<Skyline_pair> (elt->get_property ("vertical-skylines"));
800 if (v_orig->is_empty ())
803 // Find the riders associated with this grob, and merge their
804 // skylines with elt's skyline.
805 typedef multimap<Grob *, Grob *>::const_iterator GrobMapIterator;
806 pair<GrobMapIterator, GrobMapIterator> range = riders.equal_range (elt);
807 vector<Skyline_pair> rider_v_skylines;
808 for (GrobMapIterator j = range.first; j != range.second; j++)
810 Grob *rider = j->second;
811 Skyline_pair *v_rider = unsmob<Skyline_pair> (rider->get_property ("vertical-skylines"));
814 Skyline_pair copy (*v_rider);
815 copy.shift (rider->relative_coordinate (x_common, X_AXIS));
816 copy.raise (rider->relative_coordinate (y_common, Y_AXIS));
817 rider_v_skylines.push_back (copy);
820 Skyline_pair v_skylines (*v_orig);
821 v_skylines.shift (elt->relative_coordinate (x_common, X_AXIS));
822 v_skylines.raise (elt->relative_coordinate (y_common, Y_AXIS));
823 v_skylines.merge (Skyline_pair (rider_v_skylines));
825 avoid_outside_staff_collisions (elt,
829 (*all_v_skylines)[dir],
830 (*all_paddings)[dir],
831 (*all_horizon_paddings)[dir],
834 elt->set_property ("outside-staff-priority", SCM_BOOL_F);
835 (*all_v_skylines)[dir].push_back (v_skylines);
836 (*all_paddings)[dir].push_back (padding);
837 (*all_horizon_paddings)[dir].push_back (horizon_padding);
839 swap (elements, skipped_elements);
840 skipped_elements.clear ();
844 // If the Grob has a Y-ancestor with outside-staff-priority, return it.
845 // Otherwise, return 0.
847 Axis_group_interface::outside_staff_ancestor (Grob *me)
849 Grob *parent = me->get_parent (Y_AXIS);
853 if (scm_is_number (parent->get_property ("outside-staff-priority")))
856 return outside_staff_ancestor (parent);
859 // It is tricky to correctly handle skyline placement of cross-staff grobs.
860 // For example, cross-staff beams cannot be formatted until the distance between
861 // staves is known and therefore any grobs that depend on the beam cannot be placed
862 // until the skylines are known. On the other hand, the distance between staves should
863 // really depend on position of the cross-staff grobs that lie between them.
864 // Currently, we just leave cross-staff grobs out of the
865 // skyline altogether, but this could mean that staves are placed so close together
866 // that there is no room for the cross-staff grob. It also means, of course, that
867 // we don't get the benefits of skyline placement for cross-staff grobs.
869 Axis_group_interface::skyline_spacing (Grob *me)
871 extract_grob_set (me, unsmob<Grob_array> (me->get_object ("vertical-skyline-elements")) ? "vertical-skyline-elements" : "elements", fakeelements);
872 vector<Grob *> elements (fakeelements);
873 for (vsize i = 0; i < elements.size (); i++)
875 As a sanity check, we make sure that no grob with an outside staff priority
876 has a Y-parent that also has an outside staff priority, which would result
879 if (scm_is_number (elements[i]->get_property ("outside-staff-priority"))
880 && outside_staff_ancestor (elements[i]))
882 elements[i]->warning ("Cannot set outside-staff-priority for element and elements' Y parent.");
883 elements[i]->set_property ("outside-staff-priority", SCM_BOOL_F);
886 /* For grobs with an outside-staff-priority, the sorting function might
887 call extent and cause suicide. This breaks the contract that is required
888 for the STL sort function. To avoid this, we make sure that any suicides
889 are triggered beforehand.
891 for (vsize i = 0; i < elements.size (); i++)
892 if (scm_is_number (elements[i]->get_property ("outside-staff-priority")))
893 elements[i]->extent (elements[i], X_AXIS);
895 vector_sort (elements, staff_priority_less);
896 Grob *x_common = common_refpoint_of_array (elements, me, X_AXIS);
897 Grob *y_common = common_refpoint_of_array (elements, me, Y_AXIS);
901 me->programming_error("Some of my vertical-skyline-elements"
902 " are outside my VerticalAxisGroup.");
906 // A rider is a grob that is not outside-staff, but has an outside-staff
907 // ancestor. In that case, the rider gets moved along with its ancestor.
908 multimap<Grob *, Grob *> riders;
911 vector<Skyline_pair> inside_staff_skylines;
913 for (i = 0; i < elements.size ()
914 && !scm_is_number (elements[i]->get_property ("outside-staff-priority")); i++)
916 Grob *elt = elements[i];
917 Grob *ancestor = outside_staff_ancestor (elt);
918 if (!(to_boolean (elt->get_property ("cross-staff")) || ancestor))
919 add_interior_skylines (elt, x_common, y_common, &inside_staff_skylines);
921 riders.insert (pair<Grob *, Grob *> (ancestor, elt));
924 Skyline_pair skylines (inside_staff_skylines);
926 // These are the skylines of all outside-staff grobs
927 // that have already been processed. We keep them around in order to
928 // check them for collisions with the currently active outside-staff grob.
929 Drul_array<vector<Skyline_pair> > all_v_skylines;
930 Drul_array<vector<Real> > all_paddings;
931 Drul_array<vector<Real> > all_horizon_paddings;
932 for (UP_and_DOWN (d))
934 all_v_skylines[d].push_back (skylines);
935 all_paddings[d].push_back (0);
936 all_horizon_paddings[d].push_back (0);
939 for (; i < elements.size (); i++)
941 if (to_boolean (elements[i]->get_property ("cross-staff")))
944 // Collect all the outside-staff grobs that have a particular priority.
945 SCM priority = elements[i]->get_property ("outside-staff-priority");
946 vector<Grob *> current_elts;
947 current_elts.push_back (elements[i]);
948 while (i + 1 < elements.size ()
949 && scm_is_eq (elements[i + 1]->get_property ("outside-staff-priority"), priority))
951 if (!to_boolean (elements[i + 1]->get_property ("cross-staff")))
952 current_elts.push_back (elements[i + 1]);
956 add_grobs_of_one_priority (me,
959 &all_horizon_paddings,
966 // Now everything in all_v_skylines has been shifted appropriately; merge
967 // them all into skylines to get the complete outline.
968 Skyline_pair other_skylines (all_v_skylines[UP]);
969 other_skylines.merge (Skyline_pair (all_v_skylines[DOWN]));
970 skylines.merge (other_skylines);
972 // We began by shifting my skyline to be relative to the common refpoint; now
974 skylines.shift (-me->relative_coordinate (x_common, X_AXIS));
979 MAKE_SCHEME_CALLBACK (Axis_group_interface, print, 1)
981 Axis_group_interface::print (SCM smob)
986 Grob *me = unsmob<Grob> (smob);
988 if (Skyline_pair *s = unsmob<Skyline_pair> (me->get_property ("vertical-skylines")))
990 ret.add_stencil (Lookup::points_to_line_stencil (0.1, (*s)[UP].to_points (X_AXIS))
991 .in_color (255, 0, 255));
992 ret.add_stencil (Lookup::points_to_line_stencil (0.1, (*s)[DOWN].to_points (X_AXIS))
993 .in_color (0, 255, 255));
995 return ret.smobbed_copy ();
998 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_pure_staff_staff_spacing, 3)
1000 Axis_group_interface::calc_pure_staff_staff_spacing (SCM smob, SCM start, SCM end)
1002 return calc_maybe_pure_staff_staff_spacing (unsmob<Grob> (smob),
1008 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_staff_staff_spacing, 1)
1010 Axis_group_interface::calc_staff_staff_spacing (SCM smob)
1012 return calc_maybe_pure_staff_staff_spacing (unsmob<Grob> (smob),
1019 Axis_group_interface::calc_maybe_pure_staff_staff_spacing (Grob *me, bool pure, int start, int end)
1021 Grob *grouper = unsmob<Grob> (me->get_object ("staff-grouper"));
1025 bool within_group = Staff_grouper_interface::maybe_pure_within_group (grouper, me, pure, start, end);
1027 return grouper->get_maybe_pure_property ("staff-staff-spacing", pure, start, end);
1029 return grouper->get_maybe_pure_property ("staffgroup-staff-spacing", pure, start, end);
1031 return me->get_maybe_pure_property ("default-staff-staff-spacing", pure, start, end);
1034 ADD_INTERFACE (Axis_group_interface,
1035 "An object that groups other layout objects.",
1037 // TODO: some of these properties are specific to
1038 // VerticalAxisGroup. We should split off a
1039 // vertical-axis-group-interface.
1041 "adjacent-pure-heights "
1043 "bound-alignment-interfaces "
1044 "default-staff-staff-spacing "
1048 "nonstaff-nonstaff-spacing "
1049 "nonstaff-relatedstaff-spacing "
1050 "nonstaff-unrelatedstaff-spacing "
1051 "pure-relevant-grobs "
1052 "pure-relevant-items "
1053 "pure-relevant-spanners "
1057 "staff-staff-spacing "