]> git.donarmstrong.com Git - lilypond.git/blob - lily/align-interface.cc
Fix 1240.
[lilypond.git] / lily / align-interface.cc
1 /*
2   This file is part of LilyPond, the GNU music typesetter.
3
4   Copyright (C) 2000--2010 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 // If include_fixed_spacing is true, the manually fixed spacings
147 // induced by stretchable=0 or alignment-distances are included
148 // in the minimum translations here.  If you want to find the minimum
149 // height of a system, include_fixed_spacing should be true.  If you
150 // want to actually lay out the page, then it should be false (or
151 // else centered dynamics will break when there is a fixed alignment).
152 vector<Real>
153 Align_interface::get_minimum_translations (Grob *me,
154                                            vector<Grob*> const &all_grobs,
155                                            Axis a,
156                                            bool include_fixed_spacing,
157                                            bool pure, int start, int end)
158 {
159   if (!pure && a == Y_AXIS && dynamic_cast<Spanner*> (me) && !me->get_system ())
160     me->programming_error ("vertical alignment called before line-breaking");
161   
162   Direction stacking_dir = robust_scm2dir (me->get_property ("stacking-dir"),
163                                            DOWN);
164   vector<Grob*> elems (all_grobs); // writable copy
165   vector<Skyline_pair> skylines;
166
167   get_skylines (me, &elems, a, pure, start, end, &skylines);
168
169   Real where = 0;
170   Real default_padding = robust_scm2double (me->get_property ("padding"), 0.0);
171   vector<Real> translates;
172   Skyline down_skyline (stacking_dir);
173   Real last_spaceable_element_pos = 0;
174   Grob *last_spaceable_element = 0;
175   int spaceable_count = 0;
176   for (vsize j = 0; j < elems.size (); j++)
177     {
178       Real dy = 0;
179       Real padding = default_padding;
180
181       if (j == 0)
182         dy = skylines[j][-stacking_dir].max_height ();
183       else
184         {
185           down_skyline.merge (skylines[j-1][stacking_dir]);
186           dy = down_skyline.distance (skylines[j][-stacking_dir]);
187
188           SCM spec = Page_layout_problem::get_spacing_spec (elems[j-1], elems[j], pure, start, end);
189           Page_layout_problem::read_spacing_spec (spec, &padding, ly_symbol2scm ("padding"));
190
191           Real min_distance = 0;
192           if (Page_layout_problem::read_spacing_spec (spec, &min_distance, ly_symbol2scm ("minimum-distance")))
193             dy = max (dy, min_distance);
194
195           if (include_fixed_spacing)
196             dy = max (dy, Page_layout_problem::get_fixed_spacing (elems[j-1], elems[j], spaceable_count, pure, start, end));
197
198           if (Page_layout_problem::is_spaceable (elems[j]) && last_spaceable_element)
199             {
200               // Spaceable staves may have
201               // constraints coming from the previous spaceable staff
202               // as well as from the previous staff.
203               spec = Page_layout_problem::get_spacing_spec (last_spaceable_element, elems[j], pure, start, end);
204               Real spaceable_padding = 0;
205               Page_layout_problem::read_spacing_spec (spec,
206                                                       &spaceable_padding,
207                                                       ly_symbol2scm ("padding"));
208               padding = max (padding, spaceable_padding);
209
210               Real min_distance = 0;
211               if (Page_layout_problem::read_spacing_spec (spec,
212                                                           &min_distance,
213                                                           ly_symbol2scm ("minimum-distance")))
214                 dy = max (dy, min_distance + stacking_dir*(last_spaceable_element_pos - where));
215
216               if (include_fixed_spacing)
217                 dy = max (dy, Page_layout_problem::get_fixed_spacing (last_spaceable_element, elems[j], spaceable_count,
218                                                                       pure, start, end));
219             }
220         }
221
222       if (isinf (dy)) /* if the skyline is empty, maybe max_height is infinity_f */
223         dy = 0.0;
224
225       dy = max (0.0, dy + padding);
226       down_skyline.raise (-stacking_dir * dy);
227       where += stacking_dir * dy;
228       translates.push_back (where);
229
230       if (Page_layout_problem::is_spaceable (elems[j]))
231         {
232           spaceable_count++;
233           last_spaceable_element = elems[j];
234           last_spaceable_element_pos = where;
235         }
236     }
237
238   // So far, we've computed the translates for all the non-empty elements.
239   // Here, we set the translates for the empty elements: an empty element
240   // gets the same translation as the last non-empty element before it.
241   vector<Real> all_translates;
242   if (!translates.empty ())
243     {
244       Real w = translates[0];
245       for  (vsize i = 0, j = 0; j < all_grobs.size (); j++)
246         {
247           if (i < elems.size () && all_grobs[j] == elems[i])
248             w = translates[i++];
249           all_translates.push_back (w);
250         }
251     }
252   return all_translates;
253 }
254
255 void
256 Align_interface::align_elements_to_ideal_distances (Grob *me)
257 {
258   System *sys = me->get_system ();
259   if (sys)
260     {
261       Page_layout_problem layout (NULL, SCM_EOL, scm_list_1 (sys->self_scm ()));
262       layout.solution (true);
263     }
264   else
265     programming_error ("vertical alignment called before line breaking");
266 }
267
268 void
269 Align_interface::align_elements_to_minimum_distances (Grob *me, Axis a)
270 {
271   extract_grob_set (me, "elements", all_grobs);
272
273   vector<Real> translates = get_minimum_translations (me, all_grobs, a, true, false, 0, 0);
274   if (translates.size ())
275     for (vsize j = 0; j < all_grobs.size (); j++)
276       all_grobs[j]->translate_axis (translates[j], a);
277 }
278
279 Real
280 Align_interface::get_pure_child_y_translation (Grob *me, Grob *ch, int start, int end)
281 {
282   extract_grob_set (me, "elements", all_grobs);
283   vector<Real> translates = get_minimum_translations (me, all_grobs, Y_AXIS, true, true, start, end);
284
285   if (translates.size ())
286     {
287       for (vsize i = 0; i < all_grobs.size (); i++)
288         if (all_grobs[i] == ch)
289           return translates[i];
290     }
291   else
292     return 0;
293
294   programming_error ("tried to get a translation for something that is no child of mine");
295   return 0;
296 }
297
298 Axis
299 Align_interface::axis (Grob *me)
300 {
301   return Axis (scm_to_int (scm_car (me->get_property ("axes"))));
302 }
303
304 void
305 Align_interface::add_element (Grob *me, Grob *element)
306 {
307   Axis a = Align_interface::axis (me);
308   SCM sym = axis_offset_symbol (a);
309   SCM proc = axis_parent_positioning (a);
310     
311   element->set_property (sym, proc);
312   Axis_group_interface::add_element (me, element);
313 }
314
315 void
316 Align_interface::set_ordered (Grob *me)
317 {
318   SCM ga_scm = me->get_object ("elements");
319   Grob_array *ga = unsmob_grob_array (ga_scm);
320   if (!ga)
321     {
322       ga_scm = Grob_array::make_array ();
323       ga = unsmob_grob_array (ga_scm);
324       me->set_object ("elements", ga_scm);
325     }
326
327   ga->set_ordered (true);
328 }
329
330 ADD_INTERFACE (Align_interface,
331                "Order grobs from top to bottom, left to right, right to left"
332                " or bottom to top.  For vertical alignments of staves, the"
333                " @code{break-system-details} of the left"
334                " @rinternals{NonMusicalPaperColumn} may be set to tune"
335                " vertical spacing.",
336                
337                /* properties */
338                "align-dir "
339                "axes "
340                "elements "
341                "padding "
342                "positioning-done "
343                "stacking-dir "
344                );