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");
94 for (vsize i = 0; i + 1 < breaks.size (); i++)
96 int r = Paper_column::get_rank (cols[breaks[i]]);
101 ext.unite (ly_scm2interval (scm_c_vector_ref (extents, i)));
107 MAKE_SCHEME_CALLBACK (Axis_group_interface, adjacent_pure_heights, 1)
109 Axis_group_interface::adjacent_pure_heights (SCM smob)
111 Grob *me = unsmob_grob (smob);
113 Grob *common = calc_pure_elts_and_common (me);
114 extract_grob_set (me, "pure-relevant-items", items);
115 extract_grob_set (me, "pure-relevant-spanners", spanners);
117 Paper_score *ps = get_root_system (me)->paper_score ();
118 vector<vsize> breaks = ps->get_break_indices ();
119 vector<Grob*> cols = ps->get_columns ();
121 SCM ret = scm_c_make_vector (breaks.size () - 1, SCM_EOL);
123 for (vsize i = 0; i + 1 < breaks.size (); i++)
125 int start = Paper_column::get_rank (cols[breaks[i]]);
126 int end = Paper_column::get_rank (cols[breaks[i+1]]);
129 for (vsize j = it_index; j < items.size (); j++)
131 Item *it = dynamic_cast<Item*> (items[j]);
132 int rank = it->get_column ()->get_rank ();
134 if (rank <= end && it->pure_is_visible (start, end))
136 Interval dims = items[j]->pure_height (common, start, end);
137 if (!dims.is_empty ())
147 for (vsize j = 0; j < spanners.size (); j++)
149 Interval_t<int> rank_span = spanners[j]->spanned_rank_interval ();
150 if (rank_span[LEFT] <= end && rank_span[RIGHT] >= start)
152 Interval dims = spanners[j]->pure_height (common, start, end);
153 if (!dims.is_empty ())
158 scm_vector_set_x (ret, scm_from_int (i), ly_interval2scm (iv));
164 Axis_group_interface::relative_pure_height (Grob *me, int start, int end)
166 /* It saves a _lot_ of time if we assume a VerticalAxisGroup is additive
167 (ie. height (i, k) = max (height (i, j) height (j, k)) for all i <= j <= k).
168 Unfortunately, it isn't always true, particularly if there is a
169 VerticalAlignment somewhere in the descendants.
171 Apart from PianoStaff, which has a fixed VerticalAlignment so it doesn't
172 count, the only VerticalAlignment comes from Score. This makes it
173 reasonably safe to assume that if our parent is a VerticalAlignment,
174 we can assume additivity and cache things nicely. */
175 Grob *p = me->get_parent (Y_AXIS);
176 if (p && Align_interface::has_interface (p))
177 return Axis_group_interface::cached_pure_height (me, start, end);
179 Grob *common = calc_pure_elts_and_common (me);
180 extract_grob_set (me, "pure-relevant-items", items);
181 extract_grob_set (me, "pure-relevant-spanners", spanners);
185 for (vsize i = 0; i < items.size (); i++)
187 Item *it = dynamic_cast<Item*> (items[i]);
188 int rank = it->get_column ()->get_rank ();
192 else if (rank >= start && it->pure_is_visible (start, end))
194 Interval dims = it->pure_height (common, start, end);
195 if (!dims.is_empty ())
200 for (vsize i = 0; i < spanners.size (); i++)
202 Interval_t<int> rank_span = spanners[i]->spanned_rank_interval ();
203 if (rank_span[LEFT] <= end && rank_span[RIGHT] >= start)
205 Interval dims = spanners[i]->pure_height (common, start, end);
206 if (!dims.is_empty ())
213 MAKE_SCHEME_CALLBACK (Axis_group_interface, width, 1);
215 Axis_group_interface::width (SCM smob)
217 Grob *me = unsmob_grob (smob);
218 return generic_group_extent (me, X_AXIS);
221 MAKE_SCHEME_CALLBACK (Axis_group_interface, height, 1);
223 Axis_group_interface::height (SCM smob)
225 Grob *me = unsmob_grob (smob);
226 return generic_group_extent (me, Y_AXIS);
229 MAKE_SCHEME_CALLBACK (Axis_group_interface, pure_height, 3);
231 Axis_group_interface::pure_height (SCM smob, SCM start_scm, SCM end_scm)
233 int start = robust_scm2int (start_scm, 0);
234 int end = robust_scm2int (end_scm, INT_MAX);
235 Grob *me = unsmob_grob (smob);
237 /* Maybe we are in the second pass of a two-pass spacing run. In that
238 case, the Y-extent of a system is already given to us */
239 System *system = dynamic_cast<System*> (me);
242 SCM line_break_details = system->column (start)->get_property ("line-break-system-details");
243 SCM system_y_extent = scm_assq (ly_symbol2scm ("system-Y-extent"), line_break_details);
244 if (scm_is_pair (system_y_extent))
245 return scm_cdr (system_y_extent);
248 return ly_interval2scm (pure_group_height (me, start, end));
251 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_skylines, 1);
253 Axis_group_interface::calc_skylines (SCM smob)
255 Grob *me = unsmob_grob (smob);
256 extract_grob_set (me, "elements", elts);
257 Skyline_pair skylines = skyline_spacing (me, elts);
259 /* add a minimum-Y-extent-sized box to the skyline */
260 SCM min_y_extent = me->get_property ("minimum-Y-extent");
261 if (is_number_pair (min_y_extent))
263 Box b (me->extent (me, X_AXIS), ly_scm2interval (min_y_extent));
264 skylines.insert (b, 0, X_AXIS);
266 return skylines.smobbed_copy ();
269 /* whereas calc_skylines calculates skylines for axis-groups with a lot of
270 visible children, combine_skylines is designed for axis-groups whose only
271 children are other axis-groups (ie. VerticalAlignment). Rather than
272 calculating all the skylines from scratch, we just merge the skylines
275 MAKE_SCHEME_CALLBACK (Axis_group_interface, combine_skylines, 1);
277 Axis_group_interface::combine_skylines (SCM smob)
279 Grob *me = unsmob_grob (smob);
280 extract_grob_set (me, "elements", elements);
281 Grob *y_common = common_refpoint_of_array (elements, me, Y_AXIS);
283 assert (y_common == me);
286 for (vsize i = 0; i < elements.size (); i++)
288 SCM skyline_scm = elements[i]->get_property ("vertical-skylines");
289 if (Skyline_pair::unsmob (skyline_scm))
291 Real offset = elements[i]->relative_coordinate (y_common, Y_AXIS);
292 Skyline_pair other = *Skyline_pair::unsmob (skyline_scm);
293 other.raise (offset);
297 return ret.smobbed_copy ();
301 Axis_group_interface::generic_group_extent (Grob *me, Axis a)
303 /* trigger the callback to do skyline-spacing on the children */
305 (void) me->get_property ("vertical-skylines");
307 extract_grob_set (me, "elements", elts);
308 Grob *common = common_refpoint_of_array (elts, me, a);
310 Real my_coord = me->relative_coordinate (common, a);
311 Interval r (relative_group_extent (elts, common, a));
313 return ly_interval2scm (r - my_coord);
318 Axis_group_interface::calc_pure_elts_and_common (Grob *me)
320 if (Grob *c = unsmob_grob (me->get_object ("pure-Y-common")))
323 extract_grob_set (me, "elements", elts);
325 vector<Grob*> relevant_items;
326 vector<Grob*> relevant_spanners;
327 SCM pure_relevant_p = ly_lily_module_constant ("pure-relevant?");
329 for (vsize i = 0; i < elts.size (); i++)
331 if (to_boolean (scm_apply_1 (pure_relevant_p, elts[i]->self_scm (), SCM_EOL)))
333 if (dynamic_cast<Item*> (elts[i]))
334 relevant_items.push_back (elts[i]);
335 else if (dynamic_cast<Spanner*> (elts[i]))
336 relevant_spanners.push_back (elts[i]);
340 Item *it = dynamic_cast<Item*> (elts[i]);
345 Item *piece = it->find_prebroken_piece (d);
346 if (piece && to_boolean (scm_apply_1 (pure_relevant_p, piece->self_scm (), SCM_EOL)))
347 relevant_items.push_back (piece);
349 while (flip (&d) != LEFT);
351 vector_sort (relevant_items, Item::less);
353 Grob *common = common_refpoint_of_array (relevant_items, me, Y_AXIS);
354 common = common_refpoint_of_array (relevant_spanners, common, Y_AXIS);
356 me->set_object ("pure-Y-common", common->self_scm ());
358 SCM items_scm = Grob_array::make_array ();
359 SCM spanners_scm = Grob_array::make_array ();
361 unsmob_grob_array (items_scm)->set_array (relevant_items);
362 unsmob_grob_array (spanners_scm)->set_array (relevant_spanners);
363 me->set_object ("pure-relevant-items", items_scm);
364 me->set_object ("pure-relevant-spanners", spanners_scm);
369 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_x_common, 1);
371 Axis_group_interface::calc_x_common (SCM grob)
373 Grob *me = unsmob_grob (grob);
375 extract_grob_set (me, "elements", elts);
376 Grob *common = common_refpoint_of_array (elts, me, X_AXIS);
377 return common->self_scm ();
380 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_y_common, 1);
382 Axis_group_interface::calc_y_common (SCM grob)
384 Grob *me = unsmob_grob (grob);
386 extract_grob_set (me, "elements", elts);
387 Grob *common = common_refpoint_of_array (elts, me, Y_AXIS);
388 return common->self_scm ();
392 Axis_group_interface::pure_group_height (Grob *me, int start, int end)
394 Grob *common = calc_pure_elts_and_common (me);
396 Real my_coord = me->relative_coordinate (common, Y_AXIS);
397 Interval r (relative_pure_height (me, start, end));
403 Axis_group_interface::get_children (Grob *me, vector<Grob*> *found)
405 found->push_back (me);
407 if (!has_interface (me))
410 extract_grob_set (me, "elements", elements);
411 for (vsize i = 0; i < elements.size (); i++)
413 Grob *e = elements[i];
414 Axis_group_interface::get_children (e, found);
419 staff_priority_less (Grob * const &g1, Grob * const &g2)
421 Real priority_1 = robust_scm2double (g1->get_property ("outside-staff-priority"), -infinity_f);
422 Real priority_2 = robust_scm2double (g2->get_property ("outside-staff-priority"), -infinity_f);
424 if (priority_1 < priority_2)
426 else if (priority_1 > priority_2)
429 /* if neither grob has an outside-staff priority, the ordering will have no
430 effect -- we just need to choose a consistent ordering. We do this to
431 avoid the side-effect of calculating extents. */
432 if (isinf (priority_1))
435 /* if there is no preference in staff priority, choose the left-most one */
436 Grob *common = g1->common_refpoint (g2, X_AXIS);
437 Real start_1 = g1->extent (common, X_AXIS)[LEFT];
438 Real start_2 = g2->extent (common, X_AXIS)[LEFT];
439 return start_1 < start_2;
443 add_boxes (Grob *me, Grob *x_common, Grob *y_common, vector<Box> *const boxes, Skyline_pair *skylines)
445 /* if a child has skylines, use them instead of the extent box */
446 if (Skyline_pair *pair = Skyline_pair::unsmob (me->get_property ("vertical-skylines")))
448 Skyline_pair s = *pair;
449 s.shift (me->relative_coordinate (x_common, X_AXIS));
450 s.raise (me->relative_coordinate (y_common, Y_AXIS));
453 else if (Grob_array *elements = unsmob_grob_array (me->get_object ("elements")))
455 for (vsize i = 0; i < elements->size (); i++)
456 add_boxes (elements->grob (i), x_common, y_common, boxes, skylines);
458 else if (!scm_is_number (me->get_property ("outside-staff-priority"))
459 && !to_boolean (me->get_property ("cross-staff")))
461 boxes->push_back (Box (me->extent (x_common, X_AXIS),
462 me->extent (y_common, Y_AXIS)));
466 /* We want to avoid situations like this:
474 The point is that "still more text" should be positioned under
475 "more text". In order to achieve this, we place the grobs in several
476 passes. We keep track of the right-most horizontal position that has been
477 affected by the current pass so far (actually we keep track of 2
478 positions, one for above the staff, one for below).
480 In each pass, we loop through the unplaced grobs from left to right.
481 If the grob overlaps the right-most affected position, we place it
482 (and then update the right-most affected position to point to the right
483 edge of the just-placed grob). Otherwise, we skip it until the next pass.
486 add_grobs_of_one_priority (Skyline_pair *const skylines,
487 vector<Grob*> elements,
492 Drul_array<Real> last_affected_position;
495 while (!elements.empty ())
497 last_affected_position[UP] = -infinity_f;
498 last_affected_position[DOWN] = -infinity_f;
500 for (vsize i = elements.size (); i--;)
502 Direction dir = get_grob_direction (elements[i]);
505 warning (_ ("an outside-staff object should have a direction, defaulting to up"));
509 Box b (elements[i]->extent (x_common, X_AXIS),
510 elements[i]->extent (y_common, Y_AXIS));
511 SCM horizon_padding_scm = elements[i]->get_property ("outside-staff-horizontal-padding");
512 Real horizon_padding = robust_scm2double (horizon_padding_scm, 0.0);
514 if (b[X_AXIS][LEFT] - 2*horizon_padding < last_affected_position[dir])
517 if (!b[X_AXIS].is_empty () && !b[Y_AXIS].is_empty ())
521 Skyline other = Skyline (boxes, horizon_padding, X_AXIS, -dir);
522 Real padding = robust_scm2double (elements[i]->get_property ("outside-staff-padding"), 0.5);
523 Real dist = (*skylines)[dir].distance (other) + padding;
527 b.translate (Offset (0, dir*dist));
528 elements[i]->translate_axis (dir*dist, Y_AXIS);
530 (*skylines)[dir].insert (b, 0, X_AXIS);
531 elements[i]->set_property ("outside-staff-priority", SCM_BOOL_F);
532 last_affected_position[dir] = b[X_AXIS][RIGHT];
536 Ugh: quadratic. --hwn
538 elements.erase (elements.begin () + i);
544 Axis_group_interface::skyline_spacing (Grob *me, vector<Grob*> elements)
546 vector_sort (elements, staff_priority_less);
547 Grob *x_common = common_refpoint_of_array (elements, me, X_AXIS);
548 Grob *y_common = common_refpoint_of_array (elements, me, Y_AXIS);
550 assert (y_common == me);
555 Skyline_pair skylines;
556 for (i = 0; i < elements.size ()
557 && !scm_is_number (elements[i]->get_property ("outside-staff-priority")); i++)
558 add_boxes (elements[i], x_common, y_common, &boxes, &skylines);
560 SCM padding_scm = me->get_property ("skyline-horizontal-padding");
561 Real padding = robust_scm2double (padding_scm, 0.1);
562 skylines.merge (Skyline_pair (boxes, padding, X_AXIS));
563 for (; i < elements.size (); i++)
565 SCM priority = elements[i]->get_property ("outside-staff-priority");
566 vector<Grob*> current_elts;
567 current_elts.push_back (elements[i]);
568 while (i + 1 < elements.size ()
569 && scm_eq_p (elements[i+1]->get_property ("outside-staff-priority"), priority))
570 current_elts.push_back (elements[++i]);
572 add_grobs_of_one_priority (&skylines, current_elts, x_common, y_common);
574 skylines.shift (-me->relative_coordinate (x_common, X_AXIS));
578 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_max_stretch, 1)
580 Axis_group_interface::calc_max_stretch (SCM smob)
582 Grob *me = unsmob_grob (smob);
584 extract_grob_set (me, "elements", elts);
586 for (vsize i = 0; i < elts.size (); i++)
587 if (Axis_group_interface::has_interface (elts[i]))
588 ret += robust_scm2double (elts[i]->get_property ("max-stretch"), 0.0);
590 return scm_from_double (ret);
593 extern bool debug_skylines;
594 MAKE_SCHEME_CALLBACK (Axis_group_interface, print, 1)
596 Axis_group_interface::print (SCM smob)
601 Grob *me = unsmob_grob (smob);
603 if (Skyline_pair *s = Skyline_pair::unsmob (me->get_property ("vertical-skylines")))
605 ret.add_stencil (Lookup::points_to_line_stencil (0.1, (*s)[UP].to_points (X_AXIS)).in_color (255, 0, 255));
606 ret.add_stencil (Lookup::points_to_line_stencil (0.1, (*s)[DOWN].to_points (X_AXIS)).in_color (0, 255, 255));
608 return ret.smobbed_copy ();
611 ADD_INTERFACE (Axis_group_interface,
613 "An object that groups other layout objects.",
618 "adjacent-pure-heights "
621 "keep-fixed-while-stretching "
624 "pure-relevant-items "
625 "pure-relevant-spanners "