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,
86 vector<Grob*> const &elts,
90 Paper_score *ps = get_root_system (me)->paper_score ();
91 vector<vsize> breaks = ps->get_break_indices ();
92 vector<Grob*> cols = ps->get_columns ();
93 vsize start_index = VPOS;
94 vsize end_index = VPOS;
96 for (vsize i = 0; i < breaks.size (); i++)
98 int r = Paper_column::get_rank (cols[breaks[i]]);
105 end_index = breaks.size () - 1;
107 if (start_index == VPOS || end_index == VPOS)
109 programming_error (_ ("tried to calculate pure-height at a non-breakpoint"));
110 return Interval (0, 0);
113 SCM extents = me->get_property ("cached-pure-extents");
114 if (!scm_is_vector (extents))
116 extents = scm_c_make_vector (breaks.size () - 1, SCM_EOL);
117 for (vsize i = 0; i + 1 < breaks.size (); i++)
119 int st = Paper_column::get_rank (cols[breaks[i]]);
120 int ed = Paper_column::get_rank (cols[breaks[i+1]]);
121 Interval iv = relative_pure_height (me, elts, common, st, ed, false);
122 scm_vector_set_x (extents, scm_from_int (i), ly_interval2scm (iv));
124 me->set_property ("cached-pure-extents", extents);
128 for (vsize i = start_index; i < end_index; i++)
129 ext.unite (ly_scm2interval (scm_c_vector_ref (extents, i)));
134 Axis_group_interface::relative_pure_height (Grob *me,
135 vector<Grob*> const &elts,
140 /* It saves a _lot_ of time if we assume a VerticalAxisGroup is additive
141 (ie. height (i, k) = height (i, j) + height (j, k) for all i <= j <= k).
142 Unfortunately, it isn't always true, particularly if there is a
143 VerticalAlignment somewhere in the descendants.
145 Apart from PianoStaff, which has a fixed VerticalAlignment so it doesn't
146 count, the only VerticalAlignment comes from Score. This makes it
147 reasonably safe to assume that if our parent is a VerticalAlignment,
148 we can assume additivity and cache things nicely. */
149 Grob *p = me->get_parent (Y_AXIS);
150 if (use_cache && p && Align_interface::has_interface (p))
151 return Axis_group_interface::cached_pure_height (me, elts, common, start, end);
155 for (vsize i = 0; i < elts.size (); i++)
157 Interval_t<int> rank_span = elts[i]->spanned_rank_interval ();
158 Item *it = dynamic_cast<Item*> (elts[i]);
159 if (rank_span[LEFT] <= end && rank_span[RIGHT] >= start && (!it || it->pure_is_visible (start, end)))
161 Interval dims = elts[i]->pure_height (common, start, end);
162 if (!dims.is_empty ())
169 MAKE_SCHEME_CALLBACK (Axis_group_interface, width, 1);
171 Axis_group_interface::width (SCM smob)
173 Grob *me = unsmob_grob (smob);
174 return generic_group_extent (me, X_AXIS);
177 MAKE_SCHEME_CALLBACK (Axis_group_interface, height, 1);
179 Axis_group_interface::height (SCM smob)
181 Grob *me = unsmob_grob (smob);
182 return generic_group_extent (me, Y_AXIS);
185 MAKE_SCHEME_CALLBACK (Axis_group_interface, pure_height, 3);
187 Axis_group_interface::pure_height (SCM smob, SCM start_scm, SCM end_scm)
189 int start = robust_scm2int (start_scm, 0);
190 int end = robust_scm2int (end_scm, INT_MAX);
191 Grob *me = unsmob_grob (smob);
193 /* Maybe we are in the second pass of a two-pass spacing run. In that
194 case, the Y-extent of a system is already given to us */
195 System *system = dynamic_cast<System*> (me);
198 SCM line_break_details = system->column (start)->get_property ("line-break-system-details");
199 SCM system_y_extent = scm_assq (ly_symbol2scm ("system-Y-extent"), line_break_details);
200 if (scm_is_pair (system_y_extent))
201 return scm_cdr (system_y_extent);
204 return pure_group_height (me, start, end);
207 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_skylines, 1);
209 Axis_group_interface::calc_skylines (SCM smob)
211 Grob *me = unsmob_grob (smob);
212 extract_grob_set (me, "elements", elts);
213 Skyline_pair skylines = skyline_spacing (me, elts);
215 /* add a minimum-Y-extent-sized box to the skyline */
216 SCM min_y_extent = me->get_property ("minimum-Y-extent");
217 if (is_number_pair (min_y_extent))
219 Box b (me->extent (me, X_AXIS), ly_scm2interval (min_y_extent));
220 skylines.insert (b, 0, X_AXIS);
222 return skylines.smobbed_copy ();
225 /* whereas calc_skylines calculates skylines for axis-groups with a lot of
226 visible children, combine_skylines is designed for axis-groups whose only
227 children are other axis-groups (ie. VerticalAlignment). Rather than
228 calculating all the skylines from scratch, we just merge the skylines
231 MAKE_SCHEME_CALLBACK (Axis_group_interface, combine_skylines, 1);
233 Axis_group_interface::combine_skylines (SCM smob)
235 Grob *me = unsmob_grob (smob);
236 extract_grob_set (me, "elements", elements);
237 Grob *y_common = common_refpoint_of_array (elements, me, Y_AXIS);
239 assert (y_common == me);
242 for (vsize i = 0; i < elements.size (); i++)
244 SCM skyline_scm = elements[i]->get_property ("vertical-skylines");
245 if (Skyline_pair::unsmob (skyline_scm))
247 Real offset = elements[i]->relative_coordinate (y_common, Y_AXIS);
248 Skyline_pair other = *Skyline_pair::unsmob (skyline_scm);
249 other.raise (offset);
253 return ret.smobbed_copy ();
257 Axis_group_interface::generic_group_extent (Grob *me, Axis a)
259 /* trigger the callback to do skyline-spacing on the children */
261 (void) me->get_property ("vertical-skylines");
263 extract_grob_set (me, "elements", elts);
264 Grob *common = common_refpoint_of_array (elts, me, a);
266 Real my_coord = me->relative_coordinate (common, a);
267 Interval r (relative_group_extent (elts, common, a));
269 return ly_interval2scm (r - my_coord);
274 Axis_group_interface::calc_pure_elts_and_common (Grob *me)
276 if (Grob *c = unsmob_grob (me->get_object ("pure-Y-common")))
279 extract_grob_set (me, "elements", elts);
281 vector<Grob*> relevant_elts;
282 SCM pure_relevant_p = ly_lily_module_constant ("pure-relevant?");
284 for (vsize i = 0; i < elts.size (); i++)
286 if (to_boolean (scm_apply_1 (pure_relevant_p, elts[i]->self_scm (), SCM_EOL)))
287 relevant_elts.push_back (elts[i]);
289 Item *it = dynamic_cast<Item*> (elts[i]);
294 Item *piece = it->find_prebroken_piece (d);
295 if (piece && to_boolean (scm_apply_1 (pure_relevant_p, piece->self_scm (), SCM_EOL)))
296 relevant_elts.push_back (piece);
298 while (flip (&d) != LEFT);
301 Grob *common = common_refpoint_of_array (relevant_elts, me, Y_AXIS);
302 me->set_object ("pure-Y-common", common->self_scm ());
304 SCM ga_scm = Grob_array::make_array ();
305 Grob_array *ga = unsmob_grob_array (ga_scm);
306 ga->set_array (relevant_elts);
307 me->set_object ("pure-relevant-elements", ga_scm);
312 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_x_common, 1);
314 Axis_group_interface::calc_x_common (SCM grob)
316 Grob *me = unsmob_grob (grob);
318 extract_grob_set (me, "elements", elts);
319 Grob *common = common_refpoint_of_array (elts, me, X_AXIS);
320 return common->self_scm ();
323 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_y_common, 1);
325 Axis_group_interface::calc_y_common (SCM grob)
327 Grob *me = unsmob_grob (grob);
329 extract_grob_set (me, "elements", elts);
330 Grob *common = common_refpoint_of_array (elts, me, Y_AXIS);
331 return common->self_scm ();
335 Axis_group_interface::pure_group_height (Grob *me, int start, int end)
337 Grob *common = calc_pure_elts_and_common (me);
339 extract_grob_set (me, "pure-relevant-elements", elts);
340 Real my_coord = me->relative_coordinate (common, Y_AXIS);
341 Interval r (relative_pure_height (me, elts, common, start, end, true));
343 return ly_interval2scm (r - my_coord);
347 Axis_group_interface::get_children (Grob *me, vector<Grob*> *found)
349 found->push_back (me);
351 if (!has_interface (me))
354 extract_grob_set (me, "elements", elements);
355 for (vsize i = 0; i < elements.size (); i++)
357 Grob *e = elements[i];
358 Axis_group_interface::get_children (e, found);
363 staff_priority_less (Grob * const &g1, Grob * const &g2)
365 Real priority_1 = robust_scm2double (g1->get_property ("outside-staff-priority"), -infinity_f);
366 Real priority_2 = robust_scm2double (g2->get_property ("outside-staff-priority"), -infinity_f);
368 if (priority_1 < priority_2)
370 else if (priority_1 > priority_2)
373 /* if neither grob has an outside-staff priority, the ordering will have no
374 effect -- we just need to choose a consistent ordering. We do this to
375 avoid the side-effect of calculating extents. */
376 if (isinf (priority_1))
379 /* if there is no preference in staff priority, choose the left-most one */
380 Grob *common = g1->common_refpoint (g2, X_AXIS);
381 Real start_1 = g1->extent (common, X_AXIS)[LEFT];
382 Real start_2 = g2->extent (common, X_AXIS)[LEFT];
383 return start_1 < start_2;
387 add_boxes (Grob *me, Grob *x_common, Grob *y_common, vector<Box> *const boxes, Skyline_pair *skylines)
389 /* if a child has skylines, use them instead of the extent box */
390 if (Skyline_pair *pair = Skyline_pair::unsmob (me->get_property ("vertical-skylines")))
392 Skyline_pair s = *pair;
393 s.shift (me->relative_coordinate (x_common, X_AXIS));
394 s.raise (me->relative_coordinate (y_common, Y_AXIS));
397 else if (Grob_array *elements = unsmob_grob_array (me->get_object ("elements")))
399 for (vsize i = 0; i < elements->size (); i++)
400 add_boxes (elements->grob (i), x_common, y_common, boxes, skylines);
402 else if (!scm_is_number (me->get_property ("outside-staff-priority"))
403 && !to_boolean (me->get_property ("cross-staff")))
405 boxes->push_back (Box (me->extent (x_common, X_AXIS),
406 me->extent (y_common, Y_AXIS)));
410 /* We want to avoid situations like this:
418 The point is that "still more text" should be positioned under
419 "more text". In order to achieve this, we place the grobs in several
420 passes. We keep track of the right-most horizontal position that has been
421 affected by the current pass so far (actually we keep track of 2
422 positions, one for above the staff, one for below).
424 In each pass, we loop through the unplaced grobs from left to right.
425 If the grob overlaps the right-most affected position, we place it
426 (and then update the right-most affected position to point to the right
427 edge of the just-placed grob). Otherwise, we skip it until the next pass.
430 add_grobs_of_one_priority (Skyline_pair *const skylines,
431 vector<Grob*> elements,
436 Drul_array<Real> last_affected_position;
439 while (!elements.empty ())
441 last_affected_position[UP] = -infinity_f;
442 last_affected_position[DOWN] = -infinity_f;
444 for (vsize i = elements.size (); i--;)
446 Direction dir = get_grob_direction (elements[i]);
449 warning (_ ("an outside-staff object should have a direction, defaulting to up"));
453 Box b (elements[i]->extent (x_common, X_AXIS),
454 elements[i]->extent (y_common, Y_AXIS));
455 SCM horizon_padding_scm = elements[i]->get_property ("outside-staff-horizontal-padding");
456 Real horizon_padding = robust_scm2double (horizon_padding_scm, 0.0);
458 if (b[X_AXIS][LEFT] - 2*horizon_padding < last_affected_position[dir])
461 if (!b[X_AXIS].is_empty () && !b[Y_AXIS].is_empty ())
465 Skyline other = Skyline (boxes, horizon_padding, X_AXIS, -dir);
466 Real padding = robust_scm2double (elements[i]->get_property ("outside-staff-padding"), 0.5);
467 Real dist = (*skylines)[dir].distance (other) + padding;
471 b.translate (Offset (0, dir*dist));
472 elements[i]->translate_axis (dir*dist, Y_AXIS);
474 (*skylines)[dir].insert (b, 0, X_AXIS);
475 elements[i]->set_property ("outside-staff-priority", SCM_BOOL_F);
476 last_affected_position[dir] = b[X_AXIS][RIGHT];
480 Ugh: quadratic. --hwn
482 elements.erase (elements.begin () + i);
488 Axis_group_interface::skyline_spacing (Grob *me, vector<Grob*> elements)
490 vector_sort (elements, staff_priority_less);
491 Grob *x_common = common_refpoint_of_array (elements, me, X_AXIS);
492 Grob *y_common = common_refpoint_of_array (elements, me, Y_AXIS);
494 assert (y_common == me);
499 Skyline_pair skylines;
500 for (i = 0; i < elements.size ()
501 && !scm_is_number (elements[i]->get_property ("outside-staff-priority")); i++)
502 add_boxes (elements[i], x_common, y_common, &boxes, &skylines);
504 SCM padding_scm = me->get_property ("skyline-horizontal-padding");
505 Real padding = robust_scm2double (padding_scm, 0.1);
506 skylines.merge (Skyline_pair (boxes, padding, X_AXIS));
507 for (; i < elements.size (); i++)
509 SCM priority = elements[i]->get_property ("outside-staff-priority");
510 vector<Grob*> current_elts;
511 current_elts.push_back (elements[i]);
512 while (i + 1 < elements.size ()
513 && scm_eq_p (elements[i+1]->get_property ("outside-staff-priority"), priority))
514 current_elts.push_back (elements[++i]);
516 add_grobs_of_one_priority (&skylines, current_elts, x_common, y_common);
518 skylines.shift (-me->relative_coordinate (x_common, X_AXIS));
522 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_max_stretch, 1)
524 Axis_group_interface::calc_max_stretch (SCM smob)
526 Grob *me = unsmob_grob (smob);
528 extract_grob_set (me, "elements", elts);
530 for (vsize i = 0; i < elts.size (); i++)
531 if (Axis_group_interface::has_interface (elts[i]))
532 ret += robust_scm2double (elts[i]->get_property ("max-stretch"), 0.0);
534 return scm_from_double (ret);
537 extern bool debug_skylines;
538 MAKE_SCHEME_CALLBACK (Axis_group_interface, print, 1)
540 Axis_group_interface::print (SCM smob)
545 Grob *me = unsmob_grob (smob);
547 if (Skyline_pair *s = Skyline_pair::unsmob (me->get_property ("vertical-skylines")))
549 ret.add_stencil (Lookup::points_to_line_stencil (0.1, (*s)[UP].to_points (X_AXIS)).in_color (255, 0, 255));
550 ret.add_stencil (Lookup::points_to_line_stencil (0.1, (*s)[DOWN].to_points (X_AXIS)).in_color (0, 255, 255));
552 return ret.smobbed_copy ();
555 ADD_INTERFACE (Axis_group_interface,
557 "An object that groups other layout objects.",
564 "keep-fixed-while-stretching "
567 "pure-relevant-elements "
569 "cached-pure-extents "