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)
158 && !Hara_kiri_group_spanner::request_suicide (g, start, end))
160 Interval begin_of_line_extent = Axis_group_interface::begin_of_line_pure_height (g, start);
161 if (!begin_of_line_extent.is_empty ())
164 b[a] = begin_of_line_extent;
165 b[other_axis (a)] = Interval (-infinity_f, -1);
166 skylines.insert (b, 0, other_axis (a));
171 if (skylines.is_empty ())
172 elements->erase (elements->begin () + i);
174 ret->push_back (skylines);
180 Align_interface::get_extents_aligned_translates (Grob *me,
181 vector<Grob*> const &all_grobs,
183 bool pure, int start, int end)
185 Spanner *me_spanner = dynamic_cast<Spanner *> (me);
188 SCM line_break_details = SCM_EOL;
189 if (a == Y_AXIS && me_spanner)
192 line_break_details = get_root_system (me)->column (start)->get_property ("line-break-system-details");
194 line_break_details = me_spanner->get_bound (LEFT)->get_property ("line-break-system-details");
196 if (!me->get_system () && !pure)
197 me->programming_error ("vertical alignment called before line-breaking");
200 Direction stacking_dir = robust_scm2dir (me->get_property ("stacking-dir"),
203 vector<Grob*> elems (all_grobs); // writable copy
204 vector<Skyline_pair> skylines;
206 get_skylines (me, &elems, a, pure, start, end, &skylines);
209 /* TODO: extra-space stuff belongs to two-pass spacing. Delete me */
210 SCM extra_space_handle = scm_assq (ly_symbol2scm ("alignment-extra-space"), line_break_details);
211 Real extra_space = robust_scm2double (scm_is_pair (extra_space_handle)
212 ? scm_cdr (extra_space_handle)
216 Real padding = robust_scm2double (me->get_property ("padding"), 0.0);
217 vector<Real> translates;
218 Skyline down_skyline (stacking_dir);
219 for (vsize j = 0; j < elems.size (); j++)
223 dy = skylines[j][-stacking_dir].max_height ();
226 down_skyline.merge (skylines[j-1][stacking_dir]);
227 dy = down_skyline.distance (skylines[j][-stacking_dir]);
230 if (isinf (dy)) /* if the skyline is empty, maybe max_height is infinity_f */
233 dy = max (0.0, dy + padding + extra_space / elems.size ());
234 down_skyline.raise (-stacking_dir * dy);
235 where += stacking_dir * dy;
236 translates.push_back (where);
239 SCM offsets_handle = scm_assq (ly_symbol2scm ("alignment-offsets"),
241 if (scm_is_pair (offsets_handle))
245 for (SCM s = scm_cdr (offsets_handle);
246 scm_is_pair (s) && i < translates.size (); s = scm_cdr (s), i++)
248 if (scm_is_number (scm_car (s)))
249 translates[i] = scm_to_double (scm_car (s));
253 // So far, we've computed the translates for all the non-empty elements.
254 // Here, we set the translates for the empty elements: an empty element
255 // gets the same translation as the last non-empty element before it.
256 vector<Real> all_translates;
257 if (!translates.empty ())
259 Real w = translates[0];
260 for (vsize i = 0, j = 0; j < all_grobs.size (); j++)
262 if (i < elems.size () && all_grobs[j] == elems[i])
264 all_translates.push_back (w);
267 return all_translates;
271 Align_interface::align_elements_to_extents (Grob *me, Axis a)
273 extract_grob_set (me, "elements", all_grobs);
275 vector<Real> translates = get_extents_aligned_translates (me, all_grobs, a, false, 0, 0);
276 if (translates.size ())
277 for (vsize j = 0; j < all_grobs.size (); j++)
278 all_grobs[j]->translate_axis (translates[j], a);
281 /* After we have already determined the y-offsets of our children, we may still
282 want to stretch them a little. */
284 Align_interface::stretch (Grob *me, Real amount, Axis a)
286 extract_grob_set (me, "elements", elts);
287 Real non_empty_elts = stretchable_children_count (me);
289 Direction dir = robust_scm2dir (me->get_property ("stacking-dir"), DOWN);
290 for (vsize i = 1; i < elts.size (); i++)
292 if (!elts[i]->extent (me, a).is_empty ()
293 && !to_boolean (elts[i]->get_property ("keep-fixed-while-stretching")))
294 offset += amount / non_empty_elts;
295 elts[i]->translate_axis (dir * offset, a);
297 me->flush_extent_cache (Y_AXIS);
301 Align_interface::get_pure_child_y_translation (Grob *me, Grob *ch, int start, int end)
303 extract_grob_set (me, "elements", all_grobs);
304 SCM dy_scm = me->get_property ("forced-distance");
306 if (scm_is_number (dy_scm))
308 Real dy = scm_to_double (dy_scm) * robust_scm2dir (me->get_property ("stacking-dir"), DOWN);
310 for (vsize i = 0; i < all_grobs.size (); i++)
312 if (all_grobs[i] == ch)
314 if (!Hara_kiri_group_spanner::has_interface (all_grobs[i])
315 || !Hara_kiri_group_spanner::request_suicide (all_grobs[i], start, end))
321 vector<Real> translates = get_extents_aligned_translates (me, all_grobs, Y_AXIS, true, start, end);
323 if (translates.size ())
325 for (vsize i = 0; i < all_grobs.size (); i++)
326 if (all_grobs[i] == ch)
327 return translates[i];
333 programming_error (_ ("tried to get a translation for something that is no child of mine"));
338 Align_interface::axis (Grob *me)
340 return Axis (scm_to_int (scm_car (me->get_property ("axes"))));
344 Align_interface::add_element (Grob *me, Grob *element)
346 Axis a = Align_interface::axis (me);
347 SCM sym = axis_offset_symbol (a);
348 SCM proc = axis_parent_positioning (a);
350 element->set_property (sym, proc);
351 Axis_group_interface::add_element (me, element);
355 Align_interface::set_ordered (Grob *me)
357 SCM ga_scm = me->get_object ("elements");
358 Grob_array *ga = unsmob_grob_array (ga_scm);
361 ga_scm = Grob_array::make_array ();
362 ga = unsmob_grob_array (ga_scm);
363 me->set_object ("elements", ga_scm);
366 ga->set_ordered (true);
370 Align_interface::stretchable_children_count (Grob const *me)
372 extract_grob_set (me, "elements", elts);
375 /* start at 1: we will never move the first child while stretching */
376 for (vsize i = 1; i < elts.size (); i++)
377 if (!to_boolean (elts[i]->get_property ("keep-fixed-while-stretching"))
378 && !elts[i]->extent (elts[i], Y_AXIS).is_empty ())
384 MAKE_SCHEME_CALLBACK (Align_interface, calc_max_stretch, 1)
386 Align_interface::calc_max_stretch (SCM smob)
388 Grob *me = unsmob_grob (smob);
389 Spanner *spanner_me = dynamic_cast<Spanner*> (me);
392 if (spanner_me && stretchable_children_count (me) > 0)
394 Paper_column *left = dynamic_cast<Paper_column*> (spanner_me->get_bound (LEFT));
395 Real height = me->extent (me, Y_AXIS).length ();
396 SCM line_break_details = left->get_property ("line-break-system-details");
397 SCM fixed_offsets = scm_assq (ly_symbol2scm ("alignment-offsets"),
400 /* if there are fixed offsets, we refuse to stretch */
401 if (fixed_offsets != SCM_BOOL_F)
404 ret = height * height / 80.0; /* why this, exactly? -- jneem */
406 return scm_from_double (ret);
409 ADD_INTERFACE (Align_interface,
410 "Order grobs from top to bottom, left to right, right to left"
411 " or bottom to top. For vertical alignments of staves, the"
412 " @code{break-system-details} of the left"
413 " @rinternals{NonMusicalPaperColumn} may be set to tune"
414 " vertical spacing. Set @code{alignment-extra-space} to add"
415 " extra space for staves. Set"
416 " @code{fixed-alignment-extra-space} to force staves in"
417 " @code{PianoStaff}s further apart.",