]> git.donarmstrong.com Git - lilypond.git/blob - lily/align-interface.cc
Merge remote-tracking branch 'origin/translation'
[lilypond.git] / lily / align-interface.cc
1 /*
2   This file is part of LilyPond, the GNU music typesetter.
3
4   Copyright (C) 2000--2014 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 MAKE_SCHEME_CALLBACK (Align_interface, align_to_minimum_distances, 1);
36 SCM
37 Align_interface::align_to_minimum_distances (SCM smob)
38 {
39   Grob *me = unsmob_grob (smob);
40
41   me->set_property ("positioning-done", SCM_BOOL_T);
42
43   SCM axis = scm_car (me->get_property ("axes"));
44   Axis ax = Axis (scm_to_int (axis));
45
46   Align_interface::align_elements_to_minimum_distances (me, ax);
47
48   return SCM_BOOL_T;
49 }
50
51 MAKE_SCHEME_CALLBACK (Align_interface, align_to_ideal_distances, 1);
52 SCM
53 Align_interface::align_to_ideal_distances (SCM smob)
54 {
55   Grob *me = unsmob_grob (smob);
56
57   me->set_property ("positioning-done", SCM_BOOL_T);
58
59   Align_interface::align_elements_to_ideal_distances (me);
60
61   return SCM_BOOL_T;
62 }
63
64 /* Return upper and lower skylines for VerticalAxisGroup g. If the extent
65    is non-empty but there is no skyline available (or pure is true), just
66    create a flat skyline from the bounding box */
67 // TODO(jneem): the pure and non-pure parts seem to share very little
68 // code. Split them into 2 functions, perhaps?
69 static Skyline_pair
70 get_skylines (Grob *g,
71               Axis a,
72               Grob *other_common,
73               bool pure, int start, int end)
74 {
75   Skyline_pair skylines;
76
77   if (!pure)
78     {
79       Skyline_pair *skys = Skyline_pair::unsmob (g->get_property (a == Y_AXIS
80                                                                   ? "vertical-skylines"
81                                                                   : "horizontal-skylines"));
82       if (skys)
83         skylines = *skys;
84
85       /* This skyline was calculated relative to the grob g. In order to compare it to
86          skylines belonging to other grobs, we need to shift it so that it is relative
87          to the common reference. */
88       Real offset = g->relative_coordinate (other_common, other_axis (a));
89       skylines.shift (offset);
90     }
91   else if (Hara_kiri_group_spanner::request_suicide (g, start, end))
92     return skylines;
93   else
94     {
95       assert (a == Y_AXIS);
96       Interval extent = g->pure_height (g, start, end);
97
98       // This is a hack to get better accuracy on the pure-height of VerticalAlignment.
99       // It's quite common for a treble clef to be the highest element of one system
100       // and for a low note (or lyrics) to be the lowest note on another. The two will
101       // never collide, but the pure-height stuff only works with bounding boxes, so it
102       // doesn't know that. The result is a significant over-estimation of the pure-height,
103       // especially on systems with many staves. To correct for this, we build a skyline
104       // in two parts: the part we did above contains most of the grobs (note-heads, etc.)
105       // while the bit we're about to do only contains the breakable grobs at the beginning
106       // of the system. This way, the tall treble clefs are only compared with the treble
107       // clefs of the other staff and they will be ignored if the staff above is, for example,
108       // lyrics.
109       if (Axis_group_interface::has_interface (g))
110         {
111           extent = Axis_group_interface::rest_of_line_pure_height (g, start, end);
112           Interval begin_of_line_extent = Axis_group_interface::begin_of_line_pure_height (g, start);
113           if (!begin_of_line_extent.is_empty ())
114             {
115               Box b;
116               b[a] = begin_of_line_extent;
117               b[other_axis (a)] = Interval (-infinity_f, -1);
118               skylines.insert (b, other_axis (a));
119             }
120         }
121
122       if (!extent.is_empty ())
123         {
124           Box b;
125           b[a] = extent;
126           b[other_axis (a)] = Interval (0, infinity_f);
127           skylines.insert (b, other_axis (a));
128         }
129     }
130   return skylines;
131 }
132
133 vector<Real>
134 Align_interface::get_minimum_translations (Grob *me,
135                                            vector<Grob *> const &all_grobs,
136                                            Axis a)
137 {
138   return internal_get_minimum_translations (me, all_grobs, a, true, false, 0, 0);
139 }
140
141 vector<Real>
142 Align_interface::get_pure_minimum_translations (Grob *me,
143                                                 vector<Grob *> const &all_grobs,
144                                                 Axis a, int start, int end)
145 {
146   return internal_get_minimum_translations (me, all_grobs, a, true, true, start, end);
147 }
148
149 vector<Real>
150 Align_interface::get_minimum_translations_without_min_dist (Grob *me,
151                                                             vector<Grob *> const &all_grobs,
152                                                             Axis a)
153 {
154   return internal_get_minimum_translations (me, all_grobs, a, false, false, 0, 0);
155 }
156
157 // If include_fixed_spacing is false, the only constraints that will be measured
158 // here are those that result from collisions (+ padding) and the spacing spec
159 // between adjacent staves.
160 // If include_fixed_spacing is true, constraints from line-break-system-details,
161 // basic-distance+stretchable=0, and staff-staff-spacing of spaceable staves
162 // with loose lines in between, are included as well.
163 // - If you want to find the minimum height of a system, include_fixed_spacing should be true.
164 // - If you're going to actually lay out the page, then it should be false (or
165 //   else centered dynamics will break when there is a fixed alignment).
166 vector<Real>
167 Align_interface::internal_get_minimum_translations (Grob *me,
168                                                     vector<Grob *> const &elems,
169                                                     Axis a,
170                                                     bool include_fixed_spacing,
171                                                     bool pure, int start, int end)
172 {
173   if (!pure && a == Y_AXIS && dynamic_cast<Spanner *> (me) && !me->get_system ())
174     me->programming_error ("vertical alignment called before line-breaking");
175
176   // check the cache
177   if (pure)
178     {
179       SCM fv = ly_assoc_get (scm_cons (scm_from_int (start), scm_from_int (end)),
180                              me->get_property ("minimum-translations-alist"),
181                              SCM_EOL);
182       if (fv != SCM_EOL)
183         return ly_scm2floatvector (fv);
184     }
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
196   Grob *other_common = common_refpoint_of_array (elems, me, other_axis (a));
197
198   Real where = 0;
199   Real default_padding = robust_scm2double (me->get_property ("padding"), 0.0);
200   vector<Real> translates;
201   Skyline down_skyline (stacking_dir);
202   Grob *last_nonempty_element = 0;
203   Real last_spaceable_element_pos = 0;
204   Grob *last_spaceable_element = 0;
205   Skyline last_spaceable_skyline (stacking_dir);
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       Skyline_pair skyline = get_skylines (elems[j], a, other_common, pure, start, end);
213
214       if (skyline.is_empty ())
215         {
216           translates.push_back (where);
217           continue;
218         }
219
220       if (!last_nonempty_element)
221         {
222           dy = skyline[-stacking_dir].max_height () + padding;
223           for (vsize k = j; k-- > 0;)
224             translates[k] = stacking_dir * dy;
225         }
226       else
227         {
228           SCM spec = Page_layout_problem::get_spacing_spec (last_nonempty_element, elems[j], pure, start, end);
229           Page_layout_problem::read_spacing_spec (spec, &padding, ly_symbol2scm ("padding"));
230
231           dy = down_skyline.distance (skyline[-stacking_dir]) + padding;
232
233           Real spec_distance = 0;
234           if (Page_layout_problem::read_spacing_spec (spec, &spec_distance, ly_symbol2scm ("minimum-distance")))
235             dy = max (dy, spec_distance);
236           // Consider the likely final spacing when estimating distance between staves of the full score
237           if (INT_MAX == end && 0 == start
238               && Page_layout_problem::read_spacing_spec (spec, &spec_distance, ly_symbol2scm ("basic-distance")))
239             dy = max (dy, spec_distance);
240
241           if (include_fixed_spacing && Page_layout_problem::is_spaceable (elems[j]) && last_spaceable_element)
242             {
243               // Spaceable staves may have
244               // constraints coming from the previous spaceable staff
245               // as well as from the previous staff.
246               spec = Page_layout_problem::get_spacing_spec (last_spaceable_element, elems[j], pure, start, end);
247               Real spaceable_padding = 0;
248               Page_layout_problem::read_spacing_spec (spec,
249                                                       &spaceable_padding,
250                                                       ly_symbol2scm ("padding"));
251               dy = max (dy, (last_spaceable_skyline.distance (skyline[-stacking_dir])
252                              + stacking_dir * (last_spaceable_element_pos - where) + spaceable_padding));
253
254               Real spaceable_min_distance = 0;
255               if (Page_layout_problem::read_spacing_spec (spec,
256                                                           &spaceable_min_distance,
257                                                           ly_symbol2scm ("minimum-distance")))
258                 dy = max (dy, spaceable_min_distance + stacking_dir * (last_spaceable_element_pos - where));
259
260               dy = max (dy, Page_layout_problem::get_fixed_spacing (last_spaceable_element, elems[j], spaceable_count,
261                                                                     pure, start, end));
262             }
263         }
264
265       dy = max (0.0, dy);
266       down_skyline.raise (-stacking_dir * dy);
267       down_skyline.merge (skyline[stacking_dir]);
268       where += stacking_dir * dy;
269       translates.push_back (where);
270
271       if (Page_layout_problem::is_spaceable (elems[j]))
272         {
273           spaceable_count++;
274           last_spaceable_element = elems[j];
275           last_spaceable_element_pos = where;
276           last_spaceable_skyline = down_skyline;
277         }
278       last_nonempty_element = elems[j];
279     }
280
281   if (pure)
282     {
283       SCM mta = me->get_property ("minimum-translations-alist");
284       mta = scm_cons (scm_cons (scm_cons (scm_from_int (start), scm_from_int (end)),
285                                 ly_floatvector2scm (translates)),
286                       mta);
287       me->set_property ("minimum-translations-alist", mta);
288     }
289   return translates;
290 }
291
292 void
293 Align_interface::align_elements_to_ideal_distances (Grob *me)
294 {
295   System *sys = me->get_system ();
296   if (sys)
297     {
298       Page_layout_problem layout (NULL, SCM_EOL, scm_list_1 (sys->self_scm ()));
299       layout.solution (true);
300     }
301   else
302     programming_error ("vertical alignment called before line breaking");
303 }
304
305 void
306 Align_interface::align_elements_to_minimum_distances (Grob *me, Axis a)
307 {
308   extract_grob_set (me, "elements", all_grobs);
309
310   vector<Real> translates = get_minimum_translations (me, all_grobs, a);
311   if (translates.size ())
312     for (vsize j = 0; j < all_grobs.size (); j++)
313       all_grobs[j]->translate_axis (translates[j], a);
314 }
315
316 Real
317 Align_interface::get_pure_child_y_translation (Grob *me, Grob *ch, int start, int end)
318 {
319   extract_grob_set (me, "elements", all_grobs);
320   vector<Real> translates = get_pure_minimum_translations (me, all_grobs, Y_AXIS, start, end);
321
322   if (translates.size ())
323     {
324       for (vsize i = 0; i < all_grobs.size (); i++)
325         if (all_grobs[i] == ch)
326           return translates[i];
327     }
328   else
329     return 0;
330
331   programming_error ("tried to get a translation for something that is no child of mine");
332   return 0;
333 }
334
335 Axis
336 Align_interface::axis (Grob *me)
337 {
338   return Axis (scm_to_int (scm_car (me->get_property ("axes"))));
339 }
340
341 void
342 Align_interface::add_element (Grob *me, Grob *element)
343 {
344   Axis a = Align_interface::axis (me);
345   SCM sym = axis_offset_symbol (a);
346   SCM proc = axis_parent_positioning (a);
347
348   element->set_property (sym, proc);
349   Axis_group_interface::add_element (me, element);
350 }
351
352 void
353 Align_interface::set_ordered (Grob *me)
354 {
355   SCM ga_scm = me->get_object ("elements");
356   Grob_array *ga = unsmob_grob_array (ga_scm);
357   if (!ga)
358     {
359       ga_scm = Grob_array::make_array ();
360       ga = unsmob_grob_array (ga_scm);
361       me->set_object ("elements", ga_scm);
362     }
363
364   ga->set_ordered (true);
365 }
366
367 ADD_INTERFACE (Align_interface,
368                "Order grobs from top to bottom, left to right, right to left"
369                " or bottom to top.  For vertical alignments of staves, the"
370                " @code{break-system-details} of the left"
371                " @rinternals{NonMusicalPaperColumn} may be set to tune"
372                " vertical spacing.",
373
374                /* properties */
375                "align-dir "
376                "axes "
377                "elements "
378                "minimum-translations-alist "
379                "padding "
380                "positioning-done "
381                "stacking-dir "
382               );