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
155 && !to_boolean (spanners[j]->get_property ("cross-staff")))
157 Interval dims = spanners[j]->pure_height (common, start, end);
158 if (!dims.is_empty ())
163 scm_vector_set_x (ret, scm_from_int (i), ly_interval2scm (iv));
169 Axis_group_interface::relative_pure_height (Grob *me, int start, int end)
171 /* It saves a _lot_ of time if we assume a VerticalAxisGroup is additive
172 (ie. height (i, k) = max (height (i, j) height (j, k)) for all i <= j <= k).
173 Unfortunately, it isn't always true, particularly if there is a
174 VerticalAlignment somewhere in the descendants.
176 Apart from PianoStaff, which has a fixed VerticalAlignment so it doesn't
177 count, the only VerticalAlignment comes from Score. This makes it
178 reasonably safe to assume that if our parent is a VerticalAlignment,
179 we can assume additivity and cache things nicely. */
180 Grob *p = me->get_parent (Y_AXIS);
181 if (p && Align_interface::has_interface (p))
182 return Axis_group_interface::cached_pure_height (me, start, end);
184 Grob *common = calc_pure_elts_and_common (me);
185 extract_grob_set (me, "pure-relevant-items", items);
186 extract_grob_set (me, "pure-relevant-spanners", spanners);
190 for (vsize i = 0; i < items.size (); i++)
192 Item *it = dynamic_cast<Item*> (items[i]);
193 int rank = it->get_column ()->get_rank ();
197 else if (rank >= start && it->pure_is_visible (start, end)
198 && !to_boolean (it->get_property ("cross-staff")))
200 Interval dims = it->pure_height (common, start, end);
201 if (!dims.is_empty ())
206 for (vsize i = 0; i < spanners.size (); i++)
208 Interval_t<int> rank_span = spanners[i]->spanned_rank_interval ();
209 if (rank_span[LEFT] <= end && rank_span[RIGHT] >= start
210 && !to_boolean (spanners[i]->get_property ("cross-staff")))
212 Interval dims = spanners[i]->pure_height (common, start, end);
213 if (!dims.is_empty ())
220 MAKE_SCHEME_CALLBACK (Axis_group_interface, width, 1);
222 Axis_group_interface::width (SCM smob)
224 Grob *me = unsmob_grob (smob);
225 return generic_group_extent (me, X_AXIS);
228 MAKE_SCHEME_CALLBACK (Axis_group_interface, height, 1);
230 Axis_group_interface::height (SCM smob)
232 Grob *me = unsmob_grob (smob);
233 return generic_group_extent (me, Y_AXIS);
236 MAKE_SCHEME_CALLBACK (Axis_group_interface, pure_height, 3);
238 Axis_group_interface::pure_height (SCM smob, SCM start_scm, SCM end_scm)
240 int start = robust_scm2int (start_scm, 0);
241 int end = robust_scm2int (end_scm, INT_MAX);
242 Grob *me = unsmob_grob (smob);
244 /* Maybe we are in the second pass of a two-pass spacing run. In that
245 case, the Y-extent of a system is already given to us */
246 System *system = dynamic_cast<System*> (me);
249 SCM line_break_details = system->column (start)->get_property ("line-break-system-details");
250 SCM system_y_extent = scm_assq (ly_symbol2scm ("system-Y-extent"), line_break_details);
251 if (scm_is_pair (system_y_extent))
252 return scm_cdr (system_y_extent);
255 return ly_interval2scm (pure_group_height (me, start, end));
258 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_skylines, 1);
260 Axis_group_interface::calc_skylines (SCM smob)
262 Grob *me = unsmob_grob (smob);
263 extract_grob_set (me, "elements", elts);
264 Skyline_pair skylines = skyline_spacing (me, elts);
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);
282 Grob *x_common = common_refpoint_of_array (elements, me, X_AXIS);
285 programming_error ("combining skylines that don't belong to me");
288 for (vsize i = 0; i < elements.size (); i++)
290 SCM skyline_scm = elements[i]->get_property ("vertical-skylines");
291 if (Skyline_pair::unsmob (skyline_scm))
293 Real offset = elements[i]->relative_coordinate (y_common, Y_AXIS);
294 Skyline_pair other = *Skyline_pair::unsmob (skyline_scm);
295 other.raise (offset);
296 other.shift (elements[i]->relative_coordinate (x_common, X_AXIS));
300 return ret.smobbed_copy ();
304 Axis_group_interface::generic_group_extent (Grob *me, Axis a)
306 /* trigger the callback to do skyline-spacing on the children */
308 (void) me->get_property ("vertical-skylines");
310 extract_grob_set (me, "elements", elts);
311 Grob *common = common_refpoint_of_array (elts, me, a);
313 Real my_coord = me->relative_coordinate (common, a);
314 Interval r (relative_group_extent (elts, common, a));
316 return ly_interval2scm (r - my_coord);
319 /* This is like generic_group_extent, but it only counts the grobs that
320 are children of some other axis-group. This is uncached; if it becomes
321 commonly used, it may be necessary to cache it somehow. */
323 Axis_group_interface::staff_extent (Grob *me, Grob *refp, Axis ext_a, Grob *staff, Axis parent_a)
325 extract_grob_set (me, "elements", elts);
326 vector<Grob*> new_elts;
328 for (vsize i = 0; i < elts.size (); i++)
329 if (elts[i]->common_refpoint (staff, parent_a) == staff)
330 new_elts.push_back (elts[i]);
332 return relative_group_extent (new_elts, refp, ext_a);
337 Axis_group_interface::calc_pure_elts_and_common (Grob *me)
339 if (Grob *c = unsmob_grob (me->get_object ("pure-Y-common")))
342 extract_grob_set (me, "elements", elts);
344 vector<Grob*> relevant_items;
345 vector<Grob*> relevant_spanners;
346 SCM pure_relevant_p = ly_lily_module_constant ("pure-relevant?");
348 for (vsize i = 0; i < elts.size (); i++)
350 if (to_boolean (scm_apply_1 (pure_relevant_p, elts[i]->self_scm (), SCM_EOL)))
352 if (dynamic_cast<Item*> (elts[i]))
353 relevant_items.push_back (elts[i]);
354 else if (dynamic_cast<Spanner*> (elts[i]))
355 relevant_spanners.push_back (elts[i]);
359 Item *it = dynamic_cast<Item*> (elts[i]);
364 Item *piece = it->find_prebroken_piece (d);
365 if (piece && to_boolean (scm_apply_1 (pure_relevant_p, piece->self_scm (), SCM_EOL)))
366 relevant_items.push_back (piece);
368 while (flip (&d) != LEFT);
370 vector_sort (relevant_items, Item::less);
372 Grob *common = common_refpoint_of_array (relevant_items, me, Y_AXIS);
373 common = common_refpoint_of_array (relevant_spanners, common, Y_AXIS);
375 me->set_object ("pure-Y-common", common->self_scm ());
377 SCM items_scm = Grob_array::make_array ();
378 SCM spanners_scm = Grob_array::make_array ();
380 unsmob_grob_array (items_scm)->set_array (relevant_items);
381 unsmob_grob_array (spanners_scm)->set_array (relevant_spanners);
382 me->set_object ("pure-relevant-items", items_scm);
383 me->set_object ("pure-relevant-spanners", spanners_scm);
388 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_x_common, 1);
390 Axis_group_interface::calc_x_common (SCM grob)
392 Grob *me = unsmob_grob (grob);
394 extract_grob_set (me, "elements", elts);
395 Grob *common = common_refpoint_of_array (elts, me, X_AXIS);
396 return common->self_scm ();
399 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_y_common, 1);
401 Axis_group_interface::calc_y_common (SCM grob)
403 Grob *me = unsmob_grob (grob);
405 extract_grob_set (me, "elements", elts);
406 Grob *common = common_refpoint_of_array (elts, me, Y_AXIS);
407 return common->self_scm ();
411 Axis_group_interface::pure_group_height (Grob *me, int start, int end)
413 Grob *common = calc_pure_elts_and_common (me);
415 Real my_coord = me->relative_coordinate (common, Y_AXIS);
416 Interval r (relative_pure_height (me, start, end));
422 Axis_group_interface::get_children (Grob *me, vector<Grob*> *found)
424 found->push_back (me);
426 if (!has_interface (me))
429 extract_grob_set (me, "elements", elements);
430 for (vsize i = 0; i < elements.size (); i++)
432 Grob *e = elements[i];
433 Axis_group_interface::get_children (e, found);
438 staff_priority_less (Grob * const &g1, Grob * const &g2)
440 Real priority_1 = robust_scm2double (g1->get_property ("outside-staff-priority"), -infinity_f);
441 Real priority_2 = robust_scm2double (g2->get_property ("outside-staff-priority"), -infinity_f);
443 if (priority_1 < priority_2)
445 else if (priority_1 > priority_2)
448 /* if neither grob has an outside-staff priority, the ordering will have no
449 effect -- we just need to choose a consistent ordering. We do this to
450 avoid the side-effect of calculating extents. */
451 if (isinf (priority_1))
454 /* if there is no preference in staff priority, choose the left-most one */
455 Grob *common = g1->common_refpoint (g2, X_AXIS);
456 Real start_1 = g1->extent (common, X_AXIS)[LEFT];
457 Real start_2 = g2->extent (common, X_AXIS)[LEFT];
458 return start_1 < start_2;
462 add_boxes (Grob *me, Grob *x_common, Grob *y_common, vector<Box> *const boxes, Skyline_pair *skylines)
464 /* if a child has skylines, use them instead of the extent box */
465 if (Skyline_pair *pair = Skyline_pair::unsmob (me->get_property ("vertical-skylines")))
467 Skyline_pair s = *pair;
468 s.shift (me->relative_coordinate (x_common, X_AXIS));
469 s.raise (me->relative_coordinate (y_common, Y_AXIS));
472 else if (Grob_array *elements = unsmob_grob_array (me->get_object ("elements")))
474 for (vsize i = 0; i < elements->size (); i++)
475 add_boxes (elements->grob (i), x_common, y_common, boxes, skylines);
477 else if (!scm_is_number (me->get_property ("outside-staff-priority"))
478 && !to_boolean (me->get_property ("cross-staff")))
480 boxes->push_back (Box (me->extent (x_common, X_AXIS),
481 me->extent (y_common, Y_AXIS)));
485 /* We want to avoid situations like this:
493 The point is that "still more text" should be positioned under
494 "more text". In order to achieve this, we place the grobs in several
495 passes. We keep track of the right-most horizontal position that has been
496 affected by the current pass so far (actually we keep track of 2
497 positions, one for above the staff, one for below).
499 In each pass, we loop through the unplaced grobs from left to right.
500 If the grob doesn't overlap the right-most affected position, we place it
501 (and then update the right-most affected position to point to the right
502 edge of the just-placed grob). Otherwise, we skip it until the next pass.
505 add_grobs_of_one_priority (Skyline_pair *const skylines,
506 vector<Grob*> elements,
511 Drul_array<Real> last_affected_position;
514 while (!elements.empty ())
516 last_affected_position[UP] = -infinity_f;
517 last_affected_position[DOWN] = -infinity_f;
519 for (vsize i = elements.size (); i--;)
521 Direction dir = get_grob_direction (elements[i]);
524 warning (_ ("an outside-staff object should have a direction, defaulting to up"));
528 Box b (elements[i]->extent (x_common, X_AXIS),
529 elements[i]->extent (y_common, Y_AXIS));
530 SCM horizon_padding_scm = elements[i]->get_property ("outside-staff-horizontal-padding");
531 Real horizon_padding = robust_scm2double (horizon_padding_scm, 0.0);
533 if (b[X_AXIS][LEFT] - 2*horizon_padding < last_affected_position[dir])
536 if (!b[X_AXIS].is_empty () && !b[Y_AXIS].is_empty ())
540 Skyline other = Skyline (boxes, horizon_padding, X_AXIS, -dir);
541 Real padding = robust_scm2double (elements[i]->get_property ("outside-staff-padding"), 0.5);
542 Real dist = (*skylines)[dir].distance (other) + padding;
546 b.translate (Offset (0, dir*dist));
547 elements[i]->translate_axis (dir*dist, Y_AXIS);
549 (*skylines)[dir].insert (b, 0, X_AXIS);
550 elements[i]->set_property ("outside-staff-priority", SCM_BOOL_F);
551 last_affected_position[dir] = b[X_AXIS][RIGHT];
555 Ugh: quadratic. --hwn
557 elements.erase (elements.begin () + i);
563 Axis_group_interface::skyline_spacing (Grob *me, vector<Grob*> elements)
565 vector_sort (elements, staff_priority_less);
566 Grob *x_common = common_refpoint_of_array (elements, me, X_AXIS);
567 Grob *y_common = common_refpoint_of_array (elements, me, Y_AXIS);
569 assert (y_common == me);
574 Skyline_pair skylines;
575 for (i = 0; i < elements.size ()
576 && !scm_is_number (elements[i]->get_property ("outside-staff-priority")); i++)
577 add_boxes (elements[i], x_common, y_common, &boxes, &skylines);
579 SCM padding_scm = me->get_property ("skyline-horizontal-padding");
580 Real padding = robust_scm2double (padding_scm, 0.1);
581 skylines.merge (Skyline_pair (boxes, padding, X_AXIS));
582 for (; i < elements.size (); i++)
584 SCM priority = elements[i]->get_property ("outside-staff-priority");
585 vector<Grob*> current_elts;
586 current_elts.push_back (elements[i]);
587 while (i + 1 < elements.size ()
588 && scm_eq_p (elements[i+1]->get_property ("outside-staff-priority"), priority))
589 current_elts.push_back (elements[++i]);
591 add_grobs_of_one_priority (&skylines, current_elts, x_common, y_common);
593 skylines.shift (-me->relative_coordinate (x_common, X_AXIS));
597 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_max_stretch, 1)
599 Axis_group_interface::calc_max_stretch (SCM smob)
601 Grob *me = unsmob_grob (smob);
603 extract_grob_set (me, "elements", elts);
605 for (vsize i = 0; i < elts.size (); i++)
606 if (Axis_group_interface::has_interface (elts[i]))
607 ret += robust_scm2double (elts[i]->get_property ("max-stretch"), 0.0);
609 return scm_from_double (ret);
612 extern bool debug_skylines;
613 MAKE_SCHEME_CALLBACK (Axis_group_interface, print, 1)
615 Axis_group_interface::print (SCM smob)
620 Grob *me = unsmob_grob (smob);
622 if (Skyline_pair *s = Skyline_pair::unsmob (me->get_property ("vertical-skylines")))
624 ret.add_stencil (Lookup::points_to_line_stencil (0.1, (*s)[UP].to_points (X_AXIS)).in_color (255, 0, 255));
625 ret.add_stencil (Lookup::points_to_line_stencil (0.1, (*s)[DOWN].to_points (X_AXIS)).in_color (0, 255, 255));
627 return ret.smobbed_copy ();
630 ADD_INTERFACE (Axis_group_interface,
632 "An object that groups other layout objects.",
637 "adjacent-pure-heights "
640 "keep-fixed-while-stretching "
644 "pure-relevant-items "
645 "pure-relevant-spanners "