]> git.donarmstrong.com Git - lilypond.git/blob - lily/align-interface.cc
Refactor distribute_loose_lines.
[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 "page-layout-problem.hh"
16 #include "paper-book.hh"
17 #include "paper-column.hh"
18 #include "pointer-group-interface.hh"
19 #include "spanner.hh"
20 #include "skyline-pair.hh"
21 #include "system.hh"
22 #include "warn.hh"
23
24
25 MAKE_SCHEME_CALLBACK (Align_interface, align_to_minimum_distances, 1);
26 SCM
27 Align_interface::align_to_minimum_distances (SCM smob)
28 {
29   Grob *me = unsmob_grob (smob);
30
31   me->set_property ("positioning-done", SCM_BOOL_T);
32
33   SCM axis = scm_car (me->get_property ("axes"));
34   Axis ax = Axis (scm_to_int (axis));
35
36   Align_interface::align_elements_to_minimum_distances (me, ax);
37
38   return SCM_BOOL_T;
39 }
40
41 MAKE_SCHEME_CALLBACK (Align_interface, align_to_ideal_distances, 1);
42 SCM
43 Align_interface::align_to_ideal_distances (SCM smob)
44 {
45   Grob *me = unsmob_grob (smob);
46
47   me->set_property ("positioning-done", SCM_BOOL_T);
48
49   Align_interface::align_elements_to_ideal_distances (me);
50
51   return SCM_BOOL_T;
52 }
53
54 /* for each grob, find its upper and lower skylines. If the grob has
55    an empty extent, delete it from the list instead. If the extent is
56    non-empty but there is no skyline available (or pure is true), just
57    create a flat skyline from the bounding box */
58 // TODO(jneem): the pure and non-pure parts seem to share very little
59 // code. Split them into 2 functions, perhaps?
60 static void
61 get_skylines (Grob *me,
62               vector<Grob*> *const elements,
63               Axis a,
64               bool pure, int start, int end,
65               vector<Skyline_pair> *const ret)
66 {
67   Grob *other_common = common_refpoint_of_array (*elements, me, other_axis (a));
68   
69   for (vsize i = elements->size (); i--;)
70     {
71       Grob *g = (*elements)[i];
72       Skyline_pair skylines;
73
74       if (!pure)
75         {
76           Skyline_pair *skys = Skyline_pair::unsmob (g->get_property (a == Y_AXIS
77                                                                       ? "vertical-skylines"
78                                                                       : "horizontal-skylines"));
79           if (skys)
80             skylines = *skys;
81
82           /* This skyline was calculated relative to the grob g. In order to compare it to
83              skylines belonging to other grobs, we need to shift it so that it is relative
84              to the common reference. */
85           Real offset = g->relative_coordinate (other_common, other_axis (a));
86           skylines.shift (offset);
87         }
88       else
89         {
90           assert (a == Y_AXIS);
91           Interval extent = g->pure_height (g, start, end);
92           if (!extent.is_empty ())
93             {
94               Box b;
95               b[a] = extent;
96               b[other_axis (a)] = Interval (0, infinity_f);
97               skylines.insert (b, 0, other_axis (a));
98             }
99
100           // This is a hack to get better accuracy on the pure-height of VerticalAlignment.
101           // It's quite common for a treble clef to be the highest element of one system
102           // and for a low note (or lyrics) to be the lowest note on another. The two will
103           // never collide, but the pure-height stuff only works with bounding boxes, so it
104           // doesn't know that. The result is a significant over-estimation of the pure-height,
105           // especially on systems with many staves. To correct for this, we build a skyline
106           // in two parts: the part we did above contains most of the grobs (note-heads, etc.)
107           // while the bit we're about to do only contains the breakable grobs at the beginning
108           // of the system. This way, the tall treble clefs are only compared with the treble
109           // clefs of the other staff and they will be ignored if the staff above is, for example,
110           // lyrics.
111           if (Axis_group_interface::has_interface (g)
112               && !Hara_kiri_group_spanner::request_suicide (g, start, end))
113             {
114               Interval begin_of_line_extent = Axis_group_interface::begin_of_line_pure_height (g, start);
115               if (!begin_of_line_extent.is_empty ())
116                 {
117                   Box b;
118                   b[a] = begin_of_line_extent;
119                   b[other_axis (a)] = Interval (-infinity_f, -1);
120                   skylines.insert (b, 0, other_axis (a));
121                 }
122             }
123         }
124
125       if (skylines.is_empty ())
126         elements->erase (elements->begin () + i);
127       else
128         ret->push_back (skylines);
129     }
130   reverse (*ret);
131 }
132
133 vector<Real>
134 Align_interface::get_minimum_translations (Grob *me,
135                                            vector<Grob*> const &all_grobs,
136                                            Axis a,
137                                            bool pure, int start, int end)
138 {
139   if (!pure && a == Y_AXIS && dynamic_cast<Spanner*> (me) && !me->get_system ())
140     me->programming_error ("vertical alignment called before line-breaking");
141   
142   Direction stacking_dir = robust_scm2dir (me->get_property ("stacking-dir"),
143                                            DOWN);
144   vector<Grob*> elems (all_grobs); // writable copy
145   vector<Skyline_pair> skylines;
146
147   get_skylines (me, &elems, a, pure, start, end, &skylines);
148
149   SCM forced_distances = ly_assoc_get (ly_symbol2scm ("alignment-distances"),
150                                        Page_layout_problem::get_details (me),
151                                        SCM_EOL);
152
153   Real where = 0;
154   Real default_padding = robust_scm2double (me->get_property ("padding"), 0.0);
155   vector<Real> translates;
156   Skyline down_skyline (stacking_dir);
157   SCM last_spaceable_element_details = SCM_EOL;
158   Real last_spaceable_element_pos = 0;
159   bool found_spaceable_element = false;
160   for (vsize j = 0; j < elems.size (); j++)
161     {
162       Real dy = 0;
163       Real padding = default_padding;
164
165       if (j == 0)
166         dy = skylines[j][-stacking_dir].max_height ();
167       else
168         {
169           down_skyline.merge (skylines[j-1][stacking_dir]);
170           dy = down_skyline.distance (skylines[j][-stacking_dir]);
171         }
172
173       if (isinf (dy)) /* if the skyline is empty, maybe max_height is infinity_f */
174         dy = 0.0;
175
176       if (Page_layout_problem::is_spaceable (elems[j]))
177         {
178           Real min_distance = 0;
179           Page_layout_problem::read_spacing_spec (last_spaceable_element_details,
180                                                   &padding,
181                                                   ly_symbol2scm ("padding"));
182           if (Page_layout_problem::read_spacing_spec (last_spaceable_element_details,
183                                                       &min_distance,
184                                                       ly_symbol2scm ("minimum-distance")))
185             dy = max (dy, min_distance + stacking_dir*(last_spaceable_element_pos - where));
186
187           if (found_spaceable_element && scm_is_pair (forced_distances))
188             {
189               SCM forced_dist = scm_car (forced_distances);
190               forced_distances = scm_cdr (forced_distances);
191
192               if (scm_is_number (forced_dist))
193                 dy = scm_to_double (forced_dist) + stacking_dir * (last_spaceable_element_pos - where);
194             }
195           last_spaceable_element_details = elems[j]->get_property ("next-staff-spacing");
196           found_spaceable_element = true;
197         }
198       else
199         {
200           // TODO: provide support for min-distance and padding for non-spaceable elements also.
201         }
202
203       dy = max (0.0, dy + padding);
204       down_skyline.raise (-stacking_dir * dy);
205       where += stacking_dir * dy;
206       translates.push_back (where);
207
208       if (Page_layout_problem::is_spaceable (elems[j]))
209         last_spaceable_element_pos = where;
210     }
211
212   // So far, we've computed the translates for all the non-empty elements.
213   // Here, we set the translates for the empty elements: an empty element
214   // gets the same translation as the last non-empty element before it.
215   vector<Real> all_translates;
216   if (!translates.empty ())
217     {
218       Real w = translates[0];
219       for  (vsize i = 0, j = 0; j < all_grobs.size (); j++)
220         {
221           if (i < elems.size () && all_grobs[j] == elems[i])
222             w = translates[i++];
223           all_translates.push_back (w);
224         }
225     }
226   return all_translates;
227 }
228
229 void
230 Align_interface::align_elements_to_ideal_distances (Grob *me)
231 {
232   System *sys = me->get_system ();
233   Page_layout_problem layout (NULL, SCM_EOL, scm_list_1 (sys->self_scm ()));
234
235   layout.solution (true);
236 }
237
238 void
239 Align_interface::align_elements_to_minimum_distances (Grob *me, Axis a)
240 {
241   extract_grob_set (me, "elements", all_grobs);
242
243   vector<Real> translates = get_minimum_translations (me, all_grobs, a, false, 0, 0);
244   if (translates.size ())
245     for (vsize j = 0; j < all_grobs.size (); j++)
246       all_grobs[j]->translate_axis (translates[j], a);
247 }
248
249 Real
250 Align_interface::get_pure_child_y_translation (Grob *me, Grob *ch, int start, int end)
251 {
252   extract_grob_set (me, "elements", all_grobs);
253   SCM dy_scm = me->get_property ("forced-distance");
254
255   if (scm_is_number (dy_scm))
256     {
257       Real dy = scm_to_double (dy_scm) * robust_scm2dir (me->get_property ("stacking-dir"), DOWN);
258       Real pos = 0;
259       for (vsize i = 0; i < all_grobs.size (); i++)
260         {
261           if (all_grobs[i] == ch)
262             return pos;
263           if (!Hara_kiri_group_spanner::has_interface (all_grobs[i])
264               || !Hara_kiri_group_spanner::request_suicide (all_grobs[i], start, end))
265             pos += dy;
266         }
267     }
268   else
269     {
270       vector<Real> translates = get_minimum_translations (me, all_grobs, Y_AXIS, true, start, end);
271
272       if (translates.size ())
273         {
274           for (vsize i = 0; i < all_grobs.size (); i++)
275             if (all_grobs[i] == ch)
276               return translates[i];
277         }
278       else
279         return 0;
280     }
281
282   programming_error ("tried to get a translation for something that is no child of mine");
283   return 0;
284 }
285
286 Axis
287 Align_interface::axis (Grob *me)
288 {
289   return Axis (scm_to_int (scm_car (me->get_property ("axes"))));
290 }
291
292 void
293 Align_interface::add_element (Grob *me, Grob *element)
294 {
295   Axis a = Align_interface::axis (me);
296   SCM sym = axis_offset_symbol (a);
297   SCM proc = axis_parent_positioning (a);
298     
299   element->set_property (sym, proc);
300   Axis_group_interface::add_element (me, element);
301 }
302
303 void
304 Align_interface::set_ordered (Grob *me)
305 {
306   SCM ga_scm = me->get_object ("elements");
307   Grob_array *ga = unsmob_grob_array (ga_scm);
308   if (!ga)
309     {
310       ga_scm = Grob_array::make_array ();
311       ga = unsmob_grob_array (ga_scm);
312       me->set_object ("elements", ga_scm);
313     }
314
315   ga->set_ordered (true);
316 }
317
318 ADD_INTERFACE (Align_interface,
319                "Order grobs from top to bottom, left to right, right to left"
320                " or bottom to top.  For vertical alignments of staves, the"
321                " @code{break-system-details} of the left"
322                " @rinternals{NonMusicalPaperColumn} may be set to tune"
323                " vertical spacing.  Set @code{alignment-extra-space} to add"
324                " extra space for staves.  Set"
325                " @code{fixed-alignment-extra-space} to force staves in"
326                " @code{PianoStaff}s further apart.",
327                
328                /* properties */
329                "align-dir "
330                "axes "
331                "elements "
332                "padding "
333                "positioning-done "
334                "stacking-dir "
335                "threshold "
336                );