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);
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);
321 Axis_group_interface::calc_pure_elts_and_common (Grob *me)
323 if (Grob *c = unsmob_grob (me->get_object ("pure-Y-common")))
326 extract_grob_set (me, "elements", elts);
328 vector<Grob*> relevant_items;
329 vector<Grob*> relevant_spanners;
330 SCM pure_relevant_p = ly_lily_module_constant ("pure-relevant?");
332 for (vsize i = 0; i < elts.size (); i++)
334 if (to_boolean (scm_apply_1 (pure_relevant_p, elts[i]->self_scm (), SCM_EOL)))
336 if (dynamic_cast<Item*> (elts[i]))
337 relevant_items.push_back (elts[i]);
338 else if (dynamic_cast<Spanner*> (elts[i]))
339 relevant_spanners.push_back (elts[i]);
343 Item *it = dynamic_cast<Item*> (elts[i]);
348 Item *piece = it->find_prebroken_piece (d);
349 if (piece && to_boolean (scm_apply_1 (pure_relevant_p, piece->self_scm (), SCM_EOL)))
350 relevant_items.push_back (piece);
352 while (flip (&d) != LEFT);
354 vector_sort (relevant_items, Item::less);
356 Grob *common = common_refpoint_of_array (relevant_items, me, Y_AXIS);
357 common = common_refpoint_of_array (relevant_spanners, common, Y_AXIS);
359 me->set_object ("pure-Y-common", common->self_scm ());
361 SCM items_scm = Grob_array::make_array ();
362 SCM spanners_scm = Grob_array::make_array ();
364 unsmob_grob_array (items_scm)->set_array (relevant_items);
365 unsmob_grob_array (spanners_scm)->set_array (relevant_spanners);
366 me->set_object ("pure-relevant-items", items_scm);
367 me->set_object ("pure-relevant-spanners", spanners_scm);
372 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_x_common, 1);
374 Axis_group_interface::calc_x_common (SCM grob)
376 Grob *me = unsmob_grob (grob);
378 extract_grob_set (me, "elements", elts);
379 Grob *common = common_refpoint_of_array (elts, me, X_AXIS);
380 return common->self_scm ();
383 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_y_common, 1);
385 Axis_group_interface::calc_y_common (SCM grob)
387 Grob *me = unsmob_grob (grob);
389 extract_grob_set (me, "elements", elts);
390 Grob *common = common_refpoint_of_array (elts, me, Y_AXIS);
391 return common->self_scm ();
395 Axis_group_interface::pure_group_height (Grob *me, int start, int end)
397 Grob *common = calc_pure_elts_and_common (me);
399 Real my_coord = me->relative_coordinate (common, Y_AXIS);
400 Interval r (relative_pure_height (me, start, end));
406 Axis_group_interface::get_children (Grob *me, vector<Grob*> *found)
408 found->push_back (me);
410 if (!has_interface (me))
413 extract_grob_set (me, "elements", elements);
414 for (vsize i = 0; i < elements.size (); i++)
416 Grob *e = elements[i];
417 Axis_group_interface::get_children (e, found);
422 staff_priority_less (Grob * const &g1, Grob * const &g2)
424 Real priority_1 = robust_scm2double (g1->get_property ("outside-staff-priority"), -infinity_f);
425 Real priority_2 = robust_scm2double (g2->get_property ("outside-staff-priority"), -infinity_f);
427 if (priority_1 < priority_2)
429 else if (priority_1 > priority_2)
432 /* if neither grob has an outside-staff priority, the ordering will have no
433 effect -- we just need to choose a consistent ordering. We do this to
434 avoid the side-effect of calculating extents. */
435 if (isinf (priority_1))
438 /* if there is no preference in staff priority, choose the left-most one */
439 Grob *common = g1->common_refpoint (g2, X_AXIS);
440 Real start_1 = g1->extent (common, X_AXIS)[LEFT];
441 Real start_2 = g2->extent (common, X_AXIS)[LEFT];
442 return start_1 < start_2;
446 add_boxes (Grob *me, Grob *x_common, Grob *y_common, vector<Box> *const boxes, Skyline_pair *skylines)
448 /* if a child has skylines, use them instead of the extent box */
449 if (Skyline_pair *pair = Skyline_pair::unsmob (me->get_property ("vertical-skylines")))
451 Skyline_pair s = *pair;
452 s.shift (me->relative_coordinate (x_common, X_AXIS));
453 s.raise (me->relative_coordinate (y_common, Y_AXIS));
456 else if (Grob_array *elements = unsmob_grob_array (me->get_object ("elements")))
458 for (vsize i = 0; i < elements->size (); i++)
459 add_boxes (elements->grob (i), x_common, y_common, boxes, skylines);
461 else if (!scm_is_number (me->get_property ("outside-staff-priority"))
462 && !to_boolean (me->get_property ("cross-staff")))
464 boxes->push_back (Box (me->extent (x_common, X_AXIS),
465 me->extent (y_common, Y_AXIS)));
469 /* We want to avoid situations like this:
477 The point is that "still more text" should be positioned under
478 "more text". In order to achieve this, we place the grobs in several
479 passes. We keep track of the right-most horizontal position that has been
480 affected by the current pass so far (actually we keep track of 2
481 positions, one for above the staff, one for below).
483 In each pass, we loop through the unplaced grobs from left to right.
484 If the grob doesn't overlap the right-most affected position, we place it
485 (and then update the right-most affected position to point to the right
486 edge of the just-placed grob). Otherwise, we skip it until the next pass.
489 add_grobs_of_one_priority (Skyline_pair *const skylines,
490 vector<Grob*> elements,
495 Drul_array<Real> last_affected_position;
498 while (!elements.empty ())
500 last_affected_position[UP] = -infinity_f;
501 last_affected_position[DOWN] = -infinity_f;
503 for (vsize i = elements.size (); i--;)
505 Direction dir = get_grob_direction (elements[i]);
508 warning (_ ("an outside-staff object should have a direction, defaulting to up"));
512 Box b (elements[i]->extent (x_common, X_AXIS),
513 elements[i]->extent (y_common, Y_AXIS));
514 SCM horizon_padding_scm = elements[i]->get_property ("outside-staff-horizontal-padding");
515 Real horizon_padding = robust_scm2double (horizon_padding_scm, 0.0);
517 if (b[X_AXIS][LEFT] - 2*horizon_padding < last_affected_position[dir])
520 if (!b[X_AXIS].is_empty () && !b[Y_AXIS].is_empty ())
524 Skyline other = Skyline (boxes, horizon_padding, X_AXIS, -dir);
525 Real padding = robust_scm2double (elements[i]->get_property ("outside-staff-padding"), 0.5);
526 Real dist = (*skylines)[dir].distance (other) + padding;
530 b.translate (Offset (0, dir*dist));
531 elements[i]->translate_axis (dir*dist, Y_AXIS);
533 (*skylines)[dir].insert (b, 0, X_AXIS);
534 elements[i]->set_property ("outside-staff-priority", SCM_BOOL_F);
535 last_affected_position[dir] = b[X_AXIS][RIGHT];
539 Ugh: quadratic. --hwn
541 elements.erase (elements.begin () + i);
547 Axis_group_interface::skyline_spacing (Grob *me, vector<Grob*> elements)
549 vector_sort (elements, staff_priority_less);
550 Grob *x_common = common_refpoint_of_array (elements, me, X_AXIS);
551 Grob *y_common = common_refpoint_of_array (elements, me, Y_AXIS);
553 assert (y_common == me);
558 Skyline_pair skylines;
559 for (i = 0; i < elements.size ()
560 && !scm_is_number (elements[i]->get_property ("outside-staff-priority")); i++)
561 add_boxes (elements[i], x_common, y_common, &boxes, &skylines);
563 SCM padding_scm = me->get_property ("skyline-horizontal-padding");
564 Real padding = robust_scm2double (padding_scm, 0.1);
565 skylines.merge (Skyline_pair (boxes, padding, X_AXIS));
566 for (; i < elements.size (); i++)
568 SCM priority = elements[i]->get_property ("outside-staff-priority");
569 vector<Grob*> current_elts;
570 current_elts.push_back (elements[i]);
571 while (i + 1 < elements.size ()
572 && scm_eq_p (elements[i+1]->get_property ("outside-staff-priority"), priority))
573 current_elts.push_back (elements[++i]);
575 add_grobs_of_one_priority (&skylines, current_elts, x_common, y_common);
577 skylines.shift (-me->relative_coordinate (x_common, X_AXIS));
581 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_max_stretch, 1)
583 Axis_group_interface::calc_max_stretch (SCM smob)
585 Grob *me = unsmob_grob (smob);
587 extract_grob_set (me, "elements", elts);
589 for (vsize i = 0; i < elts.size (); i++)
590 if (Axis_group_interface::has_interface (elts[i]))
591 ret += robust_scm2double (elts[i]->get_property ("max-stretch"), 0.0);
593 return scm_from_double (ret);
596 extern bool debug_skylines;
597 MAKE_SCHEME_CALLBACK (Axis_group_interface, print, 1)
599 Axis_group_interface::print (SCM smob)
604 Grob *me = unsmob_grob (smob);
606 if (Skyline_pair *s = Skyline_pair::unsmob (me->get_property ("vertical-skylines")))
608 ret.add_stencil (Lookup::points_to_line_stencil (0.1, (*s)[UP].to_points (X_AXIS)).in_color (255, 0, 255));
609 ret.add_stencil (Lookup::points_to_line_stencil (0.1, (*s)[DOWN].to_points (X_AXIS)).in_color (0, 255, 255));
611 return ret.smobbed_copy ();
614 ADD_INTERFACE (Axis_group_interface,
616 "An object that groups other layout objects.",
621 "adjacent-pure-heights "
624 "keep-fixed-while-stretching "
628 "pure-relevant-items "
629 "pure-relevant-spanners "