1 /*
2   This file is part of LilyPond, the GNU music typesetter.
3 
4   Copyright (C) 2000--2020 Han-Wen Nienhuys <hanwen@xs4all.nl>
5 
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.
10 
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.
15 
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/>.
18 */
19 
20 #include "axis-group-interface.hh"
21 
22 #include "align-interface.hh"
23 #include "directional-element-interface.hh"
24 #include "grob-array.hh"
25 #include "hara-kiri-group-spanner.hh"
26 #include "international.hh"
27 #include "interval-set.hh"
28 #include "lookup.hh"
29 #include "paper-column.hh"
30 #include "paper-score.hh"
31 #include "pointer-group-interface.hh"
32 #include "separation-item.hh"
33 #include "skyline-pair.hh"
34 #include "staff-grouper-interface.hh"
35 #include "stem.hh"
36 #include "stencil.hh"
37 #include "system.hh"
38 #include "warn.hh"
39 #include "unpure-pure-container.hh"
40 
41 #include <map>
42 
43 using std::multimap;
44 using std::pair;
45 using std::string;
46 using std::vector;
47 
48 static bool
49 pure_staff_priority_less (Grob *const &g1, Grob *const &g2);
50 
51 /* gets the relevant version of `g` in the context of a line running
52    from `start` to `end` */
53 Grob *
pure_subst_prebroken_piece(Grob * g,vsize start,vsize end)54 pure_subst_prebroken_piece (Grob *g, vsize start, vsize end)
55 {
56   Item *it = dynamic_cast<Item *> (g);
57   if (!it)
58     return g;
59 
60   if (it->get_column ()->get_rank () == int (start))
61     {
62       return it->find_prebroken_piece (RIGHT);
63     }
64   if (it->get_column ()->get_rank () == int (end))
65     {
66       return it->find_prebroken_piece (LEFT);
67     }
68 
69   return it;
70 }
71 
72 Real Axis_group_interface::default_outside_staff_padding_ = 0.46;
73 
74 Real
get_default_outside_staff_padding()75 Axis_group_interface::get_default_outside_staff_padding ()
76 {
77   return default_outside_staff_padding_;
78 }
79 
80 void
add_element(Grob * me,Grob * e)81 Axis_group_interface::add_element (Grob *me, Grob *e)
82 {
83   SCM axes = get_property (me, "axes");
84   if (!scm_is_pair (axes))
85     programming_error ("axes should be nonempty");
86 
87   for (SCM ax = axes; scm_is_pair (ax); ax = scm_cdr (ax))
88     {
89       Axis a = (Axis) scm_to_int (scm_car (ax));
90 
91       if (!e->get_parent (a))
92         e->set_parent (me, a);
93 
94       set_object (e, (a == X_AXIS)
95                   ? ly_symbol2scm ("axis-group-parent-X")
96                   : ly_symbol2scm ("axis-group-parent-Y"),
97                   me->self_scm ());
98     }
99 
100   /* must be ordered, because Align_interface also uses
101      Axis_group_interface  */
102   Pointer_group_interface::add_grob (me, ly_symbol2scm ("elements"), e);
103 }
104 
105 bool
has_axis(Grob * me,Axis a)106 Axis_group_interface::has_axis (Grob *me, Axis a)
107 {
108   SCM axes = get_property (me, "axes");
109 
110   return scm_is_true (scm_memq (to_scm (a), axes));
111 }
112 
113 Interval
relative_group_extent(vector<Grob * > const & elts,Grob * common,Axis a)114 Axis_group_interface::relative_group_extent (vector<Grob *> const &elts,
115                                              Grob *common, Axis a)
116 {
117   return relative_maybe_bound_group_extent (elts, common, a, false);
118 }
119 
120 Interval
relative_maybe_bound_group_extent(vector<Grob * > const & elts,Grob * common,Axis a,bool bound)121 Axis_group_interface::relative_maybe_bound_group_extent (vector<Grob *> const &elts,
122                                                          Grob *common, Axis a, bool bound)
123 {
124   Interval r;
125   for (vsize i = 0; i < elts.size (); i++)
126     {
127       Grob *se = elts[i];
128       if (!from_scm<bool> (get_property (se, "cross-staff")))
129         {
130           Interval dims = (bound && has_interface<Axis_group_interface> (se)
131                            ? generic_bound_extent (se, common, a)
132                            : se->extent (common, a));
133           if (!dims.is_empty ())
134             r.unite (dims);
135         }
136     }
137   return r;
138 }
139 
140 Interval
generic_bound_extent(Grob * me,Grob * common,Axis a)141 Axis_group_interface::generic_bound_extent (Grob *me, Grob *common, Axis a)
142 {
143   /* trigger the callback to do skyline-spacing on the children */
144   if (a == Y_AXIS)
145     (void) get_property (me, "vertical-skylines");
146 
147   extract_grob_set (me, "elements", elts);
148   vector<Grob *> new_elts;
149 
150   SCM interfaces = get_property (me, "bound-alignment-interfaces");
151 
152   for (vsize i = 0; i < elts.size (); i++)
153     for (SCM l = interfaces; scm_is_pair (l); l = scm_cdr (l))
154       if (elts[i]->internal_has_interface (scm_car (l)))
155         new_elts.push_back (elts[i]);
156 
157   if (!new_elts.size ())
158     return robust_relative_extent (me, common, a);
159 
160   if (!common)
161     common = common_refpoint_of_array (new_elts, me, a);
162 
163   return relative_maybe_bound_group_extent (new_elts, common, a, true);
164 }
165 
166 Interval
sum_partial_pure_heights(Grob * me,int start,int end)167 Axis_group_interface::sum_partial_pure_heights (Grob *me, int start, int end)
168 {
169   Interval iv = begin_of_line_pure_height (me, start);
170   iv.unite (rest_of_line_pure_height (me, start, end));
171 
172   return iv;
173 }
174 
175 Interval
part_of_line_pure_height(Grob * me,bool begin,vsize start,vsize end)176 Axis_group_interface::part_of_line_pure_height (Grob *me, bool begin, vsize start, vsize end)
177 {
178   Spanner *sp = dynamic_cast<Spanner *> (me);
179   if (!sp)
180     return Interval (0, 0);
181   SCM cache_symbol = begin
182                      ? ly_symbol2scm ("begin-of-line-pure-height")
183                      : ly_symbol2scm ("rest-of-line-pure-height");
184   SCM cached = sp->get_cached_pure_property (cache_symbol, start, end);
185   if (scm_is_pair (cached))
186     return from_scm (cached, Interval (0, 0));
187 
188   SCM adjacent_pure_heights = get_property (me, "adjacent-pure-heights");
189   Interval ret;
190 
191   if (!scm_is_pair (adjacent_pure_heights))
192     ret = Interval (0, 0);
193   else
194     {
195       SCM these_pure_heights = begin
196                                ? scm_car (adjacent_pure_heights)
197                                : scm_cdr (adjacent_pure_heights);
198 
199       if (scm_is_vector (these_pure_heights))
200         ret = combine_pure_heights (me, these_pure_heights, start, end);
201       else
202         ret = Interval (0, 0);
203     }
204 
205   sp->cache_pure_property (cache_symbol, start, end, to_scm (ret));
206   return ret;
207 }
208 
209 Interval
begin_of_line_pure_height(Grob * me,vsize start)210 Axis_group_interface::begin_of_line_pure_height (Grob *me, vsize start)
211 {
212   return part_of_line_pure_height (me, true, start, start + 1);
213 }
214 
215 Interval
rest_of_line_pure_height(Grob * me,vsize start,vsize end)216 Axis_group_interface::rest_of_line_pure_height (Grob *me, vsize start, vsize end)
217 {
218   return part_of_line_pure_height (me, false, start, end);
219 }
220 
221 Interval
combine_pure_heights(Grob * me,SCM measure_extents,vsize start,vsize end)222 Axis_group_interface::combine_pure_heights (Grob *me, SCM measure_extents,
223                                             vsize start, vsize end)
224 {
225   Paper_score *ps = get_root_system (me)->paper_score ();
226   vector<vsize> const &break_ranks = ps->get_break_ranks ();
227   auto it = lower_bound (break_ranks.begin (), break_ranks.end (), start);
228   vsize break_idx = it - break_ranks.begin ();
229   vector<vsize> const &breaks = ps->get_break_indices ();
230   vector<Paper_column *> const &cols = ps->get_columns ();
231 
232   Interval ext;
233   for (vsize i = break_idx; i + 1 < breaks.size (); i++)
234     {
235       vsize r = cols[breaks[i]]->get_rank ();
236       if (r >= end)
237         break;
238 
239       ext.unite (from_scm<Interval> (scm_c_vector_ref (measure_extents, i)));
240     }
241 
242   return ext;
243 }
244 
245 // adjacent-pure-heights is a pair of vectors, each of which has one element
246 // for every measure in the score. The first vector stores, for each measure,
247 // the combined height of the elements that are present only when the bar
248 // is at the beginning of a line. The second vector stores, for each measure,
249 // the combined height of the elements that are present only when the bar
250 // is not at the beginning of a line.
251 MAKE_SCHEME_CALLBACK (Axis_group_interface, adjacent_pure_heights, 1)
252 SCM
adjacent_pure_heights(SCM smob)253 Axis_group_interface::adjacent_pure_heights (SCM smob)
254 {
255   Grob *me = unsmob<Grob> (smob);
256 
257   Grob *common = unsmob<Grob> (get_object (me, "pure-Y-common"));
258   extract_grob_set (me, "pure-relevant-grobs", elts);
259 
260   Paper_score *ps = get_root_system (me)->paper_score ();
261   vector<vsize> const &ranks = ps->get_break_ranks ();
262 
263   vector<Interval> begin_line_heights;
264   vector<Interval> mid_line_heights;
265   vector<Interval> begin_line_staff_heights;
266   vector<Interval> mid_line_staff_heights;
267   begin_line_heights.resize (ranks.size () - 1);
268   mid_line_heights.resize (ranks.size () - 1);
269 
270   for (vsize i = 0; i < elts.size (); ++i)
271     {
272       Grob *g = elts[i];
273 
274       if (from_scm<bool> (get_property (g, "cross-staff")))
275         continue;
276 
277       if (!g->is_live ())
278         {
279           Item *it = dynamic_cast<Item *> (g);
280           if (!it)
281             continue;
282           if (!it->get_column ())
283             continue;
284         }
285 
286       bool outside_staff = scm_is_number (get_property (g, "outside-staff-priority"));
287       Real padding = from_scm<double> (get_property (g, "outside-staff-padding"), get_default_outside_staff_padding ());
288 
289       // When we encounter the first outside-staff grob, make a copy
290       // of the current heights to use as an estimate for the staff heights.
291       // Note that the outside-staff approximation that we use here doesn't
292       // consider any collisions that might occur between outside-staff grobs,
293       // but only the fact that outside-staff grobs may need to be raised above
294       // the staff.
295       if (outside_staff && begin_line_staff_heights.empty ())
296         {
297           begin_line_staff_heights = begin_line_heights;
298           mid_line_staff_heights = mid_line_heights;
299         }
300 
301       // TODO: consider a pure version of get_grob_direction?
302       Direction d = from_scm<Direction> (get_property_data (g, "direction"));
303       d = (d == CENTER) ? UP : d;
304 
305       Interval_t<vsize> rank_span (g->spanned_rank_interval ());
306       vsize first_break
307         = lower_bound (ranks, rank_span[LEFT], std::less<vsize> ());
308       if (first_break > 0 && ranks[first_break] >= rank_span[LEFT])
309         first_break--;
310 
311       for (vsize j = first_break;
312            j + 1 < ranks.size () && ranks[j] <= rank_span[RIGHT]; ++j)
313         {
314           vsize start = ranks[j];
315           vsize end = ranks[j + 1];
316 
317           // Take grobs that are visible with respect to a slightly longer line.
318           // Otherwise, we will never include grobs at breakpoints which aren't
319           // end-of-line-visible.
320           vsize visibility_end = j + 2 < ranks.size () ? ranks[j + 2] : end;
321 
322           Grob *maybe_subst
323             = pure_subst_prebroken_piece (g, start, visibility_end);
324           if (Item::break_visible (maybe_subst))
325             {
326               Interval dims = maybe_subst->pure_y_extent (common, start, end);
327               if (!dims.is_empty ())
328                 {
329                   if (rank_span[LEFT] <= start)
330                     {
331                       if (outside_staff)
332                         begin_line_heights[j].unite (begin_line_staff_heights[j].union_disjoint (dims, padding, d));
333                       else
334                         begin_line_heights[j].unite (dims);
335                     }
336                   if (rank_span[RIGHT] > start)
337                     {
338                       if (outside_staff)
339                         mid_line_heights[j].unite (mid_line_staff_heights[j].union_disjoint (dims, padding, d));
340                       else
341                         mid_line_heights[j].unite (dims);
342                     }
343                 }
344             }
345         }
346     }
347 
348   // Convert begin_line_heights and min_line_heights to SCM.
349   SCM begin_scm = scm_c_make_vector (ranks.size () - 1, SCM_EOL);
350   SCM mid_scm = scm_c_make_vector (ranks.size () - 1, SCM_EOL);
351   for (vsize i = 0; i < begin_line_heights.size (); ++i)
352     {
353       scm_c_vector_set_x (begin_scm, i,
354                           to_scm (begin_line_heights[i]));
355       scm_c_vector_set_x (mid_scm, i,
356                           to_scm (mid_line_heights[i]));
357     }
358 
359   return scm_cons (begin_scm, mid_scm);
360 }
361 
362 Interval
relative_pure_height(Grob * me,int start,int end)363 Axis_group_interface::relative_pure_height (Grob *me, int start, int end)
364 {
365   /* It saves a _lot_ of time if we assume a VerticalAxisGroup is additive
366      (ie. height (i, k) = std::max (height (i, j) height (j, k)) for all i <= j <= k).
367      Unfortunately, it isn't always true, particularly if there is a
368      VerticalAlignment somewhere in the descendants.
369 
370      Usually, the only VerticalAlignment comes from Score. This makes it
371      reasonably safe to assume that if our parent is a VerticalAlignment,
372      we can assume additivity and cache things nicely. */
373   Grob *p = me->get_y_parent ();
374   if (has_interface<Align_interface> (p))
375     return Axis_group_interface::sum_partial_pure_heights (me, start, end);
376 
377   Grob *common = unsmob<Grob> (get_object (me, "pure-Y-common"));
378   extract_grob_set (me, "pure-relevant-grobs", elts);
379 
380   Interval r;
381   for (vsize i = 0; i < elts.size (); i++)
382     {
383       Grob *g = pure_subst_prebroken_piece (elts[i], start, end);
384 
385       Interval_t<int> rank_span = g->spanned_rank_interval ();
386       if (rank_span[LEFT] <= end && rank_span[RIGHT] >= start
387           && Item::break_visible (g)
388           && !(from_scm<bool> (get_property (g, "cross-staff"))
389                && has_interface<Stem> (g)))
390         {
391           Interval dims = g->pure_y_extent (common, start, end);
392           if (!dims.is_empty ())
393             r.unite (dims);
394         }
395     }
396   return r;
397 }
398 
399 MAKE_SCHEME_CALLBACK (Axis_group_interface, width, 1);
400 SCM
width(SCM smob)401 Axis_group_interface::width (SCM smob)
402 {
403   Grob *me = unsmob<Grob> (smob);
404   return generic_group_extent (me, X_AXIS);
405 }
406 
407 MAKE_SCHEME_CALLBACK (Axis_group_interface, height, 1);
408 SCM
height(SCM smob)409 Axis_group_interface::height (SCM smob)
410 {
411   Grob *me = unsmob<Grob> (smob);
412   return generic_group_extent (me, Y_AXIS);
413 }
414 
415 MAKE_SCHEME_CALLBACK (Axis_group_interface, pure_height, 3);
416 SCM
pure_height(SCM smob,SCM start_scm,SCM end_scm)417 Axis_group_interface::pure_height (SCM smob, SCM start_scm, SCM end_scm)
418 {
419   int start = from_scm (start_scm, 0);
420   int end = from_scm (end_scm, INT_MAX);
421   Grob *me = unsmob<Grob> (smob);
422 
423   /* Maybe we are in the second pass of a two-pass spacing run. In that
424      case, the Y-extent of a system is already given to us */
425   System *system = dynamic_cast<System *> (me);
426   if (system)
427     {
428       SCM line_break_details = get_property (system->column (start), "line-break-system-details");
429       SCM system_y_extent = scm_assq (ly_symbol2scm ("system-Y-extent"), line_break_details);
430       if (scm_is_pair (system_y_extent))
431         return scm_cdr (system_y_extent);
432     }
433 
434   return to_scm (pure_group_height (me, start, end));
435 }
436 
437 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_skylines, 1);
438 SCM
calc_skylines(SCM smob)439 Axis_group_interface::calc_skylines (SCM smob)
440 {
441   Grob *me = unsmob<Grob> (smob);
442   Skyline_pair skylines = skyline_spacing (me);
443   return skylines.smobbed_copy ();
444 }
445 
446 /* whereas calc_skylines calculates skylines for axis-groups with a lot of
447    visible children, combine_skylines is designed for axis-groups whose only
448    children are other axis-groups (ie. VerticalAlignment). Rather than
449    calculating all the skylines from scratch, we just merge the skylines
450    of the children.
451 */
452 MAKE_SCHEME_CALLBACK (Axis_group_interface, combine_skylines, 1);
453 SCM
combine_skylines(SCM smob)454 Axis_group_interface::combine_skylines (SCM smob)
455 {
456   Grob *me = unsmob<Grob> (smob);
457   extract_grob_set (me, "elements", elements);
458   Grob *y_common = common_refpoint_of_array (elements, me, Y_AXIS);
459   Grob *x_common = common_refpoint_of_array (elements, me, X_AXIS);
460 
461   if (y_common != me)
462     programming_error ("combining skylines that don't belong to me");
463 
464   Skyline_pair ret;
465   for (vsize i = 0; i < elements.size (); i++)
466     {
467       SCM skyline_scm = get_property (elements[i], "vertical-skylines");
468       if (unsmob<Skyline_pair> (skyline_scm))
469         {
470           Real offset = elements[i]->relative_coordinate (y_common, Y_AXIS);
471           Skyline_pair other = *unsmob<Skyline_pair> (skyline_scm);
472           other.raise (offset);
473           other.shift (elements[i]->relative_coordinate (x_common, X_AXIS));
474           ret.merge (other);
475         }
476     }
477   return ret.smobbed_copy ();
478 }
479 
480 SCM
generic_group_extent(Grob * me,Axis a)481 Axis_group_interface::generic_group_extent (Grob *me, Axis a)
482 {
483   extract_grob_set (me, "elements", elts);
484 
485   /* trigger the callback to do skyline-spacing on the children */
486   if (a == Y_AXIS)
487     for (vsize i = 0; i < elts.size (); i++)
488       if (!(has_interface<Stem> (elts[i])
489             && from_scm<bool> (get_property (elts[i], "cross-staff"))))
490         (void) get_property (elts[i], "vertical-skylines");
491 
492   Grob *common = common_refpoint_of_array (elts, me, a);
493 
494   Real my_coord = me->relative_coordinate (common, a);
495   Interval r (relative_group_extent (elts, common, a));
496 
497   return to_scm (r - my_coord);
498 }
499 
500 /* This is like generic_group_extent, but it only counts the grobs that
501    are children of some other axis-group. This is uncached; if it becomes
502    commonly used, it may be necessary to cache it somehow. */
503 Interval
staff_extent(Grob * me,Grob * refp,Axis ext_a,Grob * staff,Axis parent_a)504 Axis_group_interface::staff_extent (Grob *me, Grob *refp, Axis ext_a, Grob *staff, Axis parent_a)
505 {
506   extract_grob_set (me, "elements", elts);
507   vector<Grob *> new_elts;
508 
509   for (vsize i = 0; i < elts.size (); i++)
510     if (elts[i]->common_refpoint (staff, parent_a) == staff)
511       new_elts.push_back (elts[i]);
512 
513   return relative_group_extent (new_elts, refp, ext_a);
514 }
515 
516 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_pure_relevant_grobs, 1);
517 SCM
calc_pure_relevant_grobs(SCM smob)518 Axis_group_interface::calc_pure_relevant_grobs (SCM smob)
519 {
520   Grob *me = unsmob<Grob> (smob);
521   /* TODO: Filter out elements that belong to a different Axis_group,
522      such as the tie in
523      << \new Staff=A { c'1~ \change Staff=B c'}
524         \new Staff=B { \clef bass R1 R } >>
525     because thier location relative to this Axis_group is not known before
526     page layout.  For now, we need to trap this case in calc_pure_y_common.
527   */
528   return internal_calc_pure_relevant_grobs (me, "elements");
529 }
530 
531 SCM
internal_calc_pure_relevant_grobs(Grob * me,const string & grob_set_name)532 Axis_group_interface::internal_calc_pure_relevant_grobs (Grob *me, const string &grob_set_name)
533 {
534   extract_grob_set (me, grob_set_name.c_str (), elts);
535 
536   vector<Grob *> relevant_grobs;
537 
538   for (vsize i = 0; i < elts.size (); i++)
539     {
540       if (Item *it = dynamic_cast<Item *> (elts[i]))
541         {
542           if (it->original ())
543             continue;
544         }
545       /* This might include potentially suicided items. Callers should
546          look at the relevant prebroken clone where necesary */
547       relevant_grobs.push_back (elts[i]);
548     }
549 
550   vector_sort (relevant_grobs, pure_staff_priority_less);
551   SCM grobs_scm = Grob_array::make_array ();
552   unsmob<Grob_array> (grobs_scm)->set_array (relevant_grobs);
553 
554   return grobs_scm;
555 }
556 
557 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_pure_y_common, 1);
558 SCM
calc_pure_y_common(SCM smob)559 Axis_group_interface::calc_pure_y_common (SCM smob)
560 {
561   Grob *me = unsmob<Grob> (smob);
562 
563   extract_grob_set (me, "pure-relevant-grobs", elts);
564   Grob *common = common_refpoint_of_array (elts, me, Y_AXIS);
565   if (common != me && has_interface<Align_interface> (common))
566     {
567       me->programming_error ("My pure_y_common is a VerticalAlignment,"
568                              " which might contain several staves.");
569       common = me;
570     }
571   if (!common)
572     {
573       me->programming_error ("No common parent found in calc_pure_y_common.");
574       return SCM_EOL;
575     }
576 
577   return common->self_scm ();
578 }
579 
580 SCM
calc_common(Grob * me,Axis axis)581 Axis_group_interface::calc_common (Grob *me, Axis axis)
582 {
583   extract_grob_set (me, "elements", elts);
584   Grob *common = common_refpoint_of_array (elts, me, axis);
585   if (!common)
586     {
587       me->programming_error ("No common parent found in calc_common axis.");
588       return SCM_EOL;
589     }
590 
591   return common->self_scm ();
592 }
593 
594 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_x_common, 1);
595 SCM
calc_x_common(SCM grob)596 Axis_group_interface::calc_x_common (SCM grob)
597 {
598   return calc_common (unsmob<Grob> (grob), X_AXIS);
599 }
600 
601 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_y_common, 1);
602 SCM
calc_y_common(SCM grob)603 Axis_group_interface::calc_y_common (SCM grob)
604 {
605   return calc_common (unsmob<Grob> (grob), Y_AXIS);
606 }
607 
608 Interval
pure_group_height(Grob * me,int start,int end)609 Axis_group_interface::pure_group_height (Grob *me, int start, int end)
610 {
611   Grob *common = unsmob<Grob> (get_object (me, "pure-Y-common"));
612 
613   if (!common)
614     {
615       programming_error ("no pure Y common refpoint");
616       return Interval ();
617     }
618   Real my_coord = me->pure_relative_y_coordinate (common, start, end);
619   Interval r (relative_pure_height (me, start, end));
620 
621   return r - my_coord;
622 }
623 
624 void
get_children(Grob * me,vector<Grob * > * found)625 Axis_group_interface::get_children (Grob *me, vector<Grob *> *found)
626 {
627   found->push_back (me);
628 
629   if (!has_interface<Axis_group_interface> (me))
630     return;
631 
632   extract_grob_set (me, "elements", elements);
633   for (vsize i = 0; i < elements.size (); i++)
634     {
635       Grob *e = elements[i];
636       Axis_group_interface::get_children (e, found);
637     }
638 }
639 
640 static bool
staff_priority_less(Grob * const & g1,Grob * const & g2)641 staff_priority_less (Grob *const &g1, Grob *const &g2)
642 {
643   Real priority_1 = from_scm<double> (get_property (g1, "outside-staff-priority"), -infinity_f);
644   Real priority_2 = from_scm<double> (get_property (g2, "outside-staff-priority"), -infinity_f);
645 
646   if (priority_1 < priority_2)
647     return true;
648   else if (priority_1 > priority_2)
649     return false;
650 
651   /* if neither grob has an outside-staff priority, the ordering will have no
652      effect and we assume the two grobs to be equal (none of the two is less).
653      We do this to avoid the side-effect of calculating extents. */
654   if (std::isinf (priority_1) && std::isinf (priority_2))
655     return false;
656 
657   /* if there is no preference in staff priority, choose the left-most one */
658   Grob *common = g1->common_refpoint (g2, X_AXIS);
659   Real start_1 = g1->extent (common, X_AXIS)[LEFT];
660   Real start_2 = g2->extent (common, X_AXIS)[LEFT];
661   return start_1 < start_2;
662 }
663 
664 static bool
pure_staff_priority_less(Grob * const & g1,Grob * const & g2)665 pure_staff_priority_less (Grob *const &g1, Grob *const &g2)
666 {
667   Grob *p[2] = {g1, g2};
668   for (int i = 0; i < 2; i++)
669     {
670       Item *it = dynamic_cast<Item *> (p[i]);
671       if (it && !it->is_live ())
672         {
673           for (LEFT_and_RIGHT (d))
674             {
675               Item *b = it->find_prebroken_piece (d);
676               if (b && b->is_live ())
677                 {
678                   p[i] = b;
679                   break;
680                 }
681             }
682         }
683     }
684 
685   Real priority_1 = from_scm<double> (get_property (p[0], "outside-staff-priority"), -infinity_f);
686   Real priority_2 = from_scm<double> (get_property (p[1], "outside-staff-priority"), -infinity_f);
687 
688   return priority_1 < priority_2;
689 }
690 
691 // Raises the grob elt (whose skylines are given by h_skyline
692 // and v_skyline) so that it doesn't intersect with staff_skyline,
693 // or with anything in other_h_skylines and other_v_skylines.
694 void
avoid_outside_staff_collisions(Grob * elt,Skyline_pair * v_skyline,Real padding,Real horizon_padding,vector<Skyline_pair> const & other_v_skylines,vector<Real> const & other_padding,vector<Real> const & other_horizon_padding,Direction const dir)695 avoid_outside_staff_collisions (Grob *elt,
696                                 Skyline_pair *v_skyline,
697                                 Real padding,
698                                 Real horizon_padding,
699                                 vector<Skyline_pair> const &other_v_skylines,
700                                 vector<Real> const &other_padding,
701                                 vector<Real> const &other_horizon_padding,
702                                 Direction const dir)
703 {
704   assert (other_v_skylines.size () == other_padding.size ());
705   assert (other_v_skylines.size () == other_horizon_padding.size ());
706   vector<Interval> forbidden_intervals;
707   for (vsize j = 0; j < other_v_skylines.size (); j++)
708     {
709       Skyline_pair const &v_other = other_v_skylines[j];
710       Real pad = std::max (padding, other_padding[j]);
711       Real horizon_pad = std::max (horizon_padding, other_horizon_padding[j]);
712 
713       // We need to push elt up by at least this much to be above v_other.
714       Real up = (*v_skyline)[DOWN].distance (v_other[UP], horizon_pad) + pad;
715       // We need to push elt down by at least this much to be below v_other.
716       Real down = (*v_skyline)[UP].distance (v_other[DOWN], horizon_pad) + pad;
717 
718       forbidden_intervals.push_back (Interval (-down, up));
719     }
720 
721   Interval_set allowed_shifts
722     = Interval_set::interval_union (forbidden_intervals).complement ();
723   Real move = allowed_shifts.nearest_point (0, dir);
724   v_skyline->raise (move);
725   elt->translate_axis (move, Y_AXIS);
726 }
727 
728 SCM
valid_outside_staff_placement_directive(Grob * me)729 valid_outside_staff_placement_directive (Grob *me)
730 {
731   SCM directive = get_property (me, "outside-staff-placement-directive");
732 
733   if (scm_is_eq (directive, ly_symbol2scm ("left-to-right-greedy"))
734       || scm_is_eq (directive, ly_symbol2scm ("left-to-right-polite"))
735       || scm_is_eq (directive, ly_symbol2scm ("right-to-left-greedy"))
736       || scm_is_eq (directive, ly_symbol2scm ("right-to-left-polite")))
737     return directive;
738 
739   me->warning (_f ("\"%s\" is not a valid outside-staff-placement-directive",
740                    robust_symbol2string (directive, "").c_str ()));
741 
742   return ly_symbol2scm ("left-to-right-polite");
743 }
744 
745 // Shifts the grobs in elements to ensure that they (and any
746 // connected riders) don't collide with the staff skylines
747 // or anything in all_X_skylines.  Afterwards, the skylines
748 // of the grobs in elements will be added to all_v_skylines.
749 static void
add_grobs_of_one_priority(Grob * me,Drul_array<vector<Skyline_pair>> * all_v_skylines,Drul_array<vector<Real>> * all_paddings,Drul_array<vector<Real>> * all_horizon_paddings,vector<Grob * > elements,Grob * x_common,Grob * y_common,multimap<Grob *,Grob * > const & riders)750 add_grobs_of_one_priority (Grob *me,
751                            Drul_array<vector<Skyline_pair> > *all_v_skylines,
752                            Drul_array<vector<Real> > *all_paddings,
753                            Drul_array<vector<Real> > *all_horizon_paddings,
754                            vector<Grob *> elements,
755                            Grob *x_common,
756                            Grob *y_common,
757                            multimap<Grob *, Grob *> const &riders)
758 {
759 
760   SCM directive
761     = valid_outside_staff_placement_directive (me);
762 
763   bool l2r = (scm_is_eq (directive, ly_symbol2scm ("left-to-right-greedy"))
764               || scm_is_eq (directive, ly_symbol2scm ("left-to-right-polite")));
765 
766   bool polite = (scm_is_eq (directive, ly_symbol2scm ("left-to-right-polite"))
767                  || scm_is_eq (directive, ly_symbol2scm ("right-to-left-polite")));
768 
769   vector<Box> boxes;
770   vector<Skyline_pair> skylines_to_merge;
771 
772   // We want to avoid situations like this:
773   //           still more text
774   //      more text
775   //   text
776   //   -------------------
777   //   staff
778   //   -------------------
779 
780   // The point is that "still more text" should be positioned under
781   // "more text".  In order to achieve this, we place the grobs in several
782   // passes.  We keep track of the right-most horizontal position that has been
783   // affected by the current pass so far (actually we keep track of 2
784   // positions, one for above the staff, one for below).
785 
786   // In each pass, we loop through the unplaced grobs from left to right.
787   // If the grob doesn't overlap the right-most affected position, we place it
788   // (and then update the right-most affected position to point to the right
789   // edge of the just-placed grob).  Otherwise, we skip it until the next pass.
790   while (!elements.empty ())
791     {
792       Drul_array<Real> last_end (-infinity_f, -infinity_f);
793       vector<Grob *> skipped_elements;
794       for (vsize i = l2r ? 0 : elements.size ();
795            l2r ? i < elements.size () : i--;
796            l2r ? i++ : 0)
797         {
798           Grob *elt = elements[i];
799           Real padding
800             = from_scm<double> (get_property (elt, "outside-staff-padding"),
801                                 Axis_group_interface
802                                 ::get_default_outside_staff_padding ());
803           Real horizon_padding
804             = from_scm<double> (get_property (elt, "outside-staff-horizontal-padding"), 0.0);
805           Interval x_extent = elt->extent (x_common, X_AXIS);
806           x_extent.widen (horizon_padding);
807 
808           Direction dir = get_grob_direction (elt);
809           if (dir == CENTER)
810             {
811               warning (_ ("an outside-staff object should have a direction, defaulting to up"));
812               dir = UP;
813             }
814 
815           if (x_extent[LEFT] <= last_end[dir] && polite)
816             {
817               skipped_elements.push_back (elt);
818               continue;
819             }
820           last_end[dir] = x_extent[RIGHT];
821 
822           Skyline_pair *v_orig = unsmob<Skyline_pair> (get_property (elt, "vertical-skylines"));
823           if (!v_orig || v_orig->is_empty ())
824             continue;
825 
826           // Find the riders associated with this grob, and merge their
827           // skylines with elt's skyline.
828           typedef multimap<Grob *, Grob *>::const_iterator GrobMapIterator;
829           pair<GrobMapIterator, GrobMapIterator> range = riders.equal_range (elt);
830           vector<Skyline_pair> rider_v_skylines;
831           for (GrobMapIterator j = range.first; j != range.second; j++)
832             {
833               Grob *rider = j->second;
834               Skyline_pair *v_rider = unsmob<Skyline_pair> (get_property (rider, "vertical-skylines"));
835               if (v_rider)
836                 {
837                   Skyline_pair copy (*v_rider);
838                   copy.shift (rider->relative_coordinate (x_common, X_AXIS));
839                   copy.raise (rider->relative_coordinate (y_common, Y_AXIS));
840                   rider_v_skylines.push_back (copy);
841                 }
842             }
843           Skyline_pair v_skylines (*v_orig);
844           v_skylines.shift (elt->relative_coordinate (x_common, X_AXIS));
845           v_skylines.raise (elt->relative_coordinate (y_common, Y_AXIS));
846           v_skylines.merge (Skyline_pair (rider_v_skylines));
847 
848           avoid_outside_staff_collisions (elt,
849                                           &v_skylines,
850                                           padding,
851                                           horizon_padding,
852                                           (*all_v_skylines)[dir],
853                                           (*all_paddings)[dir],
854                                           (*all_horizon_paddings)[dir],
855                                           dir);
856 
857           set_property (elt, "outside-staff-priority", SCM_BOOL_F);
858           (*all_v_skylines)[dir].push_back (v_skylines);
859           (*all_paddings)[dir].push_back (padding);
860           (*all_horizon_paddings)[dir].push_back (horizon_padding);
861         }
862       std::swap (elements, skipped_elements);
863       skipped_elements.clear ();
864     }
865 }
866 
867 // If the Grob has a Y-ancestor with outside-staff-priority, return it.
868 // Otherwise, return 0.
869 Grob *
outside_staff_ancestor(Grob * me)870 Axis_group_interface::outside_staff_ancestor (Grob *me)
871 {
872   Grob *parent = me->get_y_parent ();
873   if (!parent)
874     return 0;
875 
876   if (scm_is_number (get_property (parent, "outside-staff-priority")))
877     return parent;
878 
879   return outside_staff_ancestor (parent);
880 }
881 
882 // It is tricky to correctly handle skyline placement of cross-staff grobs.
883 // For example, cross-staff beams cannot be formatted until the distance between
884 // staves is known and therefore any grobs that depend on the beam cannot be placed
885 // until the skylines are known. On the other hand, the distance between staves should
886 // really depend on position of the cross-staff grobs that lie between them.
887 // Currently, we just leave cross-staff grobs out of the
888 // skyline altogether, but this could mean that staves are placed so close together
889 // that there is no room for the cross-staff grob. It also means, of course, that
890 // we don't get the benefits of skyline placement for cross-staff grobs.
891 Skyline_pair
skyline_spacing(Grob * me)892 Axis_group_interface::skyline_spacing (Grob *me)
893 {
894   extract_grob_set (me,
895                     unsmob<Grob_array> (get_object (me, "vertical-skyline-elements"))
896                     ? "vertical-skyline-elements"
897                     : "elements",
898                     fakeelements);
899   vector<Grob *> elements (fakeelements);
900   for (vsize i = 0; i < elements.size (); i++)
901     /*
902       As a sanity check, we make sure that no grob with an outside staff priority
903       has a Y-parent that also has an outside staff priority, which would result
904       in two movings.
905     */
906     if (scm_is_number (get_property (elements[i], "outside-staff-priority"))
907         && outside_staff_ancestor (elements[i]))
908       {
909         elements[i]->warning (_ ("Cannot set outside-staff-priority for element and elements' Y parent."));
910         set_property (elements[i], "outside-staff-priority", SCM_BOOL_F);
911       }
912 
913   /* For grobs with an outside-staff-priority, the sorting function might
914      call extent and cause suicide. This breaks the contract that is required
915      for the STL sort function. To avoid this, we make sure that any suicides
916      are triggered beforehand.
917   */
918   for (vsize i = 0; i < elements.size (); i++)
919     if (scm_is_number (get_property (elements[i], "outside-staff-priority")))
920       elements[i]->extent (elements[i], X_AXIS);
921 
922   vector_stable_sort (elements, staff_priority_less);
923   Grob *x_common = common_refpoint_of_array (elements, me, X_AXIS);
924   Grob *y_common = common_refpoint_of_array (elements, me, Y_AXIS);
925 
926   if (y_common != me)
927     {
928       me->programming_error ("Some of my vertical-skyline-elements"
929                              " are outside my VerticalAxisGroup.");
930       y_common = me;
931     }
932 
933   // A rider is a grob that is not outside-staff, but has an outside-staff
934   // ancestor.  In that case, the rider gets moved along with its ancestor.
935   multimap<Grob *, Grob *> riders;
936 
937   vsize i = 0;
938   vector<Skyline_pair> inside_staff_skylines;
939 
940   for (i = 0;
941        i < elements.size ()
942        && !scm_is_number (get_property (elements[i], "outside-staff-priority"));
943        i++)
944     {
945       Grob *elt = elements[i];
946       if (Grob *ancestor = outside_staff_ancestor (elt))
947         riders.insert (pair<Grob *, Grob *> (ancestor, elt));
948       else if (!from_scm<bool> (get_property (elt, "cross-staff")))
949         {
950           Skyline_pair *maybe_pair
951             = unsmob<Skyline_pair> (get_property (elt, "vertical-skylines"));
952           if (!maybe_pair)
953             continue;
954           if (maybe_pair->is_empty ())
955             continue;
956           inside_staff_skylines.push_back (*maybe_pair);
957           inside_staff_skylines.back ().shift (elt->relative_coordinate (x_common, X_AXIS));
958           inside_staff_skylines.back ().raise (elt->relative_coordinate (y_common, Y_AXIS));
959         }
960     }
961 
962   Skyline_pair skylines (inside_staff_skylines);
963 
964   // These are the skylines of all outside-staff grobs
965   // that have already been processed.  We keep them around in order to
966   // check them for collisions with the currently active outside-staff grob.
967   Drul_array<vector<Skyline_pair> > all_v_skylines;
968   Drul_array<vector<Real> > all_paddings;
969   Drul_array<vector<Real> > all_horizon_paddings;
970   for (UP_and_DOWN (d))
971     {
972       all_v_skylines[d].push_back (skylines);
973       all_paddings[d].push_back (0);
974       all_horizon_paddings[d].push_back (0);
975     }
976 
977   for (; i < elements.size (); i++)
978     {
979       if (from_scm<bool> (get_property (elements[i], "cross-staff")))
980         continue;
981 
982       // Collect all the outside-staff grobs that have a particular priority.
983       SCM priority = get_property (elements[i], "outside-staff-priority");
984       vector<Grob *> current_elts;
985       current_elts.push_back (elements[i]);
986       while (i + 1 < elements.size ()
987              && ly_is_equal (get_property (elements[i + 1], "outside-staff-priority"), priority))
988         {
989           if (!from_scm<bool> (get_property (elements[i + 1], "cross-staff")))
990             current_elts.push_back (elements[i + 1]);
991           ++i;
992         }
993 
994       add_grobs_of_one_priority (me,
995                                  &all_v_skylines,
996                                  &all_paddings,
997                                  &all_horizon_paddings,
998                                  current_elts,
999                                  x_common,
1000                                  y_common,
1001                                  riders);
1002     }
1003 
1004   // Now everything in all_v_skylines has been shifted appropriately; merge
1005   // them all into skylines to get the complete outline.
1006   Skyline_pair other_skylines (all_v_skylines[UP]);
1007   other_skylines.merge (Skyline_pair (all_v_skylines[DOWN]));
1008   skylines.merge (other_skylines);
1009 
1010   // We began by shifting my skyline to be relative to the common refpoint; now
1011   // shift it back.
1012   skylines.shift (-me->relative_coordinate (x_common, X_AXIS));
1013 
1014   return skylines;
1015 }
1016 
1017 MAKE_SCHEME_CALLBACK (Axis_group_interface, print, 1)
1018 SCM
print(SCM smob)1019 Axis_group_interface::print (SCM smob)
1020 {
1021   if (!debug_skylines)
1022     return SCM_BOOL_F;
1023 
1024   Grob *me = unsmob<Grob> (smob);
1025   Stencil ret;
1026   if (Skyline_pair *s = unsmob<Skyline_pair> (get_property (me, "vertical-skylines")))
1027     {
1028       ret.add_stencil (Lookup::points_to_line_stencil (0.1, (*s)[UP].to_points (X_AXIS))
1029                        .in_color (1.0, 0.0, 1.0));
1030       ret.add_stencil (Lookup::points_to_line_stencil (0.1, (*s)[DOWN].to_points (X_AXIS))
1031                        .in_color (0.0, 1.0, 1.0));
1032     }
1033   return ret.smobbed_copy ();
1034 }
1035 
1036 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_pure_staff_staff_spacing, 3)
1037 SCM
calc_pure_staff_staff_spacing(SCM smob,SCM start,SCM end)1038 Axis_group_interface::calc_pure_staff_staff_spacing (SCM smob, SCM start, SCM end)
1039 {
1040   return calc_maybe_pure_staff_staff_spacing (unsmob<Grob> (smob),
1041                                               true,
1042                                               scm_to_int (start),
1043                                               scm_to_int (end));
1044 }
1045 
1046 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_staff_staff_spacing, 1)
1047 SCM
calc_staff_staff_spacing(SCM smob)1048 Axis_group_interface::calc_staff_staff_spacing (SCM smob)
1049 {
1050   return calc_maybe_pure_staff_staff_spacing (unsmob<Grob> (smob),
1051                                               false,
1052                                               0,
1053                                               INT_MAX);
1054 }
1055 
1056 SCM
calc_maybe_pure_staff_staff_spacing(Grob * me,bool pure,int start,int end)1057 Axis_group_interface::calc_maybe_pure_staff_staff_spacing (Grob *me, bool pure, int start, int end)
1058 {
1059   Grob *grouper = unsmob<Grob> (get_object (me, "staff-grouper"));
1060 
1061   if (grouper)
1062     {
1063       bool within_group = Staff_grouper_interface::maybe_pure_within_group (grouper, me, pure, start, end);
1064       if (within_group)
1065         return get_maybe_pure_property (grouper, "staff-staff-spacing", pure, start, end);
1066       else
1067         return get_maybe_pure_property (grouper, "staffgroup-staff-spacing", pure, start, end);
1068     }
1069   return get_maybe_pure_property (me, "default-staff-staff-spacing", pure, start, end);
1070 }
1071 
1072 ADD_INTERFACE (Axis_group_interface,
1073                "An object that groups other layout objects.",
1074 
1075                // TODO: some of these properties are specific to
1076                // VerticalAxisGroup. We should split off a
1077                // vertical-axis-group-interface.
1078                /* properties */
1079                "adjacent-pure-heights "
1080                "axes "
1081                "bound-alignment-interfaces "
1082                "default-staff-staff-spacing "
1083                "elements "
1084                "no-alignment "
1085                "nonstaff-nonstaff-spacing "
1086                "nonstaff-relatedstaff-spacing "
1087                "nonstaff-unrelatedstaff-spacing "
1088                "pure-relevant-grobs "
1089                "pure-relevant-items "
1090                "pure-relevant-spanners "
1091                "pure-Y-common "
1092                "staff-affinity "
1093                "staff-grouper "
1094                "staff-staff-spacing "
1095                "system-Y-offset "
1096                "X-common "
1097                "Y-common "
1098               );
1099