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