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 /* add a minimum-Y-extent-sized box to the skyline */
267 SCM min_y_extent = me->get_property ("minimum-Y-extent");
268 if (is_number_pair (min_y_extent))
270 Box b (me->extent (me, X_AXIS), ly_scm2interval (min_y_extent));
271 skylines.insert (b, 0, X_AXIS);
273 return skylines.smobbed_copy ();
276 /* whereas calc_skylines calculates skylines for axis-groups with a lot of
277 visible children, combine_skylines is designed for axis-groups whose only
278 children are other axis-groups (ie. VerticalAlignment). Rather than
279 calculating all the skylines from scratch, we just merge the skylines
282 MAKE_SCHEME_CALLBACK (Axis_group_interface, combine_skylines, 1);
284 Axis_group_interface::combine_skylines (SCM smob)
286 Grob *me = unsmob_grob (smob);
287 extract_grob_set (me, "elements", elements);
288 Grob *y_common = common_refpoint_of_array (elements, me, Y_AXIS);
289 Grob *x_common = common_refpoint_of_array (elements, me, X_AXIS);
292 programming_error ("combining skylines that don't belong to me");
295 for (vsize i = 0; i < elements.size (); i++)
297 SCM skyline_scm = elements[i]->get_property ("vertical-skylines");
298 if (Skyline_pair::unsmob (skyline_scm))
300 Real offset = elements[i]->relative_coordinate (y_common, Y_AXIS);
301 Skyline_pair other = *Skyline_pair::unsmob (skyline_scm);
302 other.raise (offset);
303 other.shift (elements[i]->relative_coordinate (x_common, X_AXIS));
307 return ret.smobbed_copy ();
311 Axis_group_interface::generic_group_extent (Grob *me, Axis a)
313 /* trigger the callback to do skyline-spacing on the children */
315 (void) me->get_property ("vertical-skylines");
317 extract_grob_set (me, "elements", elts);
318 Grob *common = common_refpoint_of_array (elts, me, a);
320 Real my_coord = me->relative_coordinate (common, a);
321 Interval r (relative_group_extent (elts, common, a));
323 return ly_interval2scm (r - my_coord);
326 /* This is like generic_group_extent, but it only counts the grobs that
327 are children of some other axis-group. This is uncached; if it becomes
328 commonly used, it may be necessary to cache it somehow. */
330 Axis_group_interface::staff_extent (Grob *me, Grob *refp, Axis ext_a, Grob *staff, Axis parent_a)
332 extract_grob_set (me, "elements", elts);
333 vector<Grob*> new_elts;
335 for (vsize i = 0; i < elts.size (); i++)
336 if (elts[i]->common_refpoint (staff, parent_a) == staff)
337 new_elts.push_back (elts[i]);
339 return relative_group_extent (new_elts, refp, ext_a);
344 Axis_group_interface::calc_pure_elts_and_common (Grob *me)
346 if (Grob *c = unsmob_grob (me->get_object ("pure-Y-common")))
349 extract_grob_set (me, "elements", elts);
351 vector<Grob*> relevant_items;
352 vector<Grob*> relevant_spanners;
353 SCM pure_relevant_p = ly_lily_module_constant ("pure-relevant?");
355 for (vsize i = 0; i < elts.size (); i++)
357 if (to_boolean (scm_apply_1 (pure_relevant_p, elts[i]->self_scm (), SCM_EOL)))
359 if (dynamic_cast<Item*> (elts[i]))
360 relevant_items.push_back (elts[i]);
361 else if (dynamic_cast<Spanner*> (elts[i]))
362 relevant_spanners.push_back (elts[i]);
366 Item *it = dynamic_cast<Item*> (elts[i]);
371 Item *piece = it->find_prebroken_piece (d);
372 if (piece && to_boolean (scm_apply_1 (pure_relevant_p, piece->self_scm (), SCM_EOL)))
373 relevant_items.push_back (piece);
375 while (flip (&d) != LEFT);
377 vector_sort (relevant_items, Item::less);
379 Grob *common = common_refpoint_of_array (relevant_items, me, Y_AXIS);
380 common = common_refpoint_of_array (relevant_spanners, common, Y_AXIS);
382 me->set_object ("pure-Y-common", common->self_scm ());
384 SCM items_scm = Grob_array::make_array ();
385 SCM spanners_scm = Grob_array::make_array ();
387 unsmob_grob_array (items_scm)->set_array (relevant_items);
388 unsmob_grob_array (spanners_scm)->set_array (relevant_spanners);
389 me->set_object ("pure-relevant-items", items_scm);
390 me->set_object ("pure-relevant-spanners", spanners_scm);
395 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_x_common, 1);
397 Axis_group_interface::calc_x_common (SCM grob)
399 Grob *me = unsmob_grob (grob);
401 extract_grob_set (me, "elements", elts);
402 Grob *common = common_refpoint_of_array (elts, me, X_AXIS);
403 return common->self_scm ();
406 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_y_common, 1);
408 Axis_group_interface::calc_y_common (SCM grob)
410 Grob *me = unsmob_grob (grob);
412 extract_grob_set (me, "elements", elts);
413 Grob *common = common_refpoint_of_array (elts, me, Y_AXIS);
414 return common->self_scm ();
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);
570 Axis_group_interface::skyline_spacing (Grob *me, vector<Grob*> elements)
572 vector_sort (elements, staff_priority_less);
573 Grob *x_common = common_refpoint_of_array (elements, me, X_AXIS);
574 Grob *y_common = common_refpoint_of_array (elements, me, Y_AXIS);
576 assert (y_common == me);
581 Skyline_pair skylines;
582 for (i = 0; i < elements.size ()
583 && !scm_is_number (elements[i]->get_property ("outside-staff-priority")); i++)
584 add_boxes (elements[i], x_common, y_common, &boxes, &skylines);
586 SCM padding_scm = me->get_property ("skyline-horizontal-padding");
587 Real padding = robust_scm2double (padding_scm, 0.1);
588 skylines.merge (Skyline_pair (boxes, padding, X_AXIS));
589 for (; i < elements.size (); i++)
591 SCM priority = elements[i]->get_property ("outside-staff-priority");
592 vector<Grob*> current_elts;
593 current_elts.push_back (elements[i]);
594 while (i + 1 < elements.size ()
595 && scm_eq_p (elements[i+1]->get_property ("outside-staff-priority"), priority))
596 current_elts.push_back (elements[++i]);
598 add_grobs_of_one_priority (&skylines, current_elts, x_common, y_common);
600 skylines.shift (-me->relative_coordinate (x_common, X_AXIS));
604 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_max_stretch, 1)
606 Axis_group_interface::calc_max_stretch (SCM smob)
608 Grob *me = unsmob_grob (smob);
610 extract_grob_set (me, "elements", elts);
612 for (vsize i = 0; i < elts.size (); i++)
613 if (Axis_group_interface::has_interface (elts[i]))
614 ret += robust_scm2double (elts[i]->get_property ("max-stretch"), 0.0);
616 return scm_from_double (ret);
619 extern bool debug_skylines;
620 MAKE_SCHEME_CALLBACK (Axis_group_interface, print, 1)
622 Axis_group_interface::print (SCM smob)
627 Grob *me = unsmob_grob (smob);
629 if (Skyline_pair *s = Skyline_pair::unsmob (me->get_property ("vertical-skylines")))
631 ret.add_stencil (Lookup::points_to_line_stencil (0.1, (*s)[UP].to_points (X_AXIS)).in_color (255, 0, 255));
632 ret.add_stencil (Lookup::points_to_line_stencil (0.1, (*s)[DOWN].to_points (X_AXIS)).in_color (0, 255, 255));
634 return ret.smobbed_copy ();
637 ADD_INTERFACE (Axis_group_interface,
639 "An object that groups other layout objects.",
644 "adjacent-pure-heights "
647 "keep-fixed-while-stretching "
651 "pure-relevant-items "
652 "pure-relevant-spanners "