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