]> git.donarmstrong.com Git - lilypond.git/blob - lily/tuplet-bracket.cc
Fixes a tuplet bracket bug caused by 32570e8ac85561afc1f59712301ee80c0d69d2b3.
[lilypond.git] / lily / tuplet-bracket.cc
1 /*
2   This file is part of LilyPond, the GNU music typesetter.
3
4   Copyright (C) 1997--2011 Jan Nieuwenhuizen <janneke@gnu.org>
5   Han-Wen Nienhuys <hanwen@xs4all.nl>
6
7   LilyPond is free software: you can redistribute it and/or modify
8   it under the terms of the GNU General Public License as published by
9   the Free Software Foundation, either version 3 of the License, or
10   (at your option) any later version.
11
12   LilyPond is distributed in the hope that it will be useful,
13   but WITHOUT ANY WARRANTY; without even the implied warranty of
14   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15   GNU General Public License for more details.
16
17   You should have received a copy of the GNU General Public License
18   along with LilyPond.  If not, see <http://www.gnu.org/licenses/>.
19 */
20
21 /*
22   TODO:
23
24   - tuplet bracket should probably be subject to the same rules as
25   beam sloping/quanting.
26
27   - There is no support for kneed brackets, or nested brackets.
28
29   - number placement for parallel beams should be much more advanced:
30   for sloped beams some extra horizontal offset must be introduced.
31
32   - number placement is usually done over the center note, not the
33   graphical center.
34 */
35
36 /*
37   TODO: quantise, we don't want to collide with staff lines.
38   (or should we be above staff?)
39
40   todo: handle breaking elegantly.
41 */
42
43 #include "tuplet-bracket.hh"
44 #include "line-interface.hh"
45 #include "beam.hh"
46 #include "warn.hh"
47 #include "output-def.hh"
48 #include "font-interface.hh"
49 #include "text-interface.hh"
50 #include "stem.hh"
51 #include "note-column.hh"
52 #include "pointer-group-interface.hh"
53 #include "directional-element-interface.hh"
54 #include "spanner.hh"
55 #include "staff-symbol-referencer.hh"
56 #include "lookup.hh"
57 #include "paper-column.hh"
58 #include "moment.hh"
59
60 static Item *
61 get_x_bound_item (Grob *me_grob, Direction hdir, Direction my_dir)
62 {
63   Spanner *me = dynamic_cast<Spanner *> (me_grob);
64   Item *g = me->get_bound (hdir);
65   if (Note_column::has_interface (g)
66       && Note_column::get_stem (g)
67       && Note_column::dir (g) == my_dir)
68     g = Note_column::get_stem (g);
69
70   return g;
71 }
72
73 void
74 flatten_number_pair_property (Grob *me, Direction xdir, SCM sym)
75 {
76   Drul_array<Real> zero (0, 0);
77   Drul_array<Real> pair
78     = robust_scm2drul (me->internal_get_property (sym), zero);
79   pair[xdir] = 0.0;
80
81   me->set_property (sym, ly_interval2scm (pair));
82 }
83
84 /*
85   Return beam that encompasses the span of the tuplet bracket.
86 */
87 Grob *
88 Tuplet_bracket::parallel_beam (Grob *me_grob, vector<Grob *> const &cols,
89                                bool *equally_long)
90 {
91   Spanner *me = dynamic_cast<Spanner *> (me_grob);
92
93   if (me->get_bound (LEFT)->break_status_dir ()
94       || me->get_bound (RIGHT)->break_status_dir ())
95     return 0;
96
97   Drul_array<Grob *> stems (Note_column::get_stem (cols[0]),
98                             Note_column::get_stem (cols.back ()));
99
100   if (!stems[RIGHT]
101       || !stems[LEFT]
102       || (dynamic_cast<Item *> (stems[RIGHT])->get_column ()
103           != me->get_bound (RIGHT)->get_column ()))
104     return 0;
105
106   Drul_array<Grob *> beams;
107   Direction d = LEFT;
108   do
109     beams[d] = stems[d] ? Stem::get_beam (stems[d]) : 0;
110   while (flip (&d) != LEFT);
111
112   *equally_long = false;
113   if (! (beams[LEFT] && (beams[LEFT] == beams[RIGHT]) && !me->is_broken ()))
114     return 0;
115
116   extract_grob_set (beams[LEFT], "stems", beam_stems);
117   if (beam_stems.size () == 0)
118     {
119       programming_error ("beam under tuplet bracket has no stems");
120       *equally_long = 0;
121       return 0;
122     }
123
124   *equally_long
125     = (beam_stems[0] == stems[LEFT]
126        && beam_stems.back () == stems[RIGHT]);
127   return beams[LEFT];
128 }
129
130 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_connect_to_neighbors, 1);
131 SCM
132 Tuplet_bracket::calc_connect_to_neighbors (SCM smob)
133 {
134   Spanner *me = unsmob_spanner (smob);
135
136   Direction dir = get_grob_direction (me);
137   Drul_array<Item *> bounds (get_x_bound_item (me, LEFT, dir),
138                              get_x_bound_item (me, RIGHT, dir));
139
140   Drul_array<bool> connect_to_other (false, false);
141   Direction d = LEFT;
142   do
143     {
144       Direction break_dir = bounds[d]->break_status_dir ();
145       Spanner *orig_spanner = dynamic_cast<Spanner *> (me->original ());
146       vsize neighbor_idx = me->get_break_index () - break_dir;
147       if (break_dir
148           && d == RIGHT
149           && neighbor_idx < orig_spanner->broken_intos_.size ())
150         {
151           Grob *neighbor = orig_spanner->broken_intos_[neighbor_idx];
152
153           /* trigger possible suicide*/
154           (void) neighbor->get_property ("positions");
155         }
156
157       connect_to_other[d]
158         = (break_dir
159            && neighbor_idx < orig_spanner->broken_intos_.size ()
160            && orig_spanner->broken_intos_[neighbor_idx]->is_live ());
161     }
162   while (flip (&d) != LEFT);
163
164   if (connect_to_other[LEFT] || connect_to_other[RIGHT])
165     return scm_cons (scm_from_bool (connect_to_other[LEFT]),
166                      scm_from_bool (connect_to_other[RIGHT]));
167
168   return SCM_EOL;
169 }
170
171 Grob *
172 Tuplet_bracket::get_common_x (Spanner *me)
173 {
174   extract_grob_set (me, "note-columns", columns);
175
176   Grob *commonx = common_refpoint_of_array (columns, me, X_AXIS);
177   commonx = commonx->common_refpoint (me->get_bound (LEFT), X_AXIS);
178   commonx = commonx->common_refpoint (me->get_bound (RIGHT), X_AXIS);
179
180   return commonx;
181 }
182
183 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_control_points, 1)
184 SCM
185 Tuplet_bracket::calc_control_points (SCM smob)
186 {
187   Spanner *me = unsmob_spanner (smob);
188
189   extract_grob_set (me, "note-columns", columns);
190
191   SCM scm_positions = me->get_property ("positions");
192   if (!me->is_live ())
193     return SCM_EOL;
194
195   if (!scm_is_pair (scm_positions))
196     programming_error ("Positions should be number pair");
197
198   Drul_array<Real> positions
199     = robust_scm2drul (scm_positions, Drul_array<Real> (0, 0));
200
201   Grob *commonx = get_common_x (me);
202   Direction dir = get_grob_direction (me);
203
204   Drul_array<Item *> bounds;
205   bounds[LEFT] = get_x_bound_item (me, LEFT, dir);
206   bounds[RIGHT] = get_x_bound_item (me, RIGHT, dir);
207
208   Drul_array<bool> connect_to_other
209     = robust_scm2booldrul (me->get_property ("connect-to-neighbor"),
210                            Drul_array<bool> (false, false));
211
212   Interval x_span;
213   Direction d = LEFT;
214   do
215     {
216       x_span[d] = robust_relative_extent (bounds[d], commonx, X_AXIS)[d];
217
218       if (connect_to_other[d])
219         {
220           Interval overshoot (robust_scm2drul (me->get_property ("break-overshoot"),
221                                                Interval (-0.5, 0.0)));
222
223           if (d == RIGHT)
224             x_span[d] += d * overshoot[d];
225           else
226             x_span[d] = robust_relative_extent (bounds[d],
227                                                 commonx, X_AXIS)[RIGHT]
228                         - overshoot[LEFT];
229         }
230
231       else if (d == RIGHT
232                && (columns.empty ()
233                    || (bounds[d]->get_column ()
234                        != dynamic_cast<Item *> (columns.back ())->get_column ())))
235         {
236           /*
237             We're connecting to a column, for the last bit of a broken
238             fullLength bracket.
239           */
240           Real padding
241             = robust_scm2double (me->get_property ("full-length-padding"), 1.0);
242
243           if (bounds[d]->break_status_dir ())
244             padding = 0.0;
245
246           Real coord = bounds[d]->relative_coordinate (commonx, X_AXIS);
247           if (to_boolean (me->get_property ("full-length-to-extent")))
248             coord = robust_relative_extent (bounds[d], commonx, X_AXIS)[LEFT];
249
250           coord = max (coord, x_span[LEFT]);
251
252           x_span[d] = coord - padding;
253         }
254     }
255   while (flip (&d) != LEFT);
256
257   x_span -= me->get_bound (LEFT)->relative_coordinate (commonx, X_AXIS);
258   return scm_list_2 (ly_offset2scm (Offset (x_span[LEFT], positions[LEFT])),
259                      ly_offset2scm (Offset (x_span[RIGHT], positions[RIGHT])));
260 }
261
262 /*
263   TODO:
264
265   in the case that there is no bracket, but there is a (single) beam,
266   follow beam precisely for determining tuplet number location.
267 */
268 MAKE_SCHEME_CALLBACK (Tuplet_bracket, print, 1);
269 SCM
270 Tuplet_bracket::print (SCM smob)
271 {
272   Spanner *me = unsmob_spanner (smob);
273   Stencil mol;
274
275   extract_grob_set (me, "note-columns", columns);
276   bool equally_long = false;
277   Grob *par_beam = parallel_beam (me, columns, &equally_long);
278
279   bool bracket_visibility = !(par_beam && equally_long); // Flag, print/don't print tuplet bracket.
280   /*
281     FIXME: The type of this prop is sucky.
282   */
283   SCM bracket_vis_prop = me->get_property ("bracket-visibility");
284   bool bracket_prop = ly_scm2bool (bracket_vis_prop); // Flag, user has set bracket-visibility prop.
285   bool bracket = (bracket_vis_prop == ly_symbol2scm ("if-no-beam"));
286   if (scm_is_bool (bracket_vis_prop))
287     bracket_visibility = bracket_prop;
288   else if (bracket)
289     bracket_visibility = !par_beam;
290
291   /*
292     Don't print a tuplet bracket and number if
293     no control-points were calculated
294   */
295   SCM cpoints = me->get_property ("control-points");
296   if (scm_ilength (cpoints) < 2)
297     {
298       me->suicide ();
299       return SCM_EOL;
300     }
301   /*  if the tuplet does not span any time, i.e. a single-note tuplet, hide
302       the bracket, but still let the number be displayed.
303       Only do this if the user has not explicitly specified bracket-visibility = #t.
304   */
305   if (!to_boolean (bracket_vis_prop)
306       && (robust_scm2moment (me->get_bound (LEFT)->get_column ()->get_property ("when"), Moment (0))
307           == robust_scm2moment (me->get_bound (RIGHT)->get_column ()->get_property ("when"), Moment (0))))
308     bracket_visibility = false;
309
310   Drul_array<Offset> points;
311   points[LEFT] = ly_scm2offset (scm_car (cpoints));
312   points[RIGHT] = ly_scm2offset (scm_cadr (cpoints));
313
314   Interval x_span (points[LEFT][X_AXIS], points[RIGHT][X_AXIS]);
315   Drul_array<Real> positions (points[LEFT][Y_AXIS], points[RIGHT][Y_AXIS]);
316
317   Output_def *pap = me->layout ();
318
319   Grob *number_grob = unsmob_grob (me->get_object ("tuplet-number"));
320
321   /*
322     Don't print the bracket when it would be smaller than the number.
323     ...Unless the user has coded bracket-visibility = #t, that is.
324   */
325   Real gap = 0.;
326   if (bracket_visibility && number_grob)
327     {
328       Interval ext = number_grob->extent (number_grob, X_AXIS);
329       if (!ext.is_empty ())
330         {
331           gap = ext.length () + 1.0;
332
333           if ((0.75 * x_span.length () < gap) && !bracket_prop)
334             bracket_visibility = false;
335         }
336     }
337
338   if (bracket_visibility)
339     {
340       Drul_array<Real> zero (0, 0);
341       Real ss = Staff_symbol_referencer::staff_space (me);
342       Drul_array<Real> height
343         = robust_scm2drul (me->get_property ("edge-height"), zero);
344       Drul_array<Real> flare
345         = robust_scm2drul (me->get_property ("bracket-flare"), zero);
346       Drul_array<Real> shorten
347         = robust_scm2drul (me->get_property ("shorten-pair"), zero);
348       Drul_array<Stencil> edge_stencils;
349
350       Direction dir = get_grob_direction (me);
351
352       scale_drul (&height, -ss * dir);
353       scale_drul (&flare, ss);
354       scale_drul (&shorten, ss);
355
356       Drul_array<bool> connect_to_other
357         = robust_scm2booldrul (me->get_property ("connect-to-neighbor"),
358                                Drul_array<bool> (false, false));
359
360       Direction d = LEFT;
361       do
362         {
363           if (connect_to_other[d])
364             {
365               height[d] = 0.0;
366               flare[d] = 0.0;
367               shorten[d] = 0.0;
368
369               SCM edge_text = me->get_property ("edge-text");
370
371               if (scm_is_pair (edge_text))
372                 {
373                   SCM properties = Font_interface::text_font_alist_chain (me);
374                   SCM text = index_get_cell (edge_text, d);
375                   if (Text_interface::is_markup (text))
376                     {
377                       SCM t
378                         = Text_interface::interpret_markup (pap->self_scm (),
379                                                             properties, text);
380
381                       Stencil *edge_text = unsmob_stencil (t);
382                       edge_text->translate_axis (x_span[d] - x_span[LEFT],
383                                                  X_AXIS);
384                       edge_stencils[d] = *edge_text;
385                     }
386                 }
387             }
388         }
389       while (flip (&d) != LEFT);
390
391       Stencil brack = make_bracket (me, Y_AXIS,
392                                     points[RIGHT] - points[LEFT],
393                                     height,
394                                     /*
395                                       0.1 = more space at right due to italics
396                                       TODO: use italic correction of font.
397                                     */
398                                     Interval (-0.5, 0.5) * gap + 0.1,
399                                     flare, shorten);
400
401       do
402         {
403           if (!edge_stencils[d].is_empty ())
404             brack.add_stencil (edge_stencils[d]);
405         }
406       while (flip (&d) != LEFT);
407
408       mol.add_stencil (brack);
409     }
410
411   mol.translate (points[LEFT]);
412   return mol.smobbed_copy ();
413 }
414
415 /*
416   should move to lookup?
417
418   TODO: this will fail for very short (shorter than the flare)
419   brackets.
420 */
421 Stencil
422 Tuplet_bracket::make_bracket (Grob *me, // for line properties.
423                               Axis protrusion_axis,
424                               Offset dz,
425                               Drul_array<Real> height,
426                               Interval gap,
427                               Drul_array<Real> flare,
428                               Drul_array<Real> shorten)
429 {
430   Drul_array<Offset> corners (Offset (0, 0), dz);
431
432   Real length = dz.length ();
433   Drul_array<Offset> gap_corners;
434
435   Axis bracket_axis = other_axis (protrusion_axis);
436
437   Drul_array<Offset> straight_corners = corners;
438
439   Direction d = LEFT;
440   do
441     straight_corners[d] += -d * shorten[d] / length * dz;
442   while (flip (&d) != LEFT);
443
444   if (!gap.is_empty ())
445     {
446       do
447         gap_corners[d] = (dz * 0.5) + gap[d] / length * dz;
448       while (flip (&d) != LEFT);
449     }
450
451   Drul_array<Offset> flare_corners = straight_corners;
452   do
453     {
454       flare_corners[d][bracket_axis] = straight_corners[d][bracket_axis];
455       flare_corners[d][protrusion_axis] += height[d];
456       straight_corners[d][bracket_axis] += -d * flare[d];
457     }
458   while (flip (&d) != LEFT);
459
460   Stencil m;
461   do
462     {
463       if (!gap.is_empty ())
464         m.add_stencil (Line_interface::line (me, straight_corners[d],
465                                              gap_corners[d]));
466
467       m.add_stencil (Line_interface::line (me, straight_corners[d],
468                                            flare_corners[d]));
469     }
470
471   while (flip (&d) != LEFT);
472
473   if (gap.is_empty ())
474     m.add_stencil (Line_interface::line (me, straight_corners[LEFT],
475                                          straight_corners[RIGHT]));
476
477   return m;
478 }
479
480 void
481 Tuplet_bracket::get_bounds (Grob *me, Grob **left, Grob **right)
482 {
483   extract_grob_set (me, "note-columns", columns);
484   vsize l = 0;
485   while (l < columns.size () && Note_column::has_rests (columns[l]))
486     l++;
487
488   vsize r = columns.size ();
489   while (r > l && Note_column::has_rests (columns[r - 1]))
490     r--;
491
492   *left = *right = 0;
493
494   if (l < r)
495     {
496       *left = columns[l];
497       *right = columns[r - 1];
498     }
499 }
500
501 /*
502   use first -> last note for slope, and then correct for disturbing
503   notes in between.  */
504 void
505 Tuplet_bracket::calc_position_and_height (Grob *me_grob, Real *offset, Real *dy)
506 {
507   Spanner *me = dynamic_cast<Spanner *> (me_grob);
508
509   extract_grob_set (me, "note-columns", columns);
510   extract_grob_set (me, "tuplets", tuplets);
511
512   Grob *commony = common_refpoint_of_array (columns, me, Y_AXIS);
513   commony = common_refpoint_of_array (tuplets, commony, Y_AXIS);
514   if (Grob *st = Staff_symbol_referencer::get_staff_symbol (me))
515     commony = st->common_refpoint (commony, Y_AXIS);
516   Real my_offset = me->relative_coordinate (commony, Y_AXIS);
517
518   Grob *commonx = get_common_x (me);
519   commonx = common_refpoint_of_array (tuplets, commonx, Y_AXIS);
520
521   Interval staff;
522   Grob *st = Staff_symbol_referencer::get_staff_symbol (me);
523
524   /* staff-padding doesn't work correctly on cross-staff tuplets
525      because it only considers one staff symbol. Until this works,
526      disable it. */
527   if (st && !to_boolean (me->get_property ("cross-staff")))
528     {
529       Real pad = robust_scm2double (me->get_property ("staff-padding"), -1.0);
530       if (pad >= 0.0)
531         {
532           staff = st->extent (commony, Y_AXIS) - my_offset;
533           staff.widen (pad);
534         }
535     }
536
537   Direction dir = get_grob_direction (me);
538
539   bool equally_long = false;
540   Grob *par_beam = parallel_beam (me, columns, &equally_long);
541
542   Item *lgr = get_x_bound_item (me, LEFT, dir);
543   Item *rgr = get_x_bound_item (me, RIGHT, dir);
544   Real x0 = robust_relative_extent (lgr, commonx, X_AXIS)[LEFT];
545   Real x1 = robust_relative_extent (rgr, commonx, X_AXIS)[RIGHT];
546   bool follow_beam = par_beam
547                      && get_grob_direction (par_beam) == dir
548                      && !to_boolean (par_beam->get_property ("knee"));
549
550   vector<Offset> points;
551   if (columns.size ()
552       && follow_beam
553       && Note_column::get_stem (columns[0])
554       && Note_column::get_stem (columns.back ()))
555     {
556       Drul_array<Grob *> stems (Note_column::get_stem (columns[0]),
557                                 Note_column::get_stem (columns.back ()));
558
559       Interval poss;
560       Direction side = LEFT;
561       do
562         {
563           // Trigger setting of stem lengths if necessary.
564           if (Grob *beam = Stem::get_beam (stems[side]))
565             (void) beam->get_property ("quantized-positions");
566           poss[side] = stems[side]->extent (stems[side], Y_AXIS)[get_grob_direction (stems[side])]
567                        + stems[side]->get_parent (Y_AXIS)->relative_coordinate (commony, Y_AXIS);
568         }
569       while (flip (&side) != LEFT);
570
571       *dy = poss[RIGHT] - poss[LEFT];
572       points.push_back (Offset (stems[LEFT]->relative_coordinate (commonx, X_AXIS) - x0, poss[LEFT]));
573       points.push_back (Offset (stems[RIGHT]->relative_coordinate (commonx, X_AXIS) - x0, poss[RIGHT]));
574     }
575   else
576     {
577       /*
578         Use outer non-rest columns to determine slope
579       */
580       Grob *left_col = 0;
581       Grob *right_col = 0;
582       get_bounds (me, &left_col, &right_col);
583       if (left_col && right_col)
584         {
585           Interval rv = Note_column::cross_staff_extent (right_col, commony);
586           Interval lv = Note_column::cross_staff_extent (left_col, commony);
587           rv.unite (staff);
588           lv.unite (staff);
589
590           Real graphical_dy = rv[dir] - lv[dir];
591
592           Slice ls = Note_column::head_positions_interval (left_col);
593           Slice rs = Note_column::head_positions_interval (right_col);
594
595           Interval musical_dy;
596           musical_dy[UP] = rs[UP] - ls[UP];
597           musical_dy[DOWN] = rs[DOWN] - ls[DOWN];
598           if (sign (musical_dy[UP]) != sign (musical_dy[DOWN]))
599             *dy = 0.0;
600           else if (sign (graphical_dy) != sign (musical_dy[DOWN]))
601             *dy = 0.0;
602           else
603             *dy = graphical_dy;
604         }
605       else
606         *dy = 0;
607
608       for (vsize i = 0; i < columns.size (); i++)
609         {
610           Interval note_ext = Note_column::cross_staff_extent (columns[i],
611                                                                commony);
612           Real x = columns[i]->relative_coordinate (commonx, X_AXIS) - x0;
613
614           points.push_back (Offset (x, note_ext[dir]));
615         }
616     }
617
618   if (!follow_beam)
619     {
620       points.push_back (Offset (x0 - x0, staff[dir]));
621       points.push_back (Offset (x1 - x0, staff[dir]));
622     }
623
624   /*
625     This is a slight hack. We compute two encompass points from the
626     bbox of the smaller tuplets.
627
628     We assume that the smaller bracket is 1.0 space high.
629   */
630   Real ss = Staff_symbol_referencer::staff_space (me);
631   for (vsize i = 0; i < tuplets.size (); i++)
632     {
633       Interval tuplet_x (tuplets[i]->extent (commonx, X_AXIS));
634       Interval tuplet_y (tuplets[i]->extent (commony, Y_AXIS));
635
636       if (!tuplets[i]->is_live ())
637         continue;
638
639       Direction d = LEFT;
640       Drul_array<Real> positions
641         = robust_scm2interval (tuplets[i]->get_property ("positions"),
642                                Interval (0, 0));
643
644       Real other_dy = positions[RIGHT] - positions[LEFT];
645
646       do
647         {
648           Real y
649             = tuplet_y.linear_combination (d * sign (other_dy));
650
651           /*
652             We don't take padding into account for nested tuplets.
653             the edges can come very close to the stems, likewise for
654             nested tuplets?
655           */
656
657           points.push_back (Offset (tuplet_x[d] - x0, y));
658         }
659       while (flip (&d) != LEFT);
660     }
661
662   *offset = -dir * infinity_f;
663   Real factor = (columns.size () > 1) ? 1 / (x1 - x0) : 1.0;
664   for (vsize i = 0; i < points.size (); i++)
665     {
666       Real x = points[i][X_AXIS];
667       Real tuplety = (*dy) * x * factor + my_offset;
668
669       if (points[i][Y_AXIS] * dir > (*offset + tuplety) * dir)
670         *offset = points[i][Y_AXIS] - tuplety;
671     }
672
673   *offset += scm_to_double (me->get_property ("padding")) * dir;
674
675   /*
676     horizontal brackets should not collide with staff lines.
677
678     Kind of pointless since we put them outside the staff anyway, but
679     let's leave code for the future when possibly allow them to move
680     into the staff once again.
681
682     This doesn't seem to support cross-staff tuplets atm.
683   */
684   if (*dy == 0
685       && fabs (*offset) < ss * Staff_symbol_referencer::staff_radius (me))
686     {
687       // quantize, then do collision check.
688       *offset *= 2 / ss;
689
690       *offset = rint (*offset);
691       if (Staff_symbol_referencer::on_line (me, (int) rint (*offset)))
692         *offset += dir;
693
694       *offset *= 0.5 * ss;
695     }
696 }
697
698 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_direction, 1);
699 SCM
700 Tuplet_bracket::calc_direction (SCM smob)
701 {
702   Grob *me = unsmob_grob (smob);
703   Direction dir = Tuplet_bracket::get_default_dir (me);
704   return scm_from_int (dir);
705 }
706
707 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_positions, 1);
708 SCM
709 Tuplet_bracket::calc_positions (SCM smob)
710 {
711   Spanner *me = unsmob_spanner (smob);
712
713   Real dy = 0.0;
714   Real offset = 0.0;
715   calc_position_and_height (me, &offset, &dy);
716
717   SCM x = scm_cons (scm_from_double (offset),
718                     scm_from_double (offset + dy));
719
720   return x;
721 }
722
723 /*
724   similar to beam ?
725 */
726 Direction
727 Tuplet_bracket::get_default_dir (Grob *me)
728 {
729   Drul_array<int> dirs (0, 0);
730   extract_grob_set (me, "note-columns", columns);
731   for (vsize i = 0; i < columns.size (); i++)
732     {
733       Grob *nc = columns[i];
734       if (Note_column::has_rests (nc))
735         continue;
736       Direction d = Note_column::dir (nc);
737       if (d)
738         dirs[d]++;
739     }
740
741   if (dirs[UP] == dirs[DOWN])
742     {
743       if (dirs[UP] == 0)
744         return UP;
745       Grob *staff = Staff_symbol_referencer::get_staff_symbol (me);
746       if (!staff)
747         return UP;
748       Interval staff_extent = staff->extent (staff, Y_AXIS);
749       Interval extremal_positions;
750       extremal_positions.set_empty ();
751       for (vsize i = 0; i < columns.size (); i++)
752         {
753           Direction d = Note_column::dir (columns[i]);
754           extremal_positions[d] = minmax (d, 1.0 * Note_column::head_positions_interval (columns[i])[d], extremal_positions[d]);
755         }
756       Direction d = LEFT;
757       do
758         extremal_positions[d] = -d * (staff_extent[d] - extremal_positions[d]);
759       while (flip (&d) != LEFT);
760
761       return extremal_positions[UP] <= extremal_positions[DOWN] ? UP : DOWN;
762     }
763
764   return dirs[UP] > dirs[DOWN] ? UP : DOWN;
765 }
766
767 void
768 Tuplet_bracket::add_column (Grob *me, Item *n)
769 {
770   Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-columns"), n);
771   add_bound_item (dynamic_cast<Spanner *> (me), n);
772 }
773
774 void
775 Tuplet_bracket::add_tuplet_bracket (Grob *me, Grob *bracket)
776 {
777   Pointer_group_interface::add_grob (me, ly_symbol2scm ("tuplets"), bracket);
778 }
779
780 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_cross_staff, 1);
781 SCM
782 Tuplet_bracket::calc_cross_staff (SCM smob)
783 {
784   Grob *me = unsmob_grob (smob);
785   extract_grob_set (me, "note-columns", cols);
786   extract_grob_set (me, "tuplets", tuplets);
787
788   Grob *commony = common_refpoint_of_array (cols, me, Y_AXIS);
789   commony = common_refpoint_of_array (tuplets, commony, Y_AXIS);
790   if (Grob *st = Staff_symbol_referencer::get_staff_symbol (me))
791     commony = st->common_refpoint (commony, Y_AXIS);
792   if (me->check_cross_staff (commony))
793     return SCM_BOOL_T;
794
795   bool equally_long = false;
796   Grob *par_beam = parallel_beam (me, cols, &equally_long);
797
798   if (par_beam && to_boolean (par_beam->get_property ("cross-staff")))
799     return SCM_BOOL_T;
800
801   for (vsize i = 0; i < cols.size (); i++)
802     {
803       Grob *stem = unsmob_grob (cols[i]->get_object ("stem"));
804       if (stem && to_boolean (stem->get_property ("cross-staff")))
805         return SCM_BOOL_T;
806     }
807
808   return SCM_BOOL_F;
809 }
810
811 ADD_INTERFACE (Tuplet_bracket,
812                "A bracket with a number in the middle, used for tuplets."
813                "  When the bracket spans a line break, the value of"
814                " @code{break-overshoot} determines how far it extends"
815                " beyond the staff.  At a line break, the markups in the"
816                " @code{edge-text} are printed at the edges.",
817
818                /* properties */
819                "bracket-flare "
820                "bracket-visibility "
821                "break-overshoot "
822                "connect-to-neighbor "
823                "control-points "
824                "direction "
825                "edge-height "
826                "edge-text "
827                "full-length-padding "
828                "full-length-to-extent "
829                "gap "
830                "positions "
831                "note-columns "
832                "padding "
833                "tuplet-number "
834                "shorten-pair "
835                "staff-padding "
836                "thickness "
837                "tuplets "
838               );