]> git.donarmstrong.com Git - lilypond.git/blob - lily/side-position-interface.cc
Improvements in vertical skyline approximations (issue 2148).
[lilypond.git] / lily / side-position-interface.cc
1 /*
2   This file is part of LilyPond, the GNU music typesetter.
3
4   Copyright (C) 1998--2012 Han-Wen Nienhuys <hanwen@xs4all.nl>
5
6   LilyPond is free software: you can redistribute it and/or modify
7   it under the terms of the GNU General Public License as published by
8   the Free Software Foundation, either version 3 of the License, or
9   (at your option) any later version.
10
11   LilyPond is distributed in the hope that it will be useful,
12   but WITHOUT ANY WARRANTY; without even the implied warranty of
13   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14   GNU General Public License for more details.
15
16   You should have received a copy of the GNU General Public License
17   along with LilyPond.  If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 #include "side-position-interface.hh"
21
22 #include <cmath>                // ceil.
23 #include <algorithm>
24 #include <map>
25
26 using namespace std;
27
28 #include "accidental-interface.hh"
29 #include "axis-group-interface.hh"
30 #include "directional-element-interface.hh"
31 #include "grob.hh"
32 #include "grob-array.hh"
33 #include "main.hh"
34 #include "misc.hh"
35 #include "note-head.hh"
36 #include "note-column.hh"
37 #include "pointer-group-interface.hh"
38 #include "skyline-pair.hh"
39 #include "staff-symbol-referencer.hh"
40 #include "staff-symbol.hh"
41 #include "stem.hh"
42 #include "string-convert.hh"
43 #include "system.hh"
44 #include "warn.hh"
45
46 void
47 Side_position_interface::add_support (Grob *me, Grob *e)
48 {
49   Pointer_group_interface::add_unordered_grob (me, ly_symbol2scm ("side-support-elements"), e);
50 }
51
52 SCM
53 finish_offset (Grob *me, Direction dir, Real total_off, Real *current_offset)
54 {
55   Real ss = Staff_symbol_referencer::staff_space (me);
56   Real minimum_space = ss * robust_scm2double (me->get_property ("minimum-space"), -1);
57   total_off += dir * ss * robust_scm2double (me->get_property ("padding"), 0);
58
59   if (minimum_space >= 0
60       && dir
61       && total_off * dir < minimum_space)
62     total_off = minimum_space * dir;
63
64   if (current_offset)
65     total_off = dir * max (dir * total_off,
66                            dir * (*current_offset));
67
68   /* FIXME: 1000 should relate to paper size.  */
69   if (fabs (total_off) > 1000)
70     {
71       string msg
72         = String_convert::form_string ("Improbable offset for grob %s: %f",
73                                        me->name ().c_str (), total_off);
74
75       programming_error (msg);
76       if (strict_infinity_checking)
77         scm_misc_error (__FUNCTION__, "Improbable offset.", SCM_EOL);
78     }
79
80   return scm_from_double (total_off);
81 }
82
83 /* Put the element next to the support, optionally taking in
84    account the extent of the support.
85
86    Does not take into account the extent of ME.
87 */
88 SCM
89 Side_position_interface::general_side_position (Grob *me, Axis a, bool use_extents,
90                                                 bool include_my_extent,
91                                                 bool pure, int start, int end,
92                                                 Real *current_offset)
93 {
94   extract_grob_set (me, "side-support-elements", support);
95
96   Grob *common = common_refpoint_of_array (support, me->get_parent (a), a);
97   Grob *staff_symbol = Staff_symbol_referencer::get_staff_symbol (me);
98   bool include_staff
99     = staff_symbol
100       && a == Y_AXIS
101       && scm_is_number (me->get_property ("staff-padding"))
102       && !to_boolean (me->get_property ("quantize-position"));
103
104   Interval dim;
105   Interval staff_extents;
106   if (include_staff)
107     {
108       common = staff_symbol->common_refpoint (common, Y_AXIS);
109       staff_extents = staff_symbol->maybe_pure_extent (common, Y_AXIS, pure, start, end);
110
111       if (include_staff)
112         dim.unite (staff_extents);
113     }
114
115   Direction dir = get_grob_direction (me);
116
117   for (vsize i = 0; i < support.size (); i++)
118     {
119       Grob *e = support[i];
120
121       // In the case of a stem, we will find a note head as well
122       // ignoring the stem solves cyclic dependencies if the stem is
123       // attached to a cross-staff beam.
124       if (a == Y_AXIS
125           && Stem::has_interface (e)
126           && dir == - get_grob_direction (e))
127         continue;
128
129       if (e)
130         {
131           if (use_extents)
132             dim.unite (e->maybe_pure_extent (common, a, pure, start, end));
133           else
134             {
135               Real x = e->maybe_pure_coordinate (common, a, pure, start, end);
136               dim.unite (Interval (x, x));
137             }
138         }
139     }
140
141   if (dim.is_empty ())
142     dim = Interval (0, 0);
143
144   Real off = me->get_parent (a)->maybe_pure_coordinate (common, a, pure, start, end);
145
146   Real total_off = dim.linear_combination (dir) - off;
147   if (include_my_extent)
148     {
149       Interval iv = me->maybe_pure_extent (me, a, pure, start, end);
150       if (!iv.is_empty ())
151         {
152           if (!dir)
153             {
154               programming_error ("direction unknown, but aligned-side wanted");
155               dir = DOWN;
156             }
157           total_off += -iv[-dir];
158         }
159     }
160
161   return finish_offset (me, dir, total_off, current_offset);
162 }
163
164 SCM
165 Side_position_interface::skyline_side_position (Grob *me, Axis a,
166                                                 bool pure, int start, int end,
167                                                 Real *current_offset)
168 {
169   extract_grob_set (me, "side-support-elements", support);
170
171   Grob *common[2];
172   for (Axis ax = X_AXIS; ax < NO_AXES; incr (ax))
173     common[ax] = common_refpoint_of_array (support, ax == a ? me->get_parent (ax) : me, ax);
174
175   Grob *staff_symbol = Staff_symbol_referencer::get_staff_symbol (me);
176   Direction dir = get_grob_direction (me);
177
178   Skyline my_dim;
179   Skyline_pair *sp = Skyline_pair::unsmob (me->get_property ("vertical-skylines"));
180   if (sp && a == Y_AXIS && !pure)
181     {
182       Skyline_pair copy = Skyline_pair (*sp);
183       copy.shift (me->relative_coordinate (common[X_AXIS], X_AXIS));
184       copy.raise (me->get_parent (Y_AXIS)->relative_coordinate (common[Y_AXIS], Y_AXIS));
185       my_dim = copy[-dir];
186     }
187   else
188     {
189       Box off;
190       for (Axis ax = X_AXIS; ax < NO_AXES; incr (ax))
191         {
192           if (ax == a)
193             off[ax] = me->get_parent (ax)->maybe_pure_coordinate (common[ax], ax, pure, start, end)
194                       + me->maybe_pure_extent (me, ax, pure, start, end);
195           else
196             off[ax] = me->maybe_pure_extent (common[ax], ax, pure, start, end);
197         }
198
199       if (off[X_AXIS].is_empty () || off[Y_AXIS].is_empty ())
200         return scm_from_double (0.0);
201
202       my_dim = Skyline (off, other_axis (a), -dir);
203     }
204   bool include_staff
205     = staff_symbol
206       && a == Y_AXIS
207       && scm_is_number (me->get_property ("staff-padding"))
208       && !to_boolean (me->get_property ("quantize-position"));
209
210   vector<Box> boxes;
211   vector<Skyline_pair> skyps;
212   Real min_h = dir == LEFT ? infinity_f : -infinity_f;
213   map<Grob *, vector<Grob *> > note_column_map; // for parts of a note column
214   for (vsize i = 0; i < support.size (); i++)
215     {
216       Grob *e = support[i];
217
218       // In the case of a stem, we will find a note head as well
219       // ignoring the stem solves cyclic dependencies if the stem is
220       // attached to a cross-staff beam.
221       if (a == Y_AXIS
222           && Stem::has_interface (e)
223           && dir == - get_grob_direction (e))
224         continue;
225
226       if (e)
227         {
228           if (Accidental_interface::has_interface (e))
229             {
230               vector<Box> bs = Accidental_interface::accurate_boxes (e, common);
231               boxes.insert (boxes.end (), bs.begin (), bs.end ());
232             }
233           else
234             {
235               if (Note_column::has_interface (e->get_parent (X_AXIS))
236                   && to_boolean (me->get_property ("add-stem-support")))
237                 {
238                   note_column_map[e->get_parent (X_AXIS)].push_back (e);
239                   continue;
240                 }
241
242               Skyline_pair *sp = Skyline_pair::unsmob (e->get_property ("vertical-skylines"));
243               if (sp && a == Y_AXIS && !pure)
244                 {
245                   Skyline_pair copy = Skyline_pair (*sp);
246                   copy.shift (e->relative_coordinate (common[X_AXIS], X_AXIS));
247                   copy.raise (e->relative_coordinate (common[Y_AXIS], Y_AXIS));
248                   skyps.push_back (copy);
249                   continue;
250                 }
251               Box b;
252               for (Axis ax = X_AXIS; ax < NO_AXES; incr (ax))
253                 b[ax] = e->maybe_pure_extent (common[ax], ax, pure, start, end);
254
255               if (b[X_AXIS].is_empty () || b[Y_AXIS].is_empty ())
256                 continue;
257
258               boxes.push_back (b);
259               min_h = minmax (dir, b[a][-dir], min_h);
260             }
261         }
262     }
263
264   // this loop ensures that parts of a note column will be in the same box
265   // pushes scripts and such over stems instead of just over heads
266   for (map<Grob *, vector<Grob *> >::iterator i = note_column_map.begin (); i != note_column_map.end (); i++)
267     {
268       Box big;
269       for (vsize j = 0; j < (*i).second.size (); j++)
270         {
271           Grob *e = (*i).second[j];
272           Box b;
273           for (Axis ax = X_AXIS; ax < NO_AXES; incr (ax))
274             b[ax] = e->maybe_pure_extent (common[ax], ax, pure, start, end);
275
276           if (b[X_AXIS].is_empty () || b[Y_AXIS].is_empty ())
277             continue;
278
279           big.unite (b);
280         }
281       if (!big[X_AXIS].is_empty () && !big[Y_AXIS].is_empty ())
282         boxes.push_back (big);
283     }
284
285   Skyline dim (boxes, other_axis (a), dir);
286   if (skyps.size ())
287     {
288       Skyline_pair merged (skyps);
289       dim.merge (merged[dir]);
290     }
291   if (!boxes.size ())
292     dim.set_minimum_height (0.0);
293   else
294     dim.set_minimum_height (min_h);
295
296   if (include_staff)
297     {
298       Interval staff_extents;
299       common[Y_AXIS] = staff_symbol->common_refpoint (common[Y_AXIS], Y_AXIS);
300       staff_extents = staff_symbol->maybe_pure_extent (common[Y_AXIS], Y_AXIS, pure, start, end);
301       dim.set_minimum_height (minmax (dir, min_h, staff_extents[dir]));
302     }
303
304   Real dist = dim.distance (my_dim, 0.1); // 0.1 m4g1c value...fix...
305   Real total_off = !isinf (dist) ? dir * dist : 0.0;
306
307   return finish_offset (me, dir, total_off, current_offset);
308 }
309
310 MAKE_SCHEME_CALLBACK (Side_position_interface, y_aligned_on_support_refpoints, 1);
311 SCM
312 Side_position_interface::y_aligned_on_support_refpoints (SCM smob)
313 {
314   return general_side_position (unsmob_grob (smob), Y_AXIS, false, false, false, 0, 0, 0);
315 }
316
317 MAKE_SCHEME_CALLBACK (Side_position_interface, pure_y_aligned_on_support_refpoints, 3);
318 SCM
319 Side_position_interface::pure_y_aligned_on_support_refpoints (SCM smob, SCM start, SCM end)
320 {
321   return general_side_position (unsmob_grob (smob), Y_AXIS, false, false,
322                                 true, scm_to_int (start), scm_to_int (end), 0);
323 }
324
325 /*
326   Position next to support, taking into account my own dimensions and padding.
327 */
328 SCM
329 axis_aligned_side_helper (SCM smob, Axis a, bool pure, int start, int end, SCM current_off_scm)
330 {
331   Real r;
332   Real *current_off_ptr = 0;
333   if (scm_is_number (current_off_scm))
334     {
335       r = scm_to_double (current_off_scm);
336       current_off_ptr = &r;
337     }
338
339   return Side_position_interface::aligned_side (unsmob_grob (smob), a, pure, start, end, current_off_ptr);
340 }
341
342 MAKE_SCHEME_CALLBACK_WITH_OPTARGS (Side_position_interface, x_aligned_side, 2, 1, "");
343 SCM
344 Side_position_interface::x_aligned_side (SCM smob, SCM current_off)
345 {
346   return axis_aligned_side_helper (smob, X_AXIS, false, 0, 0, current_off);
347 }
348
349 MAKE_SCHEME_CALLBACK_WITH_OPTARGS (Side_position_interface, y_aligned_side, 2, 1, "");
350 SCM
351 Side_position_interface::y_aligned_side (SCM smob, SCM current_off)
352 {
353   return axis_aligned_side_helper (smob, Y_AXIS, false, 0, 0, current_off);
354 }
355
356 MAKE_SCHEME_CALLBACK_WITH_OPTARGS (Side_position_interface, pure_y_aligned_side, 4, 1, "");
357 SCM
358 Side_position_interface::pure_y_aligned_side (SCM smob, SCM start, SCM end, SCM cur_off)
359 {
360   return axis_aligned_side_helper (smob, Y_AXIS, true,
361                                    scm_to_int (start),
362                                    scm_to_int (end),
363                                    cur_off);
364 }
365
366 MAKE_SCHEME_CALLBACK (Side_position_interface, calc_cross_staff, 1)
367 SCM
368 Side_position_interface::calc_cross_staff (SCM smob)
369 {
370   Grob *me = unsmob_grob (smob);
371   extract_grob_set (me, "side-support-elements", elts);
372
373   for (vsize i = 0; i < elts.size (); i++)
374     if (to_boolean (elts[i]->get_property ("cross-staff")))
375       return SCM_BOOL_T;
376
377   Grob *common = common_refpoint_of_array (elts, me->get_parent (Y_AXIS), Y_AXIS);
378   return scm_from_bool (common != me->get_parent (Y_AXIS));
379 }
380
381 SCM
382 Side_position_interface::aligned_side (Grob *me, Axis a, bool pure, int start, int end,
383                                        Real *current_off)
384 {
385   Direction dir = get_grob_direction (me);
386   bool skyline = to_boolean (me->get_property ("use-skylines"));
387
388   Real o = scm_to_double (skyline && !pure
389                           ? skyline_side_position (me, a, pure, start, end, current_off)
390                           : general_side_position (me, a, true, true, pure, start, end, current_off));
391
392   /*
393     Maintain a minimum distance to the staff. This is similar to side
394     position with padding, but it will put adjoining objects on a row if
395     stuff sticks out of the staff a little.
396   */
397   Grob *staff = Staff_symbol_referencer::get_staff_symbol (me);
398   if (staff && a == Y_AXIS)
399     {
400       if (to_boolean (me->get_property ("quantize-position")))
401         {
402           Grob *common = me->common_refpoint (staff, Y_AXIS);
403           Real my_off = me->get_parent (Y_AXIS)->maybe_pure_coordinate (common, Y_AXIS, pure, start, end);
404           Real staff_off = staff->maybe_pure_coordinate (common, Y_AXIS, pure, start, end);
405           Real ss = Staff_symbol::staff_space (staff);
406           Real position = 2 * (my_off + o - staff_off) / ss;
407           Real rounded = directed_round (position, dir);
408           Grob *head = me->get_parent (X_AXIS);
409
410           Interval staff_span = Staff_symbol::line_span (staff);
411           staff_span.widen (1);
412           if (staff_span.contains (position)
413               /* In case of a ledger lines, quantize even if we're outside the staff. */
414               || (Note_head::has_interface (head)
415
416                   && abs (Staff_symbol_referencer::get_position (head)) > abs (position)))
417             {
418               o += (rounded - position) * 0.5 * ss;
419               if (Staff_symbol_referencer::on_line (me, int (rounded)))
420                 o += dir * 0.5 * ss;
421             }
422         }
423       else if (scm_is_number (me->get_property ("staff-padding")) && dir)
424         {
425           Interval iv = me->maybe_pure_extent (me, a, pure, start, end);
426
427           Real staff_padding
428             = Staff_symbol_referencer::staff_space (me)
429               * scm_to_double (me->get_property ("staff-padding"));
430
431           Grob *parent = me->get_parent (Y_AXIS);
432           Grob *common = me->common_refpoint (staff, Y_AXIS);
433           Real parent_position = parent->maybe_pure_coordinate (common, Y_AXIS, pure, start, end);
434           Real staff_position = staff->maybe_pure_coordinate (common, Y_AXIS, pure, start, end);
435           Interval staff_extent = staff->maybe_pure_extent (staff, a, pure, start, end);
436           Real diff = (dir * staff_extent[dir] + staff_padding
437                        - dir * (o + iv[-dir])
438                        + dir * (staff_position - parent_position));
439           o += dir * max (diff, 0.0);
440         }
441     }
442   return scm_from_double (o);
443 }
444
445 void
446 Side_position_interface::set_axis (Grob *me, Axis a)
447 {
448   if (!scm_is_number (me->get_property ("side-axis")))
449     {
450       me->set_property ("side-axis", scm_from_int (a));
451       chain_offset_callback (me,
452                              (a == X_AXIS)
453                              ? x_aligned_side_proc
454                              : y_aligned_side_proc,
455                              a);
456     }
457 }
458
459 Axis
460 Side_position_interface::get_axis (Grob *me)
461 {
462   if (scm_is_number (me->get_property ("side-axis")))
463     return Axis (scm_to_int (me->get_property ("side-axis")));
464
465   string msg = String_convert::form_string ("side-axis not set for grob %s.",
466                                             me->name ().c_str ());
467   me->programming_error (msg);
468   return NO_AXES;
469 }
470
471 MAKE_SCHEME_CALLBACK (Side_position_interface, move_to_extremal_staff, 1);
472 SCM
473 Side_position_interface::move_to_extremal_staff (SCM smob)
474 {
475   Grob *me = unsmob_grob (smob);
476   System *sys = dynamic_cast<System *> (me->get_system ());
477   Direction dir = get_grob_direction (me);
478   if (dir != DOWN)
479     dir = UP;
480
481   Interval iv = me->extent (sys, X_AXIS);
482   iv.widen (1.0);
483   Grob *top_staff = sys->get_extremal_staff (dir, iv);
484
485   if (!top_staff)
486     return SCM_BOOL_F;
487
488   // Only move this grob if it is a direct child of the system.  We
489   // are not interested in moving marks from other staves to the top
490   // staff; we only want to move marks from the system to the top
491   // staff.
492   if (sys != me->get_parent (Y_AXIS))
493     return SCM_BOOL_F;
494
495   me->set_parent (top_staff, Y_AXIS);
496   me->flush_extent_cache (Y_AXIS);
497   Axis_group_interface::add_element (top_staff, me);
498
499   // Remove any cross-staff side-support dependencies
500   Grob_array *ga = unsmob_grob_array (me->get_object ("side-support-elements"));
501   if (ga)
502     {
503       vector<Grob *> const &elts = ga->array ();
504       vector<Grob *> new_elts;
505       for (vsize i = 0; i < elts.size (); ++i)
506         {
507           if (me->common_refpoint (elts[i], Y_AXIS) == top_staff)
508             new_elts.push_back (elts[i]);
509         }
510       ga->set_array (new_elts);
511     }
512   return SCM_BOOL_T;
513 }
514
515 ADD_INTERFACE (Side_position_interface,
516                "Position a victim object (this one) next to other objects"
517                " (the support).  The property @code{direction} signifies where"
518                " to put the victim object relative to the support (left or"
519                " right, up or down?)\n"
520                "\n"
521                "The routine also takes the size of the staff into account if"
522                " @code{staff-padding} is set.  If undefined, the staff symbol"
523                " is ignored.",
524
525                /* properties */
526                "add-stem-support "
527                "direction "
528                "minimum-space "
529                "padding "
530                "quantize-position "
531                "side-axis "
532                "side-support-elements "
533                "slur-padding "
534                "staff-padding "
535                "use-skylines "
536               );