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