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 "page-layout-problem.hh"
16 #include "paper-book.hh"
17 #include "paper-column.hh"
18 #include "pointer-group-interface.hh"
20 #include "skyline-pair.hh"
25 MAKE_SCHEME_CALLBACK (Align_interface, align_to_minimum_distances, 1);
27 Align_interface::align_to_minimum_distances (SCM smob)
29 Grob *me = unsmob_grob (smob);
31 me->set_property ("positioning-done", SCM_BOOL_T);
33 SCM axis = scm_car (me->get_property ("axes"));
34 Axis ax = Axis (scm_to_int (axis));
36 Align_interface::align_elements_to_minimum_distances (me, ax);
41 MAKE_SCHEME_CALLBACK (Align_interface, align_to_ideal_distances, 1);
43 Align_interface::align_to_ideal_distances (SCM smob)
45 Grob *me = unsmob_grob (smob);
47 me->set_property ("positioning-done", SCM_BOOL_T);
49 Align_interface::align_elements_to_ideal_distances (me);
54 /* for each grob, find its upper and lower skylines. If the grob has
55 an empty extent, delete it from the list instead. If the extent is
56 non-empty but there is no skyline available (or pure is true), just
57 create a flat skyline from the bounding box */
58 // TODO(jneem): the pure and non-pure parts seem to share very little
59 // code. Split them into 2 functions, perhaps?
61 get_skylines (Grob *me,
62 vector<Grob*> *const elements,
64 bool pure, int start, int end,
65 vector<Skyline_pair> *const ret)
67 Grob *other_common = common_refpoint_of_array (*elements, me, other_axis (a));
69 for (vsize i = elements->size (); i--;)
71 Grob *g = (*elements)[i];
72 Skyline_pair skylines;
76 Skyline_pair *skys = Skyline_pair::unsmob (g->get_property (a == Y_AXIS
78 : "horizontal-skylines"));
82 /* This skyline was calculated relative to the grob g. In order to compare it to
83 skylines belonging to other grobs, we need to shift it so that it is relative
84 to the common reference. */
85 Real offset = g->relative_coordinate (other_common, other_axis (a));
86 skylines.shift (offset);
91 Interval extent = g->pure_height (g, start, end);
92 if (!extent.is_empty ())
96 b[other_axis (a)] = Interval (0, infinity_f);
97 skylines.insert (b, 0, other_axis (a));
100 // This is a hack to get better accuracy on the pure-height of VerticalAlignment.
101 // It's quite common for a treble clef to be the highest element of one system
102 // and for a low note (or lyrics) to be the lowest note on another. The two will
103 // never collide, but the pure-height stuff only works with bounding boxes, so it
104 // doesn't know that. The result is a significant over-estimation of the pure-height,
105 // especially on systems with many staves. To correct for this, we build a skyline
106 // in two parts: the part we did above contains most of the grobs (note-heads, etc.)
107 // while the bit we're about to do only contains the breakable grobs at the beginning
108 // of the system. This way, the tall treble clefs are only compared with the treble
109 // clefs of the other staff and they will be ignored if the staff above is, for example,
111 if (Axis_group_interface::has_interface (g)
112 && !Hara_kiri_group_spanner::request_suicide (g, start, end))
114 Interval begin_of_line_extent = Axis_group_interface::begin_of_line_pure_height (g, start);
115 if (!begin_of_line_extent.is_empty ())
118 b[a] = begin_of_line_extent;
119 b[other_axis (a)] = Interval (-infinity_f, -1);
120 skylines.insert (b, 0, other_axis (a));
125 if (skylines.is_empty ())
126 elements->erase (elements->begin () + i);
128 ret->push_back (skylines);
134 Align_interface::get_minimum_translations (Grob *me,
135 vector<Grob*> const &all_grobs,
137 bool pure, int start, int end)
139 if (!pure && a == Y_AXIS && dynamic_cast<Spanner*> (me) && !me->get_system ())
140 me->programming_error ("vertical alignment called before line-breaking");
142 Direction stacking_dir = robust_scm2dir (me->get_property ("stacking-dir"),
144 vector<Grob*> elems (all_grobs); // writable copy
145 vector<Skyline_pair> skylines;
147 get_skylines (me, &elems, a, pure, start, end, &skylines);
149 SCM forced_distances = ly_assoc_get (ly_symbol2scm ("alignment-distances"),
150 Page_layout_problem::get_details (me),
154 Real default_padding = robust_scm2double (me->get_property ("padding"), 0.0);
155 vector<Real> translates;
156 Skyline down_skyline (stacking_dir);
157 Real last_spaceable_element_pos = 0;
158 Grob *last_spaceable_element = 0;
159 for (vsize j = 0; j < elems.size (); j++)
162 Real padding = default_padding;
165 dy = skylines[j][-stacking_dir].max_height ();
168 down_skyline.merge (skylines[j-1][stacking_dir]);
169 dy = down_skyline.distance (skylines[j][-stacking_dir]);
171 SCM spec = Page_layout_problem::get_spacing_spec (elems[j-1], elems[j]);
172 Page_layout_problem::read_spacing_spec (spec, &padding, ly_symbol2scm ("padding"));
174 Real min_distance = 0;
175 if (Page_layout_problem::read_spacing_spec (spec, &min_distance, ly_symbol2scm ("minimum-distance")))
176 dy = max (dy, min_distance);
178 if (Page_layout_problem::is_spaceable (elems[j]) && last_spaceable_element)
180 // Spaceable staves may have min-distance and padding
181 // constraints coming from the previous spaceable staff
182 // as well as from the previous staff.
183 spec = Page_layout_problem::get_spacing_spec (last_spaceable_element, elems[j]);
184 Real spaceable_padding = 0;
185 Page_layout_problem::read_spacing_spec (spec,
187 ly_symbol2scm ("padding"));
188 padding = max (padding, spaceable_padding);
190 Real min_distance = 0;
191 if (Page_layout_problem::read_spacing_spec (spec,
193 ly_symbol2scm ("minimum-distance")))
194 dy = max (dy, min_distance + stacking_dir*(last_spaceable_element_pos - where));
196 if (scm_is_pair (forced_distances))
198 SCM forced_dist = scm_car (forced_distances);
199 forced_distances = scm_cdr (forced_distances);
201 if (scm_is_number (forced_dist))
202 dy = scm_to_double (forced_dist) + stacking_dir * (last_spaceable_element_pos - where);
207 if (isinf (dy)) /* if the skyline is empty, maybe max_height is infinity_f */
210 dy = max (0.0, dy + padding);
211 down_skyline.raise (-stacking_dir * dy);
212 where += stacking_dir * dy;
213 translates.push_back (where);
215 if (Page_layout_problem::is_spaceable (elems[j]))
217 last_spaceable_element = elems[j];
218 last_spaceable_element_pos = where;
222 // So far, we've computed the translates for all the non-empty elements.
223 // Here, we set the translates for the empty elements: an empty element
224 // gets the same translation as the last non-empty element before it.
225 vector<Real> all_translates;
226 if (!translates.empty ())
228 Real w = translates[0];
229 for (vsize i = 0, j = 0; j < all_grobs.size (); j++)
231 if (i < elems.size () && all_grobs[j] == elems[i])
233 all_translates.push_back (w);
236 return all_translates;
240 Align_interface::align_elements_to_ideal_distances (Grob *me)
242 System *sys = me->get_system ();
243 Page_layout_problem layout (NULL, SCM_EOL, scm_list_1 (sys->self_scm ()));
245 layout.solution (true);
249 Align_interface::align_elements_to_minimum_distances (Grob *me, Axis a)
251 extract_grob_set (me, "elements", all_grobs);
253 vector<Real> translates = get_minimum_translations (me, all_grobs, a, false, 0, 0);
254 if (translates.size ())
255 for (vsize j = 0; j < all_grobs.size (); j++)
256 all_grobs[j]->translate_axis (translates[j], a);
260 Align_interface::get_pure_child_y_translation (Grob *me, Grob *ch, int start, int end)
262 extract_grob_set (me, "elements", all_grobs);
263 SCM dy_scm = me->get_property ("forced-distance");
265 if (scm_is_number (dy_scm))
267 Real dy = scm_to_double (dy_scm) * robust_scm2dir (me->get_property ("stacking-dir"), DOWN);
269 for (vsize i = 0; i < all_grobs.size (); i++)
271 if (all_grobs[i] == ch)
273 if (!Hara_kiri_group_spanner::has_interface (all_grobs[i])
274 || !Hara_kiri_group_spanner::request_suicide (all_grobs[i], start, end))
280 vector<Real> translates = get_minimum_translations (me, all_grobs, Y_AXIS, true, start, end);
282 if (translates.size ())
284 for (vsize i = 0; i < all_grobs.size (); i++)
285 if (all_grobs[i] == ch)
286 return translates[i];
292 programming_error ("tried to get a translation for something that is no child of mine");
297 Align_interface::axis (Grob *me)
299 return Axis (scm_to_int (scm_car (me->get_property ("axes"))));
303 Align_interface::add_element (Grob *me, Grob *element)
305 Axis a = Align_interface::axis (me);
306 SCM sym = axis_offset_symbol (a);
307 SCM proc = axis_parent_positioning (a);
309 element->set_property (sym, proc);
310 Axis_group_interface::add_element (me, element);
314 Align_interface::set_ordered (Grob *me)
316 SCM ga_scm = me->get_object ("elements");
317 Grob_array *ga = unsmob_grob_array (ga_scm);
320 ga_scm = Grob_array::make_array ();
321 ga = unsmob_grob_array (ga_scm);
322 me->set_object ("elements", ga_scm);
325 ga->set_ordered (true);
328 ADD_INTERFACE (Align_interface,
329 "Order grobs from top to bottom, left to right, right to left"
330 " or bottom to top. For vertical alignments of staves, the"
331 " @code{break-system-details} of the left"
332 " @rinternals{NonMusicalPaperColumn} may be set to tune"
333 " vertical spacing. Set @code{alignment-extra-space} to add"
334 " extra space for staves. Set"
335 " @code{fixed-alignment-extra-space} to force staves in"
336 " @code{PianoStaff}s further apart.",