2 This file is part of LilyPond, the GNU music typesetter.
4 Copyright (C) 2000--2009 Han-Wen Nienhuys <hanwen@xs4all.nl>
6 LilyPond is free software: you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation, either version 3 of the License, or
9 (at your option) any later version.
11 LilyPond is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with LilyPond. If not, see <http://www.gnu.org/licenses/>.
20 #include "axis-group-interface.hh"
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"
28 #include "paper-column.hh"
29 #include "paper-score.hh"
30 #include "pointer-group-interface.hh"
31 #include "separation-item.hh"
32 #include "skyline-pair.hh"
33 #include "staff-grouper-interface.hh"
39 Axis_group_interface::add_element (Grob *me, Grob *e)
41 SCM axes = me->get_property ("axes");
42 if (!scm_is_pair (axes))
43 programming_error ("axes should be nonempty");
45 for (SCM ax = axes; scm_is_pair (ax); ax = scm_cdr (ax))
47 Axis a = (Axis) scm_to_int (scm_car (ax));
49 if (!e->get_parent (a))
50 e->set_parent (me, a);
52 e->set_object ((a == X_AXIS)
53 ? ly_symbol2scm ("axis-group-parent-X")
54 : ly_symbol2scm ("axis-group-parent-Y"),
58 /* must be ordered, because Align_interface also uses
59 Axis_group_interface */
60 Pointer_group_interface::add_grob (me, ly_symbol2scm ("elements"), e);
64 Axis_group_interface::has_axis (Grob *me, Axis a)
66 SCM axes = me->get_property ("axes");
68 return (SCM_BOOL_F != scm_memq (scm_from_int (a), axes));
72 Axis_group_interface::relative_group_extent (vector<Grob*> const &elts,
76 for (vsize i = 0; i < elts.size (); i++)
79 if (!to_boolean (se->get_property ("cross-staff")))
81 Interval dims = se->extent (common, a);
82 if (!dims.is_empty ())
90 Axis_group_interface::cached_pure_height (Grob *me, int start, int end)
92 Interval iv = begin_of_line_pure_height (me, start);
93 iv.unite (rest_of_line_pure_height (me, start, end));
99 Axis_group_interface::rest_of_line_pure_height (Grob *me, int start, int end)
101 SCM adjacent_pure_heights = me->get_property ("adjacent-pure-heights");
103 if (!scm_is_pair (adjacent_pure_heights)
104 || !scm_is_vector (scm_cdr (adjacent_pure_heights)))
105 return Interval (0, 0);
107 return combine_pure_heights (me, scm_cdr (adjacent_pure_heights), start, end);
111 Axis_group_interface::begin_of_line_pure_height (Grob *me, int start)
113 SCM adjacent_pure_heights = me->get_property ("adjacent-pure-heights");
115 if (!scm_is_pair (adjacent_pure_heights)
116 || !scm_is_vector (scm_car (adjacent_pure_heights)))
117 return Interval (0, 0);
119 return combine_pure_heights (me, scm_car (adjacent_pure_heights), start, start+1);
123 Axis_group_interface::combine_pure_heights (Grob *me, SCM measure_extents, int start, int end)
125 Paper_score *ps = get_root_system (me)->paper_score ();
126 vector<vsize> breaks = ps->get_break_indices ();
127 vector<Grob*> cols = ps->get_columns ();
130 for (vsize i = 0; i + 1 < breaks.size (); i++)
132 int r = Paper_column::get_rank (cols[breaks[i]]);
137 ext.unite (ly_scm2interval (scm_c_vector_ref (measure_extents, i)));
143 // adjacent-pure-heights is a pair of vectors, each of which has one element
144 // for every measure in the score. The first vector stores, for each measure,
145 // the combined height of the elements that are present only when the bar
146 // is at the beginning of a line. The second vector stores, for each measure,
147 // the combined height of the elements that are present only when the bar
148 // is not at the beginning of a line.
150 MAKE_SCHEME_CALLBACK (Axis_group_interface, adjacent_pure_heights, 1)
152 Axis_group_interface::adjacent_pure_heights (SCM smob)
154 Grob *me = unsmob_grob (smob);
156 Grob *common = calc_pure_elts_and_common (me);
157 extract_grob_set (me, "pure-relevant-items", items);
158 extract_grob_set (me, "pure-relevant-spanners", spanners);
160 Paper_score *ps = get_root_system (me)->paper_score ();
161 vector<vsize> breaks = ps->get_break_indices ();
162 vector<Grob*> cols = ps->get_columns ();
164 SCM begin_line_heights = scm_c_make_vector (breaks.size () - 1, SCM_EOL);
165 SCM mid_line_heights = scm_c_make_vector (breaks.size () - 1, SCM_EOL);
168 for (vsize i = 0; i + 1 < breaks.size (); i++)
170 int start = Paper_column::get_rank (cols[breaks[i]]);
171 int end = Paper_column::get_rank (cols[breaks[i+1]]);
172 Interval begin_line_iv;
173 Interval mid_line_iv;
175 for (vsize j = it_index; j < items.size (); j++)
177 Item *it = dynamic_cast<Item*> (items[j]);
178 int rank = it->get_column ()->get_rank ();
180 if (rank <= end && it->pure_is_visible (start, end)
181 && !to_boolean (it->get_property ("cross-staff")))
183 Interval dims = items[j]->pure_height (common, start, end);
184 Interval &target_iv = start == it->get_column ()->get_rank () ? begin_line_iv : mid_line_iv;
186 if (!dims.is_empty ())
187 target_iv.unite (dims);
196 for (vsize j = 0; j < spanners.size (); j++)
198 Interval_t<int> rank_span = spanners[j]->spanned_rank_interval ();
199 if (rank_span[LEFT] <= end && rank_span[RIGHT] >= start
200 && !to_boolean (spanners[j]->get_property ("cross-staff")))
202 Interval dims = spanners[j]->pure_height (common, start, end);
204 if (!dims.is_empty ())
205 mid_line_iv.unite (dims);
209 scm_vector_set_x (begin_line_heights, scm_from_int (i), ly_interval2scm (begin_line_iv));
210 scm_vector_set_x (mid_line_heights, scm_from_int (i), ly_interval2scm (mid_line_iv));
212 return scm_cons (begin_line_heights, mid_line_heights);
216 Axis_group_interface::relative_pure_height (Grob *me, int start, int end)
218 /* It saves a _lot_ of time if we assume a VerticalAxisGroup is additive
219 (ie. height (i, k) = max (height (i, j) height (j, k)) for all i <= j <= k).
220 Unfortunately, it isn't always true, particularly if there is a
221 VerticalAlignment somewhere in the descendants.
223 Apart from PianoStaff, which has a fixed VerticalAlignment so it doesn't
224 count, the only VerticalAlignment comes from Score. This makes it
225 reasonably safe to assume that if our parent is a VerticalAlignment,
226 we can assume additivity and cache things nicely. */
227 Grob *p = me->get_parent (Y_AXIS);
228 if (p && Align_interface::has_interface (p))
229 return Axis_group_interface::cached_pure_height (me, start, end);
231 Grob *common = calc_pure_elts_and_common (me);
232 extract_grob_set (me, "pure-relevant-items", items);
233 extract_grob_set (me, "pure-relevant-spanners", spanners);
237 for (vsize i = 0; i < items.size (); i++)
239 Item *it = dynamic_cast<Item*> (items[i]);
240 int rank = it->get_column ()->get_rank ();
244 else if (rank >= start && it->pure_is_visible (start, end)
245 && !to_boolean (it->get_property ("cross-staff")))
247 Interval dims = it->pure_height (common, start, end);
248 if (!dims.is_empty ())
253 for (vsize i = 0; i < spanners.size (); i++)
255 Interval_t<int> rank_span = spanners[i]->spanned_rank_interval ();
256 if (rank_span[LEFT] <= end && rank_span[RIGHT] >= start
257 && !to_boolean (spanners[i]->get_property ("cross-staff")))
259 Interval dims = spanners[i]->pure_height (common, start, end);
260 if (!dims.is_empty ())
267 MAKE_SCHEME_CALLBACK (Axis_group_interface, width, 1);
269 Axis_group_interface::width (SCM smob)
271 Grob *me = unsmob_grob (smob);
272 return generic_group_extent (me, X_AXIS);
275 MAKE_SCHEME_CALLBACK (Axis_group_interface, height, 1);
277 Axis_group_interface::height (SCM smob)
279 Grob *me = unsmob_grob (smob);
280 return generic_group_extent (me, Y_AXIS);
283 MAKE_SCHEME_CALLBACK (Axis_group_interface, pure_height, 3);
285 Axis_group_interface::pure_height (SCM smob, SCM start_scm, SCM end_scm)
287 int start = robust_scm2int (start_scm, 0);
288 int end = robust_scm2int (end_scm, INT_MAX);
289 Grob *me = unsmob_grob (smob);
291 /* Maybe we are in the second pass of a two-pass spacing run. In that
292 case, the Y-extent of a system is already given to us */
293 System *system = dynamic_cast<System*> (me);
296 SCM line_break_details = system->column (start)->get_property ("line-break-system-details");
297 SCM system_y_extent = scm_assq (ly_symbol2scm ("system-Y-extent"), line_break_details);
298 if (scm_is_pair (system_y_extent))
299 return scm_cdr (system_y_extent);
302 return ly_interval2scm (pure_group_height (me, start, end));
305 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_skylines, 1);
307 Axis_group_interface::calc_skylines (SCM smob)
309 Grob *me = unsmob_grob (smob);
310 extract_grob_set (me, "elements", elts);
311 Skyline_pair skylines = skyline_spacing (me, elts);
313 return skylines.smobbed_copy ();
316 /* whereas calc_skylines calculates skylines for axis-groups with a lot of
317 visible children, combine_skylines is designed for axis-groups whose only
318 children are other axis-groups (ie. VerticalAlignment). Rather than
319 calculating all the skylines from scratch, we just merge the skylines
322 MAKE_SCHEME_CALLBACK (Axis_group_interface, combine_skylines, 1);
324 Axis_group_interface::combine_skylines (SCM smob)
326 Grob *me = unsmob_grob (smob);
327 extract_grob_set (me, "elements", elements);
328 Grob *y_common = common_refpoint_of_array (elements, me, Y_AXIS);
329 Grob *x_common = common_refpoint_of_array (elements, me, X_AXIS);
332 programming_error ("combining skylines that don't belong to me");
335 for (vsize i = 0; i < elements.size (); i++)
337 SCM skyline_scm = elements[i]->get_property ("vertical-skylines");
338 if (Skyline_pair::unsmob (skyline_scm))
340 Real offset = elements[i]->relative_coordinate (y_common, Y_AXIS);
341 Skyline_pair other = *Skyline_pair::unsmob (skyline_scm);
342 other.raise (offset);
343 other.shift (elements[i]->relative_coordinate (x_common, X_AXIS));
347 return ret.smobbed_copy ();
351 Axis_group_interface::generic_group_extent (Grob *me, Axis a)
353 /* trigger the callback to do skyline-spacing on the children */
355 (void) me->get_property ("vertical-skylines");
357 extract_grob_set (me, "elements", elts);
358 Grob *common = common_refpoint_of_array (elts, me, a);
360 Real my_coord = me->relative_coordinate (common, a);
361 Interval r (relative_group_extent (elts, common, a));
363 return ly_interval2scm (r - my_coord);
366 /* This is like generic_group_extent, but it only counts the grobs that
367 are children of some other axis-group. This is uncached; if it becomes
368 commonly used, it may be necessary to cache it somehow. */
370 Axis_group_interface::staff_extent (Grob *me, Grob *refp, Axis ext_a, Grob *staff, Axis parent_a)
372 extract_grob_set (me, "elements", elts);
373 vector<Grob*> new_elts;
375 for (vsize i = 0; i < elts.size (); i++)
376 if (elts[i]->common_refpoint (staff, parent_a) == staff)
377 new_elts.push_back (elts[i]);
379 return relative_group_extent (new_elts, refp, ext_a);
384 Axis_group_interface::calc_pure_elts_and_common (Grob *me)
386 if (Grob *c = unsmob_grob (me->get_object ("pure-Y-common")))
389 extract_grob_set (me, "elements", elts);
391 vector<Grob*> relevant_items;
392 vector<Grob*> relevant_spanners;
393 SCM pure_relevant_p = ly_lily_module_constant ("pure-relevant?");
395 for (vsize i = 0; i < elts.size (); i++)
397 if (to_boolean (scm_apply_1 (pure_relevant_p, elts[i]->self_scm (), SCM_EOL)))
399 if (dynamic_cast<Item*> (elts[i]))
400 relevant_items.push_back (elts[i]);
401 else if (dynamic_cast<Spanner*> (elts[i]))
402 relevant_spanners.push_back (elts[i]);
406 Item *it = dynamic_cast<Item*> (elts[i]);
411 Item *piece = it->find_prebroken_piece (d);
412 if (piece && to_boolean (scm_apply_1 (pure_relevant_p, piece->self_scm (), SCM_EOL)))
413 relevant_items.push_back (piece);
415 while (flip (&d) != LEFT);
417 vector_sort (relevant_items, Item::less);
419 Grob *common = common_refpoint_of_array (relevant_items, me, Y_AXIS);
420 common = common_refpoint_of_array (relevant_spanners, common, Y_AXIS);
422 me->set_object ("pure-Y-common", common->self_scm ());
424 SCM items_scm = Grob_array::make_array ();
425 SCM spanners_scm = Grob_array::make_array ();
427 unsmob_grob_array (items_scm)->set_array (relevant_items);
428 unsmob_grob_array (spanners_scm)->set_array (relevant_spanners);
429 me->set_object ("pure-relevant-items", items_scm);
430 me->set_object ("pure-relevant-spanners", spanners_scm);
436 Axis_group_interface::calc_common (Grob *me, Axis axis)
438 extract_grob_set (me, "elements", elts);
439 Grob *common = common_refpoint_of_array (elts, me, axis);
442 me->programming_error ("No common parent found in calc_common axis.");
446 return common->self_scm ();
450 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_x_common, 1);
452 Axis_group_interface::calc_x_common (SCM grob)
454 return calc_common (unsmob_grob (grob), X_AXIS);
457 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_y_common, 1);
459 Axis_group_interface::calc_y_common (SCM grob)
461 return calc_common (unsmob_grob (grob), Y_AXIS);
465 Axis_group_interface::pure_group_height (Grob *me, int start, int end)
467 Grob *common = calc_pure_elts_and_common (me);
469 Real my_coord = me->relative_coordinate (common, Y_AXIS);
470 Interval r (relative_pure_height (me, start, end));
476 Axis_group_interface::get_children (Grob *me, vector<Grob*> *found)
478 found->push_back (me);
480 if (!has_interface (me))
483 extract_grob_set (me, "elements", elements);
484 for (vsize i = 0; i < elements.size (); i++)
486 Grob *e = elements[i];
487 Axis_group_interface::get_children (e, found);
492 staff_priority_less (Grob * const &g1, Grob * const &g2)
494 Real priority_1 = robust_scm2double (g1->get_property ("outside-staff-priority"), -infinity_f);
495 Real priority_2 = robust_scm2double (g2->get_property ("outside-staff-priority"), -infinity_f);
497 if (priority_1 < priority_2)
499 else if (priority_1 > priority_2)
502 /* if neither grob has an outside-staff priority, the ordering will have no
503 effect -- we just need to choose a consistent ordering. We do this to
504 avoid the side-effect of calculating extents. */
505 if (isinf (priority_1))
508 /* if there is no preference in staff priority, choose the left-most one */
509 Grob *common = g1->common_refpoint (g2, X_AXIS);
510 Real start_1 = g1->extent (common, X_AXIS)[LEFT];
511 Real start_2 = g2->extent (common, X_AXIS)[LEFT];
512 return start_1 < start_2;
516 add_boxes (Grob *me, Grob *x_common, Grob *y_common, vector<Box> *const boxes, Skyline_pair *skylines)
518 /* if a child has skylines, use them instead of the extent box */
519 if (Skyline_pair *pair = Skyline_pair::unsmob (me->get_property ("vertical-skylines")))
521 Skyline_pair s = *pair;
522 s.shift (me->relative_coordinate (x_common, X_AXIS));
523 s.raise (me->relative_coordinate (y_common, Y_AXIS));
526 else if (Grob_array *elements = unsmob_grob_array (me->get_object ("elements")))
528 for (vsize i = 0; i < elements->size (); i++)
529 add_boxes (elements->grob (i), x_common, y_common, boxes, skylines);
531 else if (!scm_is_number (me->get_property ("outside-staff-priority"))
532 && !to_boolean (me->get_property ("cross-staff")))
534 boxes->push_back (Box (me->extent (x_common, X_AXIS),
535 me->extent (y_common, Y_AXIS)));
539 /* We want to avoid situations like this:
547 The point is that "still more text" should be positioned under
548 "more text". In order to achieve this, we place the grobs in several
549 passes. We keep track of the right-most horizontal position that has been
550 affected by the current pass so far (actually we keep track of 2
551 positions, one for above the staff, one for below).
553 In each pass, we loop through the unplaced grobs from left to right.
554 If the grob doesn't overlap the right-most affected position, we place it
555 (and then update the right-most affected position to point to the right
556 edge of the just-placed grob). Otherwise, we skip it until the next pass.
559 add_grobs_of_one_priority (Skyline_pair *const skylines,
560 vector<Grob*> elements,
565 Drul_array<Real> last_affected_position;
568 while (!elements.empty ())
570 last_affected_position[UP] = -infinity_f;
571 last_affected_position[DOWN] = -infinity_f;
573 for (vsize i = elements.size (); i--;)
575 Direction dir = get_grob_direction (elements[i]);
578 warning (_ ("an outside-staff object should have a direction, defaulting to up"));
582 Box b (elements[i]->extent (x_common, X_AXIS),
583 elements[i]->extent (y_common, Y_AXIS));
584 SCM horizon_padding_scm = elements[i]->get_property ("outside-staff-horizontal-padding");
585 Real horizon_padding = robust_scm2double (horizon_padding_scm, 0.0);
587 if (b[X_AXIS][LEFT] - 2*horizon_padding < last_affected_position[dir])
590 if (!b[X_AXIS].is_empty () && !b[Y_AXIS].is_empty ())
594 Skyline other = Skyline (boxes, horizon_padding, X_AXIS, -dir);
595 Real padding = robust_scm2double (elements[i]->get_property ("outside-staff-padding"), 0.5);
596 Real dist = (*skylines)[dir].distance (other) + padding;
600 b.translate (Offset (0, dir*dist));
601 elements[i]->translate_axis (dir*dist, Y_AXIS);
603 skylines->insert (b, 0, X_AXIS);
604 elements[i]->set_property ("outside-staff-priority", SCM_BOOL_F);
605 last_affected_position[dir] = b[X_AXIS][RIGHT];
609 Ugh: quadratic. --hwn
611 elements.erase (elements.begin () + i);
616 // TODO: it is tricky to correctly handle skyline placement of cross-staff grobs.
617 // For example, cross-staff beams cannot be formatted until the distance between
618 // staves is known and therefore any grobs that depend on the beam cannot be placed
619 // until the skylines are known. On the other hand, the distance between staves should
620 // really depend on position of the cross-staff grobs that lie between them.
621 // Currently, we just leave cross-staff grobs out of the
622 // skyline altogether, but this could mean that staves are placed so close together
623 // that there is no room for the cross-staff grob. It also means, of course, that
624 // we don't get the benefits of skyline placement for cross-staff grobs.
626 Axis_group_interface::skyline_spacing (Grob *me, vector<Grob*> elements)
628 /* For grobs with an outside-staff-priority, the sorting function might
629 call extent and cause suicide. This breaks the contract that is required
630 for the STL sort function. To avoid this, we make sure that any suicides
631 are triggered beforehand.
633 for (vsize i = 0; i < elements.size (); i++)
634 if (scm_is_number (elements[i]->get_property ("outside-staff-priority")))
635 elements[i]->extent (elements[i], X_AXIS);
637 vector_sort (elements, staff_priority_less);
638 Grob *x_common = common_refpoint_of_array (elements, me, X_AXIS);
639 Grob *y_common = common_refpoint_of_array (elements, me, Y_AXIS);
641 assert (y_common == me);
646 Skyline_pair skylines;
647 for (i = 0; i < elements.size ()
648 && !scm_is_number (elements[i]->get_property ("outside-staff-priority")); i++)
649 if (!to_boolean (elements[i]->get_property ("cross-staff")))
650 add_boxes (elements[i], x_common, y_common, &boxes, &skylines);
652 SCM padding_scm = me->get_property ("skyline-horizontal-padding");
653 Real padding = robust_scm2double (padding_scm, 0.1);
654 skylines.merge (Skyline_pair (boxes, padding, X_AXIS));
655 for (; i < elements.size (); i++)
657 if (to_boolean (elements[i]->get_property ("cross-staff")))
660 SCM priority = elements[i]->get_property ("outside-staff-priority");
661 vector<Grob*> current_elts;
662 current_elts.push_back (elements[i]);
663 while (i + 1 < elements.size ()
664 && scm_eq_p (elements[i+1]->get_property ("outside-staff-priority"), priority))
666 if (!to_boolean (elements[i+1]->get_property ("cross-staff")))
667 current_elts.push_back (elements[i+1]);
671 add_grobs_of_one_priority (&skylines, current_elts, x_common, y_common);
673 skylines.shift (-me->relative_coordinate (x_common, X_AXIS));
677 MAKE_SCHEME_CALLBACK (Axis_group_interface, print, 1)
679 Axis_group_interface::print (SCM smob)
684 Grob *me = unsmob_grob (smob);
686 if (Skyline_pair *s = Skyline_pair::unsmob (me->get_property ("vertical-skylines")))
688 ret.add_stencil (Lookup::points_to_line_stencil (0.1, (*s)[UP].to_points (X_AXIS))
689 .in_color (255, 0, 255));
690 ret.add_stencil (Lookup::points_to_line_stencil (0.1, (*s)[DOWN].to_points (X_AXIS))
691 .in_color (0, 255, 255));
693 return ret.smobbed_copy ();
696 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_next_staff_spacing, 1)
698 Axis_group_interface::calc_next_staff_spacing (SCM smob)
700 Grob *me = unsmob_grob (smob);
701 Grob *grouper = unsmob_grob (me->get_object ("staff-grouper"));
705 Grob *last_in_group = Staff_grouper_interface::get_last_grob (grouper);
706 if (me == last_in_group)
707 return grouper->get_property ("after-last-staff-spacing");
709 return grouper->get_property ("between-staff-spacing");
711 return me->get_property ("default-next-staff-spacing");
715 Axis_group_interface::minimum_distance (Grob *g1, Grob *g2, Axis a)
717 SCM sym = ly_symbol2scm ((a == Y_AXIS) ? "vertical-skylines" : "horizontal-skylines");
719 Skyline_pair *s1 = Skyline_pair::unsmob (g1->get_property (sym));
720 Skyline_pair *s2 = Skyline_pair::unsmob (g2->get_property (sym));
722 return (*s1)[DOWN].distance ((*s2)[UP]);
726 ADD_INTERFACE (Axis_group_interface,
727 "An object that groups other layout objects.",
729 // TODO: some of these properties are specific to
730 // VerticalAxisGroup. We should split off a
731 // vertical-axis-group-interface.
735 "adjacent-pure-heights "
737 "default-next-staff-spacing "
739 "inter-loose-line-spacing "
740 "inter-staff-spacing "
741 "keep-fixed-while-stretching "
743 "non-affinity-spacing "
744 "next-staff-spacing "
747 "pure-relevant-items "
748 "pure-relevant-spanners "