2 This file is part of LilyPond, the GNU music typesetter.
4 Copyright (C) 2000--2009 Han-Wen Nienhuys <hanwen@xs4all.nl>
6 LilyPond is free software: you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation, either version 3 of the License, or
9 (at your option) any later version.
11 LilyPond is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with LilyPond. If not, see <http://www.gnu.org/licenses/>.
20 #include "align-interface.hh"
21 #include "axis-group-interface.hh"
22 #include "grob-array.hh"
23 #include "hara-kiri-group-spanner.hh"
24 #include "international.hh"
26 #include "page-layout-problem.hh"
27 #include "paper-book.hh"
28 #include "paper-column.hh"
29 #include "pointer-group-interface.hh"
31 #include "skyline-pair.hh"
36 MAKE_SCHEME_CALLBACK (Align_interface, align_to_minimum_distances, 1);
38 Align_interface::align_to_minimum_distances (SCM smob)
40 Grob *me = unsmob_grob (smob);
42 me->set_property ("positioning-done", SCM_BOOL_T);
44 SCM axis = scm_car (me->get_property ("axes"));
45 Axis ax = Axis (scm_to_int (axis));
47 Align_interface::align_elements_to_minimum_distances (me, ax);
52 MAKE_SCHEME_CALLBACK (Align_interface, align_to_ideal_distances, 1);
54 Align_interface::align_to_ideal_distances (SCM smob)
56 Grob *me = unsmob_grob (smob);
58 me->set_property ("positioning-done", SCM_BOOL_T);
60 Align_interface::align_elements_to_ideal_distances (me);
65 /* for each grob, find its upper and lower skylines. If the grob has
66 an empty extent, delete it from the list instead. If the extent is
67 non-empty but there is no skyline available (or pure is true), just
68 create a flat skyline from the bounding box */
69 // TODO(jneem): the pure and non-pure parts seem to share very little
70 // code. Split them into 2 functions, perhaps?
72 get_skylines (Grob *me,
73 vector<Grob*> *const elements,
75 bool pure, int start, int end,
76 vector<Skyline_pair> *const ret)
78 Grob *other_common = common_refpoint_of_array (*elements, me, other_axis (a));
80 for (vsize i = elements->size (); i--;)
82 Grob *g = (*elements)[i];
83 Skyline_pair skylines;
87 Skyline_pair *skys = Skyline_pair::unsmob (g->get_property (a == Y_AXIS
89 : "horizontal-skylines"));
93 /* This skyline was calculated relative to the grob g. In order to compare it to
94 skylines belonging to other grobs, we need to shift it so that it is relative
95 to the common reference. */
96 Real offset = g->relative_coordinate (other_common, other_axis (a));
97 skylines.shift (offset);
101 assert (a == Y_AXIS);
102 Interval extent = g->pure_height (g, start, end);
104 // This is a hack to get better accuracy on the pure-height of VerticalAlignment.
105 // It's quite common for a treble clef to be the highest element of one system
106 // and for a low note (or lyrics) to be the lowest note on another. The two will
107 // never collide, but the pure-height stuff only works with bounding boxes, so it
108 // doesn't know that. The result is a significant over-estimation of the pure-height,
109 // especially on systems with many staves. To correct for this, we build a skyline
110 // in two parts: the part we did above contains most of the grobs (note-heads, etc.)
111 // while the bit we're about to do only contains the breakable grobs at the beginning
112 // of the system. This way, the tall treble clefs are only compared with the treble
113 // clefs of the other staff and they will be ignored if the staff above is, for example,
115 if (Axis_group_interface::has_interface (g)
116 && !Hara_kiri_group_spanner::request_suicide (g, start, end))
118 extent = Axis_group_interface::rest_of_line_pure_height (g, start, end);
119 Interval begin_of_line_extent = Axis_group_interface::begin_of_line_pure_height (g, start);
120 if (!begin_of_line_extent.is_empty ())
123 b[a] = begin_of_line_extent;
124 b[other_axis (a)] = Interval (-infinity_f, -1);
125 skylines.insert (b, 0, other_axis (a));
129 if (!extent.is_empty ())
133 b[other_axis (a)] = Interval (0, infinity_f);
134 skylines.insert (b, 0, other_axis (a));
138 if (skylines.is_empty ())
139 elements->erase (elements->begin () + i);
141 ret->push_back (skylines);
147 Align_interface::get_minimum_translations (Grob *me,
148 vector<Grob*> const &all_grobs,
150 bool pure, int start, int end)
152 if (!pure && a == Y_AXIS && dynamic_cast<Spanner*> (me) && !me->get_system ())
153 me->programming_error ("vertical alignment called before line-breaking");
155 Direction stacking_dir = robust_scm2dir (me->get_property ("stacking-dir"),
157 vector<Grob*> elems (all_grobs); // writable copy
158 vector<Skyline_pair> skylines;
160 get_skylines (me, &elems, a, pure, start, end, &skylines);
162 SCM forced_distances = ly_assoc_get (ly_symbol2scm ("alignment-distances"),
163 Page_layout_problem::get_details (me),
167 Real default_padding = robust_scm2double (me->get_property ("padding"), 0.0);
168 vector<Real> translates;
169 Skyline down_skyline (stacking_dir);
170 Real last_spaceable_element_pos = 0;
171 Grob *last_spaceable_element = 0;
172 for (vsize j = 0; j < elems.size (); j++)
175 Real padding = default_padding;
178 dy = skylines[j][-stacking_dir].max_height ();
181 down_skyline.merge (skylines[j-1][stacking_dir]);
182 dy = down_skyline.distance (skylines[j][-stacking_dir]);
184 SCM spec = Page_layout_problem::get_spacing_spec (elems[j-1], elems[j]);
185 Page_layout_problem::read_spacing_spec (spec, &padding, ly_symbol2scm ("padding"));
187 Real min_distance = 0;
188 if (Page_layout_problem::read_spacing_spec (spec, &min_distance, ly_symbol2scm ("minimum-distance")))
189 dy = max (dy, min_distance);
191 if (Page_layout_problem::is_spaceable (elems[j]) && last_spaceable_element)
193 // Spaceable staves may have min-distance and padding
194 // constraints coming from the previous spaceable staff
195 // as well as from the previous staff.
196 spec = Page_layout_problem::get_spacing_spec (last_spaceable_element, elems[j]);
197 Real spaceable_padding = 0;
198 Page_layout_problem::read_spacing_spec (spec,
200 ly_symbol2scm ("padding"));
201 padding = max (padding, spaceable_padding);
203 Real min_distance = 0;
204 if (Page_layout_problem::read_spacing_spec (spec,
206 ly_symbol2scm ("minimum-distance")))
207 dy = max (dy, min_distance + stacking_dir*(last_spaceable_element_pos - where));
209 if (scm_is_pair (forced_distances))
211 SCM forced_dist = scm_car (forced_distances);
212 forced_distances = scm_cdr (forced_distances);
214 if (scm_is_number (forced_dist))
215 dy = scm_to_double (forced_dist) + stacking_dir * (last_spaceable_element_pos - where);
220 if (isinf (dy)) /* if the skyline is empty, maybe max_height is infinity_f */
223 dy = max (0.0, dy + padding);
224 down_skyline.raise (-stacking_dir * dy);
225 where += stacking_dir * dy;
226 translates.push_back (where);
228 if (Page_layout_problem::is_spaceable (elems[j]))
230 last_spaceable_element = elems[j];
231 last_spaceable_element_pos = where;
235 // So far, we've computed the translates for all the non-empty elements.
236 // Here, we set the translates for the empty elements: an empty element
237 // gets the same translation as the last non-empty element before it.
238 vector<Real> all_translates;
239 if (!translates.empty ())
241 Real w = translates[0];
242 for (vsize i = 0, j = 0; j < all_grobs.size (); j++)
244 if (i < elems.size () && all_grobs[j] == elems[i])
246 all_translates.push_back (w);
249 return all_translates;
253 Align_interface::align_elements_to_ideal_distances (Grob *me)
255 System *sys = me->get_system ();
256 Page_layout_problem layout (NULL, SCM_EOL, scm_list_1 (sys->self_scm ()));
258 layout.solution (true);
262 Align_interface::align_elements_to_minimum_distances (Grob *me, Axis a)
264 extract_grob_set (me, "elements", all_grobs);
266 vector<Real> translates = get_minimum_translations (me, all_grobs, a, false, 0, 0);
267 if (translates.size ())
268 for (vsize j = 0; j < all_grobs.size (); j++)
269 all_grobs[j]->translate_axis (translates[j], a);
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);
282 for (vsize i = 0; i < all_grobs.size (); i++)
284 if (all_grobs[i] == ch)
286 if (!Hara_kiri_group_spanner::has_interface (all_grobs[i])
287 || !Hara_kiri_group_spanner::request_suicide (all_grobs[i], start, end))
293 vector<Real> translates = get_minimum_translations (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];
305 programming_error ("tried to get a translation for something that is no child of mine");
310 Align_interface::axis (Grob *me)
312 return Axis (scm_to_int (scm_car (me->get_property ("axes"))));
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);
327 Align_interface::set_ordered (Grob *me)
329 SCM ga_scm = me->get_object ("elements");
330 Grob_array *ga = unsmob_grob_array (ga_scm);
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);
341 ADD_INTERFACE (Align_interface,
342 "Order grobs from top to bottom, left to right, right to left"
343 " or bottom to top. For vertical alignments of staves, the"
344 " @code{break-system-details} of the left"
345 " @rinternals{NonMusicalPaperColumn} may be set to tune"
346 " vertical spacing. Set @code{alignment-extra-space} to add"
347 " extra space for staves. Set"
348 " @code{fixed-alignment-extra-space} to force staves in"
349 " @code{PianoStaff}s further apart.",