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)
156 Interval dims = spanners[j]->pure_height (common, start, end);
157 if (!dims.is_empty ())
162 scm_vector_set_x (ret, scm_from_int (i), ly_interval2scm (iv));
168 Axis_group_interface::relative_pure_height (Grob *me, int start, int end)
170 /* It saves a _lot_ of time if we assume a VerticalAxisGroup is additive
171 (ie. height (i, k) = max (height (i, j) height (j, k)) for all i <= j <= k).
172 Unfortunately, it isn't always true, particularly if there is a
173 VerticalAlignment somewhere in the descendants.
175 Apart from PianoStaff, which has a fixed VerticalAlignment so it doesn't
176 count, the only VerticalAlignment comes from Score. This makes it
177 reasonably safe to assume that if our parent is a VerticalAlignment,
178 we can assume additivity and cache things nicely. */
179 Grob *p = me->get_parent (Y_AXIS);
180 if (p && Align_interface::has_interface (p))
181 return Axis_group_interface::cached_pure_height (me, start, end);
183 Grob *common = calc_pure_elts_and_common (me);
184 extract_grob_set (me, "pure-relevant-items", items);
185 extract_grob_set (me, "pure-relevant-spanners", spanners);
189 for (vsize i = 0; i < items.size (); i++)
191 Item *it = dynamic_cast<Item*> (items[i]);
192 int rank = it->get_column ()->get_rank ();
196 else if (rank >= start && it->pure_is_visible (start, end))
198 Interval dims = it->pure_height (common, start, end);
199 if (!dims.is_empty ())
204 for (vsize i = 0; i < spanners.size (); i++)
206 Interval_t<int> rank_span = spanners[i]->spanned_rank_interval ();
207 if (rank_span[LEFT] <= end && rank_span[RIGHT] >= start)
209 Interval dims = spanners[i]->pure_height (common, start, end);
210 if (!dims.is_empty ())
217 MAKE_SCHEME_CALLBACK (Axis_group_interface, width, 1);
219 Axis_group_interface::width (SCM smob)
221 Grob *me = unsmob_grob (smob);
222 return generic_group_extent (me, X_AXIS);
225 MAKE_SCHEME_CALLBACK (Axis_group_interface, height, 1);
227 Axis_group_interface::height (SCM smob)
229 Grob *me = unsmob_grob (smob);
230 return generic_group_extent (me, Y_AXIS);
233 MAKE_SCHEME_CALLBACK (Axis_group_interface, pure_height, 3);
235 Axis_group_interface::pure_height (SCM smob, SCM start_scm, SCM end_scm)
237 int start = robust_scm2int (start_scm, 0);
238 int end = robust_scm2int (end_scm, INT_MAX);
239 Grob *me = unsmob_grob (smob);
241 /* Maybe we are in the second pass of a two-pass spacing run. In that
242 case, the Y-extent of a system is already given to us */
243 System *system = dynamic_cast<System*> (me);
246 SCM line_break_details = system->column (start)->get_property ("line-break-system-details");
247 SCM system_y_extent = scm_assq (ly_symbol2scm ("system-Y-extent"), line_break_details);
248 if (scm_is_pair (system_y_extent))
249 return scm_cdr (system_y_extent);
252 return ly_interval2scm (pure_group_height (me, start, end));
255 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_skylines, 1);
257 Axis_group_interface::calc_skylines (SCM smob)
259 Grob *me = unsmob_grob (smob);
260 extract_grob_set (me, "elements", elts);
261 Skyline_pair skylines = skyline_spacing (me, elts);
263 /* add a minimum-Y-extent-sized box to the skyline */
264 SCM min_y_extent = me->get_property ("minimum-Y-extent");
265 if (is_number_pair (min_y_extent))
267 Box b (me->extent (me, X_AXIS), ly_scm2interval (min_y_extent));
268 skylines.insert (b, 0, X_AXIS);
270 return skylines.smobbed_copy ();
273 /* whereas calc_skylines calculates skylines for axis-groups with a lot of
274 visible children, combine_skylines is designed for axis-groups whose only
275 children are other axis-groups (ie. VerticalAlignment). Rather than
276 calculating all the skylines from scratch, we just merge the skylines
279 MAKE_SCHEME_CALLBACK (Axis_group_interface, combine_skylines, 1);
281 Axis_group_interface::combine_skylines (SCM smob)
283 Grob *me = unsmob_grob (smob);
284 extract_grob_set (me, "elements", elements);
285 Grob *y_common = common_refpoint_of_array (elements, me, Y_AXIS);
286 Grob *x_common = common_refpoint_of_array (elements, me, X_AXIS);
289 programming_error ("combining skylines that don't belong to me");
292 for (vsize i = 0; i < elements.size (); i++)
294 SCM skyline_scm = elements[i]->get_property ("vertical-skylines");
295 if (Skyline_pair::unsmob (skyline_scm))
297 Real offset = elements[i]->relative_coordinate (y_common, Y_AXIS);
298 Skyline_pair other = *Skyline_pair::unsmob (skyline_scm);
299 other.raise (offset);
300 other.shift (elements[i]->relative_coordinate (x_common, X_AXIS));
304 return ret.smobbed_copy ();
308 Axis_group_interface::generic_group_extent (Grob *me, Axis a)
310 /* trigger the callback to do skyline-spacing on the children */
312 (void) me->get_property ("vertical-skylines");
314 extract_grob_set (me, "elements", elts);
315 Grob *common = common_refpoint_of_array (elts, me, a);
317 Real my_coord = me->relative_coordinate (common, a);
318 Interval r (relative_group_extent (elts, common, a));
320 return ly_interval2scm (r - my_coord);
325 Axis_group_interface::calc_pure_elts_and_common (Grob *me)
327 if (Grob *c = unsmob_grob (me->get_object ("pure-Y-common")))
330 extract_grob_set (me, "elements", elts);
332 vector<Grob*> relevant_items;
333 vector<Grob*> relevant_spanners;
334 SCM pure_relevant_p = ly_lily_module_constant ("pure-relevant?");
336 for (vsize i = 0; i < elts.size (); i++)
338 if (to_boolean (scm_apply_1 (pure_relevant_p, elts[i]->self_scm (), SCM_EOL)))
340 if (dynamic_cast<Item*> (elts[i]))
341 relevant_items.push_back (elts[i]);
342 else if (dynamic_cast<Spanner*> (elts[i]))
343 relevant_spanners.push_back (elts[i]);
347 Item *it = dynamic_cast<Item*> (elts[i]);
352 Item *piece = it->find_prebroken_piece (d);
353 if (piece && to_boolean (scm_apply_1 (pure_relevant_p, piece->self_scm (), SCM_EOL)))
354 relevant_items.push_back (piece);
356 while (flip (&d) != LEFT);
358 vector_sort (relevant_items, Item::less);
360 Grob *common = common_refpoint_of_array (relevant_items, me, Y_AXIS);
361 common = common_refpoint_of_array (relevant_spanners, common, Y_AXIS);
363 me->set_object ("pure-Y-common", common->self_scm ());
365 SCM items_scm = Grob_array::make_array ();
366 SCM spanners_scm = Grob_array::make_array ();
368 unsmob_grob_array (items_scm)->set_array (relevant_items);
369 unsmob_grob_array (spanners_scm)->set_array (relevant_spanners);
370 me->set_object ("pure-relevant-items", items_scm);
371 me->set_object ("pure-relevant-spanners", spanners_scm);
376 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_x_common, 1);
378 Axis_group_interface::calc_x_common (SCM grob)
380 Grob *me = unsmob_grob (grob);
382 extract_grob_set (me, "elements", elts);
383 Grob *common = common_refpoint_of_array (elts, me, X_AXIS);
384 return common->self_scm ();
387 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_y_common, 1);
389 Axis_group_interface::calc_y_common (SCM grob)
391 Grob *me = unsmob_grob (grob);
393 extract_grob_set (me, "elements", elts);
394 Grob *common = common_refpoint_of_array (elts, me, Y_AXIS);
395 return common->self_scm ();
399 Axis_group_interface::pure_group_height (Grob *me, int start, int end)
401 Grob *common = calc_pure_elts_and_common (me);
403 Real my_coord = me->relative_coordinate (common, Y_AXIS);
404 Interval r (relative_pure_height (me, start, end));
410 Axis_group_interface::get_children (Grob *me, vector<Grob*> *found)
412 found->push_back (me);
414 if (!has_interface (me))
417 extract_grob_set (me, "elements", elements);
418 for (vsize i = 0; i < elements.size (); i++)
420 Grob *e = elements[i];
421 Axis_group_interface::get_children (e, found);
426 staff_priority_less (Grob * const &g1, Grob * const &g2)
428 Real priority_1 = robust_scm2double (g1->get_property ("outside-staff-priority"), -infinity_f);
429 Real priority_2 = robust_scm2double (g2->get_property ("outside-staff-priority"), -infinity_f);
431 if (priority_1 < priority_2)
433 else if (priority_1 > priority_2)
436 /* if neither grob has an outside-staff priority, the ordering will have no
437 effect -- we just need to choose a consistent ordering. We do this to
438 avoid the side-effect of calculating extents. */
439 if (isinf (priority_1))
442 /* if there is no preference in staff priority, choose the left-most one */
443 Grob *common = g1->common_refpoint (g2, X_AXIS);
444 Real start_1 = g1->extent (common, X_AXIS)[LEFT];
445 Real start_2 = g2->extent (common, X_AXIS)[LEFT];
446 return start_1 < start_2;
450 add_boxes (Grob *me, Grob *x_common, Grob *y_common, vector<Box> *const boxes, Skyline_pair *skylines)
452 /* if a child has skylines, use them instead of the extent box */
453 if (Skyline_pair *pair = Skyline_pair::unsmob (me->get_property ("vertical-skylines")))
455 Skyline_pair s = *pair;
456 s.shift (me->relative_coordinate (x_common, X_AXIS));
457 s.raise (me->relative_coordinate (y_common, Y_AXIS));
460 else if (Grob_array *elements = unsmob_grob_array (me->get_object ("elements")))
462 for (vsize i = 0; i < elements->size (); i++)
463 add_boxes (elements->grob (i), x_common, y_common, boxes, skylines);
465 else if (!scm_is_number (me->get_property ("outside-staff-priority"))
466 && !to_boolean (me->get_property ("cross-staff")))
468 boxes->push_back (Box (me->extent (x_common, X_AXIS),
469 me->extent (y_common, Y_AXIS)));
473 /* We want to avoid situations like this:
481 The point is that "still more text" should be positioned under
482 "more text". In order to achieve this, we place the grobs in several
483 passes. We keep track of the right-most horizontal position that has been
484 affected by the current pass so far (actually we keep track of 2
485 positions, one for above the staff, one for below).
487 In each pass, we loop through the unplaced grobs from left to right.
488 If the grob doesn't overlap the right-most affected position, we place it
489 (and then update the right-most affected position to point to the right
490 edge of the just-placed grob). Otherwise, we skip it until the next pass.
493 add_grobs_of_one_priority (Skyline_pair *const skylines,
494 vector<Grob*> elements,
499 Drul_array<Real> last_affected_position;
502 while (!elements.empty ())
504 last_affected_position[UP] = -infinity_f;
505 last_affected_position[DOWN] = -infinity_f;
507 for (vsize i = elements.size (); i--;)
509 Direction dir = get_grob_direction (elements[i]);
512 warning (_ ("an outside-staff object should have a direction, defaulting to up"));
516 Box b (elements[i]->extent (x_common, X_AXIS),
517 elements[i]->extent (y_common, Y_AXIS));
518 SCM horizon_padding_scm = elements[i]->get_property ("outside-staff-horizontal-padding");
519 Real horizon_padding = robust_scm2double (horizon_padding_scm, 0.0);
521 if (b[X_AXIS][LEFT] - 2*horizon_padding < last_affected_position[dir])
524 if (!b[X_AXIS].is_empty () && !b[Y_AXIS].is_empty ())
528 Skyline other = Skyline (boxes, horizon_padding, X_AXIS, -dir);
529 Real padding = robust_scm2double (elements[i]->get_property ("outside-staff-padding"), 0.5);
530 Real dist = (*skylines)[dir].distance (other) + padding;
534 b.translate (Offset (0, dir*dist));
535 elements[i]->translate_axis (dir*dist, Y_AXIS);
537 (*skylines)[dir].insert (b, 0, X_AXIS);
538 elements[i]->set_property ("outside-staff-priority", SCM_BOOL_F);
539 last_affected_position[dir] = b[X_AXIS][RIGHT];
543 Ugh: quadratic. --hwn
545 elements.erase (elements.begin () + i);
551 Axis_group_interface::skyline_spacing (Grob *me, vector<Grob*> elements)
553 vector_sort (elements, staff_priority_less);
554 Grob *x_common = common_refpoint_of_array (elements, me, X_AXIS);
555 Grob *y_common = common_refpoint_of_array (elements, me, Y_AXIS);
557 assert (y_common == me);
562 Skyline_pair skylines;
563 for (i = 0; i < elements.size ()
564 && !scm_is_number (elements[i]->get_property ("outside-staff-priority")); i++)
565 add_boxes (elements[i], x_common, y_common, &boxes, &skylines);
567 SCM padding_scm = me->get_property ("skyline-horizontal-padding");
568 Real padding = robust_scm2double (padding_scm, 0.1);
569 skylines.merge (Skyline_pair (boxes, padding, X_AXIS));
570 for (; i < elements.size (); i++)
572 SCM priority = elements[i]->get_property ("outside-staff-priority");
573 vector<Grob*> current_elts;
574 current_elts.push_back (elements[i]);
575 while (i + 1 < elements.size ()
576 && scm_eq_p (elements[i+1]->get_property ("outside-staff-priority"), priority))
577 current_elts.push_back (elements[++i]);
579 add_grobs_of_one_priority (&skylines, current_elts, x_common, y_common);
581 skylines.shift (-me->relative_coordinate (x_common, X_AXIS));
585 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_max_stretch, 1)
587 Axis_group_interface::calc_max_stretch (SCM smob)
589 Grob *me = unsmob_grob (smob);
591 extract_grob_set (me, "elements", elts);
593 for (vsize i = 0; i < elts.size (); i++)
594 if (Axis_group_interface::has_interface (elts[i]))
595 ret += robust_scm2double (elts[i]->get_property ("max-stretch"), 0.0);
597 return scm_from_double (ret);
600 extern bool debug_skylines;
601 MAKE_SCHEME_CALLBACK (Axis_group_interface, print, 1)
603 Axis_group_interface::print (SCM smob)
608 Grob *me = unsmob_grob (smob);
610 if (Skyline_pair *s = Skyline_pair::unsmob (me->get_property ("vertical-skylines")))
612 ret.add_stencil (Lookup::points_to_line_stencil (0.1, (*s)[UP].to_points (X_AXIS)).in_color (255, 0, 255));
613 ret.add_stencil (Lookup::points_to_line_stencil (0.1, (*s)[DOWN].to_points (X_AXIS)).in_color (0, 255, 255));
615 return ret.smobbed_copy ();
618 ADD_INTERFACE (Axis_group_interface,
620 "An object that groups other layout objects.",
625 "adjacent-pure-heights "
628 "keep-fixed-while-stretching "
632 "pure-relevant-items "
633 "pure-relevant-spanners "