]> git.donarmstrong.com Git - lilypond.git/blob - lily/align-interface.cc
* Ignore test failures on non-i386/amd64 architectures (Closes:
[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 static void
88 get_skylines (Grob *me,
89               vector<Grob*> *const elements,
90               Axis a,
91               bool pure, int start, int end,
92               vector<Skyline_pair> *const ret)
93 {
94   Grob *other_common = common_refpoint_of_array (*elements, me, other_axis (a));
95   
96   for (vsize i = elements->size (); i--;)
97     {
98       Grob *g = (*elements)[i];
99       Skyline_pair skylines;
100
101       if (!pure)
102         {
103           Skyline_pair *skys = Skyline_pair::unsmob (g->get_property (a == Y_AXIS
104                                                                       ? "vertical-skylines"
105                                                                       : "horizontal-skylines"));
106           if (skys)
107             skylines = *skys;
108
109           /* this is perhaps an abuse of minimum-?-extent: maybe we should create
110              another property? But it seems that the only (current) use of
111              minimum-Y-extent is to separate vertically-aligned elements */
112           SCM min_extent = g->get_property (a == X_AXIS
113                                             ? ly_symbol2scm ("minimum-X-extent")
114                                             : ly_symbol2scm ("minimum-Y-extent"));
115
116           if (is_number_pair (min_extent))
117             {
118               Box b;
119               Interval other_extent = g->extent (other_common, other_axis (a));
120               b[a] = ly_scm2interval (min_extent);
121               b[other_axis (a)] = other_extent;
122               if (!other_extent.is_empty ())
123                 skylines.insert (b, 0, other_axis (a));
124             }
125
126           /* This skyline was calculated relative to the grob g. In order to compare it to
127              skylines belonging to other grobs, we need to shift it so that it is relative
128              to the common reference. */
129           Real offset = g->relative_coordinate (other_common, other_axis (a));
130           skylines.shift (offset);
131         }
132       else
133         {
134           assert (a == Y_AXIS);
135           Interval extent = g->pure_height (g, start, end);
136           if (!extent.is_empty ())
137             {
138               Box b;
139               b[a] = extent;
140               b[other_axis (a)] = Interval (-infinity_f, infinity_f);
141               skylines.insert (b, 0, other_axis (a));
142             }
143         }
144
145       if (skylines.is_empty ())
146         elements->erase (elements->begin () + i);
147       else
148         ret->push_back (skylines);
149     }
150   reverse (*ret);
151 }
152
153 vector<Real>
154 Align_interface::get_extents_aligned_translates (Grob *me,
155                                                  vector<Grob*> const &all_grobs,
156                                                  Axis a,
157                                                  bool pure, int start, int end)
158 {
159   Spanner *me_spanner = dynamic_cast<Spanner *> (me);
160
161
162   SCM line_break_details = SCM_EOL;
163   if (a == Y_AXIS && me_spanner)
164     {
165       if (pure)
166         line_break_details = get_root_system (me)->column (start)->get_property ("line-break-system-details");
167       else
168         line_break_details = me_spanner->get_bound (LEFT)->get_property ("line-break-system-details");
169
170       if (!me->get_system () && !pure)
171         me->programming_error ("vertical alignment called before line-breaking");
172     }
173   
174   Direction stacking_dir = robust_scm2dir (me->get_property ("stacking-dir"),
175                                            DOWN);
176
177   vector<Grob*> elems (all_grobs); // writable copy
178   vector<Skyline_pair> skylines;
179
180   get_skylines (me, &elems, a, pure, start, end, &skylines);
181
182   Real where = 0;
183   /* TODO: extra-space stuff belongs to two-pass spacing. Delete me */
184   SCM extra_space_handle = scm_assq (ly_symbol2scm ("alignment-extra-space"), line_break_details);
185   Real extra_space = robust_scm2double (scm_is_pair (extra_space_handle)
186                                         ? scm_cdr (extra_space_handle)
187                                         : SCM_EOL,
188                                         0.0);
189
190   Real padding = robust_scm2double (me->get_property ("padding"), 0.0);
191   vector<Real> translates;
192   Skyline down_skyline (stacking_dir);
193   for (vsize j = 0; j < elems.size (); j++)
194     {
195       Real dy = 0;
196       if (j == 0)
197         dy = skylines[j][-stacking_dir].max_height ();
198       else
199         {
200           down_skyline.merge (skylines[j-1][stacking_dir]);
201           dy = down_skyline.distance (skylines[j][-stacking_dir]);
202         }
203
204       if (isinf (dy)) /* if the skyline is empty, maybe max_height is infinity_f */
205         dy = 0.0;
206
207       dy = max (0.0, dy + padding + extra_space / elems.size ());
208       down_skyline.raise (-stacking_dir * dy);
209       where += stacking_dir * dy;
210       translates.push_back (where);
211     }
212
213   SCM offsets_handle = scm_assq (ly_symbol2scm ("alignment-offsets"),
214                                  line_break_details);
215   if (scm_is_pair (offsets_handle))
216     {
217       vsize i = 0;
218  
219       for (SCM s = scm_cdr (offsets_handle);
220            scm_is_pair (s) && i < translates.size (); s = scm_cdr (s), i++)
221         {
222           if (scm_is_number (scm_car (s)))
223             translates[i] = scm_to_double (scm_car (s));
224         }
225     }
226
227   // So far, we've computed the translates for all the non-empty elements.
228   // Here, we set the translates for the empty elements: an empty element
229   // gets the same translation as the last non-empty element before it.
230   vector<Real> all_translates;
231   if (!translates.empty ())
232     {
233       Real w = translates[0];
234       for  (vsize i = 0, j = 0; j < all_grobs.size (); j++)
235         {
236           if (i < elems.size () && all_grobs[j] == elems[i])
237             w = translates[i++];
238           all_translates.push_back (w);
239         }
240     }
241   return all_translates;
242 }
243
244 void
245 Align_interface::align_elements_to_extents (Grob *me, Axis a)
246 {
247   extract_grob_set (me, "elements", all_grobs);
248
249   vector<Real> translates = get_extents_aligned_translates (me, all_grobs, a, false, 0, 0);
250   if (translates.size ())
251     for (vsize j = 0; j < all_grobs.size (); j++)
252       all_grobs[j]->translate_axis (translates[j], a);
253 }
254
255 /* After we have already determined the y-offsets of our children, we may still
256    want to stretch them a little. */
257 void
258 Align_interface::stretch (Grob *me, Real amount, Axis a)
259 {
260   extract_grob_set (me, "elements", elts);
261   Real non_empty_elts = stretchable_children_count (me);
262   Real offset = 0.0;
263   Direction dir = robust_scm2dir (me->get_property ("stacking-dir"), DOWN);
264   for (vsize i = 1; i < elts.size (); i++)
265     {
266       if (!elts[i]->extent (me, a).is_empty ()
267           && !to_boolean (elts[i]->get_property ("keep-fixed-while-stretching")))
268         offset += amount / non_empty_elts;
269       elts[i]->translate_axis (dir * offset, a);
270     }
271   me->flush_extent_cache (Y_AXIS);
272 }
273
274 Real
275 Align_interface::get_pure_child_y_translation (Grob *me, Grob *ch, int start, int end)
276 {
277   extract_grob_set (me, "elements", all_grobs);
278   SCM dy_scm = me->get_property ("forced-distance");
279
280   if (scm_is_number (dy_scm))
281     {
282       Real dy = scm_to_double (dy_scm) * robust_scm2dir (me->get_property ("stacking-dir"), DOWN);
283       Real pos = 0;
284       for (vsize i = 0; i < all_grobs.size (); i++)
285         {
286           if (all_grobs[i] == ch)
287             return pos;
288           if (!Hara_kiri_group_spanner::has_interface (all_grobs[i])
289               || !Hara_kiri_group_spanner::request_suicide (all_grobs[i], start, end))
290             pos += dy;
291         }
292     }
293   else
294     {
295       vector<Real> translates = get_extents_aligned_translates (me, all_grobs, Y_AXIS, true, start, end);
296
297       if (translates.size ())
298         {
299           for (vsize i = 0; i < all_grobs.size (); i++)
300             if (all_grobs[i] == ch)
301               return translates[i];
302         }
303       else
304         return 0;
305     }
306
307   programming_error (_ ("tried to get a translation for something that is no child of mine"));
308   return 0;
309 }
310
311 Axis
312 Align_interface::axis (Grob *me)
313 {
314   return Axis (scm_to_int (scm_car (me->get_property ("axes"))));
315 }
316
317 void
318 Align_interface::add_element (Grob *me, Grob *element)
319 {
320   Axis a = Align_interface::axis (me);
321   SCM sym = axis_offset_symbol (a);
322   SCM proc = axis_parent_positioning (a);
323     
324   element->set_property (sym, proc);
325   Axis_group_interface::add_element (me, element);
326 }
327
328 void
329 Align_interface::set_ordered (Grob *me)
330 {
331   SCM ga_scm = me->get_object ("elements");
332   Grob_array *ga = unsmob_grob_array (ga_scm);
333   if (!ga)
334     {
335       ga_scm = Grob_array::make_array ();
336       ga = unsmob_grob_array (ga_scm);
337       me->set_object ("elements", ga_scm);
338     }
339
340   ga->set_ordered (true);
341 }
342
343 int
344 Align_interface::stretchable_children_count (Grob const *me)
345 {
346   extract_grob_set (me, "elements", elts);
347   int ret = 0;
348
349   /* start at 1: we will never move the first child while stretching */
350   for (vsize i = 1; i < elts.size (); i++)
351     if (!to_boolean (elts[i]->get_property ("keep-fixed-while-stretching"))
352         && !elts[i]->extent (elts[i], Y_AXIS).is_empty ())
353       ret++;
354
355   return ret;
356 }
357
358 MAKE_SCHEME_CALLBACK (Align_interface, calc_max_stretch, 1)
359 SCM
360 Align_interface::calc_max_stretch (SCM smob)
361 {
362   Grob *me = unsmob_grob (smob);
363   Spanner *spanner_me = dynamic_cast<Spanner*> (me);
364   Real ret = 0;
365
366   if (spanner_me && stretchable_children_count (me) > 0)
367     {
368       Paper_column *left = dynamic_cast<Paper_column*> (spanner_me->get_bound (LEFT));
369       Real height = me->extent (me, Y_AXIS).length ();
370       SCM line_break_details = left->get_property ("line-break-system-details");
371       SCM fixed_offsets = scm_assq (ly_symbol2scm ("alignment-offsets"),
372                                     line_break_details);
373
374       /* if there are fixed offsets, we refuse to stretch */
375       if (fixed_offsets != SCM_BOOL_F)
376         ret = 0;
377       else
378         ret = height * height / 80.0; /* why this, exactly? -- jneem */
379     }
380   return scm_from_double (ret);
381 }
382
383 ADD_INTERFACE (Align_interface,
384                "Order grobs from top to bottom, left to right, right to left"
385                " or bottom to top.  For vertical alignments of staves, the"
386                " @code{break-system-details} of the left"
387                " @rinternals{NonMusicalPaperColumn} may be set to tune"
388                " vertical spacing.  Set @code{alignment-extra-space} to add"
389                " extra space for staves.  Set"
390                " @code{fixed-alignment-extra-space} to force staves in"
391                " @code{PianoStaff}s further apart.",
392                
393                /* properties */
394                "align-dir "
395                "axes "
396                "elements "
397                "padding "
398                "positioning-done "
399                "stacking-dir "
400                "threshold "
401                );