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