]> git.donarmstrong.com Git - lilypond.git/blob - lily/align-interface.cc
Add min-distance and padding for unspaced 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   Real last_spaceable_element_pos = 0;
158   Grob *last_spaceable_element = 0;
159   for (vsize j = 0; j < elems.size (); j++)
160     {
161       Real dy = 0;
162       Real padding = default_padding;
163
164       if (j == 0)
165         dy = skylines[j][-stacking_dir].max_height ();
166       else
167         {
168           down_skyline.merge (skylines[j-1][stacking_dir]);
169           dy = down_skyline.distance (skylines[j][-stacking_dir]);
170
171           SCM spec = Page_layout_problem::get_spacing_spec (elems[j-1], elems[j]);
172           Page_layout_problem::read_spacing_spec (spec, &padding, ly_symbol2scm ("padding"));
173
174           Real min_distance = 0;
175           if (Page_layout_problem::read_spacing_spec (spec, &min_distance, ly_symbol2scm ("minimum-distance")))
176             dy = max (dy, min_distance);
177
178           if (Page_layout_problem::is_spaceable (elems[j]) && last_spaceable_element)
179             {
180               // Spaceable staves may have min-distance and padding
181               // constraints coming from the previous spaceable staff
182               // as well as from the previous staff.
183               spec = Page_layout_problem::get_spacing_spec (last_spaceable_element, elems[j]);
184               Real spaceable_padding = 0;
185               Page_layout_problem::read_spacing_spec (spec,
186                                                       &spaceable_padding,
187                                                       ly_symbol2scm ("padding"));
188               padding = max (padding, spaceable_padding);
189
190               Real min_distance = 0;
191               if (Page_layout_problem::read_spacing_spec (spec,
192                                                           &min_distance,
193                                                           ly_symbol2scm ("minimum-distance")))
194                 dy = max (dy, min_distance + stacking_dir*(last_spaceable_element_pos - where));
195
196               if (scm_is_pair (forced_distances))
197                 {
198                   SCM forced_dist = scm_car (forced_distances);
199                   forced_distances = scm_cdr (forced_distances);
200
201                   if (scm_is_number (forced_dist))
202                     dy = scm_to_double (forced_dist) + stacking_dir * (last_spaceable_element_pos - where);
203                 }
204             }
205         }
206
207       if (isinf (dy)) /* if the skyline is empty, maybe max_height is infinity_f */
208         dy = 0.0;
209
210       dy = max (0.0, dy + padding);
211       down_skyline.raise (-stacking_dir * dy);
212       where += stacking_dir * dy;
213       translates.push_back (where);
214
215       if (Page_layout_problem::is_spaceable (elems[j]))
216         {
217           last_spaceable_element = elems[j];
218           last_spaceable_element_pos = where;
219         }
220     }
221
222   // So far, we've computed the translates for all the non-empty elements.
223   // Here, we set the translates for the empty elements: an empty element
224   // gets the same translation as the last non-empty element before it.
225   vector<Real> all_translates;
226   if (!translates.empty ())
227     {
228       Real w = translates[0];
229       for  (vsize i = 0, j = 0; j < all_grobs.size (); j++)
230         {
231           if (i < elems.size () && all_grobs[j] == elems[i])
232             w = translates[i++];
233           all_translates.push_back (w);
234         }
235     }
236   return all_translates;
237 }
238
239 void
240 Align_interface::align_elements_to_ideal_distances (Grob *me)
241 {
242   System *sys = me->get_system ();
243   Page_layout_problem layout (NULL, SCM_EOL, scm_list_1 (sys->self_scm ()));
244
245   layout.solution (true);
246 }
247
248 void
249 Align_interface::align_elements_to_minimum_distances (Grob *me, Axis a)
250 {
251   extract_grob_set (me, "elements", all_grobs);
252
253   vector<Real> translates = get_minimum_translations (me, all_grobs, a, false, 0, 0);
254   if (translates.size ())
255     for (vsize j = 0; j < all_grobs.size (); j++)
256       all_grobs[j]->translate_axis (translates[j], a);
257 }
258
259 Real
260 Align_interface::get_pure_child_y_translation (Grob *me, Grob *ch, int start, int end)
261 {
262   extract_grob_set (me, "elements", all_grobs);
263   SCM dy_scm = me->get_property ("forced-distance");
264
265   if (scm_is_number (dy_scm))
266     {
267       Real dy = scm_to_double (dy_scm) * robust_scm2dir (me->get_property ("stacking-dir"), DOWN);
268       Real pos = 0;
269       for (vsize i = 0; i < all_grobs.size (); i++)
270         {
271           if (all_grobs[i] == ch)
272             return pos;
273           if (!Hara_kiri_group_spanner::has_interface (all_grobs[i])
274               || !Hara_kiri_group_spanner::request_suicide (all_grobs[i], start, end))
275             pos += dy;
276         }
277     }
278   else
279     {
280       vector<Real> translates = get_minimum_translations (me, all_grobs, Y_AXIS, true, start, end);
281
282       if (translates.size ())
283         {
284           for (vsize i = 0; i < all_grobs.size (); i++)
285             if (all_grobs[i] == ch)
286               return translates[i];
287         }
288       else
289         return 0;
290     }
291
292   programming_error ("tried to get a translation for something that is no child of mine");
293   return 0;
294 }
295
296 Axis
297 Align_interface::axis (Grob *me)
298 {
299   return Axis (scm_to_int (scm_car (me->get_property ("axes"))));
300 }
301
302 void
303 Align_interface::add_element (Grob *me, Grob *element)
304 {
305   Axis a = Align_interface::axis (me);
306   SCM sym = axis_offset_symbol (a);
307   SCM proc = axis_parent_positioning (a);
308     
309   element->set_property (sym, proc);
310   Axis_group_interface::add_element (me, element);
311 }
312
313 void
314 Align_interface::set_ordered (Grob *me)
315 {
316   SCM ga_scm = me->get_object ("elements");
317   Grob_array *ga = unsmob_grob_array (ga_scm);
318   if (!ga)
319     {
320       ga_scm = Grob_array::make_array ();
321       ga = unsmob_grob_array (ga_scm);
322       me->set_object ("elements", ga_scm);
323     }
324
325   ga->set_ordered (true);
326 }
327
328 ADD_INTERFACE (Align_interface,
329                "Order grobs from top to bottom, left to right, right to left"
330                " or bottom to top.  For vertical alignments of staves, the"
331                " @code{break-system-details} of the left"
332                " @rinternals{NonMusicalPaperColumn} may be set to tune"
333                " vertical spacing.  Set @code{alignment-extra-space} to add"
334                " extra space for staves.  Set"
335                " @code{fixed-alignment-extra-space} to force staves in"
336                " @code{PianoStaff}s further apart.",
337                
338                /* properties */
339                "align-dir "
340                "axes "
341                "elements "
342                "padding "
343                "positioning-done "
344                "stacking-dir "
345                "threshold "
346                );