2 axis-group-interface.cc -- implement Axis_group_interface
4 source file of the GNU LilyPond music typesetter
6 (c) 2000--2007 Han-Wen Nienhuys <hanwen@xs4all.nl>
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"
18 #include "paper-column.hh"
19 #include "paper-score.hh"
20 #include "separation-item.hh"
26 Axis_group_interface::add_element (Grob *me, Grob *e)
28 SCM axes = me->get_property ("axes");
29 if (!scm_is_pair (axes))
30 programming_error ("axes should be nonempty");
32 for (SCM ax = axes; scm_is_pair (ax); ax = scm_cdr (ax))
34 Axis a = (Axis) scm_to_int (scm_car (ax));
36 if (!e->get_parent (a))
37 e->set_parent (me, a);
39 e->set_object ((a == X_AXIS)
40 ? ly_symbol2scm ("axis-group-parent-X")
41 : ly_symbol2scm ("axis-group-parent-Y"),
45 /* must be ordered, because Align_interface also uses
46 Axis_group_interface */
47 Pointer_group_interface::add_grob (me, ly_symbol2scm ("elements"), e);
51 Axis_group_interface::has_axis (Grob *me, Axis a)
53 SCM axes = me->get_property ("axes");
55 return (SCM_BOOL_F != scm_memq (scm_from_int (a), axes));
59 Axis_group_interface::relative_group_extent (vector<Grob*> const &elts,
63 for (vsize i = 0; i < elts.size (); i++)
66 if (!to_boolean (se->get_property ("cross-staff")))
68 Interval dims = se->extent (common, a);
69 if (!dims.is_empty ())
78 FIXME: pure extent handling has a lot of ad-hoc caching.
79 This should be done with grob property callbacks.
85 Axis_group_interface::cached_pure_height (Grob *me, int start, int end)
87 Paper_score *ps = get_root_system (me)->paper_score ();
88 vector<vsize> breaks = ps->get_break_indices ();
89 vector<Grob*> cols = ps->get_columns ();
91 SCM extents = me->get_property ("adjacent-pure-heights");
93 if (!scm_is_vector (extents))
94 return Interval (0, 0);
97 for (vsize i = 0; i + 1 < breaks.size (); i++)
99 int r = Paper_column::get_rank (cols[breaks[i]]);
104 ext.unite (ly_scm2interval (scm_c_vector_ref (extents, i)));
110 MAKE_SCHEME_CALLBACK (Axis_group_interface, adjacent_pure_heights, 1)
112 Axis_group_interface::adjacent_pure_heights (SCM smob)
114 Grob *me = unsmob_grob (smob);
116 Grob *common = calc_pure_elts_and_common (me);
117 extract_grob_set (me, "pure-relevant-items", items);
118 extract_grob_set (me, "pure-relevant-spanners", spanners);
120 Paper_score *ps = get_root_system (me)->paper_score ();
121 vector<vsize> breaks = ps->get_break_indices ();
122 vector<Grob*> cols = ps->get_columns ();
124 SCM ret = scm_c_make_vector (breaks.size () - 1, SCM_EOL);
126 for (vsize i = 0; i + 1 < breaks.size (); i++)
128 int start = Paper_column::get_rank (cols[breaks[i]]);
129 int end = Paper_column::get_rank (cols[breaks[i+1]]);
132 for (vsize j = it_index; j < items.size (); j++)
134 Item *it = dynamic_cast<Item*> (items[j]);
135 int rank = it->get_column ()->get_rank ();
137 if (rank <= end && it->pure_is_visible (start, end)
138 && !to_boolean (it->get_property ("cross-staff")))
140 Interval dims = items[j]->pure_height (common, start, end);
141 if (!dims.is_empty ())
151 for (vsize j = 0; j < spanners.size (); j++)
153 Interval_t<int> rank_span = spanners[j]->spanned_rank_interval ();
154 if (rank_span[LEFT] <= end && rank_span[RIGHT] >= start
155 && !to_boolean (spanners[j]->get_property ("cross-staff")))
157 Interval dims = spanners[j]->pure_height (common, start, end);
158 if (!dims.is_empty ())
163 scm_vector_set_x (ret, scm_from_int (i), ly_interval2scm (iv));
169 Axis_group_interface::relative_pure_height (Grob *me, int start, int end)
171 /* It saves a _lot_ of time if we assume a VerticalAxisGroup is additive
172 (ie. height (i, k) = max (height (i, j) height (j, k)) for all i <= j <= k).
173 Unfortunately, it isn't always true, particularly if there is a
174 VerticalAlignment somewhere in the descendants.
176 Apart from PianoStaff, which has a fixed VerticalAlignment so it doesn't
177 count, the only VerticalAlignment comes from Score. This makes it
178 reasonably safe to assume that if our parent is a VerticalAlignment,
179 we can assume additivity and cache things nicely. */
180 Grob *p = me->get_parent (Y_AXIS);
181 if (p && Align_interface::has_interface (p))
182 return Axis_group_interface::cached_pure_height (me, start, end);
184 Grob *common = calc_pure_elts_and_common (me);
185 extract_grob_set (me, "pure-relevant-items", items);
186 extract_grob_set (me, "pure-relevant-spanners", spanners);
190 for (vsize i = 0; i < items.size (); i++)
192 Item *it = dynamic_cast<Item*> (items[i]);
193 int rank = it->get_column ()->get_rank ();
197 else if (rank >= start && it->pure_is_visible (start, end)
198 && !to_boolean (it->get_property ("cross-staff")))
200 Interval dims = it->pure_height (common, start, end);
201 if (!dims.is_empty ())
206 for (vsize i = 0; i < spanners.size (); i++)
208 Interval_t<int> rank_span = spanners[i]->spanned_rank_interval ();
209 if (rank_span[LEFT] <= end && rank_span[RIGHT] >= start
210 && !to_boolean (spanners[i]->get_property ("cross-staff")))
212 Interval dims = spanners[i]->pure_height (common, start, end);
213 if (!dims.is_empty ())
220 MAKE_SCHEME_CALLBACK (Axis_group_interface, width, 1);
222 Axis_group_interface::width (SCM smob)
224 Grob *me = unsmob_grob (smob);
225 return generic_group_extent (me, X_AXIS);
228 MAKE_SCHEME_CALLBACK (Axis_group_interface, height, 1);
230 Axis_group_interface::height (SCM smob)
232 Grob *me = unsmob_grob (smob);
233 return generic_group_extent (me, Y_AXIS);
236 MAKE_SCHEME_CALLBACK (Axis_group_interface, pure_height, 3);
238 Axis_group_interface::pure_height (SCM smob, SCM start_scm, SCM end_scm)
240 int start = robust_scm2int (start_scm, 0);
241 int end = robust_scm2int (end_scm, INT_MAX);
242 Grob *me = unsmob_grob (smob);
244 /* Maybe we are in the second pass of a two-pass spacing run. In that
245 case, the Y-extent of a system is already given to us */
246 System *system = dynamic_cast<System*> (me);
249 SCM line_break_details = system->column (start)->get_property ("line-break-system-details");
250 SCM system_y_extent = scm_assq (ly_symbol2scm ("system-Y-extent"), line_break_details);
251 if (scm_is_pair (system_y_extent))
252 return scm_cdr (system_y_extent);
255 return ly_interval2scm (pure_group_height (me, start, end));
258 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_skylines, 1);
260 Axis_group_interface::calc_skylines (SCM smob)
262 Grob *me = unsmob_grob (smob);
263 extract_grob_set (me, "elements", elts);
264 Skyline_pair skylines = skyline_spacing (me, elts);
266 return skylines.smobbed_copy ();
269 /* whereas calc_skylines calculates skylines for axis-groups with a lot of
270 visible children, combine_skylines is designed for axis-groups whose only
271 children are other axis-groups (ie. VerticalAlignment). Rather than
272 calculating all the skylines from scratch, we just merge the skylines
275 MAKE_SCHEME_CALLBACK (Axis_group_interface, combine_skylines, 1);
277 Axis_group_interface::combine_skylines (SCM smob)
279 Grob *me = unsmob_grob (smob);
280 extract_grob_set (me, "elements", elements);
281 Grob *y_common = common_refpoint_of_array (elements, me, Y_AXIS);
282 Grob *x_common = common_refpoint_of_array (elements, me, X_AXIS);
285 programming_error ("combining skylines that don't belong to me");
288 for (vsize i = 0; i < elements.size (); i++)
290 SCM skyline_scm = elements[i]->get_property ("vertical-skylines");
291 if (Skyline_pair::unsmob (skyline_scm))
293 Real offset = elements[i]->relative_coordinate (y_common, Y_AXIS);
294 Skyline_pair other = *Skyline_pair::unsmob (skyline_scm);
295 other.raise (offset);
296 other.shift (elements[i]->relative_coordinate (x_common, X_AXIS));
300 return ret.smobbed_copy ();
304 Axis_group_interface::generic_group_extent (Grob *me, Axis a)
306 /* trigger the callback to do skyline-spacing on the children */
308 (void) me->get_property ("vertical-skylines");
310 extract_grob_set (me, "elements", elts);
311 Grob *common = common_refpoint_of_array (elts, me, a);
313 Real my_coord = me->relative_coordinate (common, a);
314 Interval r (relative_group_extent (elts, common, a));
316 return ly_interval2scm (r - my_coord);
319 /* This is like generic_group_extent, but it only counts the grobs that
320 are children of some other axis-group. This is uncached; if it becomes
321 commonly used, it may be necessary to cache it somehow. */
323 Axis_group_interface::staff_extent (Grob *me, Grob *refp, Axis ext_a, Grob *staff, Axis parent_a)
325 extract_grob_set (me, "elements", elts);
326 vector<Grob*> new_elts;
328 for (vsize i = 0; i < elts.size (); i++)
329 if (elts[i]->common_refpoint (staff, parent_a) == staff)
330 new_elts.push_back (elts[i]);
332 return relative_group_extent (new_elts, refp, ext_a);
337 Axis_group_interface::calc_pure_elts_and_common (Grob *me)
339 if (Grob *c = unsmob_grob (me->get_object ("pure-Y-common")))
342 extract_grob_set (me, "elements", elts);
344 vector<Grob*> relevant_items;
345 vector<Grob*> relevant_spanners;
346 SCM pure_relevant_p = ly_lily_module_constant ("pure-relevant?");
348 for (vsize i = 0; i < elts.size (); i++)
350 if (to_boolean (scm_apply_1 (pure_relevant_p, elts[i]->self_scm (), SCM_EOL)))
352 if (dynamic_cast<Item*> (elts[i]))
353 relevant_items.push_back (elts[i]);
354 else if (dynamic_cast<Spanner*> (elts[i]))
355 relevant_spanners.push_back (elts[i]);
359 Item *it = dynamic_cast<Item*> (elts[i]);
364 Item *piece = it->find_prebroken_piece (d);
365 if (piece && to_boolean (scm_apply_1 (pure_relevant_p, piece->self_scm (), SCM_EOL)))
366 relevant_items.push_back (piece);
368 while (flip (&d) != LEFT);
370 vector_sort (relevant_items, Item::less);
372 Grob *common = common_refpoint_of_array (relevant_items, me, Y_AXIS);
373 common = common_refpoint_of_array (relevant_spanners, common, Y_AXIS);
375 me->set_object ("pure-Y-common", common->self_scm ());
377 SCM items_scm = Grob_array::make_array ();
378 SCM spanners_scm = Grob_array::make_array ();
380 unsmob_grob_array (items_scm)->set_array (relevant_items);
381 unsmob_grob_array (spanners_scm)->set_array (relevant_spanners);
382 me->set_object ("pure-relevant-items", items_scm);
383 me->set_object ("pure-relevant-spanners", spanners_scm);
389 Axis_group_interface::calc_common (Grob *me, Axis axis)
391 extract_grob_set (me, "elements", elts);
392 Grob *common = common_refpoint_of_array (elts, me, axis);
395 me->programming_error ("No common parent found in calc_common axis.");
399 return common->self_scm ();
403 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_x_common, 1);
405 Axis_group_interface::calc_x_common (SCM grob)
407 return calc_common (unsmob_grob (grob), X_AXIS);
410 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_y_common, 1);
412 Axis_group_interface::calc_y_common (SCM grob)
414 return calc_common (unsmob_grob (grob), Y_AXIS);
418 Axis_group_interface::pure_group_height (Grob *me, int start, int end)
420 Grob *common = calc_pure_elts_and_common (me);
422 Real my_coord = me->relative_coordinate (common, Y_AXIS);
423 Interval r (relative_pure_height (me, start, end));
429 Axis_group_interface::get_children (Grob *me, vector<Grob*> *found)
431 found->push_back (me);
433 if (!has_interface (me))
436 extract_grob_set (me, "elements", elements);
437 for (vsize i = 0; i < elements.size (); i++)
439 Grob *e = elements[i];
440 Axis_group_interface::get_children (e, found);
445 staff_priority_less (Grob * const &g1, Grob * const &g2)
447 Real priority_1 = robust_scm2double (g1->get_property ("outside-staff-priority"), -infinity_f);
448 Real priority_2 = robust_scm2double (g2->get_property ("outside-staff-priority"), -infinity_f);
450 if (priority_1 < priority_2)
452 else if (priority_1 > priority_2)
455 /* if neither grob has an outside-staff priority, the ordering will have no
456 effect -- we just need to choose a consistent ordering. We do this to
457 avoid the side-effect of calculating extents. */
458 if (isinf (priority_1))
461 /* if there is no preference in staff priority, choose the left-most one */
462 Grob *common = g1->common_refpoint (g2, X_AXIS);
463 Real start_1 = g1->extent (common, X_AXIS)[LEFT];
464 Real start_2 = g2->extent (common, X_AXIS)[LEFT];
465 return start_1 < start_2;
469 add_boxes (Grob *me, Grob *x_common, Grob *y_common, vector<Box> *const boxes, Skyline_pair *skylines)
471 /* if a child has skylines, use them instead of the extent box */
472 if (Skyline_pair *pair = Skyline_pair::unsmob (me->get_property ("vertical-skylines")))
474 Skyline_pair s = *pair;
475 s.shift (me->relative_coordinate (x_common, X_AXIS));
476 s.raise (me->relative_coordinate (y_common, Y_AXIS));
479 else if (Grob_array *elements = unsmob_grob_array (me->get_object ("elements")))
481 for (vsize i = 0; i < elements->size (); i++)
482 add_boxes (elements->grob (i), x_common, y_common, boxes, skylines);
484 else if (!scm_is_number (me->get_property ("outside-staff-priority"))
485 && !to_boolean (me->get_property ("cross-staff")))
487 boxes->push_back (Box (me->extent (x_common, X_AXIS),
488 me->extent (y_common, Y_AXIS)));
492 /* We want to avoid situations like this:
500 The point is that "still more text" should be positioned under
501 "more text". In order to achieve this, we place the grobs in several
502 passes. We keep track of the right-most horizontal position that has been
503 affected by the current pass so far (actually we keep track of 2
504 positions, one for above the staff, one for below).
506 In each pass, we loop through the unplaced grobs from left to right.
507 If the grob doesn't overlap the right-most affected position, we place it
508 (and then update the right-most affected position to point to the right
509 edge of the just-placed grob). Otherwise, we skip it until the next pass.
512 add_grobs_of_one_priority (Skyline_pair *const skylines,
513 vector<Grob*> elements,
518 Drul_array<Real> last_affected_position;
521 while (!elements.empty ())
523 last_affected_position[UP] = -infinity_f;
524 last_affected_position[DOWN] = -infinity_f;
526 for (vsize i = elements.size (); i--;)
528 Direction dir = get_grob_direction (elements[i]);
531 warning (_ ("an outside-staff object should have a direction, defaulting to up"));
535 Box b (elements[i]->extent (x_common, X_AXIS),
536 elements[i]->extent (y_common, Y_AXIS));
537 SCM horizon_padding_scm = elements[i]->get_property ("outside-staff-horizontal-padding");
538 Real horizon_padding = robust_scm2double (horizon_padding_scm, 0.0);
540 if (b[X_AXIS][LEFT] - 2*horizon_padding < last_affected_position[dir])
543 if (!b[X_AXIS].is_empty () && !b[Y_AXIS].is_empty ())
547 Skyline other = Skyline (boxes, horizon_padding, X_AXIS, -dir);
548 Real padding = robust_scm2double (elements[i]->get_property ("outside-staff-padding"), 0.5);
549 Real dist = (*skylines)[dir].distance (other) + padding;
553 b.translate (Offset (0, dir*dist));
554 elements[i]->translate_axis (dir*dist, Y_AXIS);
556 (*skylines)[dir].insert (b, 0, X_AXIS);
557 elements[i]->set_property ("outside-staff-priority", SCM_BOOL_F);
558 last_affected_position[dir] = b[X_AXIS][RIGHT];
562 Ugh: quadratic. --hwn
564 elements.erase (elements.begin () + i);
569 // TODO: it is tricky to correctly handle skyline placement of cross-staff grobs.
570 // For example, cross-staff beams cannot be formatted until the distance between
571 // staves is known and therefore any grobs that depend on the beam cannot be placed
572 // until the skylines are known. On the other hand, the distance between staves should
573 // really depend on position of the cross-staff grobs that lie between them.
574 // Currently, we just leave cross-staff grobs out of the
575 // skyline altogether, but this could mean that staves are placed so close together
576 // that there is no room for the cross-staff grob. It also means, of course, that
577 // we don't get the benefits of skyline placement for cross-staff grobs.
579 Axis_group_interface::skyline_spacing (Grob *me, vector<Grob*> elements)
581 vector_sort (elements, staff_priority_less);
582 Grob *x_common = common_refpoint_of_array (elements, me, X_AXIS);
583 Grob *y_common = common_refpoint_of_array (elements, me, Y_AXIS);
585 assert (y_common == me);
590 Skyline_pair skylines;
591 for (i = 0; i < elements.size ()
592 && !scm_is_number (elements[i]->get_property ("outside-staff-priority")); i++)
593 if (!to_boolean (elements[i]->get_property ("cross-staff")))
594 add_boxes (elements[i], x_common, y_common, &boxes, &skylines);
596 SCM padding_scm = me->get_property ("skyline-horizontal-padding");
597 Real padding = robust_scm2double (padding_scm, 0.1);
598 skylines.merge (Skyline_pair (boxes, padding, X_AXIS));
599 for (; i < elements.size (); i++)
601 if (to_boolean (elements[i]->get_property ("cross-staff")))
604 SCM priority = elements[i]->get_property ("outside-staff-priority");
605 vector<Grob*> current_elts;
606 current_elts.push_back (elements[i]);
607 while (i + 1 < elements.size ()
608 && scm_eq_p (elements[i+1]->get_property ("outside-staff-priority"), priority))
609 current_elts.push_back (elements[++i]);
611 add_grobs_of_one_priority (&skylines, current_elts, x_common, y_common);
613 skylines.shift (-me->relative_coordinate (x_common, X_AXIS));
617 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_max_stretch, 1)
619 Axis_group_interface::calc_max_stretch (SCM smob)
621 Grob *me = unsmob_grob (smob);
623 extract_grob_set (me, "elements", elts);
625 for (vsize i = 0; i < elts.size (); i++)
626 if (Axis_group_interface::has_interface (elts[i]))
627 ret += robust_scm2double (elts[i]->get_property ("max-stretch"), 0.0);
629 return scm_from_double (ret);
632 extern bool debug_skylines;
633 MAKE_SCHEME_CALLBACK (Axis_group_interface, print, 1)
635 Axis_group_interface::print (SCM smob)
640 Grob *me = unsmob_grob (smob);
642 if (Skyline_pair *s = Skyline_pair::unsmob (me->get_property ("vertical-skylines")))
644 ret.add_stencil (Lookup::points_to_line_stencil (0.1, (*s)[UP].to_points (X_AXIS)).in_color (255, 0, 255));
645 ret.add_stencil (Lookup::points_to_line_stencil (0.1, (*s)[DOWN].to_points (X_AXIS)).in_color (0, 255, 255));
647 return ret.smobbed_copy ();
650 ADD_INTERFACE (Axis_group_interface,
651 "An object that groups other layout objects.",
656 "adjacent-pure-heights "
659 "keep-fixed-while-stretching "
663 "pure-relevant-items "
664 "pure-relevant-spanners "