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