]> git.donarmstrong.com Git - lilypond.git/blob - lily/tuplet-bracket.cc
Nitpick run.
[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   /*
447     offset
448   */
449   Real factor = columns.size () > 1 ? 1 / (x1 - x0) : 1.0;
450
451   Array<Offset> points;
452   points.push (Offset (x0, staff[dir]));
453   points.push (Offset (x1, staff[dir]));
454
455   for (int i = 0; i < columns.size (); i++)
456     {
457       Interval note_ext = columns[i]->extent (commony, Y_AXIS);
458       Real notey = note_ext[dir] - me->relative_coordinate (commony, Y_AXIS);
459
460       Real x = columns[i]->relative_coordinate (commonx, X_AXIS) - x0;
461       points.push (Offset (x, notey));
462     }
463
464   /*
465     This is a slight hack. We compute two encompass points from the
466     bbox of the smaller tuplets.
467
468     We assume that the smaller bracket is 1.0 space high.
469   */
470   Real ss = Staff_symbol_referencer::staff_space (me);
471   for (int i = 0; i < tuplets.size (); i++)
472     {
473       Interval tuplet_x (tuplets[i]->extent (commonx, X_AXIS));
474       Interval tuplet_y (tuplets[i]->extent (commony, Y_AXIS));
475
476       Direction d = LEFT;
477       Real lp = scm_to_double (tuplets[i]->get_property ("left-position"));
478       Real rp = scm_to_double (tuplets[i]->get_property ("right-position"));
479       Real other_dy = rp - lp;
480
481       do
482         {
483           Real y
484             = tuplet_y.linear_combination (d * sign (other_dy));
485
486 #if 0
487           /*
488             Let's not take padding into account for nested tuplets.
489             the edges can come very close to the stems, likewise for
490             nested tuplets?
491           */
492           Drul_array<Real> my_height
493             = robust_scm2drul (me->get_property ("edge-height"),
494                                Interval (0, 0));
495           if (dynamic_cast<Spanner *> (tuplets[i])->get_bound (d)
496               == me->get_bound (d))
497             y += dir * my_height[d];
498 #endif
499
500           points.push (Offset (tuplet_x[d] - x0, y));
501         }
502       while (flip (&d) != LEFT);
503     }
504
505   for (int i = 0; i < points.size (); i++)
506     {
507       Real x = points[i][X_AXIS];
508       Real tuplety = *dy * x * factor;
509
510       if (points[i][Y_AXIS] * dir > (*offset + tuplety) * dir)
511         *offset = points[i][Y_AXIS] - tuplety * sign (*dy) * dir;
512     }
513
514   *offset += scm_to_double (me->get_property ("padding")) * dir;
515
516   /*
517     horizontal brackets should not collide with staff lines.
518
519     Kind of pointless since we put them outside the staff anyway, but
520     let's leave code for the future when possibly allow them to move
521     into the staff once again.
522   */
523   if (*dy == 0
524       && fabs (*offset) < ss * Staff_symbol_referencer::staff_radius (me))
525     {
526       // quantize, then do collision check.
527       *offset *= 2 / ss;
528
529       *offset = rint (*offset);
530       if (Staff_symbol_referencer::on_staffline (me, (int) rint (*offset)))
531         *offset += dir;
532
533       *offset *= 0.5 * ss;
534     }
535 }
536
537 /*
538   We depend on the beams if there are any.
539 */
540 MAKE_SCHEME_CALLBACK (Tuplet_bracket, before_line_breaking, 1);
541 SCM
542 Tuplet_bracket::before_line_breaking (SCM smob)
543 {
544   Grob *me = unsmob_grob (smob);
545   extract_grob_set (me, "note-columns", columns);
546
547   for (int i = columns.size (); i--;)
548     {
549       Grob *s = Note_column::get_stem (columns[i]);
550       Grob *b = s ? Stem::get_beam (s) : 0;
551       if (b)
552         me->add_dependency (b);
553     }
554   return SCM_UNSPECIFIED;
555 }
556
557 MAKE_SCHEME_CALLBACK (Tuplet_bracket, after_line_breaking, 1);
558
559 SCM
560 Tuplet_bracket::after_line_breaking (SCM smob)
561 {
562   Grob *me = unsmob_grob (smob);
563   extract_grob_set (me, "note-columns", columns);
564
565   Direction dir = get_grob_direction (me);
566   if (!dir)
567     {
568       dir = Tuplet_bracket::get_default_dir (me);
569       set_grob_direction (me, dir);
570     }
571
572   bool equally_long = false;
573   Grob *par_beam = parallel_beam (me, columns, &equally_long);
574
575   /*
576     We follow the beam only if there is one, and we are next to it.
577   */
578   Real dy = 0.0;
579   Real offset = 0.0;
580   if (!par_beam
581       || get_grob_direction (par_beam) != dir)
582     calc_position_and_height (me, &offset, &dy);
583   else
584     {
585       SCM ps = par_beam->get_property ("positions");
586
587       Real lp = scm_to_double (scm_car (ps));
588       Real rp = scm_to_double (scm_cdr (ps));
589
590       /*
591         duh. magic.
592       */
593       offset = lp + dir * (0.5 + scm_to_double (me->get_property ("padding")));
594       dy = rp - lp;
595     }
596
597   SCM lp = me->get_property ("left-position");
598   SCM rp = me->get_property ("right-position");
599
600   if (scm_is_number (lp) && !scm_is_number (rp))
601     rp = scm_from_double (scm_to_double (lp) + dy);
602   else if (scm_is_number (rp) && !scm_is_number (lp))
603     lp = scm_from_double (scm_to_double (rp) - dy);
604   else if (!scm_is_number (rp) && !scm_is_number (lp))
605     {
606       lp = scm_from_double (offset);
607       rp = scm_from_double (offset + dy);
608     }
609
610   me->set_property ("left-position", lp);
611   me->set_property ("right-position", rp);
612
613   return SCM_UNSPECIFIED;
614 }
615
616 /*
617   similar to beam ?
618 */
619 Direction
620 Tuplet_bracket::get_default_dir (Grob *me)
621 {
622   Drul_array<int> dirs (0, 0);
623   extract_grob_set (me, "note-columns", columns);
624   for (int i = 0; i < columns.size (); i++)
625     {
626       Grob *nc = columns[i];
627       Direction d = Note_column::dir (nc);
628       if (d)
629         dirs[d]++;
630     }
631
632   return dirs[UP] >= dirs[DOWN] ? UP : DOWN;
633 }
634
635 void
636 Tuplet_bracket::add_column (Grob *me, Item *n)
637 {
638   Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-columns"), n);
639   me->add_dependency (n);
640
641   add_bound_item (dynamic_cast<Spanner *> (me), n);
642 }
643
644 void
645 Tuplet_bracket::add_tuplet_bracket (Grob *me, Grob *bracket)
646 {
647   Pointer_group_interface::add_grob (me, ly_symbol2scm ("tuplets"), bracket);
648   me->add_dependency (bracket);
649 }
650
651 ADD_INTERFACE (Tuplet_bracket,
652                "tuplet-bracket-interface",
653                "A bracket with a number in the middle, used for tuplets. "
654                "When the bracket spans  a line break, the value of "
655                "@code{break-overshoot} determines how far it extends "
656                "beyond the staff. "
657                "At a line break, the markups in the @code{edge-text} are printed "
658                "at the edges. ",
659
660                "note-columns bracket-flare edge-height shorten-pair "
661                "tuplets edge-text break-overshoot "
662                "padding left-position right-position bracket-visibility "
663                "number-visibility thickness direction");
664