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 */
88 get_skylines (Grob *me,
89 vector<Grob*> *const elements,
91 bool pure, int start, int end,
92 vector<Skyline_pair> *const ret)
94 Grob *other_common = common_refpoint_of_array (*elements, me, other_axis (a));
96 for (vsize i = elements->size (); i--;)
98 Grob *g = (*elements)[i];
99 Skyline_pair skylines;
103 Skyline_pair *skys = Skyline_pair::unsmob (g->get_property (a == Y_AXIS
104 ? "vertical-skylines"
105 : "horizontal-skylines"));
109 /* this is perhaps an abuse of minimum-?-extent: maybe we should create
110 another property? But it seems that the only (current) use of
111 minimum-Y-extent is to separate vertically-aligned elements */
112 SCM min_extent = g->get_property (a == X_AXIS
113 ? ly_symbol2scm ("minimum-X-extent")
114 : ly_symbol2scm ("minimum-Y-extent"));
116 if (is_number_pair (min_extent))
119 Interval other_extent = g->extent (other_common, other_axis (a));
120 b[a] = ly_scm2interval (min_extent);
121 b[other_axis (a)] = other_extent;
122 if (!other_extent.is_empty ())
123 skylines.insert (b, 0, other_axis (a));
126 /* This skyline was calculated relative to the grob g. In order to compare it to
127 skylines belonging to other grobs, we need to shift it so that it is relative
128 to the common reference. */
129 Real offset = g->relative_coordinate (other_common, other_axis (a));
130 skylines.shift (offset);
134 assert (a == Y_AXIS);
135 Interval extent = g->pure_height (g, start, end);
136 if (!extent.is_empty ())
140 b[other_axis (a)] = Interval (-infinity_f, infinity_f);
141 skylines.insert (b, 0, other_axis (a));
145 if (skylines.is_empty ())
146 elements->erase (elements->begin () + i);
148 ret->push_back (skylines);
154 Align_interface::get_extents_aligned_translates (Grob *me,
155 vector<Grob*> const &all_grobs,
157 bool pure, int start, int end)
159 Spanner *me_spanner = dynamic_cast<Spanner *> (me);
162 SCM line_break_details = SCM_EOL;
163 if (a == Y_AXIS && me_spanner)
166 line_break_details = get_root_system (me)->column (start)->get_property ("line-break-system-details");
168 line_break_details = me_spanner->get_bound (LEFT)->get_property ("line-break-system-details");
170 if (!me->get_system () && !pure)
171 me->programming_error ("vertical alignment called before line-breaking");
174 Direction stacking_dir = robust_scm2dir (me->get_property ("stacking-dir"),
177 vector<Grob*> elems (all_grobs); // writable copy
178 vector<Skyline_pair> skylines;
180 get_skylines (me, &elems, a, pure, start, end, &skylines);
183 /* TODO: extra-space stuff belongs to two-pass spacing. Delete me */
184 SCM extra_space_handle = scm_assq (ly_symbol2scm ("alignment-extra-space"), line_break_details);
185 Real extra_space = robust_scm2double (scm_is_pair (extra_space_handle)
186 ? scm_cdr (extra_space_handle)
190 Real padding = robust_scm2double (me->get_property ("padding"), 0.0);
191 vector<Real> translates;
192 Skyline down_skyline (stacking_dir);
193 for (vsize j = 0; j < elems.size (); j++)
197 dy = skylines[j][-stacking_dir].max_height ();
200 down_skyline.merge (skylines[j-1][stacking_dir]);
201 dy = down_skyline.distance (skylines[j][-stacking_dir]);
204 if (isinf (dy)) /* if the skyline is empty, maybe max_height is infinity_f */
207 dy = max (0.0, dy + padding + extra_space / elems.size ());
208 down_skyline.raise (-stacking_dir * dy);
209 where += stacking_dir * dy;
210 translates.push_back (where);
213 SCM offsets_handle = scm_assq (ly_symbol2scm ("alignment-offsets"),
215 if (scm_is_pair (offsets_handle))
219 for (SCM s = scm_cdr (offsets_handle);
220 scm_is_pair (s) && i < translates.size (); s = scm_cdr (s), i++)
222 if (scm_is_number (scm_car (s)))
223 translates[i] = scm_to_double (scm_car (s));
227 // So far, we've computed the translates for all the non-empty elements.
228 // Here, we set the translates for the empty elements: an empty element
229 // gets the same translation as the last non-empty element before it.
230 vector<Real> all_translates;
231 if (!translates.empty ())
233 Real w = translates[0];
234 for (vsize i = 0, j = 0; j < all_grobs.size (); j++)
236 if (i < elems.size () && all_grobs[j] == elems[i])
238 all_translates.push_back (w);
241 return all_translates;
245 Align_interface::align_elements_to_extents (Grob *me, Axis a)
247 extract_grob_set (me, "elements", all_grobs);
249 vector<Real> translates = get_extents_aligned_translates (me, all_grobs, a, false, 0, 0);
250 if (translates.size ())
251 for (vsize j = 0; j < all_grobs.size (); j++)
252 all_grobs[j]->translate_axis (translates[j], a);
255 /* After we have already determined the y-offsets of our children, we may still
256 want to stretch them a little. */
258 Align_interface::stretch (Grob *me, Real amount, Axis a)
260 extract_grob_set (me, "elements", elts);
261 Real non_empty_elts = stretchable_children_count (me);
263 Direction dir = robust_scm2dir (me->get_property ("stacking-dir"), DOWN);
264 for (vsize i = 1; i < elts.size (); i++)
266 if (!elts[i]->extent (me, a).is_empty ()
267 && !to_boolean (elts[i]->get_property ("keep-fixed-while-stretching")))
268 offset += amount / non_empty_elts;
269 elts[i]->translate_axis (dir * offset, a);
271 me->flush_extent_cache (Y_AXIS);
275 Align_interface::get_pure_child_y_translation (Grob *me, Grob *ch, int start, int end)
277 extract_grob_set (me, "elements", all_grobs);
278 SCM dy_scm = me->get_property ("forced-distance");
280 if (scm_is_number (dy_scm))
282 Real dy = scm_to_double (dy_scm) * robust_scm2dir (me->get_property ("stacking-dir"), DOWN);
284 for (vsize i = 0; i < all_grobs.size (); i++)
286 if (all_grobs[i] == ch)
288 if (!Hara_kiri_group_spanner::has_interface (all_grobs[i])
289 || !Hara_kiri_group_spanner::request_suicide (all_grobs[i], start, end))
295 vector<Real> translates = get_extents_aligned_translates (me, all_grobs, Y_AXIS, true, start, end);
297 if (translates.size ())
299 for (vsize i = 0; i < all_grobs.size (); i++)
300 if (all_grobs[i] == ch)
301 return translates[i];
307 programming_error (_ ("tried to get a translation for something that is no child of mine"));
312 Align_interface::axis (Grob *me)
314 return Axis (scm_to_int (scm_car (me->get_property ("axes"))));
318 Align_interface::add_element (Grob *me, Grob *element)
320 Axis a = Align_interface::axis (me);
321 SCM sym = axis_offset_symbol (a);
322 SCM proc = axis_parent_positioning (a);
324 element->set_property (sym, proc);
325 Axis_group_interface::add_element (me, element);
329 Align_interface::set_ordered (Grob *me)
331 SCM ga_scm = me->get_object ("elements");
332 Grob_array *ga = unsmob_grob_array (ga_scm);
335 ga_scm = Grob_array::make_array ();
336 ga = unsmob_grob_array (ga_scm);
337 me->set_object ("elements", ga_scm);
340 ga->set_ordered (true);
344 Align_interface::stretchable_children_count (Grob const *me)
346 extract_grob_set (me, "elements", elts);
349 /* start at 1: we will never move the first child while stretching */
350 for (vsize i = 1; i < elts.size (); i++)
351 if (!to_boolean (elts[i]->get_property ("keep-fixed-while-stretching"))
352 && !elts[i]->extent (elts[i], Y_AXIS).is_empty ())
358 MAKE_SCHEME_CALLBACK (Align_interface, calc_max_stretch, 1)
360 Align_interface::calc_max_stretch (SCM smob)
362 Grob *me = unsmob_grob (smob);
363 Spanner *spanner_me = dynamic_cast<Spanner*> (me);
366 if (spanner_me && stretchable_children_count (me) > 0)
368 Paper_column *left = dynamic_cast<Paper_column*> (spanner_me->get_bound (LEFT));
369 Real height = me->extent (me, Y_AXIS).length ();
370 SCM line_break_details = left->get_property ("line-break-system-details");
371 SCM fixed_offsets = scm_assq (ly_symbol2scm ("alignment-offsets"),
374 /* if there are fixed offsets, we refuse to stretch */
375 if (fixed_offsets != SCM_BOOL_F)
378 ret = height * height / 80.0; /* why this, exactly? -- jneem */
380 return scm_from_double (ret);
383 ADD_INTERFACE (Align_interface,
384 "Order grobs from top to bottom, left to right, right to left"
385 " or bottom to top. For vertical alignments of staves, the"
386 " @code{break-system-details} of the left"
387 " @rinternals{NonMusicalPaperColumn} may be set to tune"
388 " vertical spacing. Set @code{alignment-extra-space} to add"
389 " extra space for staves. Set"
390 " @code{fixed-alignment-extra-space} to force staves in"
391 " @code{PianoStaff}s further apart.",