]> git.donarmstrong.com Git - lilypond.git/blob - lily/align-interface.cc
Doc-it: chapter 1 completed
[lilypond.git] / lily / align-interface.cc
1 /*
2   This file is part of LilyPond, the GNU music typesetter.
3
4   Copyright (C) 2000--2012 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         dy = 0.0;
216       else if (!last_nonempty_element)
217         dy = skyline[-stacking_dir].max_height () + padding;
218       else
219         {
220           SCM spec = Page_layout_problem::get_spacing_spec (last_nonempty_element, elems[j], pure, start, end);
221           Page_layout_problem::read_spacing_spec (spec, &padding, ly_symbol2scm ("padding"));
222
223           dy = down_skyline.distance (skyline[-stacking_dir]) + padding;
224
225           Real spec_distance = 0;
226           if (Page_layout_problem::read_spacing_spec (spec, &spec_distance, ly_symbol2scm ("minimum-distance")))
227             dy = max (dy, spec_distance);
228           // Consider the likely final spacing when estimating distance between staves of the full score
229           if (INT_MAX == end && 0 == start
230               && Page_layout_problem::read_spacing_spec (spec, &spec_distance, ly_symbol2scm ("basic-distance")))
231             dy = max (dy, spec_distance);
232
233           if (include_fixed_spacing && Page_layout_problem::is_spaceable (elems[j]) && last_spaceable_element)
234             {
235               // Spaceable staves may have
236               // constraints coming from the previous spaceable staff
237               // as well as from the previous staff.
238               spec = Page_layout_problem::get_spacing_spec (last_spaceable_element, elems[j], pure, start, end);
239               Real spaceable_padding = 0;
240               Page_layout_problem::read_spacing_spec (spec,
241                                                       &spaceable_padding,
242                                                       ly_symbol2scm ("padding"));
243               dy = max (dy, (last_spaceable_skyline.distance (skyline[-stacking_dir])
244                              + stacking_dir * (last_spaceable_element_pos - where) + spaceable_padding));
245
246               Real spaceable_min_distance = 0;
247               if (Page_layout_problem::read_spacing_spec (spec,
248                                                           &spaceable_min_distance,
249                                                           ly_symbol2scm ("minimum-distance")))
250                 dy = max (dy, spaceable_min_distance + stacking_dir * (last_spaceable_element_pos - where));
251
252               dy = max (dy, Page_layout_problem::get_fixed_spacing (last_spaceable_element, elems[j], spaceable_count,
253                                                                     pure, start, end));
254             }
255         }
256
257       dy = max (0.0, dy);
258       down_skyline.raise (-stacking_dir * dy);
259       down_skyline.merge (skyline[stacking_dir]);
260       where += stacking_dir * dy;
261       translates.push_back (where);
262
263       if (Page_layout_problem::is_spaceable (elems[j]))
264         {
265           spaceable_count++;
266           last_spaceable_element = elems[j];
267           last_spaceable_element_pos = where;
268           last_spaceable_skyline = down_skyline;
269         }
270       if (!skyline.is_empty ())
271         last_nonempty_element = elems[j];
272     }
273
274   if (pure)
275     {
276       SCM mta = me->get_property ("minimum-translations-alist");
277       mta = scm_cons (scm_cons (scm_cons (scm_from_int (start), scm_from_int (end)),
278                                 ly_floatvector2scm (translates)),
279                       mta);
280       me->set_property ("minimum-translations-alist", mta);
281     }
282   return translates;
283 }
284
285 void
286 Align_interface::align_elements_to_ideal_distances (Grob *me)
287 {
288   System *sys = me->get_system ();
289   if (sys)
290     {
291       Page_layout_problem layout (NULL, SCM_EOL, scm_list_1 (sys->self_scm ()));
292       layout.solution (true);
293     }
294   else
295     programming_error ("vertical alignment called before line breaking");
296 }
297
298 void
299 Align_interface::align_elements_to_minimum_distances (Grob *me, Axis a)
300 {
301   extract_grob_set (me, "elements", all_grobs);
302
303   vector<Real> translates = get_minimum_translations (me, all_grobs, a);
304   if (translates.size ())
305     for (vsize j = 0; j < all_grobs.size (); j++)
306       all_grobs[j]->translate_axis (translates[j], a);
307 }
308
309 Real
310 Align_interface::get_pure_child_y_translation (Grob *me, Grob *ch, int start, int end)
311 {
312   extract_grob_set (me, "elements", all_grobs);
313   vector<Real> translates = get_pure_minimum_translations (me, all_grobs, Y_AXIS, start, end);
314
315   if (translates.size ())
316     {
317       for (vsize i = 0; i < all_grobs.size (); i++)
318         if (all_grobs[i] == ch)
319           return translates[i];
320     }
321   else
322     return 0;
323
324   programming_error ("tried to get a translation for something that is no child of mine");
325   return 0;
326 }
327
328 Axis
329 Align_interface::axis (Grob *me)
330 {
331   return Axis (scm_to_int (scm_car (me->get_property ("axes"))));
332 }
333
334 void
335 Align_interface::add_element (Grob *me, Grob *element)
336 {
337   Axis a = Align_interface::axis (me);
338   SCM sym = axis_offset_symbol (a);
339   SCM proc = axis_parent_positioning (a);
340
341   element->set_property (sym, proc);
342   Axis_group_interface::add_element (me, element);
343 }
344
345 void
346 Align_interface::set_ordered (Grob *me)
347 {
348   SCM ga_scm = me->get_object ("elements");
349   Grob_array *ga = unsmob_grob_array (ga_scm);
350   if (!ga)
351     {
352       ga_scm = Grob_array::make_array ();
353       ga = unsmob_grob_array (ga_scm);
354       me->set_object ("elements", ga_scm);
355     }
356
357   ga->set_ordered (true);
358 }
359
360 ADD_INTERFACE (Align_interface,
361                "Order grobs from top to bottom, left to right, right to left"
362                " or bottom to top.  For vertical alignments of staves, the"
363                " @code{break-system-details} of the left"
364                " @rinternals{NonMusicalPaperColumn} may be set to tune"
365                " vertical spacing.",
366
367                /* properties */
368                "align-dir "
369                "axes "
370                "elements "
371                "minimum-translations-alist "
372                "padding "
373                "positioning-done "
374                "stacking-dir "
375               );