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