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