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