]> git.donarmstrong.com Git - lilypond.git/blob - lily/tuplet-bracket.cc
* flower/include/offset.hh (class Offset): new operator /=
[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 "output-def.hh"
38 #include "font-interface.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
62 void
63 flatten_number_pair_property (Grob *me,
64                               Direction xdir,  SCM sym)
65 {
66   Drul_array<Real> zero (0, 0);
67   Drul_array<Real> pair = robust_scm2drul (me->internal_get_property (sym), zero);
68   pair[xdir] = 0.0;
69   
70   me->internal_set_property (sym, ly_interval2scm (pair));
71 }
72
73
74 Grob *
75 Tuplet_bracket::parallel_beam (Grob *me_grob, Link_array<Grob> const &cols, bool *equally_long)
76 {
77   Spanner *me = dynamic_cast<Spanner *> (me_grob);
78
79   if (me->get_bound (LEFT)->break_status_dir ()
80       || me->get_bound (RIGHT)->break_status_dir ())
81     return 0;
82
83   Drul_array<Grob*> stems (Note_column::get_stem (cols[0]),
84                            Note_column::get_stem (cols.top ()));
85
86   if (dynamic_cast<Item*> (stems[RIGHT])->get_column ()
87       != me->get_bound (RIGHT)->get_column())
88     return 0;
89
90   Drul_array<Grob*> beams;
91   Direction d = LEFT;
92   do {
93     beams[d] = stems[d] ? Stem::get_beam (stems[d]) : 0;
94   } while (flip (&d) != LEFT);
95   
96   *equally_long = false;
97   if (! (beams[LEFT] && (beams[LEFT] == beams[RIGHT]) && !me->is_broken ()))
98     return 0;
99
100   extract_grob_set (beams[LEFT], "stems", beam_stems);
101   if (beam_stems.size () == 0)
102     {
103       programming_error ("beam under tuplet bracket has no stems");
104       *equally_long = 0;
105       return 0;
106     }
107
108   *equally_long = (beam_stems[0] == stems[LEFT] && beam_stems.top () == stems[RIGHT]);
109   return beams[LEFT];
110 }
111
112
113 MAKE_SCHEME_CALLBACK(Tuplet_bracket,calc_connect_to_neighbors,1);
114 SCM
115 Tuplet_bracket::calc_connect_to_neighbors (SCM smob)
116 {
117   Spanner *me = unsmob_spanner (smob);
118
119   Direction dir = get_grob_direction (me); 
120   Drul_array<Item *> bounds (get_x_bound_item (me, LEFT, dir),
121                              get_x_bound_item (me, RIGHT, dir));
122   
123   Drul_array<bool> connect_to_other (false, false);
124   Direction d = LEFT;
125   do
126     {
127       Direction break_dir = bounds[d]->break_status_dir ();
128       
129       Spanner *orig_spanner = dynamic_cast<Spanner*> (me->original ());
130
131       int neighbor_idx = me->get_break_index () - break_dir;
132       if (break_dir
133           && d == RIGHT
134           && neighbor_idx < orig_spanner->broken_intos_.size ())
135         {
136           Grob *neighbor = orig_spanner->broken_intos_[neighbor_idx];
137
138           /* trigger possible suicide*/
139           (void) neighbor->get_property ("positions");
140         }
141
142       connect_to_other[d]
143         = (break_dir
144            && (neighbor_idx < orig_spanner->broken_intos_.size ()
145                && neighbor_idx >= 0)
146            && orig_spanner->broken_intos_[neighbor_idx]->is_live ());
147     }
148   while (flip (&d) != LEFT);
149
150
151   if (connect_to_other[LEFT] || connect_to_other[RIGHT])
152     return scm_cons (scm_from_bool (connect_to_other[LEFT]),
153                      scm_from_bool (connect_to_other[RIGHT]));
154                      
155   return SCM_EOL;
156 }
157
158 Grob* 
159 Tuplet_bracket::get_common_x (Spanner *me)
160 {
161   extract_grob_set (me, "note-columns", columns);
162
163   Grob * commonx = common_refpoint_of_array (columns, me, X_AXIS);
164   commonx = commonx->common_refpoint (me->get_bound (LEFT), X_AXIS);
165   commonx = commonx->common_refpoint (me->get_bound (RIGHT), X_AXIS);
166
167   return commonx;
168 }
169   
170 MAKE_SCHEME_CALLBACK(Tuplet_bracket,calc_control_points,1)
171 SCM
172 Tuplet_bracket::calc_control_points (SCM smob)
173 {
174   Spanner *me = unsmob_spanner (smob);
175
176   extract_grob_set (me, "note-columns", columns);
177
178   Drul_array<Real> positions
179     = ly_scm2realdrul (me->get_property ("positions"));
180
181   Grob *commonx = get_common_x (me);
182   Direction dir = get_grob_direction (me);
183
184   Drul_array<Item *> bounds;
185   bounds[LEFT] = get_x_bound_item (me, LEFT, dir);
186   bounds[RIGHT] = get_x_bound_item (me, RIGHT, dir);
187
188   Drul_array<bool> connect_to_other =
189     robust_scm2booldrul (me->get_property ("connect-to-other"),
190                          Drul_array<bool> (false, false));
191   
192     
193   Interval x_span;
194   Direction d = LEFT;
195   do
196     {
197       x_span[d] = robust_relative_extent (bounds[d], commonx, X_AXIS)[d];
198
199       if (connect_to_other[d])
200         {
201           Interval overshoot (robust_scm2drul (me->get_property ("break-overshoot"),
202                                                Interval (-0.5, 0.0)));
203
204           if (d == RIGHT)
205             x_span[d] += d * overshoot[d];
206           else
207             x_span[d] = robust_relative_extent (bounds[d], commonx, X_AXIS)[RIGHT]
208               - overshoot[LEFT];
209         }
210       else if (d == RIGHT
211                && (columns.is_empty ()
212                    || (bounds[d]->get_column ()
213                        != dynamic_cast<Item *> (columns.top ())->get_column ())))
214         {
215           /*
216             TODO: make padding tunable?
217           */
218           Real padding = 1.0;
219
220           if (bounds[d]->break_status_dir ())
221             padding = 0.0;
222           
223           x_span[d] = robust_relative_extent (bounds[d], commonx, X_AXIS) [LEFT] - padding;
224         }
225     }
226   while (flip (&d) != LEFT);
227
228   
229   
230   x_span -= me->get_bound (LEFT)->relative_coordinate (commonx, X_AXIS);
231   return scm_list_2 (ly_offset2scm (Offset (x_span[LEFT], positions[LEFT])),
232                      ly_offset2scm (Offset (x_span[RIGHT], positions[RIGHT])));
233 }
234
235 /*
236   TODO:
237
238   in the case that there is no bracket, but there is a (single) beam,
239   follow beam precisely for determining tuplet number location.
240 */
241 MAKE_SCHEME_CALLBACK (Tuplet_bracket, print, 1);
242 SCM
243 Tuplet_bracket::print (SCM smob)
244 {
245   Spanner *me = unsmob_spanner (smob);
246   Stencil mol;
247
248   extract_grob_set (me, "note-columns", columns);
249   bool equally_long = false;
250   Grob *par_beam = parallel_beam (me, columns, &equally_long);
251   
252   bool bracket_visibility = !(par_beam && equally_long);
253   /*
254     Fixme: the type of this prop is sucky.
255   */
256   SCM bracket = me->get_property ("bracket-visibility");
257   if (scm_is_bool (bracket))
258     bracket_visibility = ly_scm2bool (bracket);
259   else if (bracket == ly_symbol2scm ("if-no-beam"))
260     bracket_visibility = !par_beam;
261
262   
263   SCM cpoints =  me->get_property ("control-points");
264   Drul_array<Offset> points;
265   points[LEFT] = ly_scm2offset (scm_car (cpoints));
266   points[RIGHT] = ly_scm2offset (scm_cadr (cpoints));
267   
268   Interval x_span (points[LEFT][X_AXIS], points[RIGHT][X_AXIS]);
269   Drul_array<Real> positions (points[LEFT][Y_AXIS], points[RIGHT][Y_AXIS]);
270
271
272
273   Output_def *pap = me->layout ();
274
275   Grob *number_grob = unsmob_grob (me->get_object ("tuplet-number"));
276   
277   /*
278     No bracket when it would be smaller than the number.
279   */
280   Real gap = 0.;
281   if (bracket_visibility && number_grob)
282     {
283       Interval ext = number_grob->extent (number_grob, X_AXIS);
284       if (!ext.is_empty ())
285         {
286           gap = ext.length () + 1.0;
287       
288           if (0.75 * x_span.length () < gap)
289             bracket_visibility = false;
290         }
291     }
292
293   if (bracket_visibility)
294     {
295       Drul_array<Real> zero (0, 0);
296       Real ss = Staff_symbol_referencer::staff_space (me);
297       Drul_array<Real> height
298         = robust_scm2drul (me->get_property ("edge-height"), zero);
299       Drul_array<Real> flare
300         = robust_scm2drul (me->get_property ("bracket-flare"), zero);
301       Drul_array<Real> shorten
302         = robust_scm2drul (me->get_property ("shorten-pair"), zero);
303       Drul_array<Stencil> edge_stencils;
304
305       Direction dir = get_grob_direction (me);
306       
307       scale_drul (&height, -ss * dir);
308       scale_drul (&flare, ss);
309       scale_drul (&shorten, ss);
310
311       Drul_array<bool> connect_to_other =
312         robust_scm2booldrul (me->get_property ("connect-to-other"),
313                              Drul_array<bool> (false, false));
314
315       Direction d = LEFT;
316       do
317         {
318           if (connect_to_other[d])
319             {
320               height[d] = 0.0;
321               flare[d] = 0.0;
322               shorten[d] = 0.0;
323
324               SCM edge_text = me->get_property ("edge-text");
325
326               if (scm_is_pair (edge_text))
327                 {
328                   SCM properties = Font_interface::text_font_alist_chain (me);
329                   SCM text = index_get_cell (edge_text, d);
330                   if (Text_interface::is_markup (text))
331                     {
332                       SCM t = Text_interface::interpret_markup (pap->self_scm (),
333                                                                 properties, text);
334
335                       Stencil *edge_text = unsmob_stencil (t);
336                       edge_text->translate_axis (x_span[d] - x_span[LEFT], X_AXIS);
337                       edge_stencils[d] = *edge_text;
338                     }
339                 }
340             }
341         }
342       while (flip (&d) != LEFT);
343
344       Stencil brack = make_bracket (me, Y_AXIS,
345                                     points[RIGHT] - points[LEFT],
346                                     height,
347                                     /*
348                                       0.1 = more space at right due to italics
349                                       TODO: use italic correction of font.
350                                     */
351                                     Interval (-0.5, 0.5) * gap + 0.1,
352                                     flare, shorten);
353
354       do
355         {
356           if (!edge_stencils[d].is_empty ())
357             brack.add_stencil (edge_stencils[d]);
358         }
359       while (flip (&d) != LEFT);
360
361       mol.add_stencil (brack);
362     }
363
364   mol.translate (points[LEFT]);
365   return mol.smobbed_copy ();
366 }
367
368 /*
369   should move to lookup?
370
371   TODO: this will fail for very short (shorter than the flare)
372   brackets.
373 */
374 Stencil
375 Tuplet_bracket::make_bracket (Grob *me, // for line properties.
376                               Axis protusion_axis,
377                               Offset dz,
378                               Drul_array<Real> height,
379                               Interval gap,
380                               Drul_array<Real> flare,
381                               Drul_array<Real> shorten)
382 {
383   Drul_array<Offset> corners (Offset (0, 0), dz);
384
385   Real length = dz.length ();
386   Drul_array<Offset> gap_corners;
387
388   Axis bracket_axis = other_axis (protusion_axis);
389
390   Drul_array<Offset> straight_corners = corners;
391
392   Direction d = LEFT;
393   do
394     straight_corners[d] += -d * shorten[d] / length * dz;
395   while (flip (&d) != LEFT)
396     ;
397
398   if (gap.is_empty ())
399     gap = Interval (0, 0);
400   do
401     gap_corners[d] = (dz * 0.5) + gap[d] / length * dz;
402   while (flip (&d) != LEFT)
403     ;
404
405   Drul_array<Offset> flare_corners = straight_corners;
406   do
407     {
408       flare_corners[d][bracket_axis] = straight_corners[d][bracket_axis];
409       flare_corners[d][protusion_axis] += height[d];
410       straight_corners[d][bracket_axis] += -d * flare[d];
411     }
412   while (flip (&d) != LEFT);
413
414   Stencil m;
415   do
416     {
417       m.add_stencil (Line_interface::line (me, straight_corners[d],
418                                            gap_corners[d]));
419
420       m.add_stencil (Line_interface::line (me, straight_corners[d],
421                                            flare_corners[d]));
422     }
423   while (flip (&d) != LEFT);
424
425   return m;
426 }
427
428 void
429 Tuplet_bracket::get_bounds (Grob *me, Grob **left, Grob **right)
430 {
431   extract_grob_set (me, "note-columns", columns);
432   int l = 0;
433   while (l < columns.size () && Note_column::has_rests (columns[l]))
434     l++;
435
436   int r = columns.size ()- 1;
437   while (r >= l && Note_column::has_rests (columns[r]))
438     r--;
439
440   *left = *right = 0;
441
442   if (l <= r)
443     {
444       *left = columns[l];
445       *right = columns[r];
446     }
447 }
448
449 /*
450   use first -> last note for slope, and then correct for disturbing
451   notes in between.  */
452 void
453 Tuplet_bracket::calc_position_and_height (Grob *me_grob, Real *offset, Real *dy)
454 {
455   Spanner *me = dynamic_cast<Spanner *> (me_grob);
456
457   extract_grob_set (me, "note-columns", columns);
458   extract_grob_set (me, "tuplets", tuplets);
459
460   Grob *commony = common_refpoint_of_array (columns, me, Y_AXIS);
461   commony = common_refpoint_of_array (tuplets, commony, Y_AXIS);
462   if (Grob *st = Staff_symbol_referencer::get_staff_symbol (me))
463     commony = st->common_refpoint (commony, Y_AXIS);
464
465   Grob *commonx = get_common_x (me);
466   commonx = common_refpoint_of_array (tuplets, commonx, Y_AXIS);
467
468   Interval staff;
469   if (Grob *st = Staff_symbol_referencer::get_staff_symbol (me))
470     {
471       Real pad = robust_scm2double (me->get_property ("staff-padding"), -1.0);
472       if  (pad >= 0.0)
473         {
474           staff = st->extent (commony, Y_AXIS);
475           staff.widen (pad);
476         }
477     }
478   
479   Direction dir = get_grob_direction (me);
480
481   /*
482     Use outer non-rest columns to determine slope
483   */
484   Grob *left_col = 0;
485   Grob *right_col = 0;
486   get_bounds (me, &left_col, &right_col);
487   if (left_col && right_col)
488     {
489       Interval rv = right_col->extent (commony, Y_AXIS);
490       Interval lv = left_col->extent (commony, Y_AXIS);
491       rv.unite (staff);
492       lv.unite (staff);
493       Real graphical_dy = rv[dir] - lv[dir];
494
495       Slice ls = Note_column::head_positions_interval (left_col);
496       Slice rs = Note_column::head_positions_interval (right_col);
497
498       Interval musical_dy;
499       musical_dy[UP] = rs[UP] - ls[UP];
500       musical_dy[DOWN] = rs[DOWN] - ls[DOWN];
501       if (sign (musical_dy[UP]) != sign (musical_dy[DOWN]))
502         *dy = 0.0;
503       else if (sign (graphical_dy) != sign (musical_dy[DOWN]))
504         *dy = 0.0;
505       else
506         *dy = graphical_dy;
507     }
508   else
509     *dy = 0;
510
511   *offset = -dir * infinity_f;
512
513   Item *lgr = get_x_bound_item (me, LEFT, dir);
514   Item *rgr = get_x_bound_item (me, RIGHT, dir);
515   Real x0 = robust_relative_extent (lgr, commonx, X_AXIS)[LEFT];
516   Real x1 = robust_relative_extent (rgr, commonx, X_AXIS)[RIGHT];
517
518   Array<Offset> points;
519   points.push (Offset (x0 - x0, staff[dir]));
520   points.push (Offset (x1 - x0, staff[dir]));
521
522   for (int i = 0; i < columns.size (); i++)
523     {
524       Interval note_ext = columns[i]->extent (commony, Y_AXIS);
525       Real notey = note_ext[dir] - me->relative_coordinate (commony, Y_AXIS);
526
527       Real x = columns[i]->relative_coordinate (commonx, X_AXIS) - x0;
528       points.push (Offset (x, notey));
529     }
530
531   /*
532     This is a slight hack. We compute two encompass points from the
533     bbox of the smaller tuplets.
534
535     We assume that the smaller bracket is 1.0 space high.
536   */
537   Real ss = Staff_symbol_referencer::staff_space (me);
538   for (int i = 0; i < tuplets.size (); i++)
539     {
540       Interval tuplet_x (tuplets[i]->extent (commonx, X_AXIS));
541       Interval tuplet_y (tuplets[i]->extent (commony, Y_AXIS));
542
543       Direction d = LEFT;
544       Drul_array<Real> positions = ly_scm2realdrul (tuplets[i]->get_property ("positions"));
545
546       
547       Real other_dy = positions[RIGHT] - positions[LEFT];
548
549       do
550         {
551           Real y
552             = tuplet_y.linear_combination (d * sign (other_dy));
553
554 #if 0
555           /*
556             Let's not take padding into account for nested tuplets.
557             the edges can come very close to the stems, likewise for
558             nested tuplets?
559           */
560           Drul_array<Real> my_height
561             = robust_scm2drul (me->get_property ("edge-height"),
562                                Interval (0, 0));
563           if (dynamic_cast<Spanner *> (tuplets[i])->get_bound (d)
564               == me->get_bound (d))
565             y += dir * my_height[d];
566 #endif
567
568           points.push (Offset (tuplet_x[d] - x0, y));
569         }
570       while (flip (&d) != LEFT);
571     }
572
573   Real factor = (columns.size () > 1) ? 1 / (x1 - x0) : 1.0;
574   for (int i = 0; i < points.size (); i++)
575     {
576       Real x = points[i][X_AXIS];
577       Real tuplety = (*dy) * x * factor;
578
579       if (points[i][Y_AXIS] * dir > (*offset + tuplety) * dir)
580         *offset = points[i][Y_AXIS] - tuplety;
581     }
582
583   *offset += scm_to_double (me->get_property ("padding")) * dir;
584
585   /*
586     horizontal brackets should not collide with staff lines.
587
588     Kind of pointless since we put them outside the staff anyway, but
589     let's leave code for the future when possibly allow them to move
590     into the staff once again.
591   */
592   if (*dy == 0
593       && fabs (*offset) < ss * Staff_symbol_referencer::staff_radius (me))
594     {
595       // quantize, then do collision check.
596       *offset *= 2 / ss;
597
598       *offset = rint (*offset);
599       if (Staff_symbol_referencer::on_staffline (me, (int) rint (*offset)))
600         *offset += dir;
601
602       *offset *= 0.5 * ss;
603     }
604 }
605
606
607 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_direction, 1);
608 SCM
609 Tuplet_bracket::calc_direction (SCM smob)
610 {
611   Grob *me = unsmob_grob (smob);
612   Direction dir = Tuplet_bracket::get_default_dir (me);
613   return scm_from_int (dir);
614 }
615
616 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_positions, 1);
617 SCM
618 Tuplet_bracket::calc_positions (SCM smob)
619 {
620   Grob *me = unsmob_grob (smob);
621   extract_grob_set (me, "note-columns", columns);
622
623   if (columns.is_empty())
624     {
625       me->suicide ();
626       return scm_cons (scm_from_double (0),
627                        scm_from_double (0));
628     }
629   
630   Direction dir = get_grob_direction (me);
631   bool equally_long = false;
632   Grob *par_beam = parallel_beam (me, columns, &equally_long);
633
634   /*
635     We follow the beam only if there is one, and we are next to it.
636   */
637   Real dy = 0.0;
638   Real offset = 0.0;
639   if (!par_beam
640       || get_grob_direction (par_beam) != dir)
641     calc_position_and_height (me, &offset, &dy);
642   else
643     {
644       SCM ps = par_beam->get_property ("positions");
645
646       Real lp = scm_to_double (scm_car (ps));
647       Real rp = scm_to_double (scm_cdr (ps));
648
649       /*
650         duh. magic.
651       */
652       offset = lp + dir * (0.5 + scm_to_double (me->get_property ("padding")));
653       dy = rp - lp;
654     }
655
656   
657   SCM x = scm_cons (scm_from_double (offset),
658                     scm_from_double (offset + dy));
659   
660   return x;
661 }
662
663 /*
664   similar to beam ?
665 */
666 Direction
667 Tuplet_bracket::get_default_dir (Grob *me)
668 {
669   Drul_array<int> dirs (0, 0);
670   extract_grob_set (me, "note-columns", columns);
671   for (int i = 0; i < columns.size (); i++)
672     {
673       Grob *nc = columns[i];
674       Direction d = Note_column::dir (nc);
675       if (d)
676         dirs[d]++;
677     }
678
679   return dirs[UP] >= dirs[DOWN] ? UP : DOWN;
680 }
681
682 void
683 Tuplet_bracket::add_column (Grob *me, Item *n)
684 {
685   Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-columns"), n);
686   add_bound_item (dynamic_cast<Spanner *> (me), n);
687 }
688
689 void
690 Tuplet_bracket::add_tuplet_bracket (Grob *me, Grob *bracket)
691 {
692   Pointer_group_interface::add_grob (me, ly_symbol2scm ("tuplets"), bracket);
693 }
694
695 ADD_INTERFACE (Tuplet_bracket,
696                "tuplet-bracket-interface",
697                "A bracket with a number in the middle, used for tuplets. "
698                "When the bracket spans  a line break, the value of "
699                "@code{break-overshoot} determines how far it extends "
700                "beyond the staff. "
701                "At a line break, the markups in the @code{edge-text} are printed "
702                "at the edges. ",
703
704
705                /* properties */
706                "bracket-flare "
707                "bracket-visibility "
708                "break-overshoot "
709                "connect-to-neighbor "
710                "control-points "
711                "direction "
712                "edge-height "
713                "edge-text "
714                "gap "
715                "positions "
716                "note-columns "
717                "padding "
718                "tuplet-number "
719                "shorten-pair "
720                "staff-padding "
721                "thickness "
722                "tuplets ");
723
724