]> 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   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 "paper-column.hh"
16 #include "pointer-group-interface.hh"
17 #include "spanner.hh"
18 #include "skyline-pair.hh"
19 #include "system.hh"
20 #include "warn.hh"
21
22 /*
23   TODO: for vertical spacing, should also include a rod & spring
24   scheme of sorts into this: the alignment should default to a certain
25   distance between element refpoints, unless bbox force a bigger
26   distance.
27  */
28
29 MAKE_SCHEME_CALLBACK (Align_interface, calc_positioning_done, 1);
30 SCM
31 Align_interface::calc_positioning_done (SCM smob)
32 {
33   Grob *me = unsmob_grob (smob);
34
35   me->set_property ("positioning-done", SCM_BOOL_T);
36
37   SCM axis = scm_car (me->get_property ("axes"));
38   Axis ax = Axis (scm_to_int (axis));
39
40   Align_interface::align_elements_to_extents (me, ax);
41
42   return SCM_BOOL_T;
43 }
44
45 /*
46   TODO: This belongs to the old two-pass spacing. Delete me.
47 */
48 MAKE_SCHEME_CALLBACK (Align_interface, stretch_after_break, 1)
49 SCM
50 Align_interface::stretch_after_break (SCM grob)
51 {
52   Grob *me = unsmob_grob (grob);
53
54   Spanner *me_spanner = dynamic_cast<Spanner *> (me);
55   extract_grob_set (me, "elements", elems);
56
57   if (me_spanner && elems.size ())
58     {
59       Grob *common = common_refpoint_of_array (elems, me, Y_AXIS);
60
61       /* force position callbacks */
62       for (vsize i = 0; i < elems.size (); i++)
63         elems[i]->relative_coordinate (common, Y_AXIS);
64
65       SCM details = me_spanner->get_bound (LEFT)->get_property ("line-break-system-details");
66       SCM extra_space_handle = scm_assoc (ly_symbol2scm ("fixed-alignment-extra-space"), details);
67       
68       Real extra_space = robust_scm2double (scm_is_pair (extra_space_handle)
69                                             ? scm_cdr (extra_space_handle)
70                                             : SCM_EOL,
71                                             0.0);
72
73       Direction stacking_dir = robust_scm2dir (me->get_property ("stacking-dir"),
74                                                DOWN);
75       Real delta  = extra_space / elems.size () * stacking_dir;
76       for (vsize i = 0; i < elems.size (); i++)
77         elems[i]->translate_axis (i * delta, Y_AXIS);
78     }
79   
80   return SCM_UNSPECIFIED;
81 }
82
83 /* for each grob, find its upper and lower skylines. If the grob has
84    an empty extent, delete it from the list instead. If the extent is
85    non-empty but there is no skyline available (or pure is true), just
86    create a flat skyline from the bounding box */
87 // TODO(jneem): the pure and non-pure parts seem to share very little
88 // code. Split them into 2 functions, perhaps?
89 static void
90 get_skylines (Grob *me,
91               vector<Grob*> *const elements,
92               Axis a,
93               bool pure, int start, int end,
94               vector<Skyline_pair> *const ret)
95 {
96   Grob *other_common = common_refpoint_of_array (*elements, me, other_axis (a));
97   
98   for (vsize i = elements->size (); i--;)
99     {
100       Grob *g = (*elements)[i];
101       Skyline_pair skylines;
102
103       if (!pure)
104         {
105           Skyline_pair *skys = Skyline_pair::unsmob (g->get_property (a == Y_AXIS
106                                                                       ? "vertical-skylines"
107                                                                       : "horizontal-skylines"));
108           if (skys)
109             skylines = *skys;
110
111           /* this is perhaps an abuse of minimum-?-extent: maybe we should create
112              another property? But it seems that the only (current) use of
113              minimum-Y-extent is to separate vertically-aligned elements */
114           SCM min_extent = g->get_property (a == X_AXIS
115                                             ? ly_symbol2scm ("minimum-X-extent")
116                                             : ly_symbol2scm ("minimum-Y-extent"));
117
118           if (is_number_pair (min_extent))
119             {
120               Box b;
121               Interval other_extent = g->extent (other_common, other_axis (a));
122               b[a] = ly_scm2interval (min_extent);
123               b[other_axis (a)] = other_extent;
124               if (!other_extent.is_empty ())
125                 skylines.insert (b, 0, other_axis (a));
126             }
127
128           /* This skyline was calculated relative to the grob g. In order to compare it to
129              skylines belonging to other grobs, we need to shift it so that it is relative
130              to the common reference. */
131           Real offset = g->relative_coordinate (other_common, other_axis (a));
132           skylines.shift (offset);
133         }
134       else
135         {
136           assert (a == Y_AXIS);
137           Interval extent = g->pure_height (g, start, end);
138           if (!extent.is_empty ())
139             {
140               Box b;
141               b[a] = extent;
142               b[other_axis (a)] = Interval (0, infinity_f);
143               skylines.insert (b, 0, other_axis (a));
144             }
145
146           // This is a hack to get better accuracy on the pure-height of VerticalAlignment.
147           // It's quite common for a treble clef to be the highest element of one system
148           // and for a low note (or lyrics) to be the lowest note on another. The two will
149           // never collide, but the pure-height stuff only works with bounding boxes, so it
150           // doesn't know that. The result is a significant over-estimation of the pure-height,
151           // especially on systems with many staves. To correct for this, we build a skyline
152           // in two parts: the part we did above contains most of the grobs (note-heads, etc.)
153           // while the bit we're about to do only contains the breakable grobs at the beginning
154           // of the system. This way, the tall treble clefs are only compared with the treble
155           // clefs of the other staff and they will be ignored if the staff above is, for example,
156           // lyrics.
157           if (Axis_group_interface::has_interface (g))
158             {
159               Interval begin_of_line_extent = Axis_group_interface::begin_of_line_pure_height (g, start);
160               if (!begin_of_line_extent.is_empty ())
161                 {
162                   Box b;
163                   b[a] = begin_of_line_extent;
164                   b[other_axis (a)] = Interval (-infinity_f, -1);
165                   skylines.insert (b, 0, other_axis (a));
166                 }
167             }
168         }
169
170       if (skylines.is_empty ())
171         elements->erase (elements->begin () + i);
172       else
173         ret->push_back (skylines);
174     }
175   reverse (*ret);
176 }
177
178 vector<Real>
179 Align_interface::get_extents_aligned_translates (Grob *me,
180                                                  vector<Grob*> const &all_grobs,
181                                                  Axis a,
182                                                  bool pure, int start, int end)
183 {
184   Spanner *me_spanner = dynamic_cast<Spanner *> (me);
185
186
187   SCM line_break_details = SCM_EOL;
188   if (a == Y_AXIS && me_spanner)
189     {
190       if (pure)
191         line_break_details = get_root_system (me)->column (start)->get_property ("line-break-system-details");
192       else
193         line_break_details = me_spanner->get_bound (LEFT)->get_property ("line-break-system-details");
194
195       if (!me->get_system () && !pure)
196         me->programming_error ("vertical alignment called before line-breaking");
197     }
198   
199   Direction stacking_dir = robust_scm2dir (me->get_property ("stacking-dir"),
200                                            DOWN);
201
202   vector<Grob*> elems (all_grobs); // writable copy
203   vector<Skyline_pair> skylines;
204
205   get_skylines (me, &elems, a, pure, start, end, &skylines);
206
207   Real where = 0;
208   /* TODO: extra-space stuff belongs to two-pass spacing. Delete me */
209   SCM extra_space_handle = scm_assq (ly_symbol2scm ("alignment-extra-space"), line_break_details);
210   Real extra_space = robust_scm2double (scm_is_pair (extra_space_handle)
211                                         ? scm_cdr (extra_space_handle)
212                                         : SCM_EOL,
213                                         0.0);
214
215   Real padding = robust_scm2double (me->get_property ("padding"), 0.0);
216   vector<Real> translates;
217   Skyline down_skyline (stacking_dir);
218   for (vsize j = 0; j < elems.size (); j++)
219     {
220       Real dy = 0;
221       if (j == 0)
222         dy = skylines[j][-stacking_dir].max_height ();
223       else
224         {
225           down_skyline.merge (skylines[j-1][stacking_dir]);
226           dy = down_skyline.distance (skylines[j][-stacking_dir]);
227         }
228
229       if (isinf (dy)) /* if the skyline is empty, maybe max_height is infinity_f */
230         dy = 0.0;
231
232       dy = max (0.0, dy + padding + extra_space / elems.size ());
233       down_skyline.raise (-stacking_dir * dy);
234       where += stacking_dir * dy;
235       translates.push_back (where);
236     }
237
238   SCM offsets_handle = scm_assq (ly_symbol2scm ("alignment-offsets"),
239                                  line_break_details);
240   if (scm_is_pair (offsets_handle))
241     {
242       vsize i = 0;
243  
244       for (SCM s = scm_cdr (offsets_handle);
245            scm_is_pair (s) && i < translates.size (); s = scm_cdr (s), i++)
246         {
247           if (scm_is_number (scm_car (s)))
248             translates[i] = scm_to_double (scm_car (s));
249         }
250     }
251
252   // So far, we've computed the translates for all the non-empty elements.
253   // Here, we set the translates for the empty elements: an empty element
254   // gets the same translation as the last non-empty element before it.
255   vector<Real> all_translates;
256   if (!translates.empty ())
257     {
258       Real w = translates[0];
259       for  (vsize i = 0, j = 0; j < all_grobs.size (); j++)
260         {
261           if (i < elems.size () && all_grobs[j] == elems[i])
262             w = translates[i++];
263           all_translates.push_back (w);
264         }
265     }
266   return all_translates;
267 }
268
269 void
270 Align_interface::align_elements_to_extents (Grob *me, Axis a)
271 {
272   extract_grob_set (me, "elements", all_grobs);
273
274   vector<Real> translates = get_extents_aligned_translates (me, all_grobs, a, false, 0, 0);
275   if (translates.size ())
276     for (vsize j = 0; j < all_grobs.size (); j++)
277       all_grobs[j]->translate_axis (translates[j], a);
278 }
279
280 /* After we have already determined the y-offsets of our children, we may still
281    want to stretch them a little. */
282 void
283 Align_interface::stretch (Grob *me, Real amount, Axis a)
284 {
285   extract_grob_set (me, "elements", elts);
286   Real non_empty_elts = stretchable_children_count (me);
287   Real offset = 0.0;
288   Direction dir = robust_scm2dir (me->get_property ("stacking-dir"), DOWN);
289   for (vsize i = 1; i < elts.size (); i++)
290     {
291       if (!elts[i]->extent (me, a).is_empty ()
292           && !to_boolean (elts[i]->get_property ("keep-fixed-while-stretching")))
293         offset += amount / non_empty_elts;
294       elts[i]->translate_axis (dir * offset, a);
295     }
296   me->flush_extent_cache (Y_AXIS);
297 }
298
299 Real
300 Align_interface::get_pure_child_y_translation (Grob *me, Grob *ch, int start, int end)
301 {
302   extract_grob_set (me, "elements", all_grobs);
303   SCM dy_scm = me->get_property ("forced-distance");
304
305   if (scm_is_number (dy_scm))
306     {
307       Real dy = scm_to_double (dy_scm) * robust_scm2dir (me->get_property ("stacking-dir"), DOWN);
308       Real pos = 0;
309       for (vsize i = 0; i < all_grobs.size (); i++)
310         {
311           if (all_grobs[i] == ch)
312             return pos;
313           if (!Hara_kiri_group_spanner::has_interface (all_grobs[i])
314               || !Hara_kiri_group_spanner::request_suicide (all_grobs[i], start, end))
315             pos += dy;
316         }
317     }
318   else
319     {
320       vector<Real> translates = get_extents_aligned_translates (me, all_grobs, Y_AXIS, true, 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
332   programming_error (_ ("tried to get a translation for something that is no child of mine"));
333   return 0;
334 }
335
336 Axis
337 Align_interface::axis (Grob *me)
338 {
339   return Axis (scm_to_int (scm_car (me->get_property ("axes"))));
340 }
341
342 void
343 Align_interface::add_element (Grob *me, Grob *element)
344 {
345   Axis a = Align_interface::axis (me);
346   SCM sym = axis_offset_symbol (a);
347   SCM proc = axis_parent_positioning (a);
348     
349   element->set_property (sym, proc);
350   Axis_group_interface::add_element (me, element);
351 }
352
353 void
354 Align_interface::set_ordered (Grob *me)
355 {
356   SCM ga_scm = me->get_object ("elements");
357   Grob_array *ga = unsmob_grob_array (ga_scm);
358   if (!ga)
359     {
360       ga_scm = Grob_array::make_array ();
361       ga = unsmob_grob_array (ga_scm);
362       me->set_object ("elements", ga_scm);
363     }
364
365   ga->set_ordered (true);
366 }
367
368 int
369 Align_interface::stretchable_children_count (Grob const *me)
370 {
371   extract_grob_set (me, "elements", elts);
372   int ret = 0;
373
374   /* start at 1: we will never move the first child while stretching */
375   for (vsize i = 1; i < elts.size (); i++)
376     if (!to_boolean (elts[i]->get_property ("keep-fixed-while-stretching"))
377         && !elts[i]->extent (elts[i], Y_AXIS).is_empty ())
378       ret++;
379
380   return ret;
381 }
382
383 MAKE_SCHEME_CALLBACK (Align_interface, calc_max_stretch, 1)
384 SCM
385 Align_interface::calc_max_stretch (SCM smob)
386 {
387   Grob *me = unsmob_grob (smob);
388   Spanner *spanner_me = dynamic_cast<Spanner*> (me);
389   Real ret = 0;
390
391   if (spanner_me && stretchable_children_count (me) > 0)
392     {
393       Paper_column *left = dynamic_cast<Paper_column*> (spanner_me->get_bound (LEFT));
394       Real height = me->extent (me, Y_AXIS).length ();
395       SCM line_break_details = left->get_property ("line-break-system-details");
396       SCM fixed_offsets = scm_assq (ly_symbol2scm ("alignment-offsets"),
397                                     line_break_details);
398
399       /* if there are fixed offsets, we refuse to stretch */
400       if (fixed_offsets != SCM_BOOL_F)
401         ret = 0;
402       else
403         ret = height * height / 80.0; /* why this, exactly? -- jneem */
404     }
405   return scm_from_double (ret);
406 }
407
408 ADD_INTERFACE (Align_interface,
409                "Order grobs from top to bottom, left to right, right to left"
410                " or bottom to top.  For vertical alignments of staves, the"
411                " @code{break-system-details} of the left"
412                " @rinternals{NonMusicalPaperColumn} may be set to tune"
413                " vertical spacing.  Set @code{alignment-extra-space} to add"
414                " extra space for staves.  Set"
415                " @code{fixed-alignment-extra-space} to force staves in"
416                " @code{PianoStaff}s further apart.",
417                
418                /* properties */
419                "align-dir "
420                "axes "
421                "elements "
422                "padding "
423                "positioning-done "
424                "stacking-dir "
425                "threshold "
426                );