]> git.donarmstrong.com Git - lilypond.git/blob - lily/align-interface.cc
5741a442728ab65cb834f65402da11dd36ac2ca5
[lilypond.git] / lily / align-interface.cc
1 /*
2   This file is part of LilyPond, the GNU music typesetter.
3
4   Copyright (C) 2000--2011 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
36 MAKE_SCHEME_CALLBACK (Align_interface, align_to_minimum_distances, 1);
37 SCM
38 Align_interface::align_to_minimum_distances (SCM smob)
39 {
40   Grob *me = unsmob_grob (smob);
41
42   me->set_property ("positioning-done", SCM_BOOL_T);
43
44   SCM axis = scm_car (me->get_property ("axes"));
45   Axis ax = Axis (scm_to_int (axis));
46
47   Align_interface::align_elements_to_minimum_distances (me, ax);
48
49   return SCM_BOOL_T;
50 }
51
52 MAKE_SCHEME_CALLBACK (Align_interface, align_to_ideal_distances, 1);
53 SCM
54 Align_interface::align_to_ideal_distances (SCM smob)
55 {
56   Grob *me = unsmob_grob (smob);
57
58   me->set_property ("positioning-done", SCM_BOOL_T);
59
60   Align_interface::align_elements_to_ideal_distances (me);
61
62   return SCM_BOOL_T;
63 }
64
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?
71 static void
72 get_skylines (Grob *me,
73               vector<Grob*> *const elements,
74               Axis a,
75               bool pure, int start, int end,
76               vector<Skyline_pair> *const ret)
77 {
78   Grob *other_common = common_refpoint_of_array (*elements, me, other_axis (a));
79   
80   for (vsize i = elements->size (); i--;)
81     {
82       Grob *g = (*elements)[i];
83       Skyline_pair skylines;
84
85       if (!pure)
86         {
87           Skyline_pair *skys = Skyline_pair::unsmob (g->get_property (a == Y_AXIS
88                                                                       ? "vertical-skylines"
89                                                                       : "horizontal-skylines"));
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, 0, 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, 0, 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 true, the manually fixed spacings
171 // induced by stretchable=0 or alignment-distances are included
172 // in the minimum translations here.  If you want to find the minimum
173 // height of a system, include_fixed_spacing should be true.  If you
174 // want to actually lay out the page, then it should be false (or
175 // else centered dynamics will break when there is a fixed alignment).
176 vector<Real>
177 Align_interface::internal_get_minimum_translations (Grob *me,
178                                                     vector<Grob*> const &all_grobs,
179                                                     Axis a,
180                                                     bool include_fixed_spacing,
181                                                     bool pure, int start, int end)
182 {
183   if (!pure && a == Y_AXIS && dynamic_cast<Spanner*> (me) && !me->get_system ())
184     me->programming_error ("vertical alignment called before line-breaking");
185
186   // If include_fixed_spacing is true, we look at things like system-system-spacing
187   // and alignment-distances, which only make sense for the toplevel VerticalAlignment.
188   // If we aren't toplevel, we're working on something like BassFigureAlignment
189   // and so we definitely don't want to include alignment-distances!
190   if (!dynamic_cast<System*> (me->get_parent (Y_AXIS)))
191     include_fixed_spacing = false;
192   
193   Direction stacking_dir = robust_scm2dir (me->get_property ("stacking-dir"),
194                                            DOWN);
195   vector<Grob*> elems (all_grobs); // writable copy
196   vector<Skyline_pair> skylines;
197
198   get_skylines (me, &elems, a, pure, start, end, &skylines);
199
200   Real where = 0;
201   Real default_padding = robust_scm2double (me->get_property ("padding"), 0.0);
202   vector<Real> translates;
203   Skyline down_skyline (stacking_dir);
204   Real last_spaceable_element_pos = 0;
205   Grob *last_spaceable_element = 0;
206   int spaceable_count = 0;
207   for (vsize j = 0; j < elems.size (); j++)
208     {
209       Real dy = 0;
210       Real padding = default_padding;
211
212       if (j == 0)
213         dy = skylines[j][-stacking_dir].max_height ();
214       else
215         {
216           down_skyline.merge (skylines[j-1][stacking_dir]);
217           dy = down_skyline.distance (skylines[j][-stacking_dir]);
218
219           SCM spec = Page_layout_problem::get_spacing_spec (elems[j-1], elems[j], pure, start, end);
220           Page_layout_problem::read_spacing_spec (spec, &padding, ly_symbol2scm ("padding"));
221
222           Real min_distance = 0;
223           if (Page_layout_problem::read_spacing_spec (spec, &min_distance, ly_symbol2scm ("minimum-distance")))
224             dy = max (dy, min_distance);
225
226           if (include_fixed_spacing)
227             dy = max (dy, Page_layout_problem::get_fixed_spacing (elems[j-1], elems[j], spaceable_count, pure, start, end));
228
229           if (Page_layout_problem::is_spaceable (elems[j]) && last_spaceable_element)
230             {
231               // Spaceable staves may have
232               // constraints coming from the previous spaceable staff
233               // as well as from the previous staff.
234               spec = Page_layout_problem::get_spacing_spec (last_spaceable_element, elems[j], pure, start, end);
235               Real spaceable_padding = 0;
236               Page_layout_problem::read_spacing_spec (spec,
237                                                       &spaceable_padding,
238                                                       ly_symbol2scm ("padding"));
239               padding = max (padding, spaceable_padding);
240
241               Real min_distance = 0;
242               if (Page_layout_problem::read_spacing_spec (spec,
243                                                           &min_distance,
244                                                           ly_symbol2scm ("minimum-distance")))
245                 dy = max (dy, min_distance + stacking_dir*(last_spaceable_element_pos - where));
246
247               if (include_fixed_spacing)
248                 dy = max (dy, Page_layout_problem::get_fixed_spacing (last_spaceable_element, elems[j], spaceable_count,
249                                                                       pure, start, end));
250             }
251         }
252
253       if (isinf (dy)) /* if the skyline is empty, maybe max_height is infinity_f */
254         dy = 0.0;
255
256       dy = max (0.0, dy + padding);
257       down_skyline.raise (-stacking_dir * dy);
258       where += stacking_dir * dy;
259       translates.push_back (where);
260
261       if (Page_layout_problem::is_spaceable (elems[j]))
262         {
263           spaceable_count++;
264           last_spaceable_element = elems[j];
265           last_spaceable_element_pos = where;
266         }
267     }
268
269   // So far, we've computed the translates for all the non-empty elements.
270   // Here, we set the translates for the empty elements: an empty element
271   // gets the same translation as the last non-empty element before it.
272   vector<Real> all_translates;
273   if (!translates.empty ())
274     {
275       Real w = translates[0];
276       for  (vsize i = 0, j = 0; j < all_grobs.size (); j++)
277         {
278           if (i < elems.size () && all_grobs[j] == elems[i])
279             w = translates[i++];
280           all_translates.push_back (w);
281         }
282     }
283   return all_translates;
284 }
285
286 void
287 Align_interface::align_elements_to_ideal_distances (Grob *me)
288 {
289   System *sys = me->get_system ();
290   if (sys)
291     {
292       Page_layout_problem layout (NULL, SCM_EOL, scm_list_1 (sys->self_scm ()));
293       layout.solution (true);
294     }
295   else
296     programming_error ("vertical alignment called before line breaking");
297 }
298
299 void
300 Align_interface::align_elements_to_minimum_distances (Grob *me, Axis a)
301 {
302   extract_grob_set (me, "elements", all_grobs);
303
304   vector<Real> translates = get_minimum_translations (me, all_grobs, a);
305   if (translates.size ())
306     for (vsize j = 0; j < all_grobs.size (); j++)
307       all_grobs[j]->translate_axis (translates[j], a);
308 }
309
310 Real
311 Align_interface::get_pure_child_y_translation (Grob *me, Grob *ch, int start, int end)
312 {
313   extract_grob_set (me, "elements", all_grobs);
314   vector<Real> translates = get_pure_minimum_translations (me, all_grobs, Y_AXIS, start, end);
315
316   if (translates.size ())
317     {
318       for (vsize i = 0; i < all_grobs.size (); i++)
319         if (all_grobs[i] == ch)
320           return translates[i];
321     }
322   else
323     return 0;
324
325   programming_error ("tried to get a translation for something that is no child of mine");
326   return 0;
327 }
328
329 Axis
330 Align_interface::axis (Grob *me)
331 {
332   return Axis (scm_to_int (scm_car (me->get_property ("axes"))));
333 }
334
335 void
336 Align_interface::add_element (Grob *me, Grob *element)
337 {
338   Axis a = Align_interface::axis (me);
339   SCM sym = axis_offset_symbol (a);
340   SCM proc = axis_parent_positioning (a);
341     
342   element->set_property (sym, proc);
343   Axis_group_interface::add_element (me, element);
344 }
345
346 void
347 Align_interface::set_ordered (Grob *me)
348 {
349   SCM ga_scm = me->get_object ("elements");
350   Grob_array *ga = unsmob_grob_array (ga_scm);
351   if (!ga)
352     {
353       ga_scm = Grob_array::make_array ();
354       ga = unsmob_grob_array (ga_scm);
355       me->set_object ("elements", ga_scm);
356     }
357
358   ga->set_ordered (true);
359 }
360
361 ADD_INTERFACE (Align_interface,
362                "Order grobs from top to bottom, left to right, right to left"
363                " or bottom to top.  For vertical alignments of staves, the"
364                " @code{break-system-details} of the left"
365                " @rinternals{NonMusicalPaperColumn} may be set to tune"
366                " vertical spacing.",
367                
368                /* properties */
369                "align-dir "
370                "axes "
371                "elements "
372                "padding "
373                "positioning-done "
374                "stacking-dir "
375                );