]> git.donarmstrong.com Git - lilypond.git/blob - lily/align-interface.cc
Merge remote-tracking branch 'origin/master' into translation
[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 = Skyline_pair::unsmob (g->get_property (a == Y_AXIS
87                                                                       ? "vertical-skylines"
88                                                                       : "horizontal-skylines"));
89           if (skys)
90             skylines = *skys;
91
92           /* This skyline was calculated relative to the grob g. In order to compare it to
93              skylines belonging to other grobs, we need to shift it so that it is relative
94              to the common reference. */
95           Real offset = g->relative_coordinate (other_common, other_axis (a));
96           skylines.shift (offset);
97         }
98       else
99         {
100           assert (a == Y_AXIS);
101           Interval extent = g->pure_height (g, start, end);
102
103           // This is a hack to get better accuracy on the pure-height of VerticalAlignment.
104           // It's quite common for a treble clef to be the highest element of one system
105           // and for a low note (or lyrics) to be the lowest note on another. The two will
106           // never collide, but the pure-height stuff only works with bounding boxes, so it
107           // doesn't know that. The result is a significant over-estimation of the pure-height,
108           // especially on systems with many staves. To correct for this, we build a skyline
109           // in two parts: the part we did above contains most of the grobs (note-heads, etc.)
110           // while the bit we're about to do only contains the breakable grobs at the beginning
111           // of the system. This way, the tall treble clefs are only compared with the treble
112           // clefs of the other staff and they will be ignored if the staff above is, for example,
113           // lyrics.
114           if (Axis_group_interface::has_interface (g)
115               && !Hara_kiri_group_spanner::request_suicide (g, start, end))
116             {
117               extent = Axis_group_interface::rest_of_line_pure_height (g, start, end);
118               Interval begin_of_line_extent = Axis_group_interface::begin_of_line_pure_height (g, start);
119               if (!begin_of_line_extent.is_empty ())
120                 {
121                   Box b;
122                   b[a] = begin_of_line_extent;
123                   b[other_axis (a)] = Interval (-infinity_f, -1);
124                   skylines.insert (b, other_axis (a));
125                 }
126             }
127
128           if (!extent.is_empty ())
129             {
130               Box b;
131               b[a] = extent;
132               b[other_axis (a)] = Interval (0, infinity_f);
133               skylines.insert (b, other_axis (a));
134             }
135         }
136
137       if (skylines.is_empty ())
138         elements->erase (elements->begin () + i);
139       else
140         ret->push_back (skylines);
141     }
142   reverse (*ret);
143 }
144
145 vector<Real>
146 Align_interface::get_minimum_translations (Grob *me,
147                                            vector<Grob *> const &all_grobs,
148                                            Axis a)
149 {
150   return internal_get_minimum_translations (me, all_grobs, a, true, false, 0, 0);
151 }
152
153 vector<Real>
154 Align_interface::get_pure_minimum_translations (Grob *me,
155                                                 vector<Grob *> const &all_grobs,
156                                                 Axis a, int start, int end)
157 {
158   return internal_get_minimum_translations (me, all_grobs, a, true, true, start, end);
159 }
160
161 vector<Real>
162 Align_interface::get_minimum_translations_without_min_dist (Grob *me,
163                                                             vector<Grob *> const &all_grobs,
164                                                             Axis a)
165 {
166   return internal_get_minimum_translations (me, all_grobs, a, false, false, 0, 0);
167 }
168
169 // If include_fixed_spacing is false, the only constraints that will be measured
170 // here are those that result from collisions (+ padding) and the spacing spec
171 // between adjacent staves.
172 // If include_fixed_spacing is true, constraints from line-break-system-details,
173 // basic-distance+stretchable=0, and staff-staff-spacing of spaceable staves
174 // with loose lines in between, are included as well.
175 // - If you want to find the minimum height of a system, include_fixed_spacing should be true.
176 // - If you're going to actually lay out the page, then it should be false (or
177 //   else centered dynamics will break when there is a fixed alignment).
178 vector<Real>
179 Align_interface::internal_get_minimum_translations (Grob *me,
180                                                     vector<Grob *> const &all_grobs,
181                                                     Axis a,
182                                                     bool include_fixed_spacing,
183                                                     bool pure, int start, int end)
184 {
185   if (!pure && a == Y_AXIS && dynamic_cast<Spanner *> (me) && !me->get_system ())
186     me->programming_error ("vertical alignment called before line-breaking");
187
188   // check the cache
189   if (pure)
190     {
191       SCM fv = ly_assoc_get (scm_cons (scm_from_int (start), scm_from_int (end)),
192                              me->get_property ("minimum-translations-alist"),
193                              SCM_EOL);
194       if (fv != SCM_EOL)
195         return ly_scm2floatvector (fv);
196     }
197
198   // If include_fixed_spacing is true, we look at things like system-system-spacing
199   // and alignment-distances, which only make sense for the toplevel VerticalAlignment.
200   // If we aren't toplevel, we're working on something like BassFigureAlignment
201   // and so we definitely don't want to include alignment-distances!
202   if (!dynamic_cast<System *> (me->get_parent (Y_AXIS)))
203     include_fixed_spacing = false;
204
205   Direction stacking_dir = robust_scm2dir (me->get_property ("stacking-dir"),
206                                            DOWN);
207   vector<Grob *> elems (all_grobs); // writable copy
208   vector<Skyline_pair> skylines;
209
210   get_skylines (me, &elems, a, pure, start, end, &skylines);
211
212   Real where = 0;
213   Real default_padding = robust_scm2double (me->get_property ("padding"), 0.0);
214   vector<Real> translates;
215   Skyline down_skyline (stacking_dir);
216   Real last_spaceable_element_pos = 0;
217   Grob *last_spaceable_element = 0;
218   Skyline last_spaceable_skyline (stacking_dir);
219   int spaceable_count = 0;
220   for (vsize j = 0; j < elems.size (); j++)
221     {
222       Real dy = 0;
223       Real padding = default_padding;
224
225       if (j == 0)
226         dy = skylines[j][-stacking_dir].max_height () + padding;
227       else
228         {
229           SCM spec = Page_layout_problem::get_spacing_spec (elems[j - 1], elems[j], pure, start, end);
230           Page_layout_problem::read_spacing_spec (spec, &padding, ly_symbol2scm ("padding"));
231
232           dy = down_skyline.distance (skylines[j][-stacking_dir]) + padding;
233
234           Real spec_distance = 0;
235           if (Page_layout_problem::read_spacing_spec (spec, &spec_distance, ly_symbol2scm ("minimum-distance")))
236             dy = max (dy, spec_distance);
237           // Consider the likely final spacing when estimating distance between staves of the full score
238           if (INT_MAX == end && 0 == start
239               && Page_layout_problem::read_spacing_spec (spec, &spec_distance, ly_symbol2scm ("basic-distance")))
240             dy = max (dy, spec_distance);
241
242           if (include_fixed_spacing && Page_layout_problem::is_spaceable (elems[j]) && last_spaceable_element)
243             {
244               // Spaceable staves may have
245               // constraints coming from the previous spaceable staff
246               // as well as from the previous staff.
247               spec = Page_layout_problem::get_spacing_spec (last_spaceable_element, elems[j], pure, start, end);
248               Real spaceable_padding = 0;
249               Page_layout_problem::read_spacing_spec (spec,
250                                                       &spaceable_padding,
251                                                       ly_symbol2scm ("padding"));
252               dy = max (dy, (last_spaceable_skyline.distance (skylines[j][-stacking_dir])
253                              + stacking_dir * (last_spaceable_element_pos - where) + spaceable_padding));
254
255               Real spaceable_min_distance = 0;
256               if (Page_layout_problem::read_spacing_spec (spec,
257                                                           &spaceable_min_distance,
258                                                           ly_symbol2scm ("minimum-distance")))
259                 dy = max (dy, spaceable_min_distance + stacking_dir * (last_spaceable_element_pos - where));
260
261               dy = max (dy, Page_layout_problem::get_fixed_spacing (last_spaceable_element, elems[j], spaceable_count,
262                                                                     pure, start, end));
263             }
264         }
265
266       if (isinf (dy)) /* if the skyline is empty, maybe max_height is infinity_f */
267         dy = 0.0;
268
269       dy = max (0.0, dy);
270       down_skyline.raise (-stacking_dir * dy);
271       down_skyline.merge (skylines[j][stacking_dir]);
272       where += stacking_dir * dy;
273       translates.push_back (where);
274
275       if (Page_layout_problem::is_spaceable (elems[j]))
276         {
277           spaceable_count++;
278           last_spaceable_element = elems[j];
279           last_spaceable_element_pos = where;
280           last_spaceable_skyline = down_skyline;
281         }
282     }
283
284   // So far, we've computed the translates for all the non-empty elements.
285   // Here, we set the translates for the empty elements: an empty element
286   // gets the same translation as the last non-empty element before it.
287   vector<Real> all_translates;
288   if (!translates.empty ())
289     {
290       Real w = translates[0];
291       for (vsize i = 0, j = 0; j < all_grobs.size (); j++)
292         {
293           if (i < elems.size () && all_grobs[j] == elems[i])
294             w = translates[i++];
295           all_translates.push_back (w);
296         }
297     }
298
299   if (pure)
300     {
301       SCM mta = me->get_property ("minimum-translations-alist");
302       mta = scm_cons (scm_cons (scm_cons (scm_from_int (start), scm_from_int (end)),
303                                 ly_floatvector2scm (all_translates)),
304                       mta);
305       me->set_property ("minimum-translations-alist", mta);
306     }
307   return all_translates;
308 }
309
310 void
311 Align_interface::align_elements_to_ideal_distances (Grob *me)
312 {
313   System *sys = me->get_system ();
314   if (sys)
315     {
316       Page_layout_problem layout (NULL, SCM_EOL, scm_list_1 (sys->self_scm ()));
317       layout.solution (true);
318     }
319   else
320     programming_error ("vertical alignment called before line breaking");
321 }
322
323 void
324 Align_interface::align_elements_to_minimum_distances (Grob *me, Axis a)
325 {
326   extract_grob_set (me, "elements", all_grobs);
327
328   vector<Real> translates = get_minimum_translations (me, all_grobs, a);
329   if (translates.size ())
330     for (vsize j = 0; j < all_grobs.size (); j++)
331       all_grobs[j]->translate_axis (translates[j], a);
332 }
333
334 Real
335 Align_interface::get_pure_child_y_translation (Grob *me, Grob *ch, int start, int end)
336 {
337   extract_grob_set (me, "elements", all_grobs);
338   vector<Real> translates = get_pure_minimum_translations (me, all_grobs, Y_AXIS, start, end);
339
340   if (translates.size ())
341     {
342       for (vsize i = 0; i < all_grobs.size (); i++)
343         if (all_grobs[i] == ch)
344           return translates[i];
345     }
346   else
347     return 0;
348
349   programming_error ("tried to get a translation for something that is no child of mine");
350   return 0;
351 }
352
353 Axis
354 Align_interface::axis (Grob *me)
355 {
356   return Axis (scm_to_int (scm_car (me->get_property ("axes"))));
357 }
358
359 void
360 Align_interface::add_element (Grob *me, Grob *element)
361 {
362   Axis a = Align_interface::axis (me);
363   SCM sym = axis_offset_symbol (a);
364   SCM proc = axis_parent_positioning (a);
365
366   element->set_property (sym, proc);
367   Axis_group_interface::add_element (me, element);
368 }
369
370 void
371 Align_interface::set_ordered (Grob *me)
372 {
373   SCM ga_scm = me->get_object ("elements");
374   Grob_array *ga = unsmob_grob_array (ga_scm);
375   if (!ga)
376     {
377       ga_scm = Grob_array::make_array ();
378       ga = unsmob_grob_array (ga_scm);
379       me->set_object ("elements", ga_scm);
380     }
381
382   ga->set_ordered (true);
383 }
384
385 ADD_INTERFACE (Align_interface,
386                "Order grobs from top to bottom, left to right, right to left"
387                " or bottom to top.  For vertical alignments of staves, the"
388                " @code{break-system-details} of the left"
389                " @rinternals{NonMusicalPaperColumn} may be set to tune"
390                " vertical spacing.",
391
392                /* properties */
393                "align-dir "
394                "axes "
395                "elements "
396                "minimum-translations-alist "
397                "padding "
398                "positioning-done "
399                "stacking-dir "
400               );