2 align-interface.cc -- implement Align_interface
4 source file of the GNU LilyPond music typesetter
6 (c) 2000--2009 Han-Wen Nienhuys <hanwen@xs4all.nl>
9 #include "align-interface.hh"
10 #include "axis-group-interface.hh"
11 #include "grob-array.hh"
12 #include "hara-kiri-group-spanner.hh"
13 #include "international.hh"
15 #include "paper-column.hh"
16 #include "pointer-group-interface.hh"
18 #include "skyline-pair.hh"
23 TODO: for vertical spacing, should also include a rod & spring
24 scheme of sorts into this: the alignment should default to a certain
25 distance between element refpoints, unless bbox force a bigger
29 MAKE_SCHEME_CALLBACK (Align_interface, calc_positioning_done, 1);
31 Align_interface::calc_positioning_done (SCM smob)
33 Grob *me = unsmob_grob (smob);
35 me->set_property ("positioning-done", SCM_BOOL_T);
37 SCM axis = scm_car (me->get_property ("axes"));
38 Axis ax = Axis (scm_to_int (axis));
40 Align_interface::align_elements_to_extents (me, ax);
46 TODO: This belongs to the old two-pass spacing. Delete me.
48 MAKE_SCHEME_CALLBACK (Align_interface, stretch_after_break, 1)
50 Align_interface::stretch_after_break (SCM grob)
52 Grob *me = unsmob_grob (grob);
54 Spanner *me_spanner = dynamic_cast<Spanner *> (me);
55 extract_grob_set (me, "elements", elems);
57 if (me_spanner && elems.size ())
59 Grob *common = common_refpoint_of_array (elems, me, Y_AXIS);
61 /* force position callbacks */
62 for (vsize i = 0; i < elems.size (); i++)
63 elems[i]->relative_coordinate (common, Y_AXIS);
65 SCM details = me_spanner->get_bound (LEFT)->get_property ("line-break-system-details");
66 SCM extra_space_handle = scm_assoc (ly_symbol2scm ("fixed-alignment-extra-space"), details);
68 Real extra_space = robust_scm2double (scm_is_pair (extra_space_handle)
69 ? scm_cdr (extra_space_handle)
73 Direction stacking_dir = robust_scm2dir (me->get_property ("stacking-dir"),
75 Real delta = extra_space / elems.size () * stacking_dir;
76 for (vsize i = 0; i < elems.size (); i++)
77 elems[i]->translate_axis (i * delta, Y_AXIS);
80 return SCM_UNSPECIFIED;
83 /* for each grob, find its upper and lower skylines. If the grob has
84 an empty extent, delete it from the list instead. If the extent is
85 non-empty but there is no skyline available (or pure is true), just
86 create a flat skyline from the bounding box */
87 // TODO(jneem): the pure and non-pure parts seem to share very little
88 // code. Split them into 2 functions, perhaps?
90 get_skylines (Grob *me,
91 vector<Grob*> *const elements,
93 bool pure, int start, int end,
94 vector<Skyline_pair> *const ret)
96 Grob *other_common = common_refpoint_of_array (*elements, me, other_axis (a));
98 for (vsize i = elements->size (); i--;)
100 Grob *g = (*elements)[i];
101 Skyline_pair skylines;
105 Skyline_pair *skys = Skyline_pair::unsmob (g->get_property (a == Y_AXIS
106 ? "vertical-skylines"
107 : "horizontal-skylines"));
111 /* this is perhaps an abuse of minimum-?-extent: maybe we should create
112 another property? But it seems that the only (current) use of
113 minimum-Y-extent is to separate vertically-aligned elements */
114 SCM min_extent = g->get_property (a == X_AXIS
115 ? ly_symbol2scm ("minimum-X-extent")
116 : ly_symbol2scm ("minimum-Y-extent"));
118 if (is_number_pair (min_extent))
121 Interval other_extent = g->extent (other_common, other_axis (a));
122 b[a] = ly_scm2interval (min_extent);
123 b[other_axis (a)] = other_extent;
124 if (!other_extent.is_empty ())
125 skylines.insert (b, 0, other_axis (a));
128 /* This skyline was calculated relative to the grob g. In order to compare it to
129 skylines belonging to other grobs, we need to shift it so that it is relative
130 to the common reference. */
131 Real offset = g->relative_coordinate (other_common, other_axis (a));
132 skylines.shift (offset);
136 assert (a == Y_AXIS);
137 Interval extent = g->pure_height (g, start, end);
138 if (!extent.is_empty ())
142 b[other_axis (a)] = Interval (0, infinity_f);
143 skylines.insert (b, 0, other_axis (a));
146 // This is a hack to get better accuracy on the pure-height of VerticalAlignment.
147 // It's quite common for a treble clef to be the highest element of one system
148 // and for a low note (or lyrics) to be the lowest note on another. The two will
149 // never collide, but the pure-height stuff only works with bounding boxes, so it
150 // doesn't know that. The result is a significant over-estimation of the pure-height,
151 // especially on systems with many staves. To correct for this, we build a skyline
152 // in two parts: the part we did above contains most of the grobs (note-heads, etc.)
153 // while the bit we're about to do only contains the breakable grobs at the beginning
154 // of the system. This way, the tall treble clefs are only compared with the treble
155 // clefs of the other staff and they will be ignored if the staff above is, for example,
157 if (Axis_group_interface::has_interface (g))
159 Interval begin_of_line_extent = Axis_group_interface::begin_of_line_pure_height (g, start);
160 if (!begin_of_line_extent.is_empty ())
163 b[a] = begin_of_line_extent;
164 b[other_axis (a)] = Interval (-infinity_f, -1);
165 skylines.insert (b, 0, other_axis (a));
170 if (skylines.is_empty ())
171 elements->erase (elements->begin () + i);
173 ret->push_back (skylines);
179 Align_interface::get_extents_aligned_translates (Grob *me,
180 vector<Grob*> const &all_grobs,
182 bool pure, int start, int end)
184 Spanner *me_spanner = dynamic_cast<Spanner *> (me);
187 SCM line_break_details = SCM_EOL;
188 if (a == Y_AXIS && me_spanner)
191 line_break_details = get_root_system (me)->column (start)->get_property ("line-break-system-details");
193 line_break_details = me_spanner->get_bound (LEFT)->get_property ("line-break-system-details");
195 if (!me->get_system () && !pure)
196 me->programming_error ("vertical alignment called before line-breaking");
199 Direction stacking_dir = robust_scm2dir (me->get_property ("stacking-dir"),
202 vector<Grob*> elems (all_grobs); // writable copy
203 vector<Skyline_pair> skylines;
205 get_skylines (me, &elems, a, pure, start, end, &skylines);
208 /* TODO: extra-space stuff belongs to two-pass spacing. Delete me */
209 SCM extra_space_handle = scm_assq (ly_symbol2scm ("alignment-extra-space"), line_break_details);
210 Real extra_space = robust_scm2double (scm_is_pair (extra_space_handle)
211 ? scm_cdr (extra_space_handle)
215 Real padding = robust_scm2double (me->get_property ("padding"), 0.0);
216 vector<Real> translates;
217 Skyline down_skyline (stacking_dir);
218 for (vsize j = 0; j < elems.size (); j++)
222 dy = skylines[j][-stacking_dir].max_height ();
225 down_skyline.merge (skylines[j-1][stacking_dir]);
226 dy = down_skyline.distance (skylines[j][-stacking_dir]);
229 if (isinf (dy)) /* if the skyline is empty, maybe max_height is infinity_f */
232 dy = max (0.0, dy + padding + extra_space / elems.size ());
233 down_skyline.raise (-stacking_dir * dy);
234 where += stacking_dir * dy;
235 translates.push_back (where);
238 SCM offsets_handle = scm_assq (ly_symbol2scm ("alignment-offsets"),
240 if (scm_is_pair (offsets_handle))
244 for (SCM s = scm_cdr (offsets_handle);
245 scm_is_pair (s) && i < translates.size (); s = scm_cdr (s), i++)
247 if (scm_is_number (scm_car (s)))
248 translates[i] = scm_to_double (scm_car (s));
252 // So far, we've computed the translates for all the non-empty elements.
253 // Here, we set the translates for the empty elements: an empty element
254 // gets the same translation as the last non-empty element before it.
255 vector<Real> all_translates;
256 if (!translates.empty ())
258 Real w = translates[0];
259 for (vsize i = 0, j = 0; j < all_grobs.size (); j++)
261 if (i < elems.size () && all_grobs[j] == elems[i])
263 all_translates.push_back (w);
266 return all_translates;
270 Align_interface::align_elements_to_extents (Grob *me, Axis a)
272 extract_grob_set (me, "elements", all_grobs);
274 vector<Real> translates = get_extents_aligned_translates (me, all_grobs, a, false, 0, 0);
275 if (translates.size ())
276 for (vsize j = 0; j < all_grobs.size (); j++)
277 all_grobs[j]->translate_axis (translates[j], a);
280 /* After we have already determined the y-offsets of our children, we may still
281 want to stretch them a little. */
283 Align_interface::stretch (Grob *me, Real amount, Axis a)
285 extract_grob_set (me, "elements", elts);
286 Real non_empty_elts = stretchable_children_count (me);
288 Direction dir = robust_scm2dir (me->get_property ("stacking-dir"), DOWN);
289 for (vsize i = 1; i < elts.size (); i++)
291 if (!elts[i]->extent (me, a).is_empty ()
292 && !to_boolean (elts[i]->get_property ("keep-fixed-while-stretching")))
293 offset += amount / non_empty_elts;
294 elts[i]->translate_axis (dir * offset, a);
296 me->flush_extent_cache (Y_AXIS);
300 Align_interface::get_pure_child_y_translation (Grob *me, Grob *ch, int start, int end)
302 extract_grob_set (me, "elements", all_grobs);
303 SCM dy_scm = me->get_property ("forced-distance");
305 if (scm_is_number (dy_scm))
307 Real dy = scm_to_double (dy_scm) * robust_scm2dir (me->get_property ("stacking-dir"), DOWN);
309 for (vsize i = 0; i < all_grobs.size (); i++)
311 if (all_grobs[i] == ch)
313 if (!Hara_kiri_group_spanner::has_interface (all_grobs[i])
314 || !Hara_kiri_group_spanner::request_suicide (all_grobs[i], start, end))
320 vector<Real> translates = get_extents_aligned_translates (me, all_grobs, Y_AXIS, true, start, end);
322 if (translates.size ())
324 for (vsize i = 0; i < all_grobs.size (); i++)
325 if (all_grobs[i] == ch)
326 return translates[i];
332 programming_error (_ ("tried to get a translation for something that is no child of mine"));
337 Align_interface::axis (Grob *me)
339 return Axis (scm_to_int (scm_car (me->get_property ("axes"))));
343 Align_interface::add_element (Grob *me, Grob *element)
345 Axis a = Align_interface::axis (me);
346 SCM sym = axis_offset_symbol (a);
347 SCM proc = axis_parent_positioning (a);
349 element->set_property (sym, proc);
350 Axis_group_interface::add_element (me, element);
354 Align_interface::set_ordered (Grob *me)
356 SCM ga_scm = me->get_object ("elements");
357 Grob_array *ga = unsmob_grob_array (ga_scm);
360 ga_scm = Grob_array::make_array ();
361 ga = unsmob_grob_array (ga_scm);
362 me->set_object ("elements", ga_scm);
365 ga->set_ordered (true);
369 Align_interface::stretchable_children_count (Grob const *me)
371 extract_grob_set (me, "elements", elts);
374 /* start at 1: we will never move the first child while stretching */
375 for (vsize i = 1; i < elts.size (); i++)
376 if (!to_boolean (elts[i]->get_property ("keep-fixed-while-stretching"))
377 && !elts[i]->extent (elts[i], Y_AXIS).is_empty ())
383 MAKE_SCHEME_CALLBACK (Align_interface, calc_max_stretch, 1)
385 Align_interface::calc_max_stretch (SCM smob)
387 Grob *me = unsmob_grob (smob);
388 Spanner *spanner_me = dynamic_cast<Spanner*> (me);
391 if (spanner_me && stretchable_children_count (me) > 0)
393 Paper_column *left = dynamic_cast<Paper_column*> (spanner_me->get_bound (LEFT));
394 Real height = me->extent (me, Y_AXIS).length ();
395 SCM line_break_details = left->get_property ("line-break-system-details");
396 SCM fixed_offsets = scm_assq (ly_symbol2scm ("alignment-offsets"),
399 /* if there are fixed offsets, we refuse to stretch */
400 if (fixed_offsets != SCM_BOOL_F)
403 ret = height * height / 80.0; /* why this, exactly? -- jneem */
405 return scm_from_double (ret);
408 ADD_INTERFACE (Align_interface,
409 "Order grobs from top to bottom, left to right, right to left"
410 " or bottom to top. For vertical alignments of staves, the"
411 " @code{break-system-details} of the left"
412 " @rinternals{NonMusicalPaperColumn} may be set to tune"
413 " vertical spacing. Set @code{alignment-extra-space} to add"
414 " extra space for staves. Set"
415 " @code{fixed-alignment-extra-space} to force staves in"
416 " @code{PianoStaff}s further apart.",