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 SCM last_spaceable_element_details = SCM_EOL;
158 Real last_spaceable_element_pos = 0;
159 bool found_spaceable_element = false;
160 for (vsize j = 0; j < elems.size (); j++)
163 Real padding = default_padding;
166 dy = skylines[j][-stacking_dir].max_height ();
169 down_skyline.merge (skylines[j-1][stacking_dir]);
170 dy = down_skyline.distance (skylines[j][-stacking_dir]);
173 if (isinf (dy)) /* if the skyline is empty, maybe max_height is infinity_f */
176 if (Page_layout_problem::is_spaceable (elems[j]))
178 Real min_distance = 0;
179 Page_layout_problem::read_spacing_spec (last_spaceable_element_details,
181 ly_symbol2scm ("padding"));
182 if (Page_layout_problem::read_spacing_spec (last_spaceable_element_details,
184 ly_symbol2scm ("minimum-distance")))
185 dy = max (dy, min_distance + stacking_dir*(last_spaceable_element_pos - where));
187 if (found_spaceable_element && scm_is_pair (forced_distances))
189 SCM forced_dist = scm_car (forced_distances);
190 forced_distances = scm_cdr (forced_distances);
192 if (scm_is_number (forced_dist))
193 dy = scm_to_double (forced_dist) + stacking_dir * (last_spaceable_element_pos - where);
195 last_spaceable_element_details = elems[j]->get_property ("next-staff-padding");
196 found_spaceable_element = true;
200 // TODO: provide support for min-distance and padding for non-spaceable elements also.
203 dy = max (0.0, dy + padding);
204 down_skyline.raise (-stacking_dir * dy);
205 where += stacking_dir * dy;
206 translates.push_back (where);
208 if (Page_layout_problem::is_spaceable (elems[j]))
209 last_spaceable_element_pos = where;
212 // So far, we've computed the translates for all the non-empty elements.
213 // Here, we set the translates for the empty elements: an empty element
214 // gets the same translation as the last non-empty element before it.
215 vector<Real> all_translates;
216 if (!translates.empty ())
218 Real w = translates[0];
219 for (vsize i = 0, j = 0; j < all_grobs.size (); j++)
221 if (i < elems.size () && all_grobs[j] == elems[i])
223 all_translates.push_back (w);
226 return all_translates;
230 Align_interface::align_elements_to_ideal_distances (Grob *me)
232 System *sys = me->get_system ();
233 Page_layout_problem layout (NULL, SCM_EOL, scm_list_1 (sys->self_scm ()));
235 layout.solution (true);
239 Align_interface::align_elements_to_minimum_distances (Grob *me, Axis a)
241 extract_grob_set (me, "elements", all_grobs);
243 vector<Real> translates = get_minimum_translations (me, all_grobs, a, false, 0, 0);
244 if (translates.size ())
245 for (vsize j = 0; j < all_grobs.size (); j++)
246 all_grobs[j]->translate_axis (translates[j], a);
250 Align_interface::get_pure_child_y_translation (Grob *me, Grob *ch, int start, int end)
252 extract_grob_set (me, "elements", all_grobs);
253 SCM dy_scm = me->get_property ("forced-distance");
255 if (scm_is_number (dy_scm))
257 Real dy = scm_to_double (dy_scm) * robust_scm2dir (me->get_property ("stacking-dir"), DOWN);
259 for (vsize i = 0; i < all_grobs.size (); i++)
261 if (all_grobs[i] == ch)
263 if (!Hara_kiri_group_spanner::has_interface (all_grobs[i])
264 || !Hara_kiri_group_spanner::request_suicide (all_grobs[i], start, end))
270 vector<Real> translates = get_minimum_translations (me, all_grobs, Y_AXIS, true, start, end);
272 if (translates.size ())
274 for (vsize i = 0; i < all_grobs.size (); i++)
275 if (all_grobs[i] == ch)
276 return translates[i];
282 programming_error ("tried to get a translation for something that is no child of mine");
287 Align_interface::axis (Grob *me)
289 return Axis (scm_to_int (scm_car (me->get_property ("axes"))));
293 Align_interface::add_element (Grob *me, Grob *element)
295 Axis a = Align_interface::axis (me);
296 SCM sym = axis_offset_symbol (a);
297 SCM proc = axis_parent_positioning (a);
299 element->set_property (sym, proc);
300 Axis_group_interface::add_element (me, element);
304 Align_interface::set_ordered (Grob *me)
306 SCM ga_scm = me->get_object ("elements");
307 Grob_array *ga = unsmob_grob_array (ga_scm);
310 ga_scm = Grob_array::make_array ();
311 ga = unsmob_grob_array (ga_scm);
312 me->set_object ("elements", ga_scm);
315 ga->set_ordered (true);
318 ADD_INTERFACE (Align_interface,
319 "Order grobs from top to bottom, left to right, right to left"
320 " or bottom to top. For vertical alignments of staves, the"
321 " @code{break-system-details} of the left"
322 " @rinternals{NonMusicalPaperColumn} may be set to tune"
323 " vertical spacing. Set @code{alignment-extra-space} to add"
324 " extra space for staves. Set"
325 " @code{fixed-alignment-extra-space} to force staves in"
326 " @code{PianoStaff}s further apart.",