Bump version.
[lilypond.git] / lily / align-interface.cc
blob27839aca920d46ce08dcce76b34271d064ac1168
1 /*
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>
7 */
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"
14 #include "item.hh"
15 #include "paper-column.hh"
16 #include "pointer-group-interface.hh"
17 #include "spanner.hh"
18 #include "skyline-pair.hh"
19 #include "system.hh"
20 #include "warn.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
26 distance.
29 MAKE_SCHEME_CALLBACK (Align_interface, calc_positioning_done, 1);
30 SCM
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);
42 return SCM_BOOL_T;
46 TODO: This belongs to the old two-pass spacing. Delete me.
48 MAKE_SCHEME_CALLBACK (Align_interface, stretch_after_break, 1)
49 SCM
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)
70 : SCM_EOL,
71 0.0);
73 Direction stacking_dir = robust_scm2dir (me->get_property ("stacking-dir"),
74 DOWN);
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 static void
88 get_skylines (Grob *me,
89 vector<Grob*> *const elements,
90 Axis a,
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;
101 if (!pure)
103 Skyline_pair *skys = Skyline_pair::unsmob (g->get_property (a == Y_AXIS
104 ? "vertical-skylines"
105 : "horizontal-skylines"));
106 if (skys)
107 skylines = *skys;
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))
118 Box b;
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);
132 else
134 assert (a == Y_AXIS);
135 Interval extent = g->pure_height (g, start, end);
136 if (!extent.is_empty ())
138 Box b;
139 b[a] = extent;
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);
147 else
148 ret->push_back (skylines);
150 reverse (*ret);
153 vector<Real>
154 Align_interface::get_extents_aligned_translates (Grob *me,
155 vector<Grob*> const &all_grobs,
156 Axis a,
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)
165 if (pure)
166 line_break_details = get_root_system (me)->column (start)->get_property ("line-break-system-details");
167 else
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"),
175 DOWN);
177 vector<Grob*> elems (all_grobs); // writable copy
178 vector<Skyline_pair> skylines;
180 get_skylines (me, &elems, a, pure, start, end, &skylines);
182 Real where = 0;
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)
187 : SCM_EOL,
188 0.0);
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++)
195 Real dy = 0;
196 if (j == 0)
197 dy = skylines[j][-stacking_dir].max_height ();
198 else
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 */
205 dy = 0.0;
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"),
214 line_break_details);
215 if (scm_is_pair (offsets_handle))
217 vsize i = 0;
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 vector<Real> all_translates;
229 if (!translates.empty ())
231 Real w = translates[0];
232 for (vsize i = 0, j = 0; j < all_grobs.size (); j++)
234 if (i < elems.size () && all_grobs[j] == elems[i])
235 w = translates[i++];
236 all_translates.push_back (w);
239 return all_translates;
242 void
243 Align_interface::align_elements_to_extents (Grob *me, Axis a)
245 extract_grob_set (me, "elements", all_grobs);
247 vector<Real> translates = get_extents_aligned_translates (me, all_grobs, a, false, 0, 0);
248 if (translates.size ())
249 for (vsize j = 0; j < all_grobs.size (); j++)
250 all_grobs[j]->translate_axis (translates[j], a);
253 /* After we have already determined the y-offsets of our children, we may still
254 want to stretch them a little. */
255 void
256 Align_interface::stretch (Grob *me, Real amount, Axis a)
258 extract_grob_set (me, "elements", elts);
259 Real non_empty_elts = stretchable_children_count (me);
260 Real offset = 0.0;
261 Direction dir = robust_scm2dir (me->get_property ("stacking-dir"), DOWN);
262 for (vsize i = 1; i < elts.size (); i++)
264 if (!elts[i]->extent (me, a).is_empty ()
265 && !to_boolean (elts[i]->get_property ("keep-fixed-while-stretching")))
266 offset += amount / non_empty_elts;
267 elts[i]->translate_axis (dir * offset, a);
269 me->flush_extent_cache (Y_AXIS);
272 Real
273 Align_interface::get_pure_child_y_translation (Grob *me, Grob *ch, int start, int end)
275 extract_grob_set (me, "elements", all_grobs);
276 SCM dy_scm = me->get_property ("forced-distance");
278 if (scm_is_number (dy_scm))
280 Real dy = scm_to_double (dy_scm) * robust_scm2dir (me->get_property ("stacking-dir"), DOWN);
281 Real pos = 0;
282 for (vsize i = 0; i < all_grobs.size (); i++)
284 if (all_grobs[i] == ch)
285 return pos;
286 if (!Hara_kiri_group_spanner::has_interface (all_grobs[i])
287 || !Hara_kiri_group_spanner::request_suicide (all_grobs[i], start, end))
288 pos += dy;
291 else
293 vector<Real> translates = get_extents_aligned_translates (me, all_grobs, Y_AXIS, true, start, end);
295 if (translates.size ())
297 for (vsize i = 0; i < all_grobs.size (); i++)
298 if (all_grobs[i] == ch)
299 return translates[i];
301 else
302 return 0;
305 programming_error (_ ("tried to get a translation for something that is no child of mine"));
306 return 0;
309 Axis
310 Align_interface::axis (Grob *me)
312 return Axis (scm_to_int (scm_car (me->get_property ("axes"))));
315 void
316 Align_interface::add_element (Grob *me, Grob *element)
318 Axis a = Align_interface::axis (me);
319 SCM sym = axis_offset_symbol (a);
320 SCM proc = axis_parent_positioning (a);
322 element->set_property (sym, proc);
323 Axis_group_interface::add_element (me, element);
326 void
327 Align_interface::set_ordered (Grob *me)
329 SCM ga_scm = me->get_object ("elements");
330 Grob_array *ga = unsmob_grob_array (ga_scm);
331 if (!ga)
333 ga_scm = Grob_array::make_array ();
334 ga = unsmob_grob_array (ga_scm);
335 me->set_object ("elements", ga_scm);
338 ga->set_ordered (true);
342 Align_interface::stretchable_children_count (Grob const *me)
344 extract_grob_set (me, "elements", elts);
345 int ret = 0;
347 /* start at 1: we will never move the first child while stretching */
348 for (vsize i = 1; i < elts.size (); i++)
349 if (!to_boolean (elts[i]->get_property ("keep-fixed-while-stretching"))
350 && !elts[i]->extent (elts[i], Y_AXIS).is_empty ())
351 ret++;
353 return ret;
356 MAKE_SCHEME_CALLBACK (Align_interface, calc_max_stretch, 1)
358 Align_interface::calc_max_stretch (SCM smob)
360 Grob *me = unsmob_grob (smob);
361 Spanner *spanner_me = dynamic_cast<Spanner*> (me);
362 Real ret = 0;
364 if (spanner_me && stretchable_children_count (me) > 0)
366 Paper_column *left = dynamic_cast<Paper_column*> (spanner_me->get_bound (LEFT));
367 Real height = me->extent (me, Y_AXIS).length ();
368 SCM line_break_details = left->get_property ("line-break-system-details");
369 SCM fixed_offsets = scm_assq (ly_symbol2scm ("alignment-offsets"),
370 line_break_details);
372 /* if there are fixed offsets, we refuse to stretch */
373 if (fixed_offsets != SCM_BOOL_F)
374 ret = 0;
375 else
376 ret = height * height / 80.0; /* why this, exactly? -- jneem */
378 return scm_from_double (ret);
381 ADD_INTERFACE (Align_interface,
382 "Order grobs from top to bottom, left to right, right to left"
383 " or bottom to top. For vertical alignments of staves, the"
384 " @code{break-system-details} of the left"
385 " @rinternals{NonMusicalPaperColumn} may be set to tune"
386 " vertical spacing. Set @code{alignment-extra-space} to add"
387 " extra space for staves. Set"
388 " @code{fixed-alignment-extra-space} to force staves in"
389 " @code{PianoStaff}s further apart.",
391 /* properties */
392 "align-dir "
393 "axes "
394 "elements "
395 "padding "
396 "positioning-done "
397 "stacking-dir "
398 "threshold "