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