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);
328 Axis_group_interface::calc_pure_elts_and_common (Grob *me)
330 if (Grob *c = unsmob_grob (me->get_object ("pure-Y-common")))
333 extract_grob_set (me, "elements", elts);
335 vector<Grob*> relevant_items;
336 vector<Grob*> relevant_spanners;
337 SCM pure_relevant_p = ly_lily_module_constant ("pure-relevant?");
339 for (vsize i = 0; i < elts.size (); i++)
341 if (to_boolean (scm_apply_1 (pure_relevant_p, elts[i]->self_scm (), SCM_EOL)))
343 if (dynamic_cast<Item*> (elts[i]))
344 relevant_items.push_back (elts[i]);
345 else if (dynamic_cast<Spanner*> (elts[i]))
346 relevant_spanners.push_back (elts[i]);
350 Item *it = dynamic_cast<Item*> (elts[i]);
355 Item *piece = it->find_prebroken_piece (d);
356 if (piece && to_boolean (scm_apply_1 (pure_relevant_p, piece->self_scm (), SCM_EOL)))
357 relevant_items.push_back (piece);
359 while (flip (&d) != LEFT);
361 vector_sort (relevant_items, Item::less);
363 Grob *common = common_refpoint_of_array (relevant_items, me, Y_AXIS);
364 common = common_refpoint_of_array (relevant_spanners, common, Y_AXIS);
366 me->set_object ("pure-Y-common", common->self_scm ());
368 SCM items_scm = Grob_array::make_array ();
369 SCM spanners_scm = Grob_array::make_array ();
371 unsmob_grob_array (items_scm)->set_array (relevant_items);
372 unsmob_grob_array (spanners_scm)->set_array (relevant_spanners);
373 me->set_object ("pure-relevant-items", items_scm);
374 me->set_object ("pure-relevant-spanners", spanners_scm);
379 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_x_common, 1);
381 Axis_group_interface::calc_x_common (SCM grob)
383 Grob *me = unsmob_grob (grob);
385 extract_grob_set (me, "elements", elts);
386 Grob *common = common_refpoint_of_array (elts, me, X_AXIS);
387 return common->self_scm ();
390 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_y_common, 1);
392 Axis_group_interface::calc_y_common (SCM grob)
394 Grob *me = unsmob_grob (grob);
396 extract_grob_set (me, "elements", elts);
397 Grob *common = common_refpoint_of_array (elts, me, Y_AXIS);
398 return common->self_scm ();
402 Axis_group_interface::pure_group_height (Grob *me, int start, int end)
404 Grob *common = calc_pure_elts_and_common (me);
406 Real my_coord = me->relative_coordinate (common, Y_AXIS);
407 Interval r (relative_pure_height (me, start, end));
413 Axis_group_interface::get_children (Grob *me, vector<Grob*> *found)
415 found->push_back (me);
417 if (!has_interface (me))
420 extract_grob_set (me, "elements", elements);
421 for (vsize i = 0; i < elements.size (); i++)
423 Grob *e = elements[i];
424 Axis_group_interface::get_children (e, found);
429 staff_priority_less (Grob * const &g1, Grob * const &g2)
431 Real priority_1 = robust_scm2double (g1->get_property ("outside-staff-priority"), -infinity_f);
432 Real priority_2 = robust_scm2double (g2->get_property ("outside-staff-priority"), -infinity_f);
434 if (priority_1 < priority_2)
436 else if (priority_1 > priority_2)
439 /* if neither grob has an outside-staff priority, the ordering will have no
440 effect -- we just need to choose a consistent ordering. We do this to
441 avoid the side-effect of calculating extents. */
442 if (isinf (priority_1))
445 /* if there is no preference in staff priority, choose the left-most one */
446 Grob *common = g1->common_refpoint (g2, X_AXIS);
447 Real start_1 = g1->extent (common, X_AXIS)[LEFT];
448 Real start_2 = g2->extent (common, X_AXIS)[LEFT];
449 return start_1 < start_2;
453 add_boxes (Grob *me, Grob *x_common, Grob *y_common, vector<Box> *const boxes, Skyline_pair *skylines)
455 /* if a child has skylines, use them instead of the extent box */
456 if (Skyline_pair *pair = Skyline_pair::unsmob (me->get_property ("vertical-skylines")))
458 Skyline_pair s = *pair;
459 s.shift (me->relative_coordinate (x_common, X_AXIS));
460 s.raise (me->relative_coordinate (y_common, Y_AXIS));
463 else if (Grob_array *elements = unsmob_grob_array (me->get_object ("elements")))
465 for (vsize i = 0; i < elements->size (); i++)
466 add_boxes (elements->grob (i), x_common, y_common, boxes, skylines);
468 else if (!scm_is_number (me->get_property ("outside-staff-priority"))
469 && !to_boolean (me->get_property ("cross-staff")))
471 boxes->push_back (Box (me->extent (x_common, X_AXIS),
472 me->extent (y_common, Y_AXIS)));
476 /* We want to avoid situations like this:
484 The point is that "still more text" should be positioned under
485 "more text". In order to achieve this, we place the grobs in several
486 passes. We keep track of the right-most horizontal position that has been
487 affected by the current pass so far (actually we keep track of 2
488 positions, one for above the staff, one for below).
490 In each pass, we loop through the unplaced grobs from left to right.
491 If the grob doesn't overlap the right-most affected position, we place it
492 (and then update the right-most affected position to point to the right
493 edge of the just-placed grob). Otherwise, we skip it until the next pass.
496 add_grobs_of_one_priority (Skyline_pair *const skylines,
497 vector<Grob*> elements,
502 Drul_array<Real> last_affected_position;
505 while (!elements.empty ())
507 last_affected_position[UP] = -infinity_f;
508 last_affected_position[DOWN] = -infinity_f;
510 for (vsize i = elements.size (); i--;)
512 Direction dir = get_grob_direction (elements[i]);
515 warning (_ ("an outside-staff object should have a direction, defaulting to up"));
519 Box b (elements[i]->extent (x_common, X_AXIS),
520 elements[i]->extent (y_common, Y_AXIS));
521 SCM horizon_padding_scm = elements[i]->get_property ("outside-staff-horizontal-padding");
522 Real horizon_padding = robust_scm2double (horizon_padding_scm, 0.0);
524 if (b[X_AXIS][LEFT] - 2*horizon_padding < last_affected_position[dir])
527 if (!b[X_AXIS].is_empty () && !b[Y_AXIS].is_empty ())
531 Skyline other = Skyline (boxes, horizon_padding, X_AXIS, -dir);
532 Real padding = robust_scm2double (elements[i]->get_property ("outside-staff-padding"), 0.5);
533 Real dist = (*skylines)[dir].distance (other) + padding;
537 b.translate (Offset (0, dir*dist));
538 elements[i]->translate_axis (dir*dist, Y_AXIS);
540 (*skylines)[dir].insert (b, 0, X_AXIS);
541 elements[i]->set_property ("outside-staff-priority", SCM_BOOL_F);
542 last_affected_position[dir] = b[X_AXIS][RIGHT];
546 Ugh: quadratic. --hwn
548 elements.erase (elements.begin () + i);
554 Axis_group_interface::skyline_spacing (Grob *me, vector<Grob*> elements)
556 vector_sort (elements, staff_priority_less);
557 Grob *x_common = common_refpoint_of_array (elements, me, X_AXIS);
558 Grob *y_common = common_refpoint_of_array (elements, me, Y_AXIS);
560 assert (y_common == me);
565 Skyline_pair skylines;
566 for (i = 0; i < elements.size ()
567 && !scm_is_number (elements[i]->get_property ("outside-staff-priority")); i++)
568 add_boxes (elements[i], x_common, y_common, &boxes, &skylines);
570 SCM padding_scm = me->get_property ("skyline-horizontal-padding");
571 Real padding = robust_scm2double (padding_scm, 0.1);
572 skylines.merge (Skyline_pair (boxes, padding, X_AXIS));
573 for (; i < elements.size (); i++)
575 SCM priority = elements[i]->get_property ("outside-staff-priority");
576 vector<Grob*> current_elts;
577 current_elts.push_back (elements[i]);
578 while (i + 1 < elements.size ()
579 && scm_eq_p (elements[i+1]->get_property ("outside-staff-priority"), priority))
580 current_elts.push_back (elements[++i]);
582 add_grobs_of_one_priority (&skylines, current_elts, x_common, y_common);
584 skylines.shift (-me->relative_coordinate (x_common, X_AXIS));
588 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_max_stretch, 1)
590 Axis_group_interface::calc_max_stretch (SCM smob)
592 Grob *me = unsmob_grob (smob);
594 extract_grob_set (me, "elements", elts);
596 for (vsize i = 0; i < elts.size (); i++)
597 if (Axis_group_interface::has_interface (elts[i]))
598 ret += robust_scm2double (elts[i]->get_property ("max-stretch"), 0.0);
600 return scm_from_double (ret);
603 extern bool debug_skylines;
604 MAKE_SCHEME_CALLBACK (Axis_group_interface, print, 1)
606 Axis_group_interface::print (SCM smob)
611 Grob *me = unsmob_grob (smob);
613 if (Skyline_pair *s = Skyline_pair::unsmob (me->get_property ("vertical-skylines")))
615 ret.add_stencil (Lookup::points_to_line_stencil (0.1, (*s)[UP].to_points (X_AXIS)).in_color (255, 0, 255));
616 ret.add_stencil (Lookup::points_to_line_stencil (0.1, (*s)[DOWN].to_points (X_AXIS)).in_color (0, 255, 255));
618 return ret.smobbed_copy ();
621 ADD_INTERFACE (Axis_group_interface,
623 "An object that groups other layout objects.",
628 "adjacent-pure-heights "
631 "keep-fixed-while-stretching "
635 "pure-relevant-items "
636 "pure-relevant-spanners "