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 vector<Real> all_translates;
254 if (!translates.empty ())
256 Real w = translates[0];
257 for (vsize i = 0, j = 0; j < all_grobs.size (); j++)
259 if (i < elems.size () && all_grobs[j] == elems[i])
261 all_translates.push_back (w);
264 return all_translates;
268 Align_interface::align_elements_to_extents (Grob *me, Axis a)
270 extract_grob_set (me, "elements", all_grobs);
272 vector<Real> translates = get_extents_aligned_translates (me, all_grobs, a, false, 0, 0);
273 if (translates.size ())
274 for (vsize j = 0; j < all_grobs.size (); j++)
275 all_grobs[j]->translate_axis (translates[j], a);
278 /* After we have already determined the y-offsets of our children, we may still
279 want to stretch them a little. */
281 Align_interface::stretch (Grob *me, Real amount, Axis a)
283 extract_grob_set (me, "elements", elts);
284 Real non_empty_elts = stretchable_children_count (me);
286 Direction dir = robust_scm2dir (me->get_property ("stacking-dir"), DOWN);
287 for (vsize i = 1; i < elts.size (); i++)
289 if (!elts[i]->extent (me, a).is_empty ()
290 && !to_boolean (elts[i]->get_property ("keep-fixed-while-stretching")))
291 offset += amount / non_empty_elts;
292 elts[i]->translate_axis (dir * offset, a);
294 me->flush_extent_cache (Y_AXIS);
298 Align_interface::get_pure_child_y_translation (Grob *me, Grob *ch, int start, int end)
300 extract_grob_set (me, "elements", all_grobs);
301 SCM dy_scm = me->get_property ("forced-distance");
303 if (scm_is_number (dy_scm))
305 Real dy = scm_to_double (dy_scm) * robust_scm2dir (me->get_property ("stacking-dir"), DOWN);
307 for (vsize i = 0; i < all_grobs.size (); i++)
309 if (all_grobs[i] == ch)
311 if (!Hara_kiri_group_spanner::has_interface (all_grobs[i])
312 || !Hara_kiri_group_spanner::request_suicide (all_grobs[i], start, end))
318 vector<Real> translates = get_extents_aligned_translates (me, all_grobs, Y_AXIS, true, start, end);
320 if (translates.size ())
322 for (vsize i = 0; i < all_grobs.size (); i++)
323 if (all_grobs[i] == ch)
324 return translates[i];
330 programming_error (_ ("tried to get a translation for something that is no child of mine"));
335 Align_interface::axis (Grob *me)
337 return Axis (scm_to_int (scm_car (me->get_property ("axes"))));
341 Align_interface::add_element (Grob *me, Grob *element)
343 Axis a = Align_interface::axis (me);
344 SCM sym = axis_offset_symbol (a);
345 SCM proc = axis_parent_positioning (a);
347 element->set_property (sym, proc);
348 Axis_group_interface::add_element (me, element);
352 Align_interface::set_ordered (Grob *me)
354 SCM ga_scm = me->get_object ("elements");
355 Grob_array *ga = unsmob_grob_array (ga_scm);
358 ga_scm = Grob_array::make_array ();
359 ga = unsmob_grob_array (ga_scm);
360 me->set_object ("elements", ga_scm);
363 ga->set_ordered (true);
367 Align_interface::stretchable_children_count (Grob const *me)
369 extract_grob_set (me, "elements", elts);
372 /* start at 1: we will never move the first child while stretching */
373 for (vsize i = 1; i < elts.size (); i++)
374 if (!to_boolean (elts[i]->get_property ("keep-fixed-while-stretching"))
375 && !elts[i]->extent (elts[i], Y_AXIS).is_empty ())
381 MAKE_SCHEME_CALLBACK (Align_interface, calc_max_stretch, 1)
383 Align_interface::calc_max_stretch (SCM smob)
385 Grob *me = unsmob_grob (smob);
386 Spanner *spanner_me = dynamic_cast<Spanner*> (me);
389 if (spanner_me && stretchable_children_count (me) > 0)
391 Paper_column *left = dynamic_cast<Paper_column*> (spanner_me->get_bound (LEFT));
392 Real height = me->extent (me, Y_AXIS).length ();
393 SCM line_break_details = left->get_property ("line-break-system-details");
394 SCM fixed_offsets = scm_assq (ly_symbol2scm ("alignment-offsets"),
397 /* if there are fixed offsets, we refuse to stretch */
398 if (fixed_offsets != SCM_BOOL_F)
401 ret = height * height / 80.0; /* why this, exactly? -- jneem */
403 return scm_from_double (ret);
406 ADD_INTERFACE (Align_interface,
407 "Order grobs from top to bottom, left to right, right to left"
408 " or bottom to top. For vertical alignments of staves, the"
409 " @code{break-system-details} of the left"
410 " @rinternals{NonMusicalPaperColumn} may be set to tune"
411 " vertical spacing. Set @code{alignment-extra-space} to add"
412 " extra space for staves. Set"
413 " @code{fixed-alignment-extra-space} to force staves in"
414 " @code{PianoStaff}s further apart.",