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 Interval dims = se->extent (common, a);
65 if (!dims.is_empty ())
73 FIXME: pure extent handling has a lot of ad-hoc caching.
74 This should be done with grob property callbacks.
80 Axis_group_interface::cached_pure_height (Grob *me,
81 vector<Grob*> const &elts,
85 Paper_score *ps = get_root_system (me)->paper_score ();
86 vector<vsize> breaks = ps->get_break_indices ();
87 vector<Grob*> cols = ps->get_columns ();
88 vsize start_index = VPOS;
89 vsize end_index = VPOS;
91 for (vsize i = 0; i < breaks.size (); i++)
93 int r = Paper_column::get_rank (cols[breaks[i]]);
100 end_index = breaks.size () - 1;
102 if (start_index == VPOS || end_index == VPOS)
104 programming_error (_ ("tried to calculate pure-height at a non-breakpoint"));
105 return Interval (0, 0);
108 SCM extents = me->get_property ("cached-pure-extents");
109 if (!scm_is_vector (extents))
111 extents = scm_c_make_vector (breaks.size () - 1, SCM_EOL);
112 for (vsize i = 0; i + 1 < breaks.size (); i++)
114 int st = Paper_column::get_rank (cols[breaks[i]]);
115 int ed = Paper_column::get_rank (cols[breaks[i+1]]);
116 Interval iv = relative_pure_height (me, elts, common, st, ed, false);
117 scm_vector_set_x (extents, scm_from_int (i), ly_interval2scm (iv));
119 me->set_property ("cached-pure-extents", extents);
123 for (vsize i = start_index; i < end_index; i++)
124 ext.unite (ly_scm2interval (scm_c_vector_ref (extents, i)));
129 Axis_group_interface::relative_pure_height (Grob *me,
130 vector<Grob*> const &elts,
135 /* It saves a _lot_ of time if we assume a VerticalAxisGroup is additive
136 (ie. height (i, k) = height (i, j) + height (j, k) for all i <= j <= k).
137 Unfortunately, it isn't always true, particularly if there is a
138 VerticalAlignment somewhere in the descendants.
140 Apart from PianoStaff, which has a fixed VerticalAlignment so it doesn't
141 count, the only VerticalAlignment comes from Score. This makes it
142 reasonably safe to assume that if our parent is a VerticalAlignment,
143 we can assume additivity and cache things nicely. */
144 Grob *p = me->get_parent (Y_AXIS);
145 if (use_cache && p && Align_interface::has_interface (p))
146 return Axis_group_interface::cached_pure_height (me, elts, common, start, end);
150 for (vsize i = 0; i < elts.size (); i++)
152 Interval_t<int> rank_span = elts[i]->spanned_rank_iv ();
153 Item *it = dynamic_cast<Item*> (elts[i]);
154 if (rank_span[LEFT] <= end && rank_span[RIGHT] >= start && (!it || it->pure_is_visible (start, end)))
156 Interval dims = elts[i]->pure_height (common, start, end);
157 if (!dims.is_empty ())
164 MAKE_SCHEME_CALLBACK (Axis_group_interface, width, 1);
166 Axis_group_interface::width (SCM smob)
168 Grob *me = unsmob_grob (smob);
169 return generic_group_extent (me, X_AXIS);
172 MAKE_SCHEME_CALLBACK (Axis_group_interface, height, 1);
174 Axis_group_interface::height (SCM smob)
176 Grob *me = unsmob_grob (smob);
177 return generic_group_extent (me, Y_AXIS);
180 MAKE_SCHEME_CALLBACK (Axis_group_interface, pure_height, 3);
182 Axis_group_interface::pure_height (SCM smob, SCM start_scm, SCM end_scm)
184 int start = robust_scm2int (start_scm, 0);
185 int end = robust_scm2int (end_scm, INT_MAX);
186 Grob *me = unsmob_grob (smob);
188 /* Maybe we are in the second pass of a two-pass spacing run. In that
189 case, the Y-extent of a system is already given to us */
190 System *system = dynamic_cast<System*> (me);
193 SCM line_break_details = system->column (start)->get_property ("line-break-system-details");
194 SCM system_y_extent = scm_assq (ly_symbol2scm ("system-Y-extent"), line_break_details);
195 if (scm_is_pair (system_y_extent))
196 return scm_cdr (system_y_extent);
199 return pure_group_height (me, start, end);
202 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_skylines, 1);
204 Axis_group_interface::calc_skylines (SCM smob)
206 Grob *me = unsmob_grob (smob);
207 extract_grob_set (me, "elements", elts);
208 return skyline_spacing (me, elts).smobbed_copy ();
211 /* whereas calc_skylines calculates skylines for axis-groups with a lot of
212 visible children, combine_skylines is designed for axis-groups whose only
213 children are other axis-groups (ie. VerticalAlignment). Rather than
214 calculating all the skylines from scratch, we just merge the skylines
217 MAKE_SCHEME_CALLBACK (Axis_group_interface, combine_skylines, 1);
219 Axis_group_interface::combine_skylines (SCM smob)
221 Grob *me = unsmob_grob (smob);
222 extract_grob_set (me, "elements", elements);
223 Grob *y_common = common_refpoint_of_array (elements, me, Y_AXIS);
225 assert (y_common == me);
228 for (vsize i = 0; i < elements.size (); i++)
230 SCM skyline_scm = elements[i]->get_property ("skylines");
231 if (Skyline_pair::unsmob (skyline_scm))
233 Real offset = elements[i]->relative_coordinate (y_common, Y_AXIS);
234 Skyline_pair other = *Skyline_pair::unsmob (skyline_scm);
235 other.raise (offset);
239 return ret.smobbed_copy ();
243 Axis_group_interface::generic_group_extent (Grob *me, Axis a)
245 /* trigger the callback to do skyline-spacing on the children */
246 (void) me->get_property ("skylines");
248 extract_grob_set (me, "elements", elts);
249 Grob *common = common_refpoint_of_array (elts, me, a);
251 Real my_coord = me->relative_coordinate (common, a);
252 Interval r (relative_group_extent (elts, common, a));
254 return ly_interval2scm (r - my_coord);
259 Axis_group_interface::calc_pure_elts_and_common (Grob *me)
261 if (Grob *c = unsmob_grob (me->get_object ("pure-Y-common")))
264 extract_grob_set (me, "elements", elts);
266 vector<Grob*> relevant_elts;
267 SCM pure_relevant_p = ly_lily_module_constant ("pure-relevant?");
269 for (vsize i = 0; i < elts.size (); i++)
271 if (to_boolean (scm_apply_1 (pure_relevant_p, elts[i]->self_scm (), SCM_EOL)))
272 relevant_elts.push_back (elts[i]);
274 Item *it = dynamic_cast<Item*> (elts[i]);
279 Item *piece = it->find_prebroken_piece (d);
280 if (piece && to_boolean (scm_apply_1 (pure_relevant_p, piece->self_scm (), SCM_EOL)))
281 relevant_elts.push_back (piece);
283 while (flip (&d) != LEFT);
286 Grob *common = common_refpoint_of_array (relevant_elts, me, Y_AXIS);
287 me->set_object ("pure-Y-common", common->self_scm ());
289 SCM ga_scm = Grob_array::make_array ();
290 Grob_array *ga = unsmob_grob_array (ga_scm);
291 ga->set_array (relevant_elts);
292 me->set_object ("pure-relevant-elements", ga_scm);
297 MAKE_SCHEME_CALLBACK(Axis_group_interface,calc_x_common, 1);
299 Axis_group_interface::calc_x_common (SCM grob)
301 Grob *me = unsmob_grob (grob);
303 extract_grob_set (me, "elements", elts);
304 Grob *common = common_refpoint_of_array (elts, me, X_AXIS);
305 return common->self_scm ();
308 MAKE_SCHEME_CALLBACK(Axis_group_interface,calc_y_common, 1);
310 Axis_group_interface::calc_y_common (SCM grob)
312 Grob *me = unsmob_grob (grob);
314 extract_grob_set (me, "elements", elts);
315 Grob *common = common_refpoint_of_array (elts, me, Y_AXIS);
316 return common->self_scm ();
320 Axis_group_interface::pure_group_height (Grob *me, int start, int end)
322 Grob *common = calc_pure_elts_and_common (me);
324 extract_grob_set (me, "pure-relevant-elements", elts);
325 Real my_coord = me->relative_coordinate (common, Y_AXIS);
326 Interval r (relative_pure_height (me, elts, common, start, end, true));
328 return ly_interval2scm (r - my_coord);
332 Axis_group_interface::get_children (Grob *me, vector<Grob*> *found)
334 found->push_back (me);
336 if (!has_interface (me))
339 extract_grob_set (me, "elements", elements);
340 for (vsize i = 0; i < elements.size (); i++)
342 Grob *e = elements[i];
343 Axis_group_interface::get_children (e, found);
348 staff_priority_less (Grob * const &g1, Grob * const &g2)
350 Real priority_1 = robust_scm2double (g1->get_property ("outside-staff-priority"), -infinity_f);
351 Real priority_2 = robust_scm2double (g2->get_property ("outside-staff-priority"), -infinity_f);
353 if (priority_1 < priority_2)
355 else if (priority_1 > priority_2)
358 /* if there is no preference in staff priority, choose the left-most one */
359 Grob *common = g1->common_refpoint (g2, X_AXIS);
360 Real start_1 = g1->extent (common, X_AXIS)[LEFT];
361 Real start_2 = g2->extent (common, X_AXIS)[LEFT];
362 return start_1 < start_2;
366 add_boxes (Grob *me, Grob *x_common, Grob *y_common, vector<Box> *const boxes)
368 /* if we are a parent, consider the children's boxes instead of mine */
369 if (Grob_array *elements = unsmob_grob_array (me->get_object ("elements")))
371 for (vsize i = 0; i < elements->size (); i++)
372 add_boxes (elements->grob (i), x_common, y_common, boxes);
374 else if (!scm_is_number (me->get_property ("outside-staff-priority")))
375 boxes->push_back (Box (me->extent (x_common, X_AXIS),
376 me->extent (y_common, Y_AXIS)));
379 /* We want to avoid situations like this:
387 The point is that "still more text" should be positioned under
388 "more text". In order to achieve this, we place the grobs in several
389 passes. We keep track of the right-most horizontal position that has been
390 affected by the current pass so far (actually we keep track of 2
391 positions, one for above the staff, one for below).
393 In each pass, we loop through the unplaced grobs from left to right.
394 If the grob overlaps the right-most affected position, we place it
395 (and then update the right-most affected position to point to the right
396 edge of the just-placed grob). Otherwise, we skip it until the next pass.
399 add_grobs_of_one_priority (Skyline_pair *const skylines,
400 vector<Grob*> elements,
405 Drul_array<Real> last_affected_position;
408 while (!elements.empty ())
410 last_affected_position[UP] = -infinity_f;
411 last_affected_position[DOWN] = -infinity_f;
413 for (vsize i = elements.size (); i--;)
415 Direction dir = get_grob_direction (elements[i]);
418 warning (_ ("an outside-staff object should have a direction, defaulting to up"));
422 Box b (elements[i]->extent (x_common, X_AXIS),
423 elements[i]->extent (y_common, Y_AXIS));
424 SCM horizon_padding_scm = elements[i]->get_property ("outside-staff-horizontal-padding");
425 Real horizon_padding = robust_scm2double (horizon_padding_scm, 0.0);
427 if (b[X_AXIS][LEFT] - 2*horizon_padding < last_affected_position[dir])
430 if (b[X_AXIS].is_empty () || b[Y_AXIS].is_empty ())
431 warning (_f ("outside-staff object %s has an empty extent", elements[i]->name ().c_str ()));
436 Skyline other = Skyline (boxes, horizon_padding, X_AXIS, -dir);
437 Real padding = robust_scm2double (elements[i]->get_property ("outside-staff-padding"), 0.5);
438 Real dist = (*skylines)[dir].distance (other) + padding;
442 b.translate (Offset (0, dir*dist));
443 elements[i]->translate_axis (dir*dist, Y_AXIS);
445 (*skylines)[dir].insert (b, 0, X_AXIS);
446 elements[i]->set_property ("outside-staff-priority", SCM_BOOL_F);
447 last_affected_position[dir] = b[X_AXIS][RIGHT];
451 Ugh: quadratic. --hwn
453 elements.erase (elements.begin () + i);
459 Axis_group_interface::skyline_spacing (Grob *me, vector<Grob*> elements)
461 vector_sort (elements, staff_priority_less);
462 Grob *x_common = common_refpoint_of_array (elements, me, X_AXIS);
463 Grob *y_common = common_refpoint_of_array (elements, me, Y_AXIS);
465 assert (y_common == me);
470 for (i = 0; i < elements.size ()
471 && !scm_is_number (elements[i]->get_property ("outside-staff-priority")); i++)
472 add_boxes (elements[i], x_common, y_common, &boxes);
474 Skyline_pair skylines (boxes, 0, X_AXIS);
475 for (; i < elements.size (); i++)
477 SCM priority = elements[i]->get_property ("outside-staff-priority");
478 vector<Grob*> current_elts;
479 current_elts.push_back (elements[i]);
480 while (i + 1 < elements.size ()
481 && scm_eq_p (elements[i+1]->get_property ("outside-staff-priority"), priority))
482 current_elts.push_back (elements[++i]);
484 add_grobs_of_one_priority (&skylines, current_elts, x_common, y_common);
489 ADD_INTERFACE (Axis_group_interface,
491 "An object that groups other layout objects.",
499 "pure-relevant-elements "
501 "cached-pure-extents "