Workaround for broken MusicXML files (percussion clef in MuseScore)
[lilypond.git] / lily / axis-group-interface.cc
blob639c8b6267a12a6450cfe7739009e7e6b311cdbb
1 /*
2 axis-group-interface.cc -- implement Axis_group_interface
4 source file of the GNU LilyPond music typesetter
6 (c) 2000--2009 Han-Wen Nienhuys <hanwen@xs4all.nl>
7 */
9 #include "axis-group-interface.hh"
11 #include "align-interface.hh"
12 #include "directional-element-interface.hh"
13 #include "pointer-group-interface.hh"
14 #include "grob-array.hh"
15 #include "hara-kiri-group-spanner.hh"
16 #include "international.hh"
17 #include "lookup.hh"
18 #include "paper-column.hh"
19 #include "paper-score.hh"
20 #include "separation-item.hh"
21 #include "skyline-pair.hh"
22 #include "stencil.hh"
23 #include "system.hh"
24 #include "warn.hh"
26 void
27 Axis_group_interface::add_element (Grob *me, Grob *e)
29 SCM axes = me->get_property ("axes");
30 if (!scm_is_pair (axes))
31 programming_error ("axes should be nonempty");
33 for (SCM ax = axes; scm_is_pair (ax); ax = scm_cdr (ax))
35 Axis a = (Axis) scm_to_int (scm_car (ax));
37 if (!e->get_parent (a))
38 e->set_parent (me, a);
40 e->set_object ((a == X_AXIS)
41 ? ly_symbol2scm ("axis-group-parent-X")
42 : ly_symbol2scm ("axis-group-parent-Y"),
43 me->self_scm ());
46 /* must be ordered, because Align_interface also uses
47 Axis_group_interface */
48 Pointer_group_interface::add_grob (me, ly_symbol2scm ("elements"), e);
51 bool
52 Axis_group_interface::has_axis (Grob *me, Axis a)
54 SCM axes = me->get_property ("axes");
56 return (SCM_BOOL_F != scm_memq (scm_from_int (a), axes));
59 Interval
60 Axis_group_interface::relative_group_extent (vector<Grob*> const &elts,
61 Grob *common, Axis a)
63 Interval r;
64 for (vsize i = 0; i < elts.size (); i++)
66 Grob *se = elts[i];
67 if (!to_boolean (se->get_property ("cross-staff")))
69 Interval dims = se->extent (common, a);
70 if (!dims.is_empty ())
71 r.unite (dims);
74 return r;
79 FIXME: pure extent handling has a lot of ad-hoc caching.
80 This should be done with grob property callbacks.
82 --hwn
85 Interval
86 Axis_group_interface::cached_pure_height (Grob *me, int start, int end)
88 Paper_score *ps = get_root_system (me)->paper_score ();
89 vector<vsize> breaks = ps->get_break_indices ();
90 vector<Grob*> cols = ps->get_columns ();
92 SCM extents = me->get_property ("adjacent-pure-heights");
94 if (!scm_is_vector (extents))
95 return Interval (0, 0);
97 Interval ext;
98 for (vsize i = 0; i + 1 < breaks.size (); i++)
100 int r = Paper_column::get_rank (cols[breaks[i]]);
101 if (r >= end)
102 break;
104 if (r >= start)
105 ext.unite (ly_scm2interval (scm_c_vector_ref (extents, i)));
108 return ext;
111 MAKE_SCHEME_CALLBACK (Axis_group_interface, adjacent_pure_heights, 1)
113 Axis_group_interface::adjacent_pure_heights (SCM smob)
115 Grob *me = unsmob_grob (smob);
117 Grob *common = calc_pure_elts_and_common (me);
118 extract_grob_set (me, "pure-relevant-items", items);
119 extract_grob_set (me, "pure-relevant-spanners", spanners);
121 Paper_score *ps = get_root_system (me)->paper_score ();
122 vector<vsize> breaks = ps->get_break_indices ();
123 vector<Grob*> cols = ps->get_columns ();
125 SCM ret = scm_c_make_vector (breaks.size () - 1, SCM_EOL);
126 vsize it_index = 0;
127 for (vsize i = 0; i + 1 < breaks.size (); i++)
129 int start = Paper_column::get_rank (cols[breaks[i]]);
130 int end = Paper_column::get_rank (cols[breaks[i+1]]);
131 Interval iv;
133 for (vsize j = it_index; j < items.size (); j++)
135 Item *it = dynamic_cast<Item*> (items[j]);
136 int rank = it->get_column ()->get_rank ();
138 if (rank <= end && it->pure_is_visible (start, end)
139 && !to_boolean (it->get_property ("cross-staff")))
141 Interval dims = items[j]->pure_height (common, start, end);
142 if (!dims.is_empty ())
143 iv.unite (dims);
146 if (rank < end)
147 it_index++;
148 else if (rank > end)
149 break;
152 for (vsize j = 0; j < spanners.size (); j++)
154 Interval_t<int> rank_span = spanners[j]->spanned_rank_interval ();
155 if (rank_span[LEFT] <= end && rank_span[RIGHT] >= start
156 && !to_boolean (spanners[j]->get_property ("cross-staff")))
158 Interval dims = spanners[j]->pure_height (common, start, end);
159 if (!dims.is_empty ())
160 iv.unite (dims);
164 scm_vector_set_x (ret, scm_from_int (i), ly_interval2scm (iv));
166 return ret;
169 Interval
170 Axis_group_interface::relative_pure_height (Grob *me, int start, int end)
172 /* It saves a _lot_ of time if we assume a VerticalAxisGroup is additive
173 (ie. height (i, k) = max (height (i, j) height (j, k)) for all i <= j <= k).
174 Unfortunately, it isn't always true, particularly if there is a
175 VerticalAlignment somewhere in the descendants.
177 Apart from PianoStaff, which has a fixed VerticalAlignment so it doesn't
178 count, the only VerticalAlignment comes from Score. This makes it
179 reasonably safe to assume that if our parent is a VerticalAlignment,
180 we can assume additivity and cache things nicely. */
181 Grob *p = me->get_parent (Y_AXIS);
182 if (p && Align_interface::has_interface (p))
183 return Axis_group_interface::cached_pure_height (me, start, end);
185 Grob *common = calc_pure_elts_and_common (me);
186 extract_grob_set (me, "pure-relevant-items", items);
187 extract_grob_set (me, "pure-relevant-spanners", spanners);
189 Interval r;
191 for (vsize i = 0; i < items.size (); i++)
193 Item *it = dynamic_cast<Item*> (items[i]);
194 int rank = it->get_column ()->get_rank ();
196 if (rank > end)
197 break;
198 else if (rank >= start && it->pure_is_visible (start, end)
199 && !to_boolean (it->get_property ("cross-staff")))
201 Interval dims = it->pure_height (common, start, end);
202 if (!dims.is_empty ())
203 r.unite (dims);
207 for (vsize i = 0; i < spanners.size (); i++)
209 Interval_t<int> rank_span = spanners[i]->spanned_rank_interval ();
210 if (rank_span[LEFT] <= end && rank_span[RIGHT] >= start
211 && !to_boolean (spanners[i]->get_property ("cross-staff")))
213 Interval dims = spanners[i]->pure_height (common, start, end);
214 if (!dims.is_empty ())
215 r.unite (dims);
218 return r;
221 MAKE_SCHEME_CALLBACK (Axis_group_interface, width, 1);
223 Axis_group_interface::width (SCM smob)
225 Grob *me = unsmob_grob (smob);
226 return generic_group_extent (me, X_AXIS);
229 MAKE_SCHEME_CALLBACK (Axis_group_interface, height, 1);
231 Axis_group_interface::height (SCM smob)
233 Grob *me = unsmob_grob (smob);
234 return generic_group_extent (me, Y_AXIS);
237 MAKE_SCHEME_CALLBACK (Axis_group_interface, pure_height, 3);
239 Axis_group_interface::pure_height (SCM smob, SCM start_scm, SCM end_scm)
241 int start = robust_scm2int (start_scm, 0);
242 int end = robust_scm2int (end_scm, INT_MAX);
243 Grob *me = unsmob_grob (smob);
245 /* Maybe we are in the second pass of a two-pass spacing run. In that
246 case, the Y-extent of a system is already given to us */
247 System *system = dynamic_cast<System*> (me);
248 if (system)
250 SCM line_break_details = system->column (start)->get_property ("line-break-system-details");
251 SCM system_y_extent = scm_assq (ly_symbol2scm ("system-Y-extent"), line_break_details);
252 if (scm_is_pair (system_y_extent))
253 return scm_cdr (system_y_extent);
256 return ly_interval2scm (pure_group_height (me, start, end));
259 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_skylines, 1);
261 Axis_group_interface::calc_skylines (SCM smob)
263 Grob *me = unsmob_grob (smob);
264 extract_grob_set (me, "elements", elts);
265 Skyline_pair skylines = skyline_spacing (me, elts);
267 return skylines.smobbed_copy ();
270 /* whereas calc_skylines calculates skylines for axis-groups with a lot of
271 visible children, combine_skylines is designed for axis-groups whose only
272 children are other axis-groups (ie. VerticalAlignment). Rather than
273 calculating all the skylines from scratch, we just merge the skylines
274 of the children.
276 MAKE_SCHEME_CALLBACK (Axis_group_interface, combine_skylines, 1);
278 Axis_group_interface::combine_skylines (SCM smob)
280 Grob *me = unsmob_grob (smob);
281 extract_grob_set (me, "elements", elements);
282 Grob *y_common = common_refpoint_of_array (elements, me, Y_AXIS);
283 Grob *x_common = common_refpoint_of_array (elements, me, X_AXIS);
285 if (y_common != me)
286 programming_error ("combining skylines that don't belong to me");
288 Skyline_pair ret;
289 for (vsize i = 0; i < elements.size (); i++)
291 SCM skyline_scm = elements[i]->get_property ("vertical-skylines");
292 if (Skyline_pair::unsmob (skyline_scm))
294 Real offset = elements[i]->relative_coordinate (y_common, Y_AXIS);
295 Skyline_pair other = *Skyline_pair::unsmob (skyline_scm);
296 other.raise (offset);
297 other.shift (elements[i]->relative_coordinate (x_common, X_AXIS));
298 ret.merge (other);
301 return ret.smobbed_copy ();
305 Axis_group_interface::generic_group_extent (Grob *me, Axis a)
307 /* trigger the callback to do skyline-spacing on the children */
308 if (a == Y_AXIS)
309 (void) me->get_property ("vertical-skylines");
311 extract_grob_set (me, "elements", elts);
312 Grob *common = common_refpoint_of_array (elts, me, a);
314 Real my_coord = me->relative_coordinate (common, a);
315 Interval r (relative_group_extent (elts, common, a));
317 return ly_interval2scm (r - my_coord);
320 /* This is like generic_group_extent, but it only counts the grobs that
321 are children of some other axis-group. This is uncached; if it becomes
322 commonly used, it may be necessary to cache it somehow. */
323 Interval
324 Axis_group_interface::staff_extent (Grob *me, Grob *refp, Axis ext_a, Grob *staff, Axis parent_a)
326 extract_grob_set (me, "elements", elts);
327 vector<Grob*> new_elts;
329 for (vsize i = 0; i < elts.size (); i++)
330 if (elts[i]->common_refpoint (staff, parent_a) == staff)
331 new_elts.push_back (elts[i]);
333 return relative_group_extent (new_elts, refp, ext_a);
337 Grob *
338 Axis_group_interface::calc_pure_elts_and_common (Grob *me)
340 if (Grob *c = unsmob_grob (me->get_object ("pure-Y-common")))
341 return c;
343 extract_grob_set (me, "elements", elts);
345 vector<Grob*> relevant_items;
346 vector<Grob*> relevant_spanners;
347 SCM pure_relevant_p = ly_lily_module_constant ("pure-relevant?");
349 for (vsize i = 0; i < elts.size (); i++)
351 if (to_boolean (scm_apply_1 (pure_relevant_p, elts[i]->self_scm (), SCM_EOL)))
353 if (dynamic_cast<Item*> (elts[i]))
354 relevant_items.push_back (elts[i]);
355 else if (dynamic_cast<Spanner*> (elts[i]))
356 relevant_spanners.push_back (elts[i]);
360 Item *it = dynamic_cast<Item*> (elts[i]);
361 Direction d = LEFT;
362 if (it)
365 Item *piece = it->find_prebroken_piece (d);
366 if (piece && to_boolean (scm_apply_1 (pure_relevant_p, piece->self_scm (), SCM_EOL)))
367 relevant_items.push_back (piece);
369 while (flip (&d) != LEFT);
371 vector_sort (relevant_items, Item::less);
373 Grob *common = common_refpoint_of_array (relevant_items, me, Y_AXIS);
374 common = common_refpoint_of_array (relevant_spanners, common, Y_AXIS);
376 me->set_object ("pure-Y-common", common->self_scm ());
378 SCM items_scm = Grob_array::make_array ();
379 SCM spanners_scm = Grob_array::make_array ();
381 unsmob_grob_array (items_scm)->set_array (relevant_items);
382 unsmob_grob_array (spanners_scm)->set_array (relevant_spanners);
383 me->set_object ("pure-relevant-items", items_scm);
384 me->set_object ("pure-relevant-spanners", spanners_scm);
386 return common;
390 Axis_group_interface::calc_common (Grob *me, Axis axis)
392 extract_grob_set (me, "elements", elts);
393 Grob *common = common_refpoint_of_array (elts, me, axis);
394 if (!common)
396 me->programming_error ("No common parent found in calc_common axis.");
397 return SCM_EOL;
400 return common->self_scm ();
404 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_x_common, 1);
406 Axis_group_interface::calc_x_common (SCM grob)
408 return calc_common (unsmob_grob (grob), X_AXIS);
411 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_y_common, 1);
413 Axis_group_interface::calc_y_common (SCM grob)
415 return calc_common (unsmob_grob (grob), Y_AXIS);
418 Interval
419 Axis_group_interface::pure_group_height (Grob *me, int start, int end)
421 Grob *common = calc_pure_elts_and_common (me);
423 Real my_coord = me->relative_coordinate (common, Y_AXIS);
424 Interval r (relative_pure_height (me, start, end));
426 return r - my_coord;
429 void
430 Axis_group_interface::get_children (Grob *me, vector<Grob*> *found)
432 found->push_back (me);
434 if (!has_interface (me))
435 return;
437 extract_grob_set (me, "elements", elements);
438 for (vsize i = 0; i < elements.size (); i++)
440 Grob *e = elements[i];
441 Axis_group_interface::get_children (e, found);
445 bool
446 staff_priority_less (Grob * const &g1, Grob * const &g2)
448 Real priority_1 = robust_scm2double (g1->get_property ("outside-staff-priority"), -infinity_f);
449 Real priority_2 = robust_scm2double (g2->get_property ("outside-staff-priority"), -infinity_f);
451 if (priority_1 < priority_2)
452 return true;
453 else if (priority_1 > priority_2)
454 return false;
456 /* if neither grob has an outside-staff priority, the ordering will have no
457 effect -- we just need to choose a consistent ordering. We do this to
458 avoid the side-effect of calculating extents. */
459 if (isinf (priority_1))
460 return g1 < g2;
462 /* if there is no preference in staff priority, choose the left-most one */
463 Grob *common = g1->common_refpoint (g2, X_AXIS);
464 Real start_1 = g1->extent (common, X_AXIS)[LEFT];
465 Real start_2 = g2->extent (common, X_AXIS)[LEFT];
466 return start_1 < start_2;
469 static void
470 add_boxes (Grob *me, Grob *x_common, Grob *y_common, vector<Box> *const boxes, Skyline_pair *skylines)
472 /* if a child has skylines, use them instead of the extent box */
473 if (Skyline_pair *pair = Skyline_pair::unsmob (me->get_property ("vertical-skylines")))
475 Skyline_pair s = *pair;
476 s.shift (me->relative_coordinate (x_common, X_AXIS));
477 s.raise (me->relative_coordinate (y_common, Y_AXIS));
478 skylines->merge (s);
480 else if (Grob_array *elements = unsmob_grob_array (me->get_object ("elements")))
482 for (vsize i = 0; i < elements->size (); i++)
483 add_boxes (elements->grob (i), x_common, y_common, boxes, skylines);
485 else if (!scm_is_number (me->get_property ("outside-staff-priority"))
486 && !to_boolean (me->get_property ("cross-staff")))
488 boxes->push_back (Box (me->extent (x_common, X_AXIS),
489 me->extent (y_common, Y_AXIS)));
493 /* We want to avoid situations like this:
494 still more text
495 more text
496 text
497 -------------------
498 staff
499 -------------------
501 The point is that "still more text" should be positioned under
502 "more text". In order to achieve this, we place the grobs in several
503 passes. We keep track of the right-most horizontal position that has been
504 affected by the current pass so far (actually we keep track of 2
505 positions, one for above the staff, one for below).
507 In each pass, we loop through the unplaced grobs from left to right.
508 If the grob doesn't overlap the right-most affected position, we place it
509 (and then update the right-most affected position to point to the right
510 edge of the just-placed grob). Otherwise, we skip it until the next pass.
512 static void
513 add_grobs_of_one_priority (Skyline_pair *const skylines,
514 vector<Grob*> elements,
515 Grob *x_common,
516 Grob *y_common)
518 vector<Box> boxes;
519 Drul_array<Real> last_affected_position;
521 reverse (elements);
522 while (!elements.empty ())
524 last_affected_position[UP] = -infinity_f;
525 last_affected_position[DOWN] = -infinity_f;
526 /* do one pass */
527 for (vsize i = elements.size (); i--;)
529 Direction dir = get_grob_direction (elements[i]);
530 if (dir == CENTER)
532 warning (_ ("an outside-staff object should have a direction, defaulting to up"));
533 dir = UP;
536 Box b (elements[i]->extent (x_common, X_AXIS),
537 elements[i]->extent (y_common, Y_AXIS));
538 SCM horizon_padding_scm = elements[i]->get_property ("outside-staff-horizontal-padding");
539 Real horizon_padding = robust_scm2double (horizon_padding_scm, 0.0);
541 if (b[X_AXIS][LEFT] - 2*horizon_padding < last_affected_position[dir])
542 continue;
544 if (!b[X_AXIS].is_empty () && !b[Y_AXIS].is_empty ())
546 boxes.clear ();
547 boxes.push_back (b);
548 Skyline other = Skyline (boxes, horizon_padding, X_AXIS, -dir);
549 Real padding = robust_scm2double (elements[i]->get_property ("outside-staff-padding"), 0.5);
550 Real dist = (*skylines)[dir].distance (other) + padding;
552 if (dist > 0)
554 b.translate (Offset (0, dir*dist));
555 elements[i]->translate_axis (dir*dist, Y_AXIS);
557 (*skylines)[dir].insert (b, 0, X_AXIS);
558 elements[i]->set_property ("outside-staff-priority", SCM_BOOL_F);
559 last_affected_position[dir] = b[X_AXIS][RIGHT];
563 Ugh: quadratic. --hwn
565 elements.erase (elements.begin () + i);
570 // TODO: it is tricky to correctly handle skyline placement of cross-staff grobs.
571 // For example, cross-staff beams cannot be formatted until the distance between
572 // staves is known and therefore any grobs that depend on the beam cannot be placed
573 // until the skylines are known. On the other hand, the distance between staves should
574 // really depend on position of the cross-staff grobs that lie between them.
575 // Currently, we just leave cross-staff grobs out of the
576 // skyline altogether, but this could mean that staves are placed so close together
577 // that there is no room for the cross-staff grob. It also means, of course, that
578 // we don't get the benefits of skyline placement for cross-staff grobs.
579 Skyline_pair
580 Axis_group_interface::skyline_spacing (Grob *me, vector<Grob*> elements)
582 /* For grobs with an outside-staff-priority, the sorting function might
583 call extent and cause suicide. This breaks the contract that is required
584 for the STL sort function. To avoid this, we make sure that any suicides
585 are triggered beforehand.
587 for (vsize i = 0; i < elements.size (); i++)
588 if (scm_is_number (elements[i]->get_property ("outside-staff-priority")))
589 elements[i]->extent (elements[i], X_AXIS);
591 vector_sort (elements, staff_priority_less);
592 Grob *x_common = common_refpoint_of_array (elements, me, X_AXIS);
593 Grob *y_common = common_refpoint_of_array (elements, me, Y_AXIS);
595 assert (y_common == me);
597 vsize i = 0;
598 vector<Box> boxes;
600 Skyline_pair skylines;
601 for (i = 0; i < elements.size ()
602 && !scm_is_number (elements[i]->get_property ("outside-staff-priority")); i++)
603 if (!to_boolean (elements[i]->get_property ("cross-staff")))
604 add_boxes (elements[i], x_common, y_common, &boxes, &skylines);
606 SCM padding_scm = me->get_property ("skyline-horizontal-padding");
607 Real padding = robust_scm2double (padding_scm, 0.1);
608 skylines.merge (Skyline_pair (boxes, padding, X_AXIS));
609 for (; i < elements.size (); i++)
611 if (to_boolean (elements[i]->get_property ("cross-staff")))
612 continue;
614 SCM priority = elements[i]->get_property ("outside-staff-priority");
615 vector<Grob*> current_elts;
616 current_elts.push_back (elements[i]);
617 while (i + 1 < elements.size ()
618 && scm_eq_p (elements[i+1]->get_property ("outside-staff-priority"), priority))
619 current_elts.push_back (elements[++i]);
621 add_grobs_of_one_priority (&skylines, current_elts, x_common, y_common);
623 skylines.shift (-me->relative_coordinate (x_common, X_AXIS));
624 return skylines;
627 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_max_stretch, 1)
629 Axis_group_interface::calc_max_stretch (SCM smob)
631 Grob *me = unsmob_grob (smob);
632 Real ret = 0;
633 extract_grob_set (me, "elements", elts);
635 for (vsize i = 0; i < elts.size (); i++)
636 if (Axis_group_interface::has_interface (elts[i]))
637 ret += robust_scm2double (elts[i]->get_property ("max-stretch"), 0.0);
639 return scm_from_double (ret);
642 MAKE_SCHEME_CALLBACK (Axis_group_interface, print, 1)
644 Axis_group_interface::print (SCM smob)
646 if (!debug_skylines)
647 return SCM_BOOL_F;
649 Grob *me = unsmob_grob (smob);
650 Stencil ret;
651 if (Skyline_pair *s = Skyline_pair::unsmob (me->get_property ("vertical-skylines")))
653 ret.add_stencil (Lookup::points_to_line_stencil (0.1, (*s)[UP].to_points (X_AXIS))
654 .in_color (255, 0, 255));
655 ret.add_stencil (Lookup::points_to_line_stencil (0.1, (*s)[DOWN].to_points (X_AXIS))
656 .in_color (0, 255, 255));
658 return ret.smobbed_copy ();
661 ADD_INTERFACE (Axis_group_interface,
662 "An object that groups other layout objects.",
664 /* properties */
665 "X-common "
666 "Y-common "
667 "adjacent-pure-heights "
668 "axes "
669 "elements "
670 "keep-fixed-while-stretching "
671 "max-stretch "
672 "no-alignment "
673 "pure-Y-common "
674 "pure-relevant-items "
675 "pure-relevant-spanners "
676 "vertical-skylines "