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