]> git.donarmstrong.com Git - lilypond.git/blob - lily/tuplet-bracket.cc
(parse_symbol_list): Bugfix.
[lilypond.git] / lily / tuplet-bracket.cc
1 /*
2   tuplet-bracket.cc -- implement Tuplet_bracket
3
4   source file of the GNU LilyPond music typesetter
5
6   (c) 1997--2005 Jan Nieuwenhuizen <janneke@gnu.org>
7   Han-Wen Nienhuys <hanwen@xs4all.nl>
8 */
9
10 /*
11   TODO:
12
13   - tuplet bracket should probably be subject to the same rules as
14   beam sloping/quanting.
15
16   - There is no support for kneed brackets, or nested brackets.
17
18   - number placement for parallel beams should be much more advanced:
19   for sloped beams some extra horizontal offset must be introduced.
20
21   - number placement is usually done over the center note, not the
22   graphical center.
23 */
24
25 /*
26   TODO: quantise, we don't want to collide with staff lines.
27   (or should we be above staff?)
28
29   todo: handle breaking elegantly.
30 */
31
32 #include <math.h>
33
34 #include "tuplet-bracket.hh"
35 #include "line-interface.hh"
36 #include "beam.hh"
37 #include "warn.hh"
38 #include "font-interface.hh"
39 #include "output-def.hh"
40 #include "text-interface.hh"
41 #include "stem.hh"
42 #include "note-column.hh"
43 #include "pointer-group-interface.hh"
44 #include "directional-element-interface.hh"
45 #include "spanner.hh"
46 #include "staff-symbol-referencer.hh"
47 #include "lookup.hh"
48
49 static Item *
50 get_x_bound_item (Grob *me_grob, Direction hdir, Direction my_dir)
51 {
52   Spanner *me = dynamic_cast<Spanner *> (me_grob);
53   Item *g = me->get_bound (hdir);
54   if (Note_column::has_interface (g)
55       && Note_column::get_stem (g)
56       && Note_column::dir (g) == my_dir)
57     {
58       g = Note_column::get_stem (g);
59     }
60
61   return g;
62 }
63
64 Grob *
65 Tuplet_bracket::parallel_beam (Grob *me_grob, Link_array<Grob> const &cols, bool *equally_long)
66 {
67   Spanner *me = dynamic_cast<Spanner *> (me_grob);
68
69   if (me->get_bound (LEFT)->break_status_dir ()
70       || me->get_bound (RIGHT)->break_status_dir ())
71     return 0;
72
73   Grob *s1 = Note_column::get_stem (cols[0]);
74   Grob *s2 = Note_column::get_stem (cols.top ());
75
76   if (s2 != me->get_bound (RIGHT))
77     return 0;
78
79   Grob *b1 = s1 ? Stem::get_beam (s1) : 0;
80   Grob *b2 = s2 ? Stem::get_beam (s2) : 0;
81
82   *equally_long = false;
83   if (! (b1 && (b1 == b2) && !me->is_broken ()))
84     return 0;
85
86   extract_grob_set (b1, "stems", beam_stems);
87   if (beam_stems.size () == 0)
88     {
89       programming_error ("beam under tuplet bracket has no stems");
90       *equally_long = 0;
91       return 0;
92     }
93
94   *equally_long = (beam_stems[0] == s1 && beam_stems.top () == s2);
95   return b1;
96 }
97
98 /*
99   TODO:
100
101   in the case that there is no bracket, but there is a (single) beam,
102   follow beam precisely for determining tuplet number location.
103 */
104 MAKE_SCHEME_CALLBACK (Tuplet_bracket, print, 1);
105 SCM
106 Tuplet_bracket::print (SCM smob)
107 {
108   Spanner *me = unsmob_spanner (smob);
109   Stencil mol;
110   extract_grob_set (me, "note-columns", columns);
111
112   {
113     SCM lp = me->get_property ("left-position");
114     SCM rp = me->get_property ("right-position");
115
116     if (!scm_is_number (rp) || !scm_is_number (lp))
117       {
118         /*
119           UGH. dependency tracking!
120         */
121         extract_grob_set (me, "tuplets", tuplets);
122         for (int i = 0; i < tuplets.size (); i++)
123           Tuplet_bracket::print (tuplets[i]->self_scm ());
124
125         after_line_breaking (smob);
126       }
127   }
128
129   Real ly = robust_scm2double (me->get_property ("left-position"), 0);
130   Real ry = robust_scm2double (me->get_property ("right-position"), 0);
131
132   bool equally_long = false;
133   Grob *par_beam = parallel_beam (me, columns, &equally_long);
134
135   Spanner *sp = dynamic_cast<Spanner *> (me);
136
137   bool bracket_visibility = !(par_beam && equally_long);
138   bool number_visibility = true;
139
140   /*
141     Fixme: the type of this prop is sucky.
142   */
143   SCM bracket = me->get_property ("bracket-visibility");
144   if (scm_is_bool (bracket))
145     {
146       bracket_visibility = ly_scm2bool (bracket);
147     }
148   else if (bracket == ly_symbol2scm ("if-no-beam"))
149     bracket_visibility = !par_beam;
150
151   SCM numb = me->get_property ("number-visibility");
152   if (scm_is_bool (numb))
153     {
154       number_visibility = ly_scm2bool (numb);
155     }
156   else if (numb == ly_symbol2scm ("if-no-beam"))
157     number_visibility = !par_beam;
158
159   Grob *commonx = common_refpoint_of_array (columns, me, X_AXIS);
160   commonx = commonx->common_refpoint (sp->get_bound (LEFT), X_AXIS);
161   commonx = commonx->common_refpoint (sp->get_bound (RIGHT), X_AXIS);
162
163   Direction dir = get_grob_direction (me);
164
165   Drul_array<Item *> bounds;
166   bounds[LEFT] = get_x_bound_item (me, LEFT, dir);
167   bounds[RIGHT] = get_x_bound_item (me, RIGHT, dir);
168
169   Drul_array<bool> connect_to_other;
170   Interval x_span;
171   Direction d = LEFT;
172   do
173     {
174       x_span[d] = robust_relative_extent (bounds[d], commonx, X_AXIS)[d];
175       Direction break_dir = bounds[d]->break_status_dir ();
176       Spanner *orig_spanner = dynamic_cast<Spanner *> (me->original_);
177       connect_to_other[d]
178         = (break_dir
179            && (me->get_break_index () - break_dir < orig_spanner->broken_intos_.size ()));
180
181       if (connect_to_other[d])
182         {
183           Interval overshoot (robust_scm2drul (me->get_property ("break-overshoot"),
184                                                Interval (-0.5, 0.0)));
185
186           if (d == RIGHT)
187             x_span[d] += d * overshoot[d];
188           else
189             x_span[d] = robust_relative_extent (bounds[d], commonx, X_AXIS)[RIGHT]
190               - overshoot[LEFT];
191         }
192       else if (d == RIGHT
193                && (columns.is_empty ()
194                    || (bounds[d]->get_column ()
195                        != dynamic_cast<Item *> (columns.top ())->get_column ())))
196         {
197           /*
198             TODO: make padding tunable?
199           */
200           x_span[d] = robust_relative_extent (bounds[d], commonx, X_AXIS) [LEFT] - 1.0;
201         }
202     }
203   while (flip (&d) != LEFT);
204
205   Real w = x_span.length ();
206   SCM number = me->get_property ("text");
207
208   Output_def *pap = me->get_layout ();
209   Stencil num;
210   if (scm_is_string (number) && number_visibility)
211     {
212       SCM properties = Font_interface::text_font_alist_chain (me);
213       SCM snum = Text_interface::interpret_markup (pap->self_scm (),
214                                                    properties, number);
215       num = *unsmob_stencil (snum);
216       num.align_to (X_AXIS, CENTER);
217       num.translate_axis (w / 2, X_AXIS);
218       num.align_to (Y_AXIS, CENTER);
219
220       num.translate_axis ((ry - ly) / 2, Y_AXIS);
221
222       mol.add_stencil (num);
223     }
224
225   /*
226     No bracket when it would be smaller than the number.
227
228     TODO: should use GAP in calculation too.
229   */
230   if (bracket_visibility && number_visibility
231       && mol.extent (X_AXIS).length () > w)
232     {
233       bracket_visibility = false;
234     }
235
236   if (bracket_visibility)
237     {
238       Real gap = 0.;
239
240       if (!num.extent (X_AXIS).is_empty ())
241         gap = num.extent (X_AXIS).length () + 1.0;
242
243       Drul_array<Real> zero (0, 0);
244       Real ss = Staff_symbol_referencer::staff_space (me);
245       Drul_array<Real> height
246         = robust_scm2drul (me->get_property ("edge-height"), zero);
247       Drul_array<Real> flare
248         = robust_scm2drul (me->get_property ("bracket-flare"), zero);
249       Drul_array<Real> shorten
250         = robust_scm2drul (me->get_property ("shorten-pair"), zero);
251       Drul_array<Stencil> edge_stencils;
252
253       scale_drul (&height, -ss * dir);
254       scale_drul (&flare, ss);
255       scale_drul (&shorten, ss);
256       do
257         {
258           if (connect_to_other[d])
259             {
260               height[d] = 0.0;
261               flare[d] = 0.0;
262               shorten[d] = 0.0;
263
264               SCM edge_text = me->get_property ("edge-text");
265
266               if (scm_is_pair (edge_text))
267                 {
268                   SCM properties = Font_interface::text_font_alist_chain (me);
269                   SCM text = index_get_cell (edge_text, d);
270                   if (Text_interface::is_markup (text))
271                     {
272                       SCM t = Text_interface::interpret_markup (pap->self_scm (), properties, text);
273
274                       Stencil *edge_text = unsmob_stencil (t);
275                       edge_text->translate_axis (x_span[d] - x_span[LEFT], X_AXIS);
276                       edge_stencils[d] = *edge_text;
277                     }
278                 }
279             }
280         }
281       while (flip (&d) != LEFT);
282
283       Stencil brack = make_bracket (me, Y_AXIS,
284                                     Offset (w, ry - ly),
285                                     height,
286                                     /*
287                                       0.1 = more space at right due to italics
288                                       TODO: use italic correction of font.
289                                     */
290                                     Interval (-0.5, 0.5) * gap + 0.1,
291                                     flare, shorten);
292
293       do
294         {
295           if (!edge_stencils[d].is_empty ())
296             brack.add_stencil (edge_stencils[d]);
297         }
298       while (flip (&d) != LEFT);
299
300       mol.add_stencil (brack);
301     }
302
303   mol.translate_axis (ly, Y_AXIS);
304   mol.translate_axis (x_span[LEFT]
305                       - sp->get_bound (LEFT)->relative_coordinate (commonx, X_AXIS), X_AXIS);
306   return mol.smobbed_copy ();
307 }
308
309 /*
310   should move to lookup?
311
312   TODO: this will fail for very short (shorter than the flare)
313   brackets.
314 */
315 Stencil
316 Tuplet_bracket::make_bracket (Grob *me, // for line properties.
317                               Axis protusion_axis,
318                               Offset dz,
319                               Drul_array<Real> height,
320                               Interval gap,
321                               Drul_array<Real> flare,
322                               Drul_array<Real> shorten)
323 {
324   Drul_array<Offset> corners (Offset (0, 0), dz);
325
326   Real length = dz.length ();
327   Drul_array<Offset> gap_corners;
328
329   Axis bracket_axis = other_axis (protusion_axis);
330
331   Drul_array<Offset> straight_corners = corners;
332
333   Direction d = LEFT;
334   do
335     {
336       straight_corners[d] += -d * shorten[d] / length * dz;
337     }
338   while (flip (&d) != LEFT);
339
340   if (gap.is_empty ())
341     gap = Interval (0, 0);
342   do
343     {
344       gap_corners[d] = (dz * 0.5) + gap[d] / length * dz;
345     }
346   while (flip (&d) != LEFT);
347
348   Drul_array<Offset> flare_corners = straight_corners;
349   do
350     {
351       flare_corners[d][bracket_axis] = straight_corners[d][bracket_axis];
352       flare_corners[d][protusion_axis] += height[d];
353       straight_corners[d][bracket_axis] += -d * flare[d];
354     }
355   while (flip (&d) != LEFT);
356
357   Stencil m;
358   do
359     {
360       m.add_stencil (Line_interface::line (me, straight_corners[d],
361                                            gap_corners[d]));
362
363       m.add_stencil (Line_interface::line (me, straight_corners[d],
364                                            flare_corners[d]));
365     }
366   while (flip (&d) != LEFT);
367
368   return m;
369 }
370
371 void
372 Tuplet_bracket::get_bounds (Grob *me, Grob **left, Grob **right)
373 {
374   extract_grob_set (me, "note-columns", columns);
375   int l = 0;
376   while (l < columns.size () && Note_column::has_rests (columns[l]))
377     l++;
378
379   int r = columns.size ()- 1;
380   while (r >= l && Note_column::has_rests (columns[r]))
381     r--;
382
383   *left = *right = 0;
384
385   if (l <= r)
386     {
387       *left = columns[l];
388       *right = columns[r];
389     }
390 }
391
392 /*
393   use first -> last note for slope, and then correct for disturbing
394   notes in between.  */
395 void
396 Tuplet_bracket::calc_position_and_height (Grob *me_grob, Real *offset, Real *dy)
397 {
398   Spanner *me = dynamic_cast<Spanner *> (me_grob);
399
400   extract_grob_set (me, "note-columns", columns);
401   extract_grob_set (me, "tuplets", tuplets);
402
403   Grob *commony = common_refpoint_of_array (columns, me, Y_AXIS);
404   commony = common_refpoint_of_array (tuplets, commony, Y_AXIS);
405   if (Grob *st = Staff_symbol_referencer::get_staff_symbol (me))
406     {
407       commony = st->common_refpoint (commony, Y_AXIS);
408     }
409
410   Grob *commonx = common_refpoint_of_array (columns, me, X_AXIS);
411   commonx = common_refpoint_of_array (tuplets, commonx, Y_AXIS);
412   commonx = commonx->common_refpoint (me->get_bound (LEFT), X_AXIS);
413   commonx = commonx->common_refpoint (me->get_bound (RIGHT), X_AXIS);
414
415   Interval staff;
416   if (Grob *st = Staff_symbol_referencer::get_staff_symbol (me))
417     staff = st->extent (commony, Y_AXIS);
418
419   Direction dir = get_grob_direction (me);
420
421   /*
422     Use outer non-rest columns to determine slope
423   */
424   Grob *left_col = 0;
425   Grob *right_col = 0;
426   get_bounds (me, &left_col, &right_col);
427   if (left_col && right_col)
428     {
429       Interval rv = right_col->extent (commony, Y_AXIS);
430       Interval lv = left_col->extent (commony, Y_AXIS);
431       rv.unite (staff);
432       lv.unite (staff);
433       Real graphical_dy = rv[dir] - lv[dir];
434
435       Slice ls = Note_column::head_positions_interval (left_col);
436       Slice rs = Note_column::head_positions_interval (right_col);
437
438       Interval musical_dy;
439       musical_dy[UP] = rs[UP] - ls[UP];
440       musical_dy[DOWN] = rs[DOWN] - ls[DOWN];
441       if (sign (musical_dy[UP]) != sign (musical_dy[DOWN]))
442         *dy = 0.0;
443       else if (sign (graphical_dy) != sign (musical_dy[DOWN]))
444         *dy = 0.0;
445       else
446         *dy = graphical_dy;
447     }
448   else
449     *dy = 0;
450
451   *offset = -dir * infinity_f;
452
453   Item *lgr = get_x_bound_item (me, LEFT, dir);
454   Item *rgr = get_x_bound_item (me, RIGHT, dir);
455   Real x0 = robust_relative_extent (lgr, commonx, X_AXIS)[LEFT];
456   Real x1 = robust_relative_extent (rgr, commonx, X_AXIS)[RIGHT];
457
458   /*
459     offset
460   */
461   Real factor = columns.size () > 1 ? 1 / (x1 - x0) : 1.0;
462
463   Array<Offset> points;
464   points.push (Offset (x0, staff[dir]));
465   points.push (Offset (x1, staff[dir]));
466
467   for (int i = 0; i < columns.size (); i++)
468     {
469       Interval note_ext = columns[i]->extent (commony, Y_AXIS);
470       Real notey = note_ext[dir] - me->relative_coordinate (commony, Y_AXIS);
471
472       Real x = columns[i]->relative_coordinate (commonx, X_AXIS) - x0;
473       points.push (Offset (x, notey));
474     }
475
476   /*
477     This is a slight hack. We compute two encompass points from the
478     bbox of the smaller tuplets.
479
480     We assume that the smaller bracket is 1.0 space high.
481   */
482   Real ss = Staff_symbol_referencer::staff_space (me);
483   for (int i = 0; i < tuplets.size (); i++)
484     {
485       Interval tuplet_x (tuplets[i]->extent (commonx, X_AXIS));
486       Interval tuplet_y (tuplets[i]->extent (commony, Y_AXIS));
487
488       Direction d = LEFT;
489       Real lp = scm_to_double (tuplets[i]->get_property ("left-position"));
490       Real rp = scm_to_double (tuplets[i]->get_property ("right-position"));
491       Real other_dy = rp - lp;
492
493       do
494         {
495           Real y
496             = tuplet_y.linear_combination (d * sign (other_dy));
497
498 #if 0
499           /*
500             Let's not take padding into account for nested tuplets.
501             the edges can come very close to the stems, likewise for
502             nested tuplets?
503           */
504           Drul_array<Real> my_height
505             = robust_scm2drul (me->get_property ("edge-height"),
506                                Interval (0, 0));
507           if (dynamic_cast<Spanner *> (tuplets[i])->get_bound (d)
508               == me->get_bound (d))
509             y += dir * my_height[d];
510 #endif
511
512           points.push (Offset (tuplet_x[d] - x0, y));
513         }
514       while (flip (&d) != LEFT);
515     }
516
517   for (int i = 0; i < points.size (); i++)
518     {
519       Real x = points[i][X_AXIS];
520       Real tuplety = *dy * x * factor;
521
522       if (points[i][Y_AXIS] * dir > (*offset + tuplety) * dir)
523         *offset = points[i][Y_AXIS] - tuplety * sign (*dy) * dir;
524     }
525
526   *offset += scm_to_double (me->get_property ("padding")) * dir;
527
528   /*
529     horizontal brackets should not collide with staff lines.
530
531     Kind of pointless since we put them outside the staff anyway, but
532     let's leave code for the future when possibly allow them to move
533     into the staff once again.
534   */
535   if (*dy == 0
536       && fabs (*offset) < ss * Staff_symbol_referencer::staff_radius (me))
537     {
538       // quantize, then do collision check.
539       *offset *= 2 / ss;
540
541       *offset = rint (*offset);
542       if (Staff_symbol_referencer::on_staffline (me, (int) rint (*offset)))
543         *offset += dir;
544
545       *offset *= 0.5 * ss;
546     }
547 }
548
549 /*
550   We depend on the beams if there are any.
551 */
552 MAKE_SCHEME_CALLBACK (Tuplet_bracket, before_line_breaking, 1);
553 SCM
554 Tuplet_bracket::before_line_breaking (SCM smob)
555 {
556   Grob *me = unsmob_grob (smob);
557   extract_grob_set (me, "note-columns", columns);
558
559   for (int i = columns.size (); i--;)
560     {
561       Grob *s = Note_column::get_stem (columns[i]);
562       Grob *b = s ? Stem::get_beam (s) : 0;
563       if (b)
564         me->add_dependency (b);
565     }
566   return SCM_UNSPECIFIED;
567 }
568
569 MAKE_SCHEME_CALLBACK (Tuplet_bracket, after_line_breaking, 1);
570
571 SCM
572 Tuplet_bracket::after_line_breaking (SCM smob)
573 {
574   Grob *me = unsmob_grob (smob);
575   extract_grob_set (me, "note-columns", columns);
576
577   Direction dir = get_grob_direction (me);
578   if (!dir)
579     {
580       dir = Tuplet_bracket::get_default_dir (me);
581       set_grob_direction (me, dir);
582     }
583
584   bool equally_long = false;
585   Grob *par_beam = parallel_beam (me, columns, &equally_long);
586
587   /*
588     We follow the beam only if there is one, and we are next to it.
589   */
590   Real dy = 0.0;
591   Real offset = 0.0;
592   if (!par_beam
593       || get_grob_direction (par_beam) != dir)
594     {
595       calc_position_and_height (me, &offset, &dy);
596     }
597   else
598     {
599       SCM ps = par_beam->get_property ("positions");
600
601       Real lp = scm_to_double (scm_car (ps));
602       Real rp = scm_to_double (scm_cdr (ps));
603
604       /*
605         duh. magic.
606       */
607       offset = lp + dir * (0.5 + scm_to_double (me->get_property ("padding")));
608       dy = rp - lp;
609     }
610
611   SCM lp = me->get_property ("left-position");
612   SCM rp = me->get_property ("right-position");
613
614   if (scm_is_number (lp) && !scm_is_number (rp))
615     {
616       rp = scm_from_double (scm_to_double (lp) + dy);
617     }
618   else if (scm_is_number (rp) && !scm_is_number (lp))
619     {
620       lp = scm_from_double (scm_to_double (rp) - dy);
621     }
622   else if (!scm_is_number (rp) && !scm_is_number (lp))
623     {
624       lp = scm_from_double (offset);
625       rp = scm_from_double (offset + dy);
626     }
627
628   me->set_property ("left-position", lp);
629   me->set_property ("right-position", rp);
630
631   return SCM_UNSPECIFIED;
632 }
633
634 /*
635   similar to beam ?
636 */
637 Direction
638 Tuplet_bracket::get_default_dir (Grob *me)
639 {
640   Drul_array<int> dirs (0, 0);
641   extract_grob_set (me, "note-columns", columns);
642   for (int i = 0; i < columns.size (); i++)
643     {
644       Grob *nc = columns[i];
645       Direction d = Note_column::dir (nc);
646       if (d)
647         dirs[d]++;
648     }
649
650   return dirs[UP] >= dirs[DOWN] ? UP : DOWN;
651 }
652
653 void
654 Tuplet_bracket::add_column (Grob *me, Item *n)
655 {
656   Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-columns"), n);
657   me->add_dependency (n);
658
659   add_bound_item (dynamic_cast<Spanner *> (me), n);
660 }
661
662 void
663 Tuplet_bracket::add_tuplet_bracket (Grob *me, Grob *bracket)
664 {
665   Pointer_group_interface::add_grob (me, ly_symbol2scm ("tuplets"), bracket);
666   me->add_dependency (bracket);
667 }
668
669
670 ADD_INTERFACE (Tuplet_bracket,
671                "tuplet-bracket-interface",
672                "A bracket with a number in the middle, used for tuplets. "
673                "When the bracket spans  a line break, the value of "
674                "@code{break-overshoot} determines how far it extends "
675                "beyond the staff. "
676                "At a line break, the markups in the @code{edge-text} are printed "
677                "at the edges. ",
678
679                "note-columns bracket-flare edge-height shorten-pair "
680                "tuplets edge-text break-overshoot "
681                "padding left-position right-position bracket-visibility "
682                "number-visibility thickness direction");
683