2 This file is part of LilyPond, the GNU music typesetter.
4 Copyright (C) 2000--2010 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"
40 Axis_group_interface::add_element (Grob *me, Grob *e)
42 SCM axes = me->get_property ("axes");
43 if (!scm_is_pair (axes))
44 programming_error ("axes should be nonempty");
46 for (SCM ax = axes; scm_is_pair (ax); ax = scm_cdr (ax))
48 Axis a = (Axis) scm_to_int (scm_car (ax));
50 if (!e->get_parent (a))
51 e->set_parent (me, a);
53 e->set_object ((a == X_AXIS)
54 ? ly_symbol2scm ("axis-group-parent-X")
55 : ly_symbol2scm ("axis-group-parent-Y"),
59 /* must be ordered, because Align_interface also uses
60 Axis_group_interface */
61 Pointer_group_interface::add_grob (me, ly_symbol2scm ("elements"), e);
65 Axis_group_interface::has_axis (Grob *me, Axis a)
67 SCM axes = me->get_property ("axes");
69 return (SCM_BOOL_F != scm_memq (scm_from_int (a), axes));
73 Axis_group_interface::relative_group_extent (vector<Grob*> const &elts,
77 for (vsize i = 0; i < elts.size (); i++)
80 if (!to_boolean (se->get_property ("cross-staff")))
82 Interval dims = se->extent (common, a);
83 if (!dims.is_empty ())
91 Axis_group_interface::cached_pure_height (Grob *me, int start, int end)
93 Interval iv = begin_of_line_pure_height (me, start);
94 iv.unite (rest_of_line_pure_height (me, start, end));
100 Axis_group_interface::rest_of_line_pure_height (Grob *me, int start, int end)
102 SCM adjacent_pure_heights = me->get_property ("adjacent-pure-heights");
104 if (!scm_is_pair (adjacent_pure_heights)
105 || !scm_is_vector (scm_cdr (adjacent_pure_heights)))
106 return Interval (0, 0);
108 return combine_pure_heights (me, scm_cdr (adjacent_pure_heights), start, end);
112 Axis_group_interface::begin_of_line_pure_height (Grob *me, int start)
114 SCM adjacent_pure_heights = me->get_property ("adjacent-pure-heights");
116 if (!scm_is_pair (adjacent_pure_heights)
117 || !scm_is_vector (scm_car (adjacent_pure_heights)))
118 return Interval (0, 0);
120 return combine_pure_heights (me, scm_car (adjacent_pure_heights), start, start+1);
124 Axis_group_interface::combine_pure_heights (Grob *me, SCM measure_extents, int start, int end)
126 Paper_score *ps = get_root_system (me)->paper_score ();
127 vector<vsize> breaks = ps->get_break_indices ();
128 vector<Grob*> cols = ps->get_columns ();
131 for (vsize i = 0; i + 1 < breaks.size (); i++)
133 int r = Paper_column::get_rank (cols[breaks[i]]);
138 ext.unite (ly_scm2interval (scm_c_vector_ref (measure_extents, i)));
145 // adjacent-pure-heights is a pair of vectors, each of which has one element
146 // for every measure in the score. The first vector stores, for each measure,
147 // the combined height of the elements that are present only when the bar
148 // is at the beginning of a line. The second vector stores, for each measure,
149 // the combined height of the elements that are present only when the bar
150 // is not at the beginning of a line.
152 MAKE_SCHEME_CALLBACK (Axis_group_interface, adjacent_pure_heights, 1)
154 Axis_group_interface::adjacent_pure_heights (SCM smob)
156 Grob *me = unsmob_grob (smob);
158 Grob *common = unsmob_grob (me->get_object ("pure-Y-common"));
159 extract_grob_set (me, "pure-relevant-grobs", elts);
161 Paper_score *ps = get_root_system (me)->paper_score ();
162 vector<vsize> ranks = ps->get_break_ranks ();
164 vector<Interval> begin_line_heights;
165 vector<Interval> mid_line_heights;
166 begin_line_heights.resize (ranks.size () - 1);
167 mid_line_heights.resize (ranks.size () - 1);
169 for (vsize i = 0; i < elts.size (); ++i)
173 if (to_boolean (g->get_property ("cross-staff")))
176 Interval_t<int> rank_span = g->spanned_rank_interval ();
177 vsize first_break = lower_bound (ranks, (vsize)rank_span[LEFT], less<vsize> ());
178 if (first_break > 0 && ranks[first_break] >= (vsize)rank_span[LEFT])
181 for (vsize j = first_break; j+1 < ranks.size () && (int)ranks[j] <= rank_span[RIGHT]; ++j)
183 int start = ranks[j];
184 int end = ranks[j+1];
186 // Take grobs that are visible with respect to a slightly longer line.
187 // Otherwise, we will never include grobs at breakpoints which aren't
188 // end-of-line-visible.
189 int visibility_end = j + 2 < ranks.size () ? ranks[j+2] : end;
191 if (g->pure_is_visible (start, visibility_end))
193 Interval dims = g->pure_height (common, start, end);
194 if (!dims.is_empty ())
196 if (rank_span[LEFT] <= start)
197 begin_line_heights[j].unite (dims);
198 if (rank_span[RIGHT] > start)
199 mid_line_heights[j].unite (dims);
205 // Convert begin_line_heights and min_line_heights to SCM.
206 SCM begin_scm = scm_c_make_vector (ranks.size () - 1, SCM_EOL);
207 SCM mid_scm = scm_c_make_vector (ranks.size () - 1, SCM_EOL);
208 for (vsize i = 0; i < begin_line_heights.size (); ++i)
210 scm_vector_set_x (begin_scm, scm_from_int (i), ly_interval2scm (begin_line_heights[i]));
211 scm_vector_set_x (mid_scm, scm_from_int (i), ly_interval2scm (mid_line_heights[i]));
214 return scm_cons (begin_scm, mid_scm);
218 Axis_group_interface::relative_pure_height (Grob *me, int start, int end)
220 /* It saves a _lot_ of time if we assume a VerticalAxisGroup is additive
221 (ie. height (i, k) = max (height (i, j) height (j, k)) for all i <= j <= k).
222 Unfortunately, it isn't always true, particularly if there is a
223 VerticalAlignment somewhere in the descendants.
225 Usually, the only VerticalAlignment comes from Score. This makes it
226 reasonably safe to assume that if our parent is a VerticalAlignment,
227 we can assume additivity and cache things nicely. */
228 Grob *p = me->get_parent (Y_AXIS);
229 if (p && Align_interface::has_interface (p))
230 return Axis_group_interface::cached_pure_height (me, start, end);
232 Grob *common = unsmob_grob (me->get_object ("pure-Y-common"));
233 extract_grob_set (me, "pure-relevant-grobs", elts);
236 for (vsize i = 0; i < elts.size (); i++)
239 Interval_t<int> rank_span = g->spanned_rank_interval ();
240 if (rank_span[LEFT] <= end && rank_span[RIGHT] >= start
241 && g->pure_is_visible (start, end)
242 && !to_boolean (g->get_property ("cross-staff")))
244 Interval dims = g->pure_height (common, start, end);
245 if (!dims.is_empty ())
252 MAKE_SCHEME_CALLBACK (Axis_group_interface, width, 1);
254 Axis_group_interface::width (SCM smob)
256 Grob *me = unsmob_grob (smob);
257 return generic_group_extent (me, X_AXIS);
260 MAKE_SCHEME_CALLBACK (Axis_group_interface, height, 1);
262 Axis_group_interface::height (SCM smob)
264 Grob *me = unsmob_grob (smob);
265 return generic_group_extent (me, Y_AXIS);
268 MAKE_SCHEME_CALLBACK (Axis_group_interface, pure_height, 3);
270 Axis_group_interface::pure_height (SCM smob, SCM start_scm, SCM end_scm)
272 int start = robust_scm2int (start_scm, 0);
273 int end = robust_scm2int (end_scm, INT_MAX);
274 Grob *me = unsmob_grob (smob);
276 /* Maybe we are in the second pass of a two-pass spacing run. In that
277 case, the Y-extent of a system is already given to us */
278 System *system = dynamic_cast<System*> (me);
281 SCM line_break_details = system->column (start)->get_property ("line-break-system-details");
282 SCM system_y_extent = scm_assq (ly_symbol2scm ("system-Y-extent"), line_break_details);
283 if (scm_is_pair (system_y_extent))
284 return scm_cdr (system_y_extent);
287 return ly_interval2scm (pure_group_height (me, start, end));
290 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_skylines, 1);
292 Axis_group_interface::calc_skylines (SCM smob)
294 Grob *me = unsmob_grob (smob);
295 extract_grob_set (me, "elements", elts);
296 Skyline_pair skylines = skyline_spacing (me, elts);
298 return skylines.smobbed_copy ();
301 /* whereas calc_skylines calculates skylines for axis-groups with a lot of
302 visible children, combine_skylines is designed for axis-groups whose only
303 children are other axis-groups (ie. VerticalAlignment). Rather than
304 calculating all the skylines from scratch, we just merge the skylines
307 MAKE_SCHEME_CALLBACK (Axis_group_interface, combine_skylines, 1);
309 Axis_group_interface::combine_skylines (SCM smob)
311 Grob *me = unsmob_grob (smob);
312 extract_grob_set (me, "elements", elements);
313 Grob *y_common = common_refpoint_of_array (elements, me, Y_AXIS);
314 Grob *x_common = common_refpoint_of_array (elements, me, X_AXIS);
317 programming_error ("combining skylines that don't belong to me");
320 for (vsize i = 0; i < elements.size (); i++)
322 SCM skyline_scm = elements[i]->get_property ("vertical-skylines");
323 if (Skyline_pair::unsmob (skyline_scm))
325 Real offset = elements[i]->relative_coordinate (y_common, Y_AXIS);
326 Skyline_pair other = *Skyline_pair::unsmob (skyline_scm);
327 other.raise (offset);
328 other.shift (elements[i]->relative_coordinate (x_common, X_AXIS));
332 return ret.smobbed_copy ();
336 Axis_group_interface::generic_group_extent (Grob *me, Axis a)
338 /* trigger the callback to do skyline-spacing on the children */
340 (void) me->get_property ("vertical-skylines");
342 extract_grob_set (me, "elements", elts);
343 Grob *common = common_refpoint_of_array (elts, me, a);
345 Real my_coord = me->relative_coordinate (common, a);
346 Interval r (relative_group_extent (elts, common, a));
348 return ly_interval2scm (r - my_coord);
351 /* This is like generic_group_extent, but it only counts the grobs that
352 are children of some other axis-group. This is uncached; if it becomes
353 commonly used, it may be necessary to cache it somehow. */
355 Axis_group_interface::staff_extent (Grob *me, Grob *refp, Axis ext_a, Grob *staff, Axis parent_a)
357 extract_grob_set (me, "elements", elts);
358 vector<Grob*> new_elts;
360 for (vsize i = 0; i < elts.size (); i++)
361 if (elts[i]->common_refpoint (staff, parent_a) == staff)
362 new_elts.push_back (elts[i]);
364 return relative_group_extent (new_elts, refp, ext_a);
368 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_pure_relevant_grobs, 1);
370 Axis_group_interface::calc_pure_relevant_grobs (SCM smob)
372 Grob *me = unsmob_grob (smob);
374 extract_grob_set (me, "elements", elts);
376 vector<Grob*> relevant_grobs;
377 SCM pure_relevant_p = ly_lily_module_constant ("pure-relevant?");
379 for (vsize i = 0; i < elts.size (); i++)
381 if (to_boolean (scm_apply_1 (pure_relevant_p, elts[i]->self_scm (), SCM_EOL)))
382 relevant_grobs.push_back (elts[i]);
384 if (Item *it = dynamic_cast<Item*> (elts[i]))
389 Item *piece = it->find_prebroken_piece (d);
390 if (piece && to_boolean (scm_apply_1 (pure_relevant_p, piece->self_scm (), SCM_EOL)))
391 relevant_grobs.push_back (piece);
393 while (flip (&d) != LEFT);
397 SCM grobs_scm = Grob_array::make_array ();
398 unsmob_grob_array (grobs_scm)->set_array (relevant_grobs);
403 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_pure_y_common, 1);
405 Axis_group_interface::calc_pure_y_common (SCM smob)
407 Grob *me = unsmob_grob (smob);
409 extract_grob_set (me, "pure-relevant-grobs", elts);
410 Grob *common = common_refpoint_of_array (elts, me, Y_AXIS);
413 me->programming_error ("No common parent found in calc_pure_y_common.");
417 return common->self_scm ();
421 Axis_group_interface::calc_common (Grob *me, Axis axis)
423 extract_grob_set (me, "elements", elts);
424 Grob *common = common_refpoint_of_array (elts, me, axis);
427 me->programming_error ("No common parent found in calc_common axis.");
431 return common->self_scm ();
435 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_x_common, 1);
437 Axis_group_interface::calc_x_common (SCM grob)
439 return calc_common (unsmob_grob (grob), X_AXIS);
442 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_y_common, 1);
444 Axis_group_interface::calc_y_common (SCM grob)
446 return calc_common (unsmob_grob (grob), Y_AXIS);
450 Axis_group_interface::pure_group_height (Grob *me, int start, int end)
452 Grob *common = unsmob_grob (me->get_object ("pure-Y-common"));
456 programming_error ("no pure Y common refpoint");
459 Real my_coord = me->relative_coordinate (common, Y_AXIS);
460 Interval r (relative_pure_height (me, start, end));
466 Axis_group_interface::get_children (Grob *me, vector<Grob*> *found)
468 found->push_back (me);
470 if (!has_interface (me))
473 extract_grob_set (me, "elements", elements);
474 for (vsize i = 0; i < elements.size (); i++)
476 Grob *e = elements[i];
477 Axis_group_interface::get_children (e, found);
482 staff_priority_less (Grob * const &g1, Grob * const &g2)
484 Real priority_1 = robust_scm2double (g1->get_property ("outside-staff-priority"), -infinity_f);
485 Real priority_2 = robust_scm2double (g2->get_property ("outside-staff-priority"), -infinity_f);
487 if (priority_1 < priority_2)
489 else if (priority_1 > priority_2)
492 /* if neither grob has an outside-staff priority, the ordering will have no
493 effect -- we just need to choose a consistent ordering. We do this to
494 avoid the side-effect of calculating extents. */
495 if (isinf (priority_1))
498 /* if there is no preference in staff priority, choose the left-most one */
499 Grob *common = g1->common_refpoint (g2, X_AXIS);
500 Real start_1 = g1->extent (common, X_AXIS)[LEFT];
501 Real start_2 = g2->extent (common, X_AXIS)[LEFT];
502 return start_1 < start_2;
506 add_boxes (Grob *me, Grob *x_common, Grob *y_common, vector<Box> *const boxes, Skyline_pair *skylines)
508 /* if a child has skylines, use them instead of the extent box */
509 if (Skyline_pair *pair = Skyline_pair::unsmob (me->get_property ("vertical-skylines")))
511 Skyline_pair s = *pair;
512 s.shift (me->relative_coordinate (x_common, X_AXIS));
513 s.raise (me->relative_coordinate (y_common, Y_AXIS));
516 else if (Grob_array *elements = unsmob_grob_array (me->get_object ("elements")))
518 for (vsize i = 0; i < elements->size (); i++)
519 add_boxes (elements->grob (i), x_common, y_common, boxes, skylines);
521 else if (!scm_is_number (me->get_property ("outside-staff-priority"))
522 && !to_boolean (me->get_property ("cross-staff")))
524 boxes->push_back (Box (me->extent (x_common, X_AXIS),
525 me->extent (y_common, Y_AXIS)));
529 /* We want to avoid situations like this:
537 The point is that "still more text" should be positioned under
538 "more text". In order to achieve this, we place the grobs in several
539 passes. We keep track of the right-most horizontal position that has been
540 affected by the current pass so far (actually we keep track of 2
541 positions, one for above the staff, one for below).
543 In each pass, we loop through the unplaced grobs from left to right.
544 If the grob doesn't overlap the right-most affected position, we place it
545 (and then update the right-most affected position to point to the right
546 edge of the just-placed grob). Otherwise, we skip it until the next pass.
549 add_grobs_of_one_priority (Skyline_pair *const skylines,
550 vector<Grob*> elements,
555 Drul_array<Real> last_affected_position;
558 while (!elements.empty ())
560 last_affected_position[UP] = -infinity_f;
561 last_affected_position[DOWN] = -infinity_f;
563 for (vsize i = elements.size (); i--;)
565 Direction dir = get_grob_direction (elements[i]);
568 warning (_ ("an outside-staff object should have a direction, defaulting to up"));
572 Box b (elements[i]->extent (x_common, X_AXIS),
573 elements[i]->extent (y_common, Y_AXIS));
574 SCM horizon_padding_scm = elements[i]->get_property ("outside-staff-horizontal-padding");
575 Real horizon_padding = robust_scm2double (horizon_padding_scm, 0.0);
577 if (b[X_AXIS][LEFT] - 2*horizon_padding < last_affected_position[dir])
580 if (!b[X_AXIS].is_empty () && !b[Y_AXIS].is_empty ())
584 Skyline other = Skyline (boxes, horizon_padding, X_AXIS, -dir);
585 Real padding = robust_scm2double (elements[i]->get_property ("outside-staff-padding"), 0.5);
586 Real dist = (*skylines)[dir].distance (other) + padding;
590 b.translate (Offset (0, dir*dist));
591 elements[i]->translate_axis (dir*dist, Y_AXIS);
593 skylines->insert (b, 0, X_AXIS);
594 elements[i]->set_property ("outside-staff-priority", SCM_BOOL_F);
595 last_affected_position[dir] = b[X_AXIS][RIGHT];
599 Ugh: quadratic. --hwn
601 elements.erase (elements.begin () + i);
606 // TODO: it is tricky to correctly handle skyline placement of cross-staff grobs.
607 // For example, cross-staff beams cannot be formatted until the distance between
608 // staves is known and therefore any grobs that depend on the beam cannot be placed
609 // until the skylines are known. On the other hand, the distance between staves should
610 // really depend on position of the cross-staff grobs that lie between them.
611 // Currently, we just leave cross-staff grobs out of the
612 // skyline altogether, but this could mean that staves are placed so close together
613 // that there is no room for the cross-staff grob. It also means, of course, that
614 // we don't get the benefits of skyline placement for cross-staff grobs.
616 Axis_group_interface::skyline_spacing (Grob *me, vector<Grob*> elements)
618 /* For grobs with an outside-staff-priority, the sorting function might
619 call extent and cause suicide. This breaks the contract that is required
620 for the STL sort function. To avoid this, we make sure that any suicides
621 are triggered beforehand.
623 for (vsize i = 0; i < elements.size (); i++)
624 if (scm_is_number (elements[i]->get_property ("outside-staff-priority")))
625 elements[i]->extent (elements[i], X_AXIS);
627 vector_sort (elements, staff_priority_less);
628 Grob *x_common = common_refpoint_of_array (elements, me, X_AXIS);
629 Grob *y_common = common_refpoint_of_array (elements, me, Y_AXIS);
631 assert (y_common == me);
636 Skyline_pair skylines;
637 for (i = 0; i < elements.size ()
638 && !scm_is_number (elements[i]->get_property ("outside-staff-priority")); i++)
639 if (!to_boolean (elements[i]->get_property ("cross-staff")))
640 add_boxes (elements[i], x_common, y_common, &boxes, &skylines);
642 SCM padding_scm = me->get_property ("skyline-horizontal-padding");
643 Real padding = robust_scm2double (padding_scm, 0.1);
644 skylines.merge (Skyline_pair (boxes, padding, X_AXIS));
645 for (; i < elements.size (); i++)
647 if (to_boolean (elements[i]->get_property ("cross-staff")))
650 SCM priority = elements[i]->get_property ("outside-staff-priority");
651 vector<Grob*> current_elts;
652 current_elts.push_back (elements[i]);
653 while (i + 1 < elements.size ()
654 && scm_eq_p (elements[i+1]->get_property ("outside-staff-priority"), priority))
656 if (!to_boolean (elements[i+1]->get_property ("cross-staff")))
657 current_elts.push_back (elements[i+1]);
661 add_grobs_of_one_priority (&skylines, current_elts, x_common, y_common);
663 skylines.shift (-me->relative_coordinate (x_common, X_AXIS));
667 MAKE_SCHEME_CALLBACK (Axis_group_interface, print, 1)
669 Axis_group_interface::print (SCM smob)
674 Grob *me = unsmob_grob (smob);
676 if (Skyline_pair *s = Skyline_pair::unsmob (me->get_property ("vertical-skylines")))
678 ret.add_stencil (Lookup::points_to_line_stencil (0.1, (*s)[UP].to_points (X_AXIS))
679 .in_color (255, 0, 255));
680 ret.add_stencil (Lookup::points_to_line_stencil (0.1, (*s)[DOWN].to_points (X_AXIS))
681 .in_color (0, 255, 255));
683 return ret.smobbed_copy ();
686 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_pure_next_staff_spacing, 3)
688 Axis_group_interface::calc_pure_next_staff_spacing (SCM smob, SCM start, SCM end)
690 return calc_maybe_pure_next_staff_spacing (unsmob_grob (smob),
696 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_next_staff_spacing, 1)
698 Axis_group_interface::calc_next_staff_spacing (SCM smob)
700 return calc_maybe_pure_next_staff_spacing (unsmob_grob (smob),
707 Axis_group_interface::calc_maybe_pure_next_staff_spacing (Grob *me, bool pure, int start, int end)
709 Grob *grouper = unsmob_grob (me->get_object ("staff-grouper"));
713 Grob *last_in_group = Staff_grouper_interface::get_maybe_pure_last_grob (grouper, pure, start, end);
714 if (me == last_in_group)
715 return grouper->get_maybe_pure_property ("after-last-staff-spacing", pure, start, end);
717 return grouper->get_maybe_pure_property ("between-staff-spacing", pure, start, end);
719 return me->get_maybe_pure_property ("default-next-staff-spacing", pure, start, end);
723 Axis_group_interface::minimum_distance (Grob *g1, Grob *g2, Axis a)
725 SCM sym = ly_symbol2scm ((a == Y_AXIS) ? "vertical-skylines" : "horizontal-skylines");
727 Skyline_pair *s1 = Skyline_pair::unsmob (g1->get_property (sym));
728 Skyline_pair *s2 = Skyline_pair::unsmob (g2->get_property (sym));
730 return (*s1)[DOWN].distance ((*s2)[UP]);
734 ADD_INTERFACE (Axis_group_interface,
735 "An object that groups other layout objects.",
737 // TODO: some of these properties are specific to
738 // VerticalAxisGroup. We should split off a
739 // vertical-axis-group-interface.
743 "adjacent-pure-heights "
745 "default-next-staff-spacing "
747 "inter-loose-line-spacing "
748 "inter-staff-spacing "
750 "non-affinity-spacing "
751 "next-staff-spacing "
754 "pure-relevant-grobs "
755 "pure-relevant-items "
756 "pure-relevant-spanners "