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