]> git.donarmstrong.com Git - lilypond.git/blob - lily/tuplet-bracket.cc
Merge branch 'lilypond/translation' of ssh://git.sv.gnu.org/srv/git/lilypond into...
[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       /*
557         trigger set_stem_ends
558       */
559       (void) par_beam->get_property ("quantized-positions");
560
561       Drul_array<Grob *> stems (Note_column::get_stem (columns[0]),
562                                 Note_column::get_stem (columns.back ()));
563
564       Real ss = 0.5 * Staff_symbol_referencer::staff_space (me);
565       Real lp = ss * robust_scm2double (stems[LEFT]->get_property ("stem-end-position"), 0.0)
566                 + stems[LEFT]->get_parent (Y_AXIS)->relative_coordinate (commony, Y_AXIS);
567       Real rp = ss * robust_scm2double (stems[RIGHT]->get_property ("stem-end-position"), 0.0)
568                 + stems[RIGHT]->get_parent (Y_AXIS)->relative_coordinate (commony, Y_AXIS);
569
570       *dy = rp - lp;
571       points.push_back (Offset (stems[LEFT]->relative_coordinate (commonx, X_AXIS) - x0, lp));
572       points.push_back (Offset (stems[RIGHT]->relative_coordinate (commonx, X_AXIS) - x0, rp));
573     }
574   else
575     {
576       /*
577         Use outer non-rest columns to determine slope
578       */
579       Grob *left_col = 0;
580       Grob *right_col = 0;
581       get_bounds (me, &left_col, &right_col);
582       if (left_col && right_col)
583         {
584           Interval rv = Note_column::cross_staff_extent (right_col, commony);
585           Interval lv = Note_column::cross_staff_extent (left_col, commony);
586           rv.unite (staff);
587           lv.unite (staff);
588
589           Real graphical_dy = rv[dir] - lv[dir];
590
591           Slice ls = Note_column::head_positions_interval (left_col);
592           Slice rs = Note_column::head_positions_interval (right_col);
593
594           Interval musical_dy;
595           musical_dy[UP] = rs[UP] - ls[UP];
596           musical_dy[DOWN] = rs[DOWN] - ls[DOWN];
597           if (sign (musical_dy[UP]) != sign (musical_dy[DOWN]))
598             *dy = 0.0;
599           else if (sign (graphical_dy) != sign (musical_dy[DOWN]))
600             *dy = 0.0;
601           else
602             *dy = graphical_dy;
603         }
604       else
605         *dy = 0;
606
607       for (vsize i = 0; i < columns.size (); i++)
608         {
609           Interval note_ext = Note_column::cross_staff_extent (columns[i],
610                                                                commony);
611           Real x = columns[i]->relative_coordinate (commonx, X_AXIS) - x0;
612
613           points.push_back (Offset (x, note_ext[dir]));
614         }
615     }
616
617   if (!follow_beam)
618     {
619       points.push_back (Offset (x0 - x0, staff[dir]));
620       points.push_back (Offset (x1 - x0, staff[dir]));
621     }
622
623   /*
624     This is a slight hack. We compute two encompass points from the
625     bbox of the smaller tuplets.
626
627     We assume that the smaller bracket is 1.0 space high.
628   */
629   Real ss = Staff_symbol_referencer::staff_space (me);
630   for (vsize i = 0; i < tuplets.size (); i++)
631     {
632       Interval tuplet_x (tuplets[i]->extent (commonx, X_AXIS));
633       Interval tuplet_y (tuplets[i]->extent (commony, Y_AXIS));
634
635       if (!tuplets[i]->is_live ())
636         continue;
637
638       Direction d = LEFT;
639       Drul_array<Real> positions
640         = robust_scm2interval (tuplets[i]->get_property ("positions"),
641                                Interval (0, 0));
642
643       Real other_dy = positions[RIGHT] - positions[LEFT];
644
645       do
646         {
647           Real y
648             = tuplet_y.linear_combination (d * sign (other_dy));
649
650           /*
651             We don't take padding into account for nested tuplets.
652             the edges can come very close to the stems, likewise for
653             nested tuplets?
654           */
655
656           points.push_back (Offset (tuplet_x[d] - x0, y));
657         }
658       while (flip (&d) != LEFT);
659     }
660
661   *offset = -dir * infinity_f;
662   Real factor = (columns.size () > 1) ? 1 / (x1 - x0) : 1.0;
663   for (vsize i = 0; i < points.size (); i++)
664     {
665       Real x = points[i][X_AXIS];
666       Real tuplety = (*dy) * x * factor + my_offset;
667
668       if (points[i][Y_AXIS] * dir > (*offset + tuplety) * dir)
669         *offset = points[i][Y_AXIS] - tuplety;
670     }
671
672   *offset += scm_to_double (me->get_property ("padding")) * dir;
673
674   /*
675     horizontal brackets should not collide with staff lines.
676
677     Kind of pointless since we put them outside the staff anyway, but
678     let's leave code for the future when possibly allow them to move
679     into the staff once again.
680
681     This doesn't seem to support cross-staff tuplets atm.
682   */
683   if (*dy == 0
684       && fabs (*offset) < ss * Staff_symbol_referencer::staff_radius (me))
685     {
686       // quantize, then do collision check.
687       *offset *= 2 / ss;
688
689       *offset = rint (*offset);
690       if (Staff_symbol_referencer::on_line (me, (int) rint (*offset)))
691         *offset += dir;
692
693       *offset *= 0.5 * ss;
694     }
695 }
696
697 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_direction, 1);
698 SCM
699 Tuplet_bracket::calc_direction (SCM smob)
700 {
701   Grob *me = unsmob_grob (smob);
702   Direction dir = Tuplet_bracket::get_default_dir (me);
703   return scm_from_int (dir);
704 }
705
706 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_positions, 1);
707 SCM
708 Tuplet_bracket::calc_positions (SCM smob)
709 {
710   Spanner *me = unsmob_spanner (smob);
711
712   Real dy = 0.0;
713   Real offset = 0.0;
714   calc_position_and_height (me, &offset, &dy);
715
716   SCM x = scm_cons (scm_from_double (offset),
717                     scm_from_double (offset + dy));
718
719   return x;
720 }
721
722 /*
723   similar to beam ?
724 */
725 Direction
726 Tuplet_bracket::get_default_dir (Grob *me)
727 {
728   Drul_array<int> dirs (0, 0);
729   extract_grob_set (me, "note-columns", columns);
730   for (vsize i = 0; i < columns.size (); i++)
731     {
732       Grob *nc = columns[i];
733       if (Note_column::has_rests (nc))
734         continue;
735       Direction d = Note_column::dir (nc);
736       if (d)
737         dirs[d]++;
738     }
739
740   if (dirs[UP] == dirs[DOWN])
741     {
742       if (dirs[UP] == 0)
743         return UP;
744       Grob *staff = Staff_symbol_referencer::get_staff_symbol (me);
745       if (!staff)
746         return UP;
747       Interval staff_extent = staff->extent (staff, Y_AXIS);
748       Interval extremal_positions;
749       extremal_positions.set_empty ();
750       for (vsize i = 0; i < columns.size (); i++)
751         {
752           Direction d = Note_column::dir (columns[i]);
753           extremal_positions[d] = minmax (d, 1.0 * Note_column::head_positions_interval (columns[i])[d], extremal_positions[d]);
754         }
755       Direction d = LEFT;
756       do
757         extremal_positions[d] = -d * (staff_extent[d] - extremal_positions[d]);
758       while (flip (&d) != LEFT);
759
760       return extremal_positions[UP] <= extremal_positions[DOWN] ? UP : DOWN;
761     }
762
763   return dirs[UP] > dirs[DOWN] ? UP : DOWN;
764 }
765
766 void
767 Tuplet_bracket::add_column (Grob *me, Item *n)
768 {
769   Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-columns"), n);
770   add_bound_item (dynamic_cast<Spanner *> (me), n);
771 }
772
773 void
774 Tuplet_bracket::add_tuplet_bracket (Grob *me, Grob *bracket)
775 {
776   Pointer_group_interface::add_grob (me, ly_symbol2scm ("tuplets"), bracket);
777 }
778
779 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_cross_staff, 1);
780 SCM
781 Tuplet_bracket::calc_cross_staff (SCM smob)
782 {
783   Grob *me = unsmob_grob (smob);
784   extract_grob_set (me, "note-columns", cols);
785   extract_grob_set (me, "tuplets", tuplets);
786
787   Grob *commony = common_refpoint_of_array (cols, me, Y_AXIS);
788   commony = common_refpoint_of_array (tuplets, commony, Y_AXIS);
789   if (Grob *st = Staff_symbol_referencer::get_staff_symbol (me))
790     commony = st->common_refpoint (commony, Y_AXIS);
791   if (me->check_cross_staff (commony))
792     return SCM_BOOL_T;
793
794   bool equally_long = false;
795   Grob *par_beam = parallel_beam (me, cols, &equally_long);
796
797   if (par_beam && to_boolean (par_beam->get_property ("cross-staff")))
798     return SCM_BOOL_T;
799
800   for (vsize i = 0; i < cols.size (); i++)
801     {
802       Grob *stem = unsmob_grob (cols[i]->get_object ("stem"));
803       if (stem && to_boolean (stem->get_property ("cross-staff")))
804         return SCM_BOOL_T;
805     }
806
807   return SCM_BOOL_F;
808 }
809
810 ADD_INTERFACE (Tuplet_bracket,
811                "A bracket with a number in the middle, used for tuplets."
812                "  When the bracket spans a line break, the value of"
813                " @code{break-overshoot} determines how far it extends"
814                " beyond the staff.  At a line break, the markups in the"
815                " @code{edge-text} are printed at the edges.",
816
817                /* properties */
818                "bracket-flare "
819                "bracket-visibility "
820                "break-overshoot "
821                "connect-to-neighbor "
822                "control-points "
823                "direction "
824                "edge-height "
825                "edge-text "
826                "full-length-padding "
827                "full-length-to-extent "
828                "gap "
829                "positions "
830                "note-columns "
831                "padding "
832                "tuplet-number "
833                "shorten-pair "
834                "staff-padding "
835                "thickness "
836                "tuplets "
837               );