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