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