]> git.donarmstrong.com Git - lilypond.git/blob - lily/align-interface.cc
Doc-es: pre-merge update of texidoc committishes.
[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   vector<Real> all_translates;
253
254   if (!translates.empty ())
255     {
256       Real w = translates[0];
257       for  (vsize i = 0, j = 0; j < all_grobs.size (); j++)
258         {
259           if (i < elems.size () && all_grobs[j] == elems[i])
260             w = translates[i++];
261           all_translates.push_back (w);
262         }
263     }
264   return all_translates;
265 }
266
267 void
268 Align_interface::align_elements_to_extents (Grob *me, Axis a)
269 {
270   extract_grob_set (me, "elements", all_grobs);
271
272   vector<Real> translates = get_extents_aligned_translates (me, all_grobs, a, false, 0, 0);
273   if (translates.size ())
274     for (vsize j = 0; j < all_grobs.size (); j++)
275       all_grobs[j]->translate_axis (translates[j], a);
276 }
277
278 /* After we have already determined the y-offsets of our children, we may still
279    want to stretch them a little. */
280 void
281 Align_interface::stretch (Grob *me, Real amount, Axis a)
282 {
283   extract_grob_set (me, "elements", elts);
284   Real non_empty_elts = stretchable_children_count (me);
285   Real offset = 0.0;
286   Direction dir = robust_scm2dir (me->get_property ("stacking-dir"), DOWN);
287   for (vsize i = 1; i < elts.size (); i++)
288     {
289       if (!elts[i]->extent (me, a).is_empty ()
290           && !to_boolean (elts[i]->get_property ("keep-fixed-while-stretching")))
291         offset += amount / non_empty_elts;
292       elts[i]->translate_axis (dir * offset, a);
293     }
294   me->flush_extent_cache (Y_AXIS);
295 }
296
297 Real
298 Align_interface::get_pure_child_y_translation (Grob *me, Grob *ch, int start, int end)
299 {
300   extract_grob_set (me, "elements", all_grobs);
301   SCM dy_scm = me->get_property ("forced-distance");
302
303   if (scm_is_number (dy_scm))
304     {
305       Real dy = scm_to_double (dy_scm) * robust_scm2dir (me->get_property ("stacking-dir"), DOWN);
306       Real pos = 0;
307       for (vsize i = 0; i < all_grobs.size (); i++)
308         {
309           if (all_grobs[i] == ch)
310             return pos;
311           if (!Hara_kiri_group_spanner::has_interface (all_grobs[i])
312               || !Hara_kiri_group_spanner::request_suicide (all_grobs[i], start, end))
313             pos += dy;
314         }
315     }
316   else
317     {
318       vector<Real> translates = get_extents_aligned_translates (me, all_grobs, Y_AXIS, true, start, end);
319
320       if (translates.size ())
321         {
322           for (vsize i = 0; i < all_grobs.size (); i++)
323             if (all_grobs[i] == ch)
324               return translates[i];
325         }
326       else
327         return 0;
328     }
329
330   programming_error (_ ("tried to get a translation for something that is no child of mine"));
331   return 0;
332 }
333
334 Axis
335 Align_interface::axis (Grob *me)
336 {
337   return Axis (scm_to_int (scm_car (me->get_property ("axes"))));
338 }
339
340 void
341 Align_interface::add_element (Grob *me, Grob *element)
342 {
343   Axis a = Align_interface::axis (me);
344   SCM sym = axis_offset_symbol (a);
345   SCM proc = axis_parent_positioning (a);
346     
347   element->set_property (sym, proc);
348   Axis_group_interface::add_element (me, element);
349 }
350
351 void
352 Align_interface::set_ordered (Grob *me)
353 {
354   SCM ga_scm = me->get_object ("elements");
355   Grob_array *ga = unsmob_grob_array (ga_scm);
356   if (!ga)
357     {
358       ga_scm = Grob_array::make_array ();
359       ga = unsmob_grob_array (ga_scm);
360       me->set_object ("elements", ga_scm);
361     }
362
363   ga->set_ordered (true);
364 }
365
366 int
367 Align_interface::stretchable_children_count (Grob const *me)
368 {
369   extract_grob_set (me, "elements", elts);
370   int ret = 0;
371
372   /* start at 1: we will never move the first child while stretching */
373   for (vsize i = 1; i < elts.size (); i++)
374     if (!to_boolean (elts[i]->get_property ("keep-fixed-while-stretching"))
375         && !elts[i]->extent (elts[i], Y_AXIS).is_empty ())
376       ret++;
377
378   return ret;
379 }
380
381 MAKE_SCHEME_CALLBACK (Align_interface, calc_max_stretch, 1)
382 SCM
383 Align_interface::calc_max_stretch (SCM smob)
384 {
385   Grob *me = unsmob_grob (smob);
386   Spanner *spanner_me = dynamic_cast<Spanner*> (me);
387   Real ret = 0;
388
389   if (spanner_me && stretchable_children_count (me) > 0)
390     {
391       Paper_column *left = dynamic_cast<Paper_column*> (spanner_me->get_bound (LEFT));
392       Real height = me->extent (me, Y_AXIS).length ();
393       SCM line_break_details = left->get_property ("line-break-system-details");
394       SCM fixed_offsets = scm_assq (ly_symbol2scm ("alignment-offsets"),
395                                     line_break_details);
396
397       /* if there are fixed offsets, we refuse to stretch */
398       if (fixed_offsets != SCM_BOOL_F)
399         ret = 0;
400       else
401         ret = height * height / 80.0; /* why this, exactly? -- jneem */
402     }
403   return scm_from_double (ret);
404 }
405
406 ADD_INTERFACE (Align_interface,
407                "Order grobs from top to bottom, left to right, right to left"
408                " or bottom to top.  For vertical alignments of staves, the"
409                " @code{break-system-details} of the left"
410                " @rinternals{NonMusicalPaperColumn} may be set to tune"
411                " vertical spacing.  Set @code{alignment-extra-space} to add"
412                " extra space for staves.  Set"
413                " @code{fixed-alignment-extra-space} to force staves in"
414                " @code{PianoStaff}s further apart.",
415                
416                /* properties */
417                "align-dir "
418                "axes "
419                "elements "
420                "padding "
421                "positioning-done "
422                "stacking-dir "
423                "threshold "
424                );