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