]> git.donarmstrong.com Git - lilypond.git/blob - lily/align-interface.cc
Doc-de: update macros.itely and nitpicks
[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               && !Hara_kiri_group_spanner::request_suicide (g, start, end))
159             {
160               Interval begin_of_line_extent = Axis_group_interface::begin_of_line_pure_height (g, start);
161               if (!begin_of_line_extent.is_empty ())
162                 {
163                   Box b;
164                   b[a] = begin_of_line_extent;
165                   b[other_axis (a)] = Interval (-infinity_f, -1);
166                   skylines.insert (b, 0, other_axis (a));
167                 }
168             }
169         }
170
171       if (skylines.is_empty ())
172         elements->erase (elements->begin () + i);
173       else
174         ret->push_back (skylines);
175     }
176   reverse (*ret);
177 }
178
179 vector<Real>
180 Align_interface::get_extents_aligned_translates (Grob *me,
181                                                  vector<Grob*> const &all_grobs,
182                                                  Axis a,
183                                                  bool pure, int start, int end)
184 {
185   Spanner *me_spanner = dynamic_cast<Spanner *> (me);
186
187
188   SCM line_break_details = SCM_EOL;
189   if (a == Y_AXIS && me_spanner)
190     {
191       if (pure)
192         line_break_details = get_root_system (me)->column (start)->get_property ("line-break-system-details");
193       else
194         line_break_details = me_spanner->get_bound (LEFT)->get_property ("line-break-system-details");
195
196       if (!me->get_system () && !pure)
197         me->programming_error ("vertical alignment called before line-breaking");
198     }
199   
200   Direction stacking_dir = robust_scm2dir (me->get_property ("stacking-dir"),
201                                            DOWN);
202
203   vector<Grob*> elems (all_grobs); // writable copy
204   vector<Skyline_pair> skylines;
205
206   get_skylines (me, &elems, a, pure, start, end, &skylines);
207
208   Real where = 0;
209   /* TODO: extra-space stuff belongs to two-pass spacing. Delete me */
210   SCM extra_space_handle = scm_assq (ly_symbol2scm ("alignment-extra-space"), line_break_details);
211   Real extra_space = robust_scm2double (scm_is_pair (extra_space_handle)
212                                         ? scm_cdr (extra_space_handle)
213                                         : SCM_EOL,
214                                         0.0);
215
216   Real padding = robust_scm2double (me->get_property ("padding"), 0.0);
217   vector<Real> translates;
218   Skyline down_skyline (stacking_dir);
219   for (vsize j = 0; j < elems.size (); j++)
220     {
221       Real dy = 0;
222       if (j == 0)
223         dy = skylines[j][-stacking_dir].max_height ();
224       else
225         {
226           down_skyline.merge (skylines[j-1][stacking_dir]);
227           dy = down_skyline.distance (skylines[j][-stacking_dir]);
228         }
229
230       if (isinf (dy)) /* if the skyline is empty, maybe max_height is infinity_f */
231         dy = 0.0;
232
233       dy = max (0.0, dy + padding + extra_space / elems.size ());
234       down_skyline.raise (-stacking_dir * dy);
235       where += stacking_dir * dy;
236       translates.push_back (where);
237     }
238
239   SCM offsets_handle = scm_assq (ly_symbol2scm ("alignment-offsets"),
240                                  line_break_details);
241   if (scm_is_pair (offsets_handle))
242     {
243       vsize i = 0;
244  
245       for (SCM s = scm_cdr (offsets_handle);
246            scm_is_pair (s) && i < translates.size (); s = scm_cdr (s), i++)
247         {
248           if (scm_is_number (scm_car (s)))
249             translates[i] = scm_to_double (scm_car (s));
250         }
251     }
252
253   // So far, we've computed the translates for all the non-empty elements.
254   // Here, we set the translates for the empty elements: an empty element
255   // gets the same translation as the last non-empty element before it.
256   vector<Real> all_translates;
257   if (!translates.empty ())
258     {
259       Real w = translates[0];
260       for  (vsize i = 0, j = 0; j < all_grobs.size (); j++)
261         {
262           if (i < elems.size () && all_grobs[j] == elems[i])
263             w = translates[i++];
264           all_translates.push_back (w);
265         }
266     }
267   return all_translates;
268 }
269
270 void
271 Align_interface::align_elements_to_extents (Grob *me, Axis a)
272 {
273   extract_grob_set (me, "elements", all_grobs);
274
275   vector<Real> translates = get_extents_aligned_translates (me, all_grobs, a, false, 0, 0);
276   if (translates.size ())
277     for (vsize j = 0; j < all_grobs.size (); j++)
278       all_grobs[j]->translate_axis (translates[j], a);
279 }
280
281 /* After we have already determined the y-offsets of our children, we may still
282    want to stretch them a little. */
283 void
284 Align_interface::stretch (Grob *me, Real amount, Axis a)
285 {
286   extract_grob_set (me, "elements", elts);
287   Real non_empty_elts = stretchable_children_count (me);
288   Real offset = 0.0;
289   Direction dir = robust_scm2dir (me->get_property ("stacking-dir"), DOWN);
290   for (vsize i = 1; i < elts.size (); i++)
291     {
292       if (!elts[i]->extent (me, a).is_empty ()
293           && !to_boolean (elts[i]->get_property ("keep-fixed-while-stretching")))
294         offset += amount / non_empty_elts;
295       elts[i]->translate_axis (dir * offset, a);
296     }
297   me->flush_extent_cache (Y_AXIS);
298 }
299
300 Real
301 Align_interface::get_pure_child_y_translation (Grob *me, Grob *ch, int start, int end)
302 {
303   extract_grob_set (me, "elements", all_grobs);
304   SCM dy_scm = me->get_property ("forced-distance");
305
306   if (scm_is_number (dy_scm))
307     {
308       Real dy = scm_to_double (dy_scm) * robust_scm2dir (me->get_property ("stacking-dir"), DOWN);
309       Real pos = 0;
310       for (vsize i = 0; i < all_grobs.size (); i++)
311         {
312           if (all_grobs[i] == ch)
313             return pos;
314           if (!Hara_kiri_group_spanner::has_interface (all_grobs[i])
315               || !Hara_kiri_group_spanner::request_suicide (all_grobs[i], start, end))
316             pos += dy;
317         }
318     }
319   else
320     {
321       vector<Real> translates = get_extents_aligned_translates (me, all_grobs, Y_AXIS, true, start, end);
322
323       if (translates.size ())
324         {
325           for (vsize i = 0; i < all_grobs.size (); i++)
326             if (all_grobs[i] == ch)
327               return translates[i];
328         }
329       else
330         return 0;
331     }
332
333   programming_error (_ ("tried to get a translation for something that is no child of mine"));
334   return 0;
335 }
336
337 Axis
338 Align_interface::axis (Grob *me)
339 {
340   return Axis (scm_to_int (scm_car (me->get_property ("axes"))));
341 }
342
343 void
344 Align_interface::add_element (Grob *me, Grob *element)
345 {
346   Axis a = Align_interface::axis (me);
347   SCM sym = axis_offset_symbol (a);
348   SCM proc = axis_parent_positioning (a);
349     
350   element->set_property (sym, proc);
351   Axis_group_interface::add_element (me, element);
352 }
353
354 void
355 Align_interface::set_ordered (Grob *me)
356 {
357   SCM ga_scm = me->get_object ("elements");
358   Grob_array *ga = unsmob_grob_array (ga_scm);
359   if (!ga)
360     {
361       ga_scm = Grob_array::make_array ();
362       ga = unsmob_grob_array (ga_scm);
363       me->set_object ("elements", ga_scm);
364     }
365
366   ga->set_ordered (true);
367 }
368
369 int
370 Align_interface::stretchable_children_count (Grob const *me)
371 {
372   extract_grob_set (me, "elements", elts);
373   int ret = 0;
374
375   /* start at 1: we will never move the first child while stretching */
376   for (vsize i = 1; i < elts.size (); i++)
377     if (!to_boolean (elts[i]->get_property ("keep-fixed-while-stretching"))
378         && !elts[i]->extent (elts[i], Y_AXIS).is_empty ())
379       ret++;
380
381   return ret;
382 }
383
384 MAKE_SCHEME_CALLBACK (Align_interface, calc_max_stretch, 1)
385 SCM
386 Align_interface::calc_max_stretch (SCM smob)
387 {
388   Grob *me = unsmob_grob (smob);
389   Spanner *spanner_me = dynamic_cast<Spanner*> (me);
390   Real ret = 0;
391
392   if (spanner_me && stretchable_children_count (me) > 0)
393     {
394       Paper_column *left = dynamic_cast<Paper_column*> (spanner_me->get_bound (LEFT));
395       Real height = me->extent (me, Y_AXIS).length ();
396       SCM line_break_details = left->get_property ("line-break-system-details");
397       SCM fixed_offsets = scm_assq (ly_symbol2scm ("alignment-offsets"),
398                                     line_break_details);
399
400       /* if there are fixed offsets, we refuse to stretch */
401       if (fixed_offsets != SCM_BOOL_F)
402         ret = 0;
403       else
404         ret = height * height / 80.0; /* why this, exactly? -- jneem */
405     }
406   return scm_from_double (ret);
407 }
408
409 ADD_INTERFACE (Align_interface,
410                "Order grobs from top to bottom, left to right, right to left"
411                " or bottom to top.  For vertical alignments of staves, the"
412                " @code{break-system-details} of the left"
413                " @rinternals{NonMusicalPaperColumn} may be set to tune"
414                " vertical spacing.  Set @code{alignment-extra-space} to add"
415                " extra space for staves.  Set"
416                " @code{fixed-alignment-extra-space} to force staves in"
417                " @code{PianoStaff}s further apart.",
418                
419                /* properties */
420                "align-dir "
421                "axes "
422                "elements "
423                "padding "
424                "positioning-done "
425                "stacking-dir "
426                "threshold "
427                );