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