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.
35 #include "tuplet-bracket.hh"
36 #include "line-interface.hh"
39 #include "font-interface.hh"
40 #include "output-def.hh"
41 #include "text-interface.hh"
43 #include "note-column.hh"
44 #include "pointer-group-interface.hh"
45 #include "directional-element-interface.hh"
47 #include "staff-symbol-referencer.hh"
51 get_x_bound_item (Grob *me_grob, Direction hdir, Direction my_dir)
53 Spanner *me = dynamic_cast<Spanner *> (me_grob);
54 Item *g = me->get_bound (hdir);
55 if (Note_column::has_interface (g)
56 && Note_column::get_stem (g)
57 && Note_column::dir (g) == my_dir)
58 g = Note_column::get_stem (g);
64 Tuplet_bracket::parallel_beam (Grob *me_grob, Link_array<Grob> const &cols, bool *equally_long)
66 Spanner *me = dynamic_cast<Spanner *> (me_grob);
68 if (me->get_bound (LEFT)->break_status_dir ()
69 || me->get_bound (RIGHT)->break_status_dir ())
72 Grob *s1 = Note_column::get_stem (cols[0]);
73 Grob *s2 = Note_column::get_stem (cols.top ());
75 if (s2 != me->get_bound (RIGHT))
78 Grob *b1 = s1 ? Stem::get_beam (s1) : 0;
79 Grob *b2 = s2 ? Stem::get_beam (s2) : 0;
81 *equally_long = false;
82 if (! (b1 && (b1 == b2) && !me->is_broken ()))
85 extract_grob_set (b1, "stems", beam_stems);
86 if (beam_stems.size () == 0)
88 programming_error ("beam under tuplet bracket has no stems");
93 *equally_long = (beam_stems[0] == s1 && beam_stems.top () == s2);
100 in the case that there is no bracket, but there is a (single) beam,
101 follow beam precisely for determining tuplet number location.
103 MAKE_SCHEME_CALLBACK (Tuplet_bracket, print, 1);
105 Tuplet_bracket::print (SCM smob)
107 Spanner *me = unsmob_spanner (smob);
109 extract_grob_set (me, "note-columns", columns);
112 SCM lp = me->get_property ("left-position");
113 SCM rp = me->get_property ("right-position");
115 if (!scm_is_number (rp) || !scm_is_number (lp))
118 UGH. dependency tracking!
120 extract_grob_set (me, "tuplets", tuplets);
121 for (int i = 0; i < tuplets.size (); i++)
122 Tuplet_bracket::print (tuplets[i]->self_scm ());
124 after_line_breaking (smob);
128 Real ly = robust_scm2double (me->get_property ("left-position"), 0);
129 Real ry = robust_scm2double (me->get_property ("right-position"), 0);
131 bool equally_long = false;
132 Grob *par_beam = parallel_beam (me, columns, &equally_long);
134 Spanner *sp = dynamic_cast<Spanner *> (me);
136 bool bracket_visibility = !(par_beam && equally_long);
137 bool number_visibility = true;
140 Fixme: the type of this prop is sucky.
142 SCM bracket = me->get_property ("bracket-visibility");
143 if (scm_is_bool (bracket))
144 bracket_visibility = ly_scm2bool (bracket);
145 else if (bracket == ly_symbol2scm ("if-no-beam"))
146 bracket_visibility = !par_beam;
148 SCM numb = me->get_property ("number-visibility");
149 if (scm_is_bool (numb))
150 number_visibility = ly_scm2bool (numb);
151 else if (numb == ly_symbol2scm ("if-no-beam"))
152 number_visibility = !par_beam;
154 Grob *commonx = common_refpoint_of_array (columns, me, X_AXIS);
155 commonx = commonx->common_refpoint (sp->get_bound (LEFT), X_AXIS);
156 commonx = commonx->common_refpoint (sp->get_bound (RIGHT), X_AXIS);
158 Direction dir = get_grob_direction (me);
160 Drul_array<Item *> bounds;
161 bounds[LEFT] = get_x_bound_item (me, LEFT, dir);
162 bounds[RIGHT] = get_x_bound_item (me, RIGHT, dir);
164 Drul_array<bool> connect_to_other;
169 x_span[d] = robust_relative_extent (bounds[d], commonx, X_AXIS)[d];
170 Direction break_dir = bounds[d]->break_status_dir ();
171 Spanner *orig_spanner = dynamic_cast<Spanner *> (me->original_);
174 && (me->get_break_index () - break_dir < orig_spanner->broken_intos_.size ()));
176 if (connect_to_other[d])
178 Interval overshoot (robust_scm2drul (me->get_property ("break-overshoot"),
179 Interval (-0.5, 0.0)));
182 x_span[d] += d * overshoot[d];
184 x_span[d] = robust_relative_extent (bounds[d], commonx, X_AXIS)[RIGHT]
188 && (columns.is_empty ()
189 || (bounds[d]->get_column ()
190 != dynamic_cast<Item *> (columns.top ())->get_column ())))
193 TODO: make padding tunable?
195 x_span[d] = robust_relative_extent (bounds[d], commonx, X_AXIS) [LEFT] - 1.0;
198 while (flip (&d) != LEFT);
200 Real w = x_span.length ();
201 SCM number = me->get_property ("text");
203 Output_def *pap = me->get_layout ();
205 if (scm_is_string (number) && number_visibility)
207 SCM properties = Font_interface::text_font_alist_chain (me);
208 SCM snum = Text_interface::interpret_markup (pap->self_scm (),
210 num = *unsmob_stencil (snum);
211 num.align_to (X_AXIS, CENTER);
212 num.translate_axis (w / 2, X_AXIS);
213 num.align_to (Y_AXIS, CENTER);
215 num.translate_axis ((ry - ly) / 2, Y_AXIS);
217 mol.add_stencil (num);
221 No bracket when it would be smaller than the number.
223 TODO: should use GAP in calculation too.
225 if (bracket_visibility && number_visibility
226 && mol.extent (X_AXIS).length () > w)
227 bracket_visibility = false;
229 if (bracket_visibility)
233 if (!num.extent (X_AXIS).is_empty ())
234 gap = num.extent (X_AXIS).length () + 1.0;
236 Drul_array<Real> zero (0, 0);
237 Real ss = Staff_symbol_referencer::staff_space (me);
238 Drul_array<Real> height
239 = robust_scm2drul (me->get_property ("edge-height"), zero);
240 Drul_array<Real> flare
241 = robust_scm2drul (me->get_property ("bracket-flare"), zero);
242 Drul_array<Real> shorten
243 = robust_scm2drul (me->get_property ("shorten-pair"), zero);
244 Drul_array<Stencil> edge_stencils;
246 scale_drul (&height, -ss * dir);
247 scale_drul (&flare, ss);
248 scale_drul (&shorten, ss);
251 if (connect_to_other[d])
257 SCM edge_text = me->get_property ("edge-text");
259 if (scm_is_pair (edge_text))
261 SCM properties = Font_interface::text_font_alist_chain (me);
262 SCM text = index_get_cell (edge_text, d);
263 if (Text_interface::is_markup (text))
265 SCM t = Text_interface::interpret_markup (pap->self_scm (), properties, text);
267 Stencil *edge_text = unsmob_stencil (t);
268 edge_text->translate_axis (x_span[d] - x_span[LEFT], X_AXIS);
269 edge_stencils[d] = *edge_text;
274 while (flip (&d) != LEFT);
276 Stencil brack = make_bracket (me, Y_AXIS,
280 0.1 = more space at right due to italics
281 TODO: use italic correction of font.
283 Interval (-0.5, 0.5) * gap + 0.1,
288 if (!edge_stencils[d].is_empty ())
289 brack.add_stencil (edge_stencils[d]);
291 while (flip (&d) != LEFT);
293 mol.add_stencil (brack);
296 mol.translate_axis (ly, Y_AXIS);
297 mol.translate_axis (x_span[LEFT]
298 - sp->get_bound (LEFT)->relative_coordinate (commonx, X_AXIS), X_AXIS);
299 return mol.smobbed_copy ();
303 should move to lookup?
305 TODO: this will fail for very short (shorter than the flare)
309 Tuplet_bracket::make_bracket (Grob *me, // for line properties.
312 Drul_array<Real> height,
314 Drul_array<Real> flare,
315 Drul_array<Real> shorten)
317 Drul_array<Offset> corners (Offset (0, 0), dz);
319 Real length = dz.length ();
320 Drul_array<Offset> gap_corners;
322 Axis bracket_axis = other_axis (protusion_axis);
324 Drul_array<Offset> straight_corners = corners;
328 straight_corners[d] += -d * shorten[d] / length * dz;
329 while (flip (&d) != LEFT)
333 gap = Interval (0, 0);
335 gap_corners[d] = (dz * 0.5) + gap[d] / length * dz;
336 while (flip (&d) != LEFT)
339 Drul_array<Offset> flare_corners = straight_corners;
342 flare_corners[d][bracket_axis] = straight_corners[d][bracket_axis];
343 flare_corners[d][protusion_axis] += height[d];
344 straight_corners[d][bracket_axis] += -d * flare[d];
346 while (flip (&d) != LEFT);
351 m.add_stencil (Line_interface::line (me, straight_corners[d],
354 m.add_stencil (Line_interface::line (me, straight_corners[d],
357 while (flip (&d) != LEFT);
363 Tuplet_bracket::get_bounds (Grob *me, Grob **left, Grob **right)
365 extract_grob_set (me, "note-columns", columns);
367 while (l < columns.size () && Note_column::has_rests (columns[l]))
370 int r = columns.size ()- 1;
371 while (r >= l && Note_column::has_rests (columns[r]))
384 use first -> last note for slope, and then correct for disturbing
387 Tuplet_bracket::calc_position_and_height (Grob *me_grob, Real *offset, Real *dy)
389 Spanner *me = dynamic_cast<Spanner *> (me_grob);
391 extract_grob_set (me, "note-columns", columns);
392 extract_grob_set (me, "tuplets", tuplets);
394 Grob *commony = common_refpoint_of_array (columns, me, Y_AXIS);
395 commony = common_refpoint_of_array (tuplets, commony, Y_AXIS);
396 if (Grob *st = Staff_symbol_referencer::get_staff_symbol (me))
397 commony = st->common_refpoint (commony, Y_AXIS);
399 Grob *commonx = common_refpoint_of_array (columns, me, X_AXIS);
400 commonx = common_refpoint_of_array (tuplets, commonx, Y_AXIS);
401 commonx = commonx->common_refpoint (me->get_bound (LEFT), X_AXIS);
402 commonx = commonx->common_refpoint (me->get_bound (RIGHT), X_AXIS);
405 if (Grob *st = Staff_symbol_referencer::get_staff_symbol (me))
407 staff = st->extent (commony, Y_AXIS);
408 Real pad = robust_scm2double (me->get_property ("staff-padding"), 0.5);
412 Direction dir = get_grob_direction (me);
415 Use outer non-rest columns to determine slope
419 get_bounds (me, &left_col, &right_col);
420 if (left_col && right_col)
422 Interval rv = right_col->extent (commony, Y_AXIS);
423 Interval lv = left_col->extent (commony, Y_AXIS);
426 Real graphical_dy = rv[dir] - lv[dir];
428 Slice ls = Note_column::head_positions_interval (left_col);
429 Slice rs = Note_column::head_positions_interval (right_col);
432 musical_dy[UP] = rs[UP] - ls[UP];
433 musical_dy[DOWN] = rs[DOWN] - ls[DOWN];
434 if (sign (musical_dy[UP]) != sign (musical_dy[DOWN]))
436 else if (sign (graphical_dy) != sign (musical_dy[DOWN]))
444 *offset = -dir * infinity_f;
446 Item *lgr = get_x_bound_item (me, LEFT, dir);
447 Item *rgr = get_x_bound_item (me, RIGHT, dir);
448 Real x0 = robust_relative_extent (lgr, commonx, X_AXIS)[LEFT];
449 Real x1 = robust_relative_extent (rgr, commonx, X_AXIS)[RIGHT];
451 Array<Offset> points;
452 points.push (Offset (x0 - x0, staff[dir]));
453 points.push (Offset (x1 - x0, staff[dir]));
455 for (int i = 0; i < columns.size (); i++)
457 Interval note_ext = columns[i]->extent (commony, Y_AXIS);
458 Real notey = note_ext[dir] - me->relative_coordinate (commony, Y_AXIS);
460 Real x = columns[i]->relative_coordinate (commonx, X_AXIS) - x0;
461 points.push (Offset (x, notey));
465 This is a slight hack. We compute two encompass points from the
466 bbox of the smaller tuplets.
468 We assume that the smaller bracket is 1.0 space high.
470 Real ss = Staff_symbol_referencer::staff_space (me);
471 for (int i = 0; i < tuplets.size (); i++)
473 Interval tuplet_x (tuplets[i]->extent (commonx, X_AXIS));
474 Interval tuplet_y (tuplets[i]->extent (commony, Y_AXIS));
477 Real lp = scm_to_double (tuplets[i]->get_property ("left-position"));
478 Real rp = scm_to_double (tuplets[i]->get_property ("right-position"));
479 Real other_dy = rp - lp;
484 = tuplet_y.linear_combination (d * sign (other_dy));
488 Let's not take padding into account for nested tuplets.
489 the edges can come very close to the stems, likewise for
492 Drul_array<Real> my_height
493 = robust_scm2drul (me->get_property ("edge-height"),
495 if (dynamic_cast<Spanner *> (tuplets[i])->get_bound (d)
496 == me->get_bound (d))
497 y += dir * my_height[d];
500 points.push (Offset (tuplet_x[d] - x0, y));
502 while (flip (&d) != LEFT);
505 Real factor = (columns.size () > 1) ? 1 / (x1 - x0) : 1.0;
506 for (int i = 0; i < points.size (); i++)
508 Real x = points[i][X_AXIS];
509 Real tuplety = (*dy) * x * factor;
511 if (points[i][Y_AXIS] * dir > (*offset + tuplety) * dir)
512 *offset = points[i][Y_AXIS] - tuplety;
515 *offset += scm_to_double (me->get_property ("padding")) * dir;
518 horizontal brackets should not collide with staff lines.
520 Kind of pointless since we put them outside the staff anyway, but
521 let's leave code for the future when possibly allow them to move
522 into the staff once again.
525 && fabs (*offset) < ss * Staff_symbol_referencer::staff_radius (me))
527 // quantize, then do collision check.
530 *offset = rint (*offset);
531 if (Staff_symbol_referencer::on_staffline (me, (int) rint (*offset)))
539 We depend on the beams if there are any.
541 MAKE_SCHEME_CALLBACK (Tuplet_bracket, before_line_breaking, 1);
543 Tuplet_bracket::before_line_breaking (SCM smob)
545 Grob *me = unsmob_grob (smob);
546 extract_grob_set (me, "note-columns", columns);
548 for (int i = columns.size (); i--;)
550 Grob *s = Note_column::get_stem (columns[i]);
551 Grob *b = s ? Stem::get_beam (s) : 0;
553 me->add_dependency (b);
555 return SCM_UNSPECIFIED;
558 MAKE_SCHEME_CALLBACK (Tuplet_bracket, after_line_breaking, 1);
561 Tuplet_bracket::after_line_breaking (SCM smob)
563 Grob *me = unsmob_grob (smob);
564 extract_grob_set (me, "note-columns", columns);
566 Direction dir = get_grob_direction (me);
569 dir = Tuplet_bracket::get_default_dir (me);
570 set_grob_direction (me, dir);
573 bool equally_long = false;
574 Grob *par_beam = parallel_beam (me, columns, &equally_long);
577 We follow the beam only if there is one, and we are next to it.
582 || get_grob_direction (par_beam) != dir)
583 calc_position_and_height (me, &offset, &dy);
586 SCM ps = par_beam->get_property ("positions");
588 Real lp = scm_to_double (scm_car (ps));
589 Real rp = scm_to_double (scm_cdr (ps));
594 offset = lp + dir * (0.5 + scm_to_double (me->get_property ("padding")));
598 SCM lp = me->get_property ("left-position");
599 SCM rp = me->get_property ("right-position");
601 if (scm_is_number (lp) && !scm_is_number (rp))
602 rp = scm_from_double (scm_to_double (lp) + dy);
603 else if (scm_is_number (rp) && !scm_is_number (lp))
604 lp = scm_from_double (scm_to_double (rp) - dy);
605 else if (!scm_is_number (rp) && !scm_is_number (lp))
607 lp = scm_from_double (offset);
608 rp = scm_from_double (offset + dy);
611 me->set_property ("left-position", lp);
612 me->set_property ("right-position", rp);
614 return SCM_UNSPECIFIED;
621 Tuplet_bracket::get_default_dir (Grob *me)
623 Drul_array<int> dirs (0, 0);
624 extract_grob_set (me, "note-columns", columns);
625 for (int i = 0; i < columns.size (); i++)
627 Grob *nc = columns[i];
628 Direction d = Note_column::dir (nc);
633 return dirs[UP] >= dirs[DOWN] ? UP : DOWN;
637 Tuplet_bracket::add_column (Grob *me, Item *n)
639 Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-columns"), n);
640 me->add_dependency (n);
642 add_bound_item (dynamic_cast<Spanner *> (me), n);
646 Tuplet_bracket::add_tuplet_bracket (Grob *me, Grob *bracket)
648 Pointer_group_interface::add_grob (me, ly_symbol2scm ("tuplets"), bracket);
649 me->add_dependency (bracket);
652 ADD_INTERFACE (Tuplet_bracket,
653 "tuplet-bracket-interface",
654 "A bracket with a number in the middle, used for tuplets. "
655 "When the bracket spans a line break, the value of "
656 "@code{break-overshoot} determines how far it extends "
658 "At a line break, the markups in the @code{edge-text} are printed "
663 "bracket-visibility "