]> git.donarmstrong.com Git - lilypond.git/blob - lily/align-interface.cc
Merge branch 'lilypond/translation' of ssh://git.sv.gnu.org/srv/git/lilypond into...
[lilypond.git] / lily / align-interface.cc
1 /*
2   align-interface.cc -- implement Align_interface
3
4   source file of the GNU LilyPond music typesetter
5
6   (c) 2000--2009 Han-Wen Nienhuys <hanwen@xs4all.nl>
7 */
8
9 #include "align-interface.hh"
10 #include "axis-group-interface.hh"
11 #include "grob-array.hh"
12 #include "hara-kiri-group-spanner.hh"
13 #include "international.hh"
14 #include "item.hh"
15 #include "page-layout-problem.hh"
16 #include "paper-book.hh"
17 #include "paper-column.hh"
18 #include "pointer-group-interface.hh"
19 #include "spanner.hh"
20 #include "skyline-pair.hh"
21 #include "system.hh"
22 #include "warn.hh"
23
24
25 MAKE_SCHEME_CALLBACK (Align_interface, align_to_minimum_distances, 1);
26 SCM
27 Align_interface::align_to_minimum_distances (SCM smob)
28 {
29   Grob *me = unsmob_grob (smob);
30
31   me->set_property ("positioning-done", SCM_BOOL_T);
32
33   SCM axis = scm_car (me->get_property ("axes"));
34   Axis ax = Axis (scm_to_int (axis));
35
36   Align_interface::align_elements_to_minimum_distances (me, ax);
37
38   return SCM_BOOL_T;
39 }
40
41 MAKE_SCHEME_CALLBACK (Align_interface, align_to_ideal_distances, 1);
42 SCM
43 Align_interface::align_to_ideal_distances (SCM smob)
44 {
45   Grob *me = unsmob_grob (smob);
46
47   me->set_property ("positioning-done", SCM_BOOL_T);
48
49   Align_interface::align_elements_to_ideal_distances (me);
50
51   return SCM_BOOL_T;
52 }
53
54 /* for each grob, find its upper and lower skylines. If the grob has
55    an empty extent, delete it from the list instead. If the extent is
56    non-empty but there is no skyline available (or pure is true), just
57    create a flat skyline from the bounding box */
58 // TODO(jneem): the pure and non-pure parts seem to share very little
59 // code. Split them into 2 functions, perhaps?
60 static void
61 get_skylines (Grob *me,
62               vector<Grob*> *const elements,
63               Axis a,
64               bool pure, int start, int end,
65               vector<Skyline_pair> *const ret)
66 {
67   Grob *other_common = common_refpoint_of_array (*elements, me, other_axis (a));
68   
69   for (vsize i = elements->size (); i--;)
70     {
71       Grob *g = (*elements)[i];
72       Skyline_pair skylines;
73
74       if (!pure)
75         {
76           Skyline_pair *skys = Skyline_pair::unsmob (g->get_property (a == Y_AXIS
77                                                                       ? "vertical-skylines"
78                                                                       : "horizontal-skylines"));
79           if (skys)
80             skylines = *skys;
81
82           /* This skyline was calculated relative to the grob g. In order to compare it to
83              skylines belonging to other grobs, we need to shift it so that it is relative
84              to the common reference. */
85           Real offset = g->relative_coordinate (other_common, other_axis (a));
86           skylines.shift (offset);
87         }
88       else
89         {
90           assert (a == Y_AXIS);
91           Interval extent = g->pure_height (g, start, end);
92
93           // This is a hack to get better accuracy on the pure-height of VerticalAlignment.
94           // It's quite common for a treble clef to be the highest element of one system
95           // and for a low note (or lyrics) to be the lowest note on another. The two will
96           // never collide, but the pure-height stuff only works with bounding boxes, so it
97           // doesn't know that. The result is a significant over-estimation of the pure-height,
98           // especially on systems with many staves. To correct for this, we build a skyline
99           // in two parts: the part we did above contains most of the grobs (note-heads, etc.)
100           // while the bit we're about to do only contains the breakable grobs at the beginning
101           // of the system. This way, the tall treble clefs are only compared with the treble
102           // clefs of the other staff and they will be ignored if the staff above is, for example,
103           // lyrics.
104           if (Axis_group_interface::has_interface (g)
105               && !Hara_kiri_group_spanner::request_suicide (g, start, end))
106             {
107               extent = Axis_group_interface::rest_of_line_pure_height (g, start, end);
108               Interval begin_of_line_extent = Axis_group_interface::begin_of_line_pure_height (g, start);
109               if (!begin_of_line_extent.is_empty ())
110                 {
111                   Box b;
112                   b[a] = begin_of_line_extent;
113                   b[other_axis (a)] = Interval (-infinity_f, -1);
114                   skylines.insert (b, 0, other_axis (a));
115                 }
116             }
117
118           if (!extent.is_empty ())
119             {
120               Box b;
121               b[a] = extent;
122               b[other_axis (a)] = Interval (0, infinity_f);
123               skylines.insert (b, 0, other_axis (a));
124             }
125         }
126
127       if (skylines.is_empty ())
128         elements->erase (elements->begin () + i);
129       else
130         ret->push_back (skylines);
131     }
132   reverse (*ret);
133 }
134
135 vector<Real>
136 Align_interface::get_minimum_translations (Grob *me,
137                                            vector<Grob*> const &all_grobs,
138                                            Axis a,
139                                            bool pure, int start, int end)
140 {
141   if (!pure && a == Y_AXIS && dynamic_cast<Spanner*> (me) && !me->get_system ())
142     me->programming_error ("vertical alignment called before line-breaking");
143   
144   Direction stacking_dir = robust_scm2dir (me->get_property ("stacking-dir"),
145                                            DOWN);
146   vector<Grob*> elems (all_grobs); // writable copy
147   vector<Skyline_pair> skylines;
148
149   get_skylines (me, &elems, a, pure, start, end, &skylines);
150
151   SCM forced_distances = ly_assoc_get (ly_symbol2scm ("alignment-distances"),
152                                        Page_layout_problem::get_details (me),
153                                        SCM_EOL);
154
155   Real where = 0;
156   Real default_padding = robust_scm2double (me->get_property ("padding"), 0.0);
157   vector<Real> translates;
158   Skyline down_skyline (stacking_dir);
159   Real last_spaceable_element_pos = 0;
160   Grob *last_spaceable_element = 0;
161   for (vsize j = 0; j < elems.size (); j++)
162     {
163       Real dy = 0;
164       Real padding = default_padding;
165
166       if (j == 0)
167         dy = skylines[j][-stacking_dir].max_height ();
168       else
169         {
170           down_skyline.merge (skylines[j-1][stacking_dir]);
171           dy = down_skyline.distance (skylines[j][-stacking_dir]);
172
173           SCM spec = Page_layout_problem::get_spacing_spec (elems[j-1], elems[j]);
174           Page_layout_problem::read_spacing_spec (spec, &padding, ly_symbol2scm ("padding"));
175
176           Real min_distance = 0;
177           if (Page_layout_problem::read_spacing_spec (spec, &min_distance, ly_symbol2scm ("minimum-distance")))
178             dy = max (dy, min_distance);
179
180           if (Page_layout_problem::is_spaceable (elems[j]) && last_spaceable_element)
181             {
182               // Spaceable staves may have min-distance and padding
183               // constraints coming from the previous spaceable staff
184               // as well as from the previous staff.
185               spec = Page_layout_problem::get_spacing_spec (last_spaceable_element, elems[j]);
186               Real spaceable_padding = 0;
187               Page_layout_problem::read_spacing_spec (spec,
188                                                       &spaceable_padding,
189                                                       ly_symbol2scm ("padding"));
190               padding = max (padding, spaceable_padding);
191
192               Real min_distance = 0;
193               if (Page_layout_problem::read_spacing_spec (spec,
194                                                           &min_distance,
195                                                           ly_symbol2scm ("minimum-distance")))
196                 dy = max (dy, min_distance + stacking_dir*(last_spaceable_element_pos - where));
197
198               if (scm_is_pair (forced_distances))
199                 {
200                   SCM forced_dist = scm_car (forced_distances);
201                   forced_distances = scm_cdr (forced_distances);
202
203                   if (scm_is_number (forced_dist))
204                     dy = scm_to_double (forced_dist) + stacking_dir * (last_spaceable_element_pos - where);
205                 }
206             }
207         }
208
209       if (isinf (dy)) /* if the skyline is empty, maybe max_height is infinity_f */
210         dy = 0.0;
211
212       dy = max (0.0, dy + padding);
213       down_skyline.raise (-stacking_dir * dy);
214       where += stacking_dir * dy;
215       translates.push_back (where);
216
217       if (Page_layout_problem::is_spaceable (elems[j]))
218         {
219           last_spaceable_element = elems[j];
220           last_spaceable_element_pos = where;
221         }
222     }
223
224   // So far, we've computed the translates for all the non-empty elements.
225   // Here, we set the translates for the empty elements: an empty element
226   // gets the same translation as the last non-empty element before it.
227   vector<Real> all_translates;
228   if (!translates.empty ())
229     {
230       Real w = translates[0];
231       for  (vsize i = 0, j = 0; j < all_grobs.size (); j++)
232         {
233           if (i < elems.size () && all_grobs[j] == elems[i])
234             w = translates[i++];
235           all_translates.push_back (w);
236         }
237     }
238   return all_translates;
239 }
240
241 void
242 Align_interface::align_elements_to_ideal_distances (Grob *me)
243 {
244   System *sys = me->get_system ();
245   Page_layout_problem layout (NULL, SCM_EOL, scm_list_1 (sys->self_scm ()));
246
247   layout.solution (true);
248 }
249
250 void
251 Align_interface::align_elements_to_minimum_distances (Grob *me, Axis a)
252 {
253   extract_grob_set (me, "elements", all_grobs);
254
255   vector<Real> translates = get_minimum_translations (me, all_grobs, a, false, 0, 0);
256   if (translates.size ())
257     for (vsize j = 0; j < all_grobs.size (); j++)
258       all_grobs[j]->translate_axis (translates[j], a);
259 }
260
261 Real
262 Align_interface::get_pure_child_y_translation (Grob *me, Grob *ch, int start, int end)
263 {
264   extract_grob_set (me, "elements", all_grobs);
265   SCM dy_scm = me->get_property ("forced-distance");
266
267   if (scm_is_number (dy_scm))
268     {
269       Real dy = scm_to_double (dy_scm) * robust_scm2dir (me->get_property ("stacking-dir"), DOWN);
270       Real pos = 0;
271       for (vsize i = 0; i < all_grobs.size (); i++)
272         {
273           if (all_grobs[i] == ch)
274             return pos;
275           if (!Hara_kiri_group_spanner::has_interface (all_grobs[i])
276               || !Hara_kiri_group_spanner::request_suicide (all_grobs[i], start, end))
277             pos += dy;
278         }
279     }
280   else
281     {
282       vector<Real> translates = get_minimum_translations (me, all_grobs, Y_AXIS, true, start, end);
283
284       if (translates.size ())
285         {
286           for (vsize i = 0; i < all_grobs.size (); i++)
287             if (all_grobs[i] == ch)
288               return translates[i];
289         }
290       else
291         return 0;
292     }
293
294   programming_error ("tried to get a translation for something that is no child of mine");
295   return 0;
296 }
297
298 Axis
299 Align_interface::axis (Grob *me)
300 {
301   return Axis (scm_to_int (scm_car (me->get_property ("axes"))));
302 }
303
304 void
305 Align_interface::add_element (Grob *me, Grob *element)
306 {
307   Axis a = Align_interface::axis (me);
308   SCM sym = axis_offset_symbol (a);
309   SCM proc = axis_parent_positioning (a);
310     
311   element->set_property (sym, proc);
312   Axis_group_interface::add_element (me, element);
313 }
314
315 void
316 Align_interface::set_ordered (Grob *me)
317 {
318   SCM ga_scm = me->get_object ("elements");
319   Grob_array *ga = unsmob_grob_array (ga_scm);
320   if (!ga)
321     {
322       ga_scm = Grob_array::make_array ();
323       ga = unsmob_grob_array (ga_scm);
324       me->set_object ("elements", ga_scm);
325     }
326
327   ga->set_ordered (true);
328 }
329
330 ADD_INTERFACE (Align_interface,
331                "Order grobs from top to bottom, left to right, right to left"
332                " or bottom to top.  For vertical alignments of staves, the"
333                " @code{break-system-details} of the left"
334                " @rinternals{NonMusicalPaperColumn} may be set to tune"
335                " vertical spacing.  Set @code{alignment-extra-space} to add"
336                " extra space for staves.  Set"
337                " @code{fixed-alignment-extra-space} to force staves in"
338                " @code{PianoStaff}s further apart.",
339                
340                /* properties */
341                "align-dir "
342                "axes "
343                "elements "
344                "padding "
345                "positioning-done "
346                "stacking-dir "
347                );