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"
17 #include "paper-column.hh"
18 #include "paper-score.hh"
19 #include "separation-item.hh"
24 Axis_group_interface::add_element (Grob *me, Grob *e)
26 SCM axes = me->get_property ("axes");
27 if (!scm_is_pair (axes))
28 programming_error ("axes should be nonempty");
30 for (SCM ax = axes; scm_is_pair (ax); ax = scm_cdr (ax))
32 Axis a = (Axis) scm_to_int (scm_car (ax));
34 if (!e->get_parent (a))
35 e->set_parent (me, a);
37 e->set_object ((a == X_AXIS)
38 ? ly_symbol2scm ("axis-group-parent-X")
39 : ly_symbol2scm ("axis-group-parent-Y"),
43 /* must be ordered, because Align_interface also uses
44 Axis_group_interface */
45 Pointer_group_interface::add_grob (me, ly_symbol2scm ("elements"), e);
49 Axis_group_interface::has_axis (Grob *me, Axis a)
51 SCM axes = me->get_property ("axes");
53 return (SCM_BOOL_F != scm_memq (scm_from_int (a), axes));
57 Axis_group_interface::relative_group_extent (vector<Grob*> const &elts,
61 for (vsize i = 0; i < elts.size (); i++)
64 if (!to_boolean (se->get_property ("cross-staff")))
66 Interval dims = se->extent (common, a);
67 if (!dims.is_empty ())
76 FIXME: pure extent handling has a lot of ad-hoc caching.
77 This should be done with grob property callbacks.
83 Axis_group_interface::cached_pure_height (Grob *me,
84 vector<Grob*> const &elts,
88 Paper_score *ps = get_root_system (me)->paper_score ();
89 vector<vsize> breaks = ps->get_break_indices ();
90 vector<Grob*> cols = ps->get_columns ();
91 vsize start_index = VPOS;
92 vsize end_index = VPOS;
94 for (vsize i = 0; i < breaks.size (); i++)
96 int r = Paper_column::get_rank (cols[breaks[i]]);
103 end_index = breaks.size () - 1;
105 if (start_index == VPOS || end_index == VPOS)
107 programming_error (_ ("tried to calculate pure-height at a non-breakpoint"));
108 return Interval (0, 0);
111 SCM extents = me->get_property ("cached-pure-extents");
112 if (!scm_is_vector (extents))
114 extents = scm_c_make_vector (breaks.size () - 1, SCM_EOL);
115 for (vsize i = 0; i + 1 < breaks.size (); i++)
117 int st = Paper_column::get_rank (cols[breaks[i]]);
118 int ed = Paper_column::get_rank (cols[breaks[i+1]]);
119 Interval iv = relative_pure_height (me, elts, common, st, ed, false);
120 scm_vector_set_x (extents, scm_from_int (i), ly_interval2scm (iv));
122 me->set_property ("cached-pure-extents", extents);
126 for (vsize i = start_index; i < end_index; i++)
127 ext.unite (ly_scm2interval (scm_c_vector_ref (extents, i)));
132 Axis_group_interface::relative_pure_height (Grob *me,
133 vector<Grob*> const &elts,
138 /* It saves a _lot_ of time if we assume a VerticalAxisGroup is additive
139 (ie. height (i, k) = height (i, j) + height (j, k) for all i <= j <= k).
140 Unfortunately, it isn't always true, particularly if there is a
141 VerticalAlignment somewhere in the descendants.
143 Apart from PianoStaff, which has a fixed VerticalAlignment so it doesn't
144 count, the only VerticalAlignment comes from Score. This makes it
145 reasonably safe to assume that if our parent is a VerticalAlignment,
146 we can assume additivity and cache things nicely. */
147 Grob *p = me->get_parent (Y_AXIS);
148 if (use_cache && p && Align_interface::has_interface (p))
149 return Axis_group_interface::cached_pure_height (me, elts, common, start, end);
153 for (vsize i = 0; i < elts.size (); i++)
155 Interval_t<int> rank_span = elts[i]->spanned_rank_iv ();
156 Item *it = dynamic_cast<Item*> (elts[i]);
157 if (rank_span[LEFT] <= end && rank_span[RIGHT] >= start && (!it || it->pure_is_visible (start, end)))
159 Interval dims = elts[i]->pure_height (common, start, end);
160 if (!dims.is_empty ())
167 MAKE_SCHEME_CALLBACK (Axis_group_interface, width, 1);
169 Axis_group_interface::width (SCM smob)
171 Grob *me = unsmob_grob (smob);
172 return generic_group_extent (me, X_AXIS);
175 MAKE_SCHEME_CALLBACK (Axis_group_interface, height, 1);
177 Axis_group_interface::height (SCM smob)
179 Grob *me = unsmob_grob (smob);
180 return generic_group_extent (me, Y_AXIS);
183 MAKE_SCHEME_CALLBACK (Axis_group_interface, pure_height, 3);
185 Axis_group_interface::pure_height (SCM smob, SCM start_scm, SCM end_scm)
187 int start = robust_scm2int (start_scm, 0);
188 int end = robust_scm2int (end_scm, INT_MAX);
189 Grob *me = unsmob_grob (smob);
191 /* Maybe we are in the second pass of a two-pass spacing run. In that
192 case, the Y-extent of a system is already given to us */
193 System *system = dynamic_cast<System*> (me);
196 SCM line_break_details = system->column (start)->get_property ("line-break-system-details");
197 SCM system_y_extent = scm_assq (ly_symbol2scm ("system-Y-extent"), line_break_details);
198 if (scm_is_pair (system_y_extent))
199 return scm_cdr (system_y_extent);
202 return pure_group_height (me, start, end);
205 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_skylines, 1);
207 Axis_group_interface::calc_skylines (SCM smob)
209 Grob *me = unsmob_grob (smob);
210 extract_grob_set (me, "elements", elts);
211 return skyline_spacing (me, elts).smobbed_copy ();
214 /* whereas calc_skylines calculates skylines for axis-groups with a lot of
215 visible children, combine_skylines is designed for axis-groups whose only
216 children are other axis-groups (ie. VerticalAlignment). Rather than
217 calculating all the skylines from scratch, we just merge the skylines
220 MAKE_SCHEME_CALLBACK (Axis_group_interface, combine_skylines, 1);
222 Axis_group_interface::combine_skylines (SCM smob)
224 Grob *me = unsmob_grob (smob);
225 extract_grob_set (me, "elements", elements);
226 Grob *y_common = common_refpoint_of_array (elements, me, Y_AXIS);
228 assert (y_common == me);
231 for (vsize i = 0; i < elements.size (); i++)
233 SCM skyline_scm = elements[i]->get_property ("skylines");
234 if (Skyline_pair::unsmob (skyline_scm))
236 Real offset = elements[i]->relative_coordinate (y_common, Y_AXIS);
237 Skyline_pair other = *Skyline_pair::unsmob (skyline_scm);
238 other.raise (offset);
242 return ret.smobbed_copy ();
246 Axis_group_interface::generic_group_extent (Grob *me, Axis a)
248 /* trigger the callback to do skyline-spacing on the children */
249 (void) me->get_property ("skylines");
251 extract_grob_set (me, "elements", elts);
252 Grob *common = common_refpoint_of_array (elts, me, a);
254 Real my_coord = me->relative_coordinate (common, a);
255 Interval r (relative_group_extent (elts, common, a));
257 return ly_interval2scm (r - my_coord);
262 Axis_group_interface::calc_pure_elts_and_common (Grob *me)
264 if (Grob *c = unsmob_grob (me->get_object ("pure-Y-common")))
267 extract_grob_set (me, "elements", elts);
269 vector<Grob*> relevant_elts;
270 SCM pure_relevant_p = ly_lily_module_constant ("pure-relevant?");
272 for (vsize i = 0; i < elts.size (); i++)
274 if (to_boolean (scm_apply_1 (pure_relevant_p, elts[i]->self_scm (), SCM_EOL)))
275 relevant_elts.push_back (elts[i]);
277 Item *it = dynamic_cast<Item*> (elts[i]);
282 Item *piece = it->find_prebroken_piece (d);
283 if (piece && to_boolean (scm_apply_1 (pure_relevant_p, piece->self_scm (), SCM_EOL)))
284 relevant_elts.push_back (piece);
286 while (flip (&d) != LEFT);
289 Grob *common = common_refpoint_of_array (relevant_elts, me, Y_AXIS);
290 me->set_object ("pure-Y-common", common->self_scm ());
292 SCM ga_scm = Grob_array::make_array ();
293 Grob_array *ga = unsmob_grob_array (ga_scm);
294 ga->set_array (relevant_elts);
295 me->set_object ("pure-relevant-elements", ga_scm);
300 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_x_common, 1);
302 Axis_group_interface::calc_x_common (SCM grob)
304 Grob *me = unsmob_grob (grob);
306 extract_grob_set (me, "elements", elts);
307 Grob *common = common_refpoint_of_array (elts, me, X_AXIS);
308 return common->self_scm ();
311 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_y_common, 1);
313 Axis_group_interface::calc_y_common (SCM grob)
315 Grob *me = unsmob_grob (grob);
317 extract_grob_set (me, "elements", elts);
318 Grob *common = common_refpoint_of_array (elts, me, Y_AXIS);
319 return common->self_scm ();
323 Axis_group_interface::pure_group_height (Grob *me, int start, int end)
325 Grob *common = calc_pure_elts_and_common (me);
327 extract_grob_set (me, "pure-relevant-elements", elts);
328 Real my_coord = me->relative_coordinate (common, Y_AXIS);
329 Interval r (relative_pure_height (me, elts, common, start, end, true));
331 return ly_interval2scm (r - my_coord);
335 Axis_group_interface::get_children (Grob *me, vector<Grob*> *found)
337 found->push_back (me);
339 if (!has_interface (me))
342 extract_grob_set (me, "elements", elements);
343 for (vsize i = 0; i < elements.size (); i++)
345 Grob *e = elements[i];
346 Axis_group_interface::get_children (e, found);
351 staff_priority_less (Grob * const &g1, Grob * const &g2)
353 Real priority_1 = robust_scm2double (g1->get_property ("outside-staff-priority"), -infinity_f);
354 Real priority_2 = robust_scm2double (g2->get_property ("outside-staff-priority"), -infinity_f);
356 if (priority_1 < priority_2)
358 else if (priority_1 > priority_2)
361 /* if neither grob has an outside-staff priority, the ordering will have no
362 effect -- we just need to choose a consistent ordering. We do this to
363 avoid the side-effect of calculating extents. */
364 if (isinf (priority_1))
367 /* if there is no preference in staff priority, choose the left-most one */
368 Grob *common = g1->common_refpoint (g2, X_AXIS);
369 Real start_1 = g1->extent (common, X_AXIS)[LEFT];
370 Real start_2 = g2->extent (common, X_AXIS)[LEFT];
371 return start_1 < start_2;
375 add_boxes (Grob *me, Grob *x_common, Grob *y_common, vector<Box> *const boxes)
377 /* if we are a parent, consider the children's boxes instead of mine */
378 if (Grob_array *elements = unsmob_grob_array (me->get_object ("elements")))
380 for (vsize i = 0; i < elements->size (); i++)
381 add_boxes (elements->grob (i), x_common, y_common, boxes);
383 else if (!scm_is_number (me->get_property ("outside-staff-priority"))
384 && !to_boolean (me->get_property ("cross-staff")))
385 boxes->push_back (Box (me->extent (x_common, X_AXIS),
386 me->extent (y_common, Y_AXIS)));
389 /* We want to avoid situations like this:
397 The point is that "still more text" should be positioned under
398 "more text". In order to achieve this, we place the grobs in several
399 passes. We keep track of the right-most horizontal position that has been
400 affected by the current pass so far (actually we keep track of 2
401 positions, one for above the staff, one for below).
403 In each pass, we loop through the unplaced grobs from left to right.
404 If the grob overlaps the right-most affected position, we place it
405 (and then update the right-most affected position to point to the right
406 edge of the just-placed grob). Otherwise, we skip it until the next pass.
409 add_grobs_of_one_priority (Skyline_pair *const skylines,
410 vector<Grob*> elements,
415 Drul_array<Real> last_affected_position;
418 while (!elements.empty ())
420 last_affected_position[UP] = -infinity_f;
421 last_affected_position[DOWN] = -infinity_f;
423 for (vsize i = elements.size (); i--;)
425 Direction dir = get_grob_direction (elements[i]);
428 warning (_ ("an outside-staff object should have a direction, defaulting to up"));
432 Box b (elements[i]->extent (x_common, X_AXIS),
433 elements[i]->extent (y_common, Y_AXIS));
434 SCM horizon_padding_scm = elements[i]->get_property ("outside-staff-horizontal-padding");
435 Real horizon_padding = robust_scm2double (horizon_padding_scm, 0.0);
437 if (b[X_AXIS][LEFT] - 2*horizon_padding < last_affected_position[dir])
440 if (!b[X_AXIS].is_empty () && !b[Y_AXIS].is_empty ())
444 Skyline other = Skyline (boxes, horizon_padding, X_AXIS, -dir);
445 Real padding = robust_scm2double (elements[i]->get_property ("outside-staff-padding"), 0.5);
446 Real dist = (*skylines)[dir].distance (other) + padding;
450 b.translate (Offset (0, dir*dist));
451 elements[i]->translate_axis (dir*dist, Y_AXIS);
453 (*skylines)[dir].insert (b, 0, X_AXIS);
454 elements[i]->set_property ("outside-staff-priority", SCM_BOOL_F);
455 last_affected_position[dir] = b[X_AXIS][RIGHT];
459 Ugh: quadratic. --hwn
461 elements.erase (elements.begin () + i);
467 Axis_group_interface::skyline_spacing (Grob *me, vector<Grob*> elements)
469 vector_sort (elements, staff_priority_less);
470 Grob *x_common = common_refpoint_of_array (elements, me, X_AXIS);
471 Grob *y_common = common_refpoint_of_array (elements, me, Y_AXIS);
473 assert (y_common == me);
478 for (i = 0; i < elements.size ()
479 && !scm_is_number (elements[i]->get_property ("outside-staff-priority")); i++)
480 add_boxes (elements[i], x_common, y_common, &boxes);
482 Skyline_pair skylines (boxes, 0, X_AXIS);
483 for (; i < elements.size (); i++)
485 SCM priority = elements[i]->get_property ("outside-staff-priority");
486 vector<Grob*> current_elts;
487 current_elts.push_back (elements[i]);
488 while (i + 1 < elements.size ()
489 && scm_eq_p (elements[i+1]->get_property ("outside-staff-priority"), priority))
490 current_elts.push_back (elements[++i]);
492 add_grobs_of_one_priority (&skylines, current_elts, x_common, y_common);
497 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_max_stretch, 1)
499 Axis_group_interface::calc_max_stretch (SCM smob)
501 Grob *me = unsmob_grob (smob);
503 extract_grob_set (me, "elements", elts);
505 for (vsize i = 0; i < elts.size (); i++)
506 if (Axis_group_interface::has_interface (elts[i]))
507 ret += robust_scm2double (elts[i]->get_property ("max-stretch"), 0.0);
509 return scm_from_double (ret);
512 ADD_INTERFACE (Axis_group_interface,
514 "An object that groups other layout objects.",
521 "keep-fixed-while-stretching "
524 "pure-relevant-elements "
526 "cached-pure-extents "