2 tuplet-bracket.cc -- implement Tuplet_bracket
4 source file of the GNU LilyPond music typesetter
6 (c) 1997--2005 Jan Nieuwenhuizen <janneke@gnu.org>
7 Han-Wen Nienhuys <hanwen@xs4all.nl>
13 - tuplet bracket should probably be subject to the same rules as
14 beam sloping/quanting.
16 - There is no support for kneed brackets, or nested brackets.
18 - number placement for parallel beams should be much more advanced:
19 for sloped beams some extra horizontal offset must be introduced.
21 - number placement is usually done over the center note, not the
26 TODO: quantise, we don't want to collide with staff lines.
27 (or should we be above staff?)
29 todo: handle breaking elegantly.
33 #include "tuplet-bracket.hh"
34 #include "line-interface.hh"
37 #include "font-interface.hh"
38 #include "output-def.hh"
39 #include "text-interface.hh"
41 #include "note-column.hh"
42 #include "pointer-group-interface.hh"
43 #include "directional-element-interface.hh"
45 #include "staff-symbol-referencer.hh"
49 get_x_bound_item (Grob *me_grob, Direction hdir, Direction my_dir)
51 Spanner *me = dynamic_cast<Spanner *> (me_grob);
52 Item *g = me->get_bound (hdir);
53 if (Note_column::has_interface (g)
54 && Note_column::get_stem (g)
55 && Note_column::dir (g) == my_dir)
56 g = Note_column::get_stem (g);
62 Tuplet_bracket::parallel_beam (Grob *me_grob, Link_array<Grob> const &cols, bool *equally_long)
64 Spanner *me = dynamic_cast<Spanner *> (me_grob);
66 if (me->get_bound (LEFT)->break_status_dir ()
67 || me->get_bound (RIGHT)->break_status_dir ())
70 Drul_array<Grob*> stems (Note_column::get_stem (cols[0]),
71 Note_column::get_stem (cols.top ()));
73 if (dynamic_cast<Item*> (stems[RIGHT])->get_column ()
74 != me->get_bound (RIGHT)->get_column())
77 Drul_array<Grob*> beams;
80 beams[d] = stems[d] ? Stem::get_beam (stems[d]) : 0;
81 } while (flip (&d) != LEFT);
83 *equally_long = false;
84 if (! (beams[LEFT] && (beams[LEFT] == beams[RIGHT]) && !me->is_broken ()))
87 extract_grob_set (beams[LEFT], "stems", beam_stems);
88 if (beam_stems.size () == 0)
90 programming_error ("beam under tuplet bracket has no stems");
95 *equally_long = (beam_stems[0] == stems[LEFT] && beam_stems.top () == stems[RIGHT]);
102 in the case that there is no bracket, but there is a (single) beam,
103 follow beam precisely for determining tuplet number location.
105 MAKE_SCHEME_CALLBACK (Tuplet_bracket, print, 1);
107 Tuplet_bracket::print (SCM smob)
109 Spanner *me = unsmob_spanner (smob);
111 extract_grob_set (me, "note-columns", columns);
113 Drul_array<Real> positions = ly_scm2realdrul (me->get_property ("positions"));
114 Real dy = positions[RIGHT] - positions[LEFT];
115 bool equally_long = false;
116 Grob *par_beam = parallel_beam (me, columns, &equally_long);
117 Spanner *sp = dynamic_cast<Spanner *> (me);
119 bool bracket_visibility = !(par_beam && equally_long);
120 bool number_visibility = true;
123 Fixme: the type of this prop is sucky.
125 SCM bracket = me->get_property ("bracket-visibility");
126 if (scm_is_bool (bracket))
127 bracket_visibility = ly_scm2bool (bracket);
128 else if (bracket == ly_symbol2scm ("if-no-beam"))
129 bracket_visibility = !par_beam;
131 SCM numb = me->get_property ("number-visibility");
132 if (scm_is_bool (numb))
133 number_visibility = ly_scm2bool (numb);
134 else if (numb == ly_symbol2scm ("if-no-beam"))
135 number_visibility = !par_beam;
137 Grob *commonx = common_refpoint_of_array (columns, me, X_AXIS);
138 commonx = commonx->common_refpoint (sp->get_bound (LEFT), X_AXIS);
139 commonx = commonx->common_refpoint (sp->get_bound (RIGHT), X_AXIS);
141 Direction dir = get_grob_direction (me);
143 Drul_array<Item *> bounds;
144 bounds[LEFT] = get_x_bound_item (me, LEFT, dir);
145 bounds[RIGHT] = get_x_bound_item (me, RIGHT, dir);
147 Drul_array<bool> connect_to_other;
152 x_span[d] = robust_relative_extent (bounds[d], commonx, X_AXIS)[d];
153 Direction break_dir = bounds[d]->break_status_dir ();
154 Spanner *orig_spanner = dynamic_cast<Spanner *> (me->original ());
156 int neighbor_idx = me->get_break_index () - break_dir;
160 && neighbor_idx < orig_spanner->broken_intos_.size ())
162 Grob *neighbor = orig_spanner->broken_intos_[neighbor_idx];
164 /* trigger possible suicide*/
165 (void) neighbor->get_property ("positions");
170 && (neighbor_idx < orig_spanner->broken_intos_.size ()
171 && neighbor_idx >= 0)
172 && orig_spanner->broken_intos_[neighbor_idx]->is_live ());
175 if (connect_to_other[d])
177 Interval overshoot (robust_scm2drul (me->get_property ("break-overshoot"),
178 Interval (-0.5, 0.0)));
181 x_span[d] += d * overshoot[d];
183 x_span[d] = robust_relative_extent (bounds[d], commonx, X_AXIS)[RIGHT]
187 && (columns.is_empty ()
188 || (bounds[d]->get_column ()
189 != dynamic_cast<Item *> (columns.top ())->get_column ())))
192 TODO: make padding tunable?
196 if (bounds[d]->break_status_dir ())
199 x_span[d] = robust_relative_extent (bounds[d], commonx, X_AXIS) [LEFT] - padding;
202 while (flip (&d) != LEFT);
204 Real w = x_span.length ();
205 SCM number = me->get_property ("text");
207 Output_def *pap = me->layout ();
209 if (scm_is_string (number) && number_visibility)
211 SCM properties = Font_interface::text_font_alist_chain (me);
212 SCM snum = Text_interface::interpret_markup (pap->self_scm (),
214 num = *unsmob_stencil (snum);
215 num.align_to (X_AXIS, CENTER);
216 num.translate_axis (w / 2, X_AXIS);
217 num.align_to (Y_AXIS, CENTER);
219 num.translate_axis (dy / 2, Y_AXIS);
221 mol.add_stencil (num);
225 No bracket when it would be smaller than the number.
228 if (bracket_visibility && number_visibility)
230 if (!num.extent (X_AXIS).is_empty ())
231 gap = num.extent (X_AXIS).length () + 1.0;
233 if (w - gap < w / 4.0)
234 bracket_visibility = false;
237 if (bracket_visibility)
239 Drul_array<Real> zero (0, 0);
240 Real ss = Staff_symbol_referencer::staff_space (me);
241 Drul_array<Real> height
242 = robust_scm2drul (me->get_property ("edge-height"), zero);
243 Drul_array<Real> flare
244 = robust_scm2drul (me->get_property ("bracket-flare"), zero);
245 Drul_array<Real> shorten
246 = robust_scm2drul (me->get_property ("shorten-pair"), zero);
247 Drul_array<Stencil> edge_stencils;
249 scale_drul (&height, -ss * dir);
250 scale_drul (&flare, ss);
251 scale_drul (&shorten, ss);
254 if (connect_to_other[d])
260 SCM edge_text = me->get_property ("edge-text");
262 if (scm_is_pair (edge_text))
264 SCM properties = Font_interface::text_font_alist_chain (me);
265 SCM text = index_get_cell (edge_text, d);
266 if (Text_interface::is_markup (text))
268 SCM t = Text_interface::interpret_markup (pap->self_scm (),
271 Stencil *edge_text = unsmob_stencil (t);
272 edge_text->translate_axis (x_span[d] - x_span[LEFT], X_AXIS);
273 edge_stencils[d] = *edge_text;
278 while (flip (&d) != LEFT);
280 Stencil brack = make_bracket (me, Y_AXIS,
284 0.1 = more space at right due to italics
285 TODO: use italic correction of font.
287 Interval (-0.5, 0.5) * gap + 0.1,
292 if (!edge_stencils[d].is_empty ())
293 brack.add_stencil (edge_stencils[d]);
295 while (flip (&d) != LEFT);
297 mol.add_stencil (brack);
300 mol.translate_axis (positions[LEFT], Y_AXIS);
301 mol.translate_axis (x_span[LEFT]
302 - sp->get_bound (LEFT)->relative_coordinate (commonx, X_AXIS), X_AXIS);
303 return mol.smobbed_copy ();
307 should move to lookup?
309 TODO: this will fail for very short (shorter than the flare)
313 Tuplet_bracket::make_bracket (Grob *me, // for line properties.
316 Drul_array<Real> height,
318 Drul_array<Real> flare,
319 Drul_array<Real> shorten)
321 Drul_array<Offset> corners (Offset (0, 0), dz);
323 Real length = dz.length ();
324 Drul_array<Offset> gap_corners;
326 Axis bracket_axis = other_axis (protusion_axis);
328 Drul_array<Offset> straight_corners = corners;
332 straight_corners[d] += -d * shorten[d] / length * dz;
333 while (flip (&d) != LEFT)
337 gap = Interval (0, 0);
339 gap_corners[d] = (dz * 0.5) + gap[d] / length * dz;
340 while (flip (&d) != LEFT)
343 Drul_array<Offset> flare_corners = straight_corners;
346 flare_corners[d][bracket_axis] = straight_corners[d][bracket_axis];
347 flare_corners[d][protusion_axis] += height[d];
348 straight_corners[d][bracket_axis] += -d * flare[d];
350 while (flip (&d) != LEFT);
355 m.add_stencil (Line_interface::line (me, straight_corners[d],
358 m.add_stencil (Line_interface::line (me, straight_corners[d],
361 while (flip (&d) != LEFT);
367 Tuplet_bracket::get_bounds (Grob *me, Grob **left, Grob **right)
369 extract_grob_set (me, "note-columns", columns);
371 while (l < columns.size () && Note_column::has_rests (columns[l]))
374 int r = columns.size ()- 1;
375 while (r >= l && Note_column::has_rests (columns[r]))
388 use first -> last note for slope, and then correct for disturbing
391 Tuplet_bracket::calc_position_and_height (Grob *me_grob, Real *offset, Real *dy)
393 Spanner *me = dynamic_cast<Spanner *> (me_grob);
395 extract_grob_set (me, "note-columns", columns);
396 extract_grob_set (me, "tuplets", tuplets);
398 Grob *commony = common_refpoint_of_array (columns, me, Y_AXIS);
399 commony = common_refpoint_of_array (tuplets, commony, Y_AXIS);
400 if (Grob *st = Staff_symbol_referencer::get_staff_symbol (me))
401 commony = st->common_refpoint (commony, Y_AXIS);
403 Grob *commonx = common_refpoint_of_array (columns, me, X_AXIS);
404 commonx = common_refpoint_of_array (tuplets, commonx, Y_AXIS);
405 commonx = commonx->common_refpoint (me->get_bound (LEFT), X_AXIS);
406 commonx = commonx->common_refpoint (me->get_bound (RIGHT), X_AXIS);
409 if (Grob *st = Staff_symbol_referencer::get_staff_symbol (me))
411 staff = st->extent (commony, Y_AXIS);
412 Real pad = robust_scm2double (me->get_property ("staff-padding"), 0.5);
416 Direction dir = get_grob_direction (me);
419 Use outer non-rest columns to determine slope
423 get_bounds (me, &left_col, &right_col);
424 if (left_col && right_col)
426 Interval rv = right_col->extent (commony, Y_AXIS);
427 Interval lv = left_col->extent (commony, Y_AXIS);
430 Real graphical_dy = rv[dir] - lv[dir];
432 Slice ls = Note_column::head_positions_interval (left_col);
433 Slice rs = Note_column::head_positions_interval (right_col);
436 musical_dy[UP] = rs[UP] - ls[UP];
437 musical_dy[DOWN] = rs[DOWN] - ls[DOWN];
438 if (sign (musical_dy[UP]) != sign (musical_dy[DOWN]))
440 else if (sign (graphical_dy) != sign (musical_dy[DOWN]))
448 *offset = -dir * infinity_f;
450 Item *lgr = get_x_bound_item (me, LEFT, dir);
451 Item *rgr = get_x_bound_item (me, RIGHT, dir);
452 Real x0 = robust_relative_extent (lgr, commonx, X_AXIS)[LEFT];
453 Real x1 = robust_relative_extent (rgr, commonx, X_AXIS)[RIGHT];
455 Array<Offset> points;
456 points.push (Offset (x0 - x0, staff[dir]));
457 points.push (Offset (x1 - x0, staff[dir]));
459 for (int i = 0; i < columns.size (); i++)
461 Interval note_ext = columns[i]->extent (commony, Y_AXIS);
462 Real notey = note_ext[dir] - me->relative_coordinate (commony, Y_AXIS);
464 Real x = columns[i]->relative_coordinate (commonx, X_AXIS) - x0;
465 points.push (Offset (x, notey));
469 This is a slight hack. We compute two encompass points from the
470 bbox of the smaller tuplets.
472 We assume that the smaller bracket is 1.0 space high.
474 Real ss = Staff_symbol_referencer::staff_space (me);
475 for (int i = 0; i < tuplets.size (); i++)
477 Interval tuplet_x (tuplets[i]->extent (commonx, X_AXIS));
478 Interval tuplet_y (tuplets[i]->extent (commony, Y_AXIS));
481 Drul_array<Real> positions = ly_scm2realdrul (tuplets[i]->get_property ("positions"));
484 Real other_dy = positions[RIGHT] - positions[LEFT];
489 = tuplet_y.linear_combination (d * sign (other_dy));
493 Let's not take padding into account for nested tuplets.
494 the edges can come very close to the stems, likewise for
497 Drul_array<Real> my_height
498 = robust_scm2drul (me->get_property ("edge-height"),
500 if (dynamic_cast<Spanner *> (tuplets[i])->get_bound (d)
501 == me->get_bound (d))
502 y += dir * my_height[d];
505 points.push (Offset (tuplet_x[d] - x0, y));
507 while (flip (&d) != LEFT);
510 Real factor = (columns.size () > 1) ? 1 / (x1 - x0) : 1.0;
511 for (int i = 0; i < points.size (); i++)
513 Real x = points[i][X_AXIS];
514 Real tuplety = (*dy) * x * factor;
516 if (points[i][Y_AXIS] * dir > (*offset + tuplety) * dir)
517 *offset = points[i][Y_AXIS] - tuplety;
520 *offset += scm_to_double (me->get_property ("padding")) * dir;
523 horizontal brackets should not collide with staff lines.
525 Kind of pointless since we put them outside the staff anyway, but
526 let's leave code for the future when possibly allow them to move
527 into the staff once again.
530 && fabs (*offset) < ss * Staff_symbol_referencer::staff_radius (me))
532 // quantize, then do collision check.
535 *offset = rint (*offset);
536 if (Staff_symbol_referencer::on_staffline (me, (int) rint (*offset)))
544 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_direction, 1);
546 Tuplet_bracket::calc_direction (SCM smob)
548 Grob *me = unsmob_grob (smob);
549 Direction dir = Tuplet_bracket::get_default_dir (me);
550 return scm_from_int (dir);
553 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_positions, 1);
555 Tuplet_bracket::calc_positions (SCM smob)
557 Grob *me = unsmob_grob (smob);
558 extract_grob_set (me, "note-columns", columns);
560 if (columns.is_empty())
563 return scm_cons (scm_from_double (0),
564 scm_from_double (0));
567 Direction dir = get_grob_direction (me);
568 bool equally_long = false;
569 Grob *par_beam = parallel_beam (me, columns, &equally_long);
572 We follow the beam only if there is one, and we are next to it.
577 || get_grob_direction (par_beam) != dir)
578 calc_position_and_height (me, &offset, &dy);
581 SCM ps = par_beam->get_property ("positions");
583 Real lp = scm_to_double (scm_car (ps));
584 Real rp = scm_to_double (scm_cdr (ps));
589 offset = lp + dir * (0.5 + scm_to_double (me->get_property ("padding")));
594 SCM x = scm_cons (scm_from_double (offset),
595 scm_from_double (offset + dy));
604 Tuplet_bracket::get_default_dir (Grob *me)
606 Drul_array<int> dirs (0, 0);
607 extract_grob_set (me, "note-columns", columns);
608 for (int i = 0; i < columns.size (); i++)
610 Grob *nc = columns[i];
611 Direction d = Note_column::dir (nc);
616 return dirs[UP] >= dirs[DOWN] ? UP : DOWN;
620 Tuplet_bracket::add_column (Grob *me, Item *n)
622 Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-columns"), n);
623 add_bound_item (dynamic_cast<Spanner *> (me), n);
627 Tuplet_bracket::add_tuplet_bracket (Grob *me, Grob *bracket)
629 Pointer_group_interface::add_grob (me, ly_symbol2scm ("tuplets"), bracket);
632 ADD_INTERFACE (Tuplet_bracket,
633 "tuplet-bracket-interface",
634 "A bracket with a number in the middle, used for tuplets. "
635 "When the bracket spans a line break, the value of "
636 "@code{break-overshoot} determines how far it extends "
638 "At a line break, the markups in the @code{edge-text} are printed "
644 "bracket-visibility "