]> git.donarmstrong.com Git - lilypond.git/blob - lily/align-interface.cc
Better approximations for cross-staff slurs
[lilypond.git] / lily / align-interface.cc
1 /*
2   This file is part of LilyPond, the GNU music typesetter.
3
4   Copyright (C) 2000--2012 Han-Wen Nienhuys <hanwen@xs4all.nl>
5
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.
10
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.
15
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/>.
18 */
19
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"
25 #include "item.hh"
26 #include "page-layout-problem.hh"
27 #include "paper-book.hh"
28 #include "paper-column.hh"
29 #include "pointer-group-interface.hh"
30 #include "spanner.hh"
31 #include "skyline-pair.hh"
32 #include "system.hh"
33 #include "warn.hh"
34
35 MAKE_SCHEME_CALLBACK (Align_interface, align_to_minimum_distances, 1);
36 SCM
37 Align_interface::align_to_minimum_distances (SCM smob)
38 {
39   Grob *me = unsmob_grob (smob);
40
41   me->set_property ("positioning-done", SCM_BOOL_T);
42
43   SCM axis = scm_car (me->get_property ("axes"));
44   Axis ax = Axis (scm_to_int (axis));
45
46   Align_interface::align_elements_to_minimum_distances (me, ax);
47
48   return SCM_BOOL_T;
49 }
50
51 MAKE_SCHEME_CALLBACK (Align_interface, align_to_ideal_distances, 1);
52 SCM
53 Align_interface::align_to_ideal_distances (SCM smob)
54 {
55   Grob *me = unsmob_grob (smob);
56
57   me->set_property ("positioning-done", SCM_BOOL_T);
58
59   Align_interface::align_elements_to_ideal_distances (me);
60
61   return SCM_BOOL_T;
62 }
63
64 /* for each grob, find its upper and lower skylines. If the grob has
65    an empty extent, delete it from the list instead. If the extent is
66    non-empty but there is no skyline available (or pure is true), just
67    create a flat skyline from the bounding box */
68 // TODO(jneem): the pure and non-pure parts seem to share very little
69 // code. Split them into 2 functions, perhaps?
70 static void
71 get_skylines (Grob *me,
72               vector<Grob *> *const elements,
73               Axis a,
74               bool pure, int start, int end,
75               vector<Skyline_pair> *const ret)
76 {
77   Grob *other_common = common_refpoint_of_array (*elements, me, other_axis (a));
78
79   for (vsize i = elements->size (); i--;)
80     {
81       Grob *g = (*elements)[i];
82       Skyline_pair skylines;
83
84       if (!pure)
85         {
86           Skyline_pair *skys = a == Y_AXIS
87                                ? Skyline_pair::unsmob (g->get_property ("vertical-skylines"))
88                                : Skyline_pair::unsmob (g->get_property ("horizontal-skylines"));
89
90           if (skys)
91             skylines = *skys;
92
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);
98         }
99       else
100         {
101           assert (a == Y_AXIS);
102           Interval extent = g->pure_height (g, start, end);
103
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,
114           // lyrics.
115           if (Axis_group_interface::has_interface (g)
116               && !Hara_kiri_group_spanner::request_suicide (g, start, end))
117             {
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 ())
121                 {
122                   Box b;
123                   b[a] = begin_of_line_extent;
124                   b[other_axis (a)] = Interval (-infinity_f, -1);
125                   skylines.insert (b, other_axis (a));
126                 }
127             }
128
129           if (!extent.is_empty ())
130             {
131               Box b;
132               b[a] = extent;
133               b[other_axis (a)] = Interval (0, infinity_f);
134               skylines.insert (b, other_axis (a));
135             }
136         }
137
138       if (skylines.is_empty ())
139         elements->erase (elements->begin () + i);
140       else
141         ret->push_back (skylines);
142     }
143   reverse (*ret);
144 }
145
146 vector<Real>
147 Align_interface::get_minimum_translations (Grob *me,
148                                            vector<Grob *> const &all_grobs,
149                                            Axis a)
150 {
151   return internal_get_minimum_translations (me, all_grobs, a, true, false, 0, 0);
152 }
153
154 vector<Real>
155 Align_interface::get_pure_minimum_translations (Grob *me,
156                                                 vector<Grob *> const &all_grobs,
157                                                 Axis a, int start, int end)
158 {
159   return internal_get_minimum_translations (me, all_grobs, a, true, true, start, end);
160 }
161
162 vector<Real>
163 Align_interface::get_minimum_translations_without_min_dist (Grob *me,
164                                                             vector<Grob *> const &all_grobs,
165                                                             Axis a)
166 {
167   return internal_get_minimum_translations (me, all_grobs, a, false, false, 0, 0);
168 }
169
170 // If include_fixed_spacing is false, the only constraints that will be measured
171 // here are those that result from collisions (+ padding) and the spacing spec
172 // between adjacent staves.
173 // If include_fixed_spacing is true, constraints from line-break-system-details,
174 // basic-distance+stretchable=0, and staff-staff-spacing of spaceable staves
175 // with loose lines in between, are included as well.
176 // - If you want to find the minimum height of a system, include_fixed_spacing should be true.
177 // - If you're going to actually lay out the page, then it should be false (or
178 //   else centered dynamics will break when there is a fixed alignment).
179 vector<Real>
180 Align_interface::internal_get_minimum_translations (Grob *me,
181                                                     vector<Grob *> const &all_grobs,
182                                                     Axis a,
183                                                     bool include_fixed_spacing,
184                                                     bool pure, int start, int end)
185 {
186   if (!pure && a == Y_AXIS && dynamic_cast<Spanner *> (me) && !me->get_system ())
187     me->programming_error ("vertical alignment called before line-breaking");
188
189   // check the cache
190   if (pure)
191     {
192       SCM fv = ly_assoc_get (scm_cons (scm_from_int (start), scm_from_int (end)),
193                              me->get_property ("minimum-translations-alist"),
194                              SCM_EOL);
195       if (fv != SCM_EOL)
196         return ly_scm2floatvector (fv);
197     }
198
199   // If include_fixed_spacing is true, we look at things like system-system-spacing
200   // and alignment-distances, which only make sense for the toplevel VerticalAlignment.
201   // If we aren't toplevel, we're working on something like BassFigureAlignment
202   // and so we definitely don't want to include alignment-distances!
203   if (!dynamic_cast<System *> (me->get_parent (Y_AXIS)))
204     include_fixed_spacing = false;
205
206   Direction stacking_dir = robust_scm2dir (me->get_property ("stacking-dir"),
207                                            DOWN);
208   vector<Grob *> elems (all_grobs); // writable copy
209   vector<Skyline_pair> skylines;
210
211   get_skylines (me, &elems, a, pure, start, end, &skylines);
212
213   Real where = 0;
214   Real default_padding = robust_scm2double (me->get_property ("padding"), 0.0);
215   vector<Real> translates;
216   Skyline down_skyline (stacking_dir);
217   Real last_spaceable_element_pos = 0;
218   Grob *last_spaceable_element = 0;
219   Skyline last_spaceable_skyline (stacking_dir);
220   int spaceable_count = 0;
221   for (vsize j = 0; j < elems.size (); j++)
222     {
223       Real dy = 0;
224       Real padding = default_padding;
225
226       if (j == 0)
227         dy = skylines[j][-stacking_dir].max_height () + padding;
228       else
229         {
230           SCM spec = Page_layout_problem::get_spacing_spec (elems[j - 1], elems[j], pure, start, end);
231           Page_layout_problem::read_spacing_spec (spec, &padding, ly_symbol2scm ("padding"));
232
233           dy = down_skyline.distance (skylines[j][-stacking_dir]) + padding;
234
235           Real spec_distance = 0;
236           if (Page_layout_problem::read_spacing_spec (spec, &spec_distance, ly_symbol2scm ("minimum-distance")))
237             dy = max (dy, spec_distance);
238           // Consider the likely final spacing when estimating distance between staves of the full score
239           if (INT_MAX == end && 0 == start
240               && Page_layout_problem::read_spacing_spec (spec, &spec_distance, ly_symbol2scm ("basic-distance")))
241             dy = max (dy, spec_distance);
242
243           if (include_fixed_spacing && Page_layout_problem::is_spaceable (elems[j]) && last_spaceable_element)
244             {
245               // Spaceable staves may have
246               // constraints coming from the previous spaceable staff
247               // as well as from the previous staff.
248               spec = Page_layout_problem::get_spacing_spec (last_spaceable_element, elems[j], pure, start, end);
249               Real spaceable_padding = 0;
250               Page_layout_problem::read_spacing_spec (spec,
251                                                       &spaceable_padding,
252                                                       ly_symbol2scm ("padding"));
253               dy = max (dy, (last_spaceable_skyline.distance (skylines[j][-stacking_dir])
254                              + stacking_dir * (last_spaceable_element_pos - where) + spaceable_padding));
255
256               Real spaceable_min_distance = 0;
257               if (Page_layout_problem::read_spacing_spec (spec,
258                                                           &spaceable_min_distance,
259                                                           ly_symbol2scm ("minimum-distance")))
260                 dy = max (dy, spaceable_min_distance + stacking_dir * (last_spaceable_element_pos - where));
261
262               dy = max (dy, Page_layout_problem::get_fixed_spacing (last_spaceable_element, elems[j], spaceable_count,
263                                                                     pure, start, end));
264             }
265         }
266
267       if (isinf (dy)) /* if the skyline is empty, maybe max_height is infinity_f */
268         dy = 0.0;
269
270       dy = max (0.0, dy);
271       down_skyline.raise (-stacking_dir * dy);
272       down_skyline.merge (skylines[j][stacking_dir]);
273       where += stacking_dir * dy;
274       translates.push_back (where);
275
276       if (Page_layout_problem::is_spaceable (elems[j]))
277         {
278           spaceable_count++;
279           last_spaceable_element = elems[j];
280           last_spaceable_element_pos = where;
281           last_spaceable_skyline = down_skyline;
282         }
283     }
284
285   // So far, we've computed the translates for all the non-empty elements.
286   // Here, we set the translates for the empty elements: an empty element
287   // gets the same translation as the last non-empty element before it.
288   vector<Real> all_translates;
289   if (!translates.empty ())
290     {
291       Real w = translates[0];
292       for (vsize i = 0, j = 0; j < all_grobs.size (); j++)
293         {
294           if (i < elems.size () && all_grobs[j] == elems[i])
295             w = translates[i++];
296           all_translates.push_back (w);
297         }
298     }
299
300   if (pure)
301     {
302       SCM mta = me->get_property ("minimum-translations-alist");
303       mta = scm_cons (scm_cons (scm_cons (scm_from_int (start), scm_from_int (end)),
304                                 ly_floatvector2scm (all_translates)),
305                       mta);
306       me->set_property ("minimum-translations-alist", mta);
307     }
308   return all_translates;
309 }
310
311 void
312 Align_interface::align_elements_to_ideal_distances (Grob *me)
313 {
314   System *sys = me->get_system ();
315   if (sys)
316     {
317       Page_layout_problem layout (NULL, SCM_EOL, scm_list_1 (sys->self_scm ()));
318       layout.solution (true);
319     }
320   else
321     programming_error ("vertical alignment called before line breaking");
322 }
323
324 void
325 Align_interface::align_elements_to_minimum_distances (Grob *me, Axis a)
326 {
327   extract_grob_set (me, "elements", all_grobs);
328
329   vector<Real> translates = get_minimum_translations (me, all_grobs, a);
330   if (translates.size ())
331     for (vsize j = 0; j < all_grobs.size (); j++)
332       all_grobs[j]->translate_axis (translates[j], a);
333 }
334
335 Real
336 Align_interface::get_pure_child_y_translation (Grob *me, Grob *ch, int start, int end)
337 {
338   extract_grob_set (me, "elements", all_grobs);
339   vector<Real> translates = get_pure_minimum_translations (me, all_grobs, Y_AXIS, start, end);
340
341   if (translates.size ())
342     {
343       for (vsize i = 0; i < all_grobs.size (); i++)
344         if (all_grobs[i] == ch)
345           return translates[i];
346     }
347   else
348     return 0;
349
350   programming_error ("tried to get a translation for something that is no child of mine");
351   return 0;
352 }
353
354 Axis
355 Align_interface::axis (Grob *me)
356 {
357   return Axis (scm_to_int (scm_car (me->get_property ("axes"))));
358 }
359
360 void
361 Align_interface::add_element (Grob *me, Grob *element)
362 {
363   Axis a = Align_interface::axis (me);
364   SCM sym = axis_offset_symbol (a);
365   SCM proc = axis_parent_positioning (a);
366
367   element->set_property (sym, proc);
368   Axis_group_interface::add_element (me, element);
369 }
370
371 void
372 Align_interface::set_ordered (Grob *me)
373 {
374   SCM ga_scm = me->get_object ("elements");
375   Grob_array *ga = unsmob_grob_array (ga_scm);
376   if (!ga)
377     {
378       ga_scm = Grob_array::make_array ();
379       ga = unsmob_grob_array (ga_scm);
380       me->set_object ("elements", ga_scm);
381     }
382
383   ga->set_ordered (true);
384 }
385
386 ADD_INTERFACE (Align_interface,
387                "Order grobs from top to bottom, left to right, right to left"
388                " or bottom to top.  For vertical alignments of staves, the"
389                " @code{break-system-details} of the left"
390                " @rinternals{NonMusicalPaperColumn} may be set to tune"
391                " vertical spacing.",
392
393                /* properties */
394                "align-dir "
395                "axes "
396                "elements "
397                "minimum-translations-alist "
398                "padding "
399                "positioning-done "
400                "stacking-dir "
401               );