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