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