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))
199 Interval dims = it->pure_height (common, start, end);
200 if (!dims.is_empty ())
205 for (vsize i = 0; i < spanners.size (); i++)
207 Interval_t<int> rank_span = spanners[i]->spanned_rank_interval ();
208 if (rank_span[LEFT] <= end && rank_span[RIGHT] >= start)
210 Interval dims = spanners[i]->pure_height (common, start, end);
211 if (!dims.is_empty ())
218 MAKE_SCHEME_CALLBACK (Axis_group_interface, width, 1);
220 Axis_group_interface::width (SCM smob)
222 Grob *me = unsmob_grob (smob);
223 return generic_group_extent (me, X_AXIS);
226 MAKE_SCHEME_CALLBACK (Axis_group_interface, height, 1);
228 Axis_group_interface::height (SCM smob)
230 Grob *me = unsmob_grob (smob);
231 return generic_group_extent (me, Y_AXIS);
234 MAKE_SCHEME_CALLBACK (Axis_group_interface, pure_height, 3);
236 Axis_group_interface::pure_height (SCM smob, SCM start_scm, SCM end_scm)
238 int start = robust_scm2int (start_scm, 0);
239 int end = robust_scm2int (end_scm, INT_MAX);
240 Grob *me = unsmob_grob (smob);
242 /* Maybe we are in the second pass of a two-pass spacing run. In that
243 case, the Y-extent of a system is already given to us */
244 System *system = dynamic_cast<System*> (me);
247 SCM line_break_details = system->column (start)->get_property ("line-break-system-details");
248 SCM system_y_extent = scm_assq (ly_symbol2scm ("system-Y-extent"), line_break_details);
249 if (scm_is_pair (system_y_extent))
250 return scm_cdr (system_y_extent);
253 return ly_interval2scm (pure_group_height (me, start, end));
256 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_skylines, 1);
258 Axis_group_interface::calc_skylines (SCM smob)
260 Grob *me = unsmob_grob (smob);
261 extract_grob_set (me, "elements", elts);
262 Skyline_pair skylines = skyline_spacing (me, elts);
264 /* add a minimum-Y-extent-sized box to the skyline */
265 SCM min_y_extent = me->get_property ("minimum-Y-extent");
266 if (is_number_pair (min_y_extent))
268 Box b (me->extent (me, X_AXIS), ly_scm2interval (min_y_extent));
269 skylines.insert (b, 0, X_AXIS);
271 return skylines.smobbed_copy ();
274 /* whereas calc_skylines calculates skylines for axis-groups with a lot of
275 visible children, combine_skylines is designed for axis-groups whose only
276 children are other axis-groups (ie. VerticalAlignment). Rather than
277 calculating all the skylines from scratch, we just merge the skylines
280 MAKE_SCHEME_CALLBACK (Axis_group_interface, combine_skylines, 1);
282 Axis_group_interface::combine_skylines (SCM smob)
284 Grob *me = unsmob_grob (smob);
285 extract_grob_set (me, "elements", elements);
286 Grob *y_common = common_refpoint_of_array (elements, me, Y_AXIS);
287 Grob *x_common = common_refpoint_of_array (elements, me, X_AXIS);
290 programming_error ("combining skylines that don't belong to me");
293 for (vsize i = 0; i < elements.size (); i++)
295 SCM skyline_scm = elements[i]->get_property ("vertical-skylines");
296 if (Skyline_pair::unsmob (skyline_scm))
298 Real offset = elements[i]->relative_coordinate (y_common, Y_AXIS);
299 Skyline_pair other = *Skyline_pair::unsmob (skyline_scm);
300 other.raise (offset);
301 other.shift (elements[i]->relative_coordinate (x_common, X_AXIS));
305 return ret.smobbed_copy ();
309 Axis_group_interface::generic_group_extent (Grob *me, Axis a)
311 /* trigger the callback to do skyline-spacing on the children */
313 (void) me->get_property ("vertical-skylines");
315 extract_grob_set (me, "elements", elts);
316 Grob *common = common_refpoint_of_array (elts, me, a);
318 Real my_coord = me->relative_coordinate (common, a);
319 Interval r (relative_group_extent (elts, common, a));
321 return ly_interval2scm (r - my_coord);
326 Axis_group_interface::calc_pure_elts_and_common (Grob *me)
328 if (Grob *c = unsmob_grob (me->get_object ("pure-Y-common")))
331 extract_grob_set (me, "elements", elts);
333 vector<Grob*> relevant_items;
334 vector<Grob*> relevant_spanners;
335 SCM pure_relevant_p = ly_lily_module_constant ("pure-relevant?");
337 for (vsize i = 0; i < elts.size (); i++)
339 if (to_boolean (scm_apply_1 (pure_relevant_p, elts[i]->self_scm (), SCM_EOL)))
341 if (dynamic_cast<Item*> (elts[i]))
342 relevant_items.push_back (elts[i]);
343 else if (dynamic_cast<Spanner*> (elts[i]))
344 relevant_spanners.push_back (elts[i]);
348 Item *it = dynamic_cast<Item*> (elts[i]);
353 Item *piece = it->find_prebroken_piece (d);
354 if (piece && to_boolean (scm_apply_1 (pure_relevant_p, piece->self_scm (), SCM_EOL)))
355 relevant_items.push_back (piece);
357 while (flip (&d) != LEFT);
359 vector_sort (relevant_items, Item::less);
361 Grob *common = common_refpoint_of_array (relevant_items, me, Y_AXIS);
362 common = common_refpoint_of_array (relevant_spanners, common, Y_AXIS);
364 me->set_object ("pure-Y-common", common->self_scm ());
366 SCM items_scm = Grob_array::make_array ();
367 SCM spanners_scm = Grob_array::make_array ();
369 unsmob_grob_array (items_scm)->set_array (relevant_items);
370 unsmob_grob_array (spanners_scm)->set_array (relevant_spanners);
371 me->set_object ("pure-relevant-items", items_scm);
372 me->set_object ("pure-relevant-spanners", spanners_scm);
377 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_x_common, 1);
379 Axis_group_interface::calc_x_common (SCM grob)
381 Grob *me = unsmob_grob (grob);
383 extract_grob_set (me, "elements", elts);
384 Grob *common = common_refpoint_of_array (elts, me, X_AXIS);
385 return common->self_scm ();
388 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_y_common, 1);
390 Axis_group_interface::calc_y_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, Y_AXIS);
396 return common->self_scm ();
400 Axis_group_interface::pure_group_height (Grob *me, int start, int end)
402 Grob *common = calc_pure_elts_and_common (me);
404 Real my_coord = me->relative_coordinate (common, Y_AXIS);
405 Interval r (relative_pure_height (me, start, end));
411 Axis_group_interface::get_children (Grob *me, vector<Grob*> *found)
413 found->push_back (me);
415 if (!has_interface (me))
418 extract_grob_set (me, "elements", elements);
419 for (vsize i = 0; i < elements.size (); i++)
421 Grob *e = elements[i];
422 Axis_group_interface::get_children (e, found);
427 staff_priority_less (Grob * const &g1, Grob * const &g2)
429 Real priority_1 = robust_scm2double (g1->get_property ("outside-staff-priority"), -infinity_f);
430 Real priority_2 = robust_scm2double (g2->get_property ("outside-staff-priority"), -infinity_f);
432 if (priority_1 < priority_2)
434 else if (priority_1 > priority_2)
437 /* if neither grob has an outside-staff priority, the ordering will have no
438 effect -- we just need to choose a consistent ordering. We do this to
439 avoid the side-effect of calculating extents. */
440 if (isinf (priority_1))
443 /* if there is no preference in staff priority, choose the left-most one */
444 Grob *common = g1->common_refpoint (g2, X_AXIS);
445 Real start_1 = g1->extent (common, X_AXIS)[LEFT];
446 Real start_2 = g2->extent (common, X_AXIS)[LEFT];
447 return start_1 < start_2;
451 add_boxes (Grob *me, Grob *x_common, Grob *y_common, vector<Box> *const boxes, Skyline_pair *skylines)
453 /* if a child has skylines, use them instead of the extent box */
454 if (Skyline_pair *pair = Skyline_pair::unsmob (me->get_property ("vertical-skylines")))
456 Skyline_pair s = *pair;
457 s.shift (me->relative_coordinate (x_common, X_AXIS));
458 s.raise (me->relative_coordinate (y_common, Y_AXIS));
461 else if (Grob_array *elements = unsmob_grob_array (me->get_object ("elements")))
463 for (vsize i = 0; i < elements->size (); i++)
464 add_boxes (elements->grob (i), x_common, y_common, boxes, skylines);
466 else if (!scm_is_number (me->get_property ("outside-staff-priority"))
467 && !to_boolean (me->get_property ("cross-staff")))
469 boxes->push_back (Box (me->extent (x_common, X_AXIS),
470 me->extent (y_common, Y_AXIS)));
474 /* We want to avoid situations like this:
482 The point is that "still more text" should be positioned under
483 "more text". In order to achieve this, we place the grobs in several
484 passes. We keep track of the right-most horizontal position that has been
485 affected by the current pass so far (actually we keep track of 2
486 positions, one for above the staff, one for below).
488 In each pass, we loop through the unplaced grobs from left to right.
489 If the grob doesn't overlap the right-most affected position, we place it
490 (and then update the right-most affected position to point to the right
491 edge of the just-placed grob). Otherwise, we skip it until the next pass.
494 add_grobs_of_one_priority (Skyline_pair *const skylines,
495 vector<Grob*> elements,
500 Drul_array<Real> last_affected_position;
503 while (!elements.empty ())
505 last_affected_position[UP] = -infinity_f;
506 last_affected_position[DOWN] = -infinity_f;
508 for (vsize i = elements.size (); i--;)
510 Direction dir = get_grob_direction (elements[i]);
513 warning (_ ("an outside-staff object should have a direction, defaulting to up"));
517 Box b (elements[i]->extent (x_common, X_AXIS),
518 elements[i]->extent (y_common, Y_AXIS));
519 SCM horizon_padding_scm = elements[i]->get_property ("outside-staff-horizontal-padding");
520 Real horizon_padding = robust_scm2double (horizon_padding_scm, 0.0);
522 if (b[X_AXIS][LEFT] - 2*horizon_padding < last_affected_position[dir])
525 if (!b[X_AXIS].is_empty () && !b[Y_AXIS].is_empty ())
529 Skyline other = Skyline (boxes, horizon_padding, X_AXIS, -dir);
530 Real padding = robust_scm2double (elements[i]->get_property ("outside-staff-padding"), 0.5);
531 Real dist = (*skylines)[dir].distance (other) + padding;
535 b.translate (Offset (0, dir*dist));
536 elements[i]->translate_axis (dir*dist, Y_AXIS);
538 (*skylines)[dir].insert (b, 0, X_AXIS);
539 elements[i]->set_property ("outside-staff-priority", SCM_BOOL_F);
540 last_affected_position[dir] = b[X_AXIS][RIGHT];
544 Ugh: quadratic. --hwn
546 elements.erase (elements.begin () + i);
552 Axis_group_interface::skyline_spacing (Grob *me, vector<Grob*> elements)
554 vector_sort (elements, staff_priority_less);
555 Grob *x_common = common_refpoint_of_array (elements, me, X_AXIS);
556 Grob *y_common = common_refpoint_of_array (elements, me, Y_AXIS);
558 assert (y_common == me);
563 Skyline_pair skylines;
564 for (i = 0; i < elements.size ()
565 && !scm_is_number (elements[i]->get_property ("outside-staff-priority")); i++)
566 add_boxes (elements[i], x_common, y_common, &boxes, &skylines);
568 SCM padding_scm = me->get_property ("skyline-horizontal-padding");
569 Real padding = robust_scm2double (padding_scm, 0.1);
570 skylines.merge (Skyline_pair (boxes, padding, X_AXIS));
571 for (; i < elements.size (); i++)
573 SCM priority = elements[i]->get_property ("outside-staff-priority");
574 vector<Grob*> current_elts;
575 current_elts.push_back (elements[i]);
576 while (i + 1 < elements.size ()
577 && scm_eq_p (elements[i+1]->get_property ("outside-staff-priority"), priority))
578 current_elts.push_back (elements[++i]);
580 add_grobs_of_one_priority (&skylines, current_elts, x_common, y_common);
582 skylines.shift (-me->relative_coordinate (x_common, X_AXIS));
586 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_max_stretch, 1)
588 Axis_group_interface::calc_max_stretch (SCM smob)
590 Grob *me = unsmob_grob (smob);
592 extract_grob_set (me, "elements", elts);
594 for (vsize i = 0; i < elts.size (); i++)
595 if (Axis_group_interface::has_interface (elts[i]))
596 ret += robust_scm2double (elts[i]->get_property ("max-stretch"), 0.0);
598 return scm_from_double (ret);
601 extern bool debug_skylines;
602 MAKE_SCHEME_CALLBACK (Axis_group_interface, print, 1)
604 Axis_group_interface::print (SCM smob)
609 Grob *me = unsmob_grob (smob);
611 if (Skyline_pair *s = Skyline_pair::unsmob (me->get_property ("vertical-skylines")))
613 ret.add_stencil (Lookup::points_to_line_stencil (0.1, (*s)[UP].to_points (X_AXIS)).in_color (255, 0, 255));
614 ret.add_stencil (Lookup::points_to_line_stencil (0.1, (*s)[DOWN].to_points (X_AXIS)).in_color (0, 255, 255));
616 return ret.smobbed_copy ();
619 ADD_INTERFACE (Axis_group_interface,
621 "An object that groups other layout objects.",
626 "adjacent-pure-heights "
629 "keep-fixed-while-stretching "
633 "pure-relevant-items "
634 "pure-relevant-spanners "