]> git.donarmstrong.com Git - lilypond.git/blob - lily/tuplet-bracket.cc
* scm/framework-ps.scm (munge-lily-font-name): Bugfix: Munge only
[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 (),
216                                                    properties, number);
217       num = *unsmob_stencil (snum);
218       num.align_to (X_AXIS, CENTER);
219       num.translate_axis (w / 2, X_AXIS);
220       num.align_to (Y_AXIS, CENTER);
221
222       num.translate_axis ((ry - ly) / 2, Y_AXIS);
223
224       mol.add_stencil (num);
225     }
226
227   /*
228     No bracket when it would be smaller than the number.
229
230     TODO: should use GAP in calculation too.
231   */
232   if (bracket_visibility && number_visibility
233       && mol.extent (X_AXIS).length () > w)
234     {
235       bracket_visibility = false;
236     }
237
238   if (bracket_visibility)
239     {
240       Real gap = 0.;
241
242       if (!num.extent (X_AXIS).is_empty ())
243         gap = num.extent (X_AXIS).length () + 1.0;
244
245       Drul_array<Real> zero (0, 0);
246       Real ss = Staff_symbol_referencer::staff_space (me);
247       Drul_array<Real> height
248         = robust_scm2drul (me->get_property ("edge-height"), zero);
249       Drul_array<Real> flare
250         = robust_scm2drul (me->get_property ("bracket-flare"), zero);
251       Drul_array<Real> shorten
252         = robust_scm2drul (me->get_property ("shorten-pair"), zero);
253       Drul_array<Stencil> edge_stencils;
254
255       scale_drul (&height, -ss * dir);
256       scale_drul (&flare, ss);
257       scale_drul (&shorten, ss);
258       do
259         {
260           if (connect_to_other[d])
261             {
262               height[d] = 0.0;
263               flare[d] = 0.0;
264               shorten[d] = 0.0;
265
266               SCM edge_text = me->get_property ("edge-text");
267
268               if (scm_is_pair (edge_text))
269                 {
270                   SCM properties = Font_interface::text_font_alist_chain (me);
271                   SCM text = index_get_cell (edge_text, d);
272                   if (Text_interface::is_markup (text))
273                     {
274                       SCM t = Text_interface::interpret_markup (pap->self_scm (), properties, 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"),
511                                Interval (0, 0));
512           if (dynamic_cast<Spanner*> (tuplets[i])->get_bound (d)
513               ==  me->get_bound (d))
514             {
515               y += dir * my_height[d];
516             }
517 #endif
518
519           points.push (Offset (tuplet_x[d] - x0, y));
520         }
521       while (flip (&d) != LEFT);
522     }
523
524   for (int i = 0; i < points.size (); i++)
525     {
526       Real x = points[i][X_AXIS];
527       Real tuplety = *dy * x * factor;
528
529       if (points[i][Y_AXIS] * dir > (*offset + tuplety) * dir)
530         *offset = points[i][Y_AXIS] - tuplety * sign (*dy) * dir;
531     }
532
533   *offset += scm_to_double (me->get_property ("padding")) * dir;
534
535   /*
536     horizontal brackets should not collide with staff lines.
537
538     Kind of pointless since we put them outside the staff anyway, but
539     let's leave code for the future when possibly allow them to move
540     into the staff once again.
541   */
542   if (*dy == 0
543       && fabs (*offset) < ss * Staff_symbol_referencer::staff_radius (me))
544     {
545       // quantize, then do collision check.
546       *offset *= 2 / ss;
547
548       *offset = rint (*offset);
549       if (Staff_symbol_referencer::on_staffline (me, (int) rint (*offset)))
550         *offset += dir;
551
552       *offset *= 0.5 * ss;
553     }
554 }
555
556 /*
557   We depend on the beams if there are any.
558 */
559 MAKE_SCHEME_CALLBACK (Tuplet_bracket, before_line_breaking, 1);
560 SCM
561 Tuplet_bracket::before_line_breaking (SCM smob)
562 {
563   Grob *me = unsmob_grob (smob);
564   extract_grob_set (me, "note-columns", columns);
565
566   for (int i = columns.size (); i--;)
567     {
568       Grob *s = Note_column::get_stem (columns[i]);
569       Grob *b = s ? Stem::get_beam (s) : 0;
570       if (b)
571         me->add_dependency (b);
572     }
573   return SCM_UNSPECIFIED;
574 }
575
576 MAKE_SCHEME_CALLBACK (Tuplet_bracket, after_line_breaking, 1);
577
578 SCM
579 Tuplet_bracket::after_line_breaking (SCM smob)
580 {
581   Grob *me = unsmob_grob (smob);
582   extract_grob_set (me, "note-columns", columns);
583
584   Direction dir = get_grob_direction (me);
585   if (!dir)
586     {
587       dir = Tuplet_bracket::get_default_dir (me);
588       set_grob_direction (me, dir);
589     }
590
591   bool equally_long = false;
592   Grob *par_beam = parallel_beam (me, columns, &equally_long);
593
594   /*
595     We follow the beam only if there is one, and we are next to it.
596   */
597   Real dy = 0.0;
598   Real offset = 0.0;
599   if (!par_beam
600       || get_grob_direction (par_beam) != dir)
601     {
602       calc_position_and_height (me, &offset, &dy);
603     }
604   else
605     {
606       SCM ps = par_beam->get_property ("positions");
607
608       Real lp = scm_to_double (scm_car (ps));
609       Real rp = scm_to_double (scm_cdr (ps));
610
611       /*
612         duh. magic.
613       */
614       offset = lp + dir * (0.5 + scm_to_double (me->get_property ("padding")));
615       dy = rp - lp;
616     }
617
618   SCM lp = me->get_property ("left-position");
619   SCM rp = me->get_property ("right-position");
620
621   if (scm_is_number (lp) && !scm_is_number (rp))
622     {
623       rp = scm_from_double (scm_to_double (lp) + dy);
624     }
625   else if (scm_is_number (rp) && !scm_is_number (lp))
626     {
627       lp = scm_from_double (scm_to_double (rp) - dy);
628     }
629   else if (!scm_is_number (rp) && !scm_is_number (lp))
630     {
631       lp = scm_from_double (offset);
632       rp = scm_from_double (offset + dy);
633     }
634
635   me->set_property ("left-position", lp);
636   me->set_property ("right-position", rp);
637
638   return SCM_UNSPECIFIED;
639 }
640
641 /*
642   similar to beam ?
643 */
644 Direction
645 Tuplet_bracket::get_default_dir (Grob *me)
646 {
647   Drul_array<int> dirs (0, 0);
648   extract_grob_set (me, "note-columns", columns);
649   for (int i = 0 ; i < columns.size (); i++)
650     {
651       Grob *nc = columns[i];
652       Direction d = Note_column::dir (nc);
653       if (d)
654         dirs[d]++;
655     }
656
657   return dirs[UP] >= dirs[DOWN] ? UP : DOWN;
658 }
659
660 void
661 Tuplet_bracket::add_column (Grob *me, Item *n)
662 {
663   Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-columns"), n);
664   me->add_dependency (n);
665
666   add_bound_item (dynamic_cast<Spanner *> (me), n);
667 }
668
669 void
670 Tuplet_bracket::add_tuplet_bracket (Grob *me, Grob *bracket)
671 {
672   Pointer_group_interface::add_grob (me, ly_symbol2scm ("tuplets"), bracket);
673   me->add_dependency (bracket);
674 }
675
676
677
678 ADD_INTERFACE (Tuplet_bracket,
679                "tuplet-bracket-interface",
680                "A bracket with a number in the middle, used for tuplets. "
681                "When the bracket spans  a line break, the value of "
682                "@code{break-overshoot} determines how far it extends "
683                "beyond the staff. "
684                "At a line break, the markups in the @code{edge-text} are printed "
685                "at the edges. ",
686
687                "note-columns bracket-flare edge-height shorten-pair "
688                "tuplets edge-text break-overshoot "
689                "padding left-position right-position bracket-visibility "
690                "number-visibility thickness direction");
691