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