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.
34 #include "tuplet-bracket.hh"
35 #include "line-interface.hh"
38 #include "font-interface.hh"
39 #include "output-def.hh"
40 #include "text-interface.hh"
42 #include "note-column.hh"
43 #include "pointer-group-interface.hh"
44 #include "directional-element-interface.hh"
46 #include "staff-symbol-referencer.hh"
50 get_x_bound_item (Grob *me_grob, Direction hdir, Direction my_dir)
52 Spanner *me = dynamic_cast<Spanner *> (me_grob);
53 Item *g = me->get_bound (hdir);
54 if (Note_column::has_interface (g)
55 && Note_column::get_stem (g)
56 && Note_column::dir (g) == my_dir)
57 g = Note_column::get_stem (g);
63 Tuplet_bracket::parallel_beam (Grob *me_grob, Link_array<Grob> const &cols, bool *equally_long)
65 Spanner *me = dynamic_cast<Spanner *> (me_grob);
67 if (me->get_bound (LEFT)->break_status_dir ()
68 || me->get_bound (RIGHT)->break_status_dir ())
71 Grob *s1 = Note_column::get_stem (cols[0]);
72 Grob *s2 = Note_column::get_stem (cols.top ());
74 if (s2 != me->get_bound (RIGHT))
77 Grob *b1 = s1 ? Stem::get_beam (s1) : 0;
78 Grob *b2 = s2 ? Stem::get_beam (s2) : 0;
80 *equally_long = false;
81 if (! (b1 && (b1 == b2) && !me->is_broken ()))
84 extract_grob_set (b1, "stems", beam_stems);
85 if (beam_stems.size () == 0)
87 programming_error ("beam under tuplet bracket has no stems");
92 *equally_long = (beam_stems[0] == s1 && beam_stems.top () == s2);
99 in the case that there is no bracket, but there is a (single) beam,
100 follow beam precisely for determining tuplet number location.
102 MAKE_SCHEME_CALLBACK (Tuplet_bracket, print, 1);
104 Tuplet_bracket::print (SCM smob)
106 Spanner *me = unsmob_spanner (smob);
108 extract_grob_set (me, "note-columns", columns);
111 SCM lp = me->get_property ("left-position");
112 SCM rp = me->get_property ("right-position");
114 if (!scm_is_number (rp) || !scm_is_number (lp))
117 UGH. dependency tracking!
119 extract_grob_set (me, "tuplets", tuplets);
120 for (int i = 0; i < tuplets.size (); i++)
121 Tuplet_bracket::print (tuplets[i]->self_scm ());
123 after_line_breaking (smob);
127 Real ly = robust_scm2double (me->get_property ("left-position"), 0);
128 Real ry = robust_scm2double (me->get_property ("right-position"), 0);
130 bool equally_long = false;
131 Grob *par_beam = parallel_beam (me, columns, &equally_long);
133 Spanner *sp = dynamic_cast<Spanner *> (me);
135 bool bracket_visibility = !(par_beam && equally_long);
136 bool number_visibility = true;
139 Fixme: the type of this prop is sucky.
141 SCM bracket = me->get_property ("bracket-visibility");
142 if (scm_is_bool (bracket))
143 bracket_visibility = ly_scm2bool (bracket);
144 else if (bracket == ly_symbol2scm ("if-no-beam"))
145 bracket_visibility = !par_beam;
147 SCM numb = me->get_property ("number-visibility");
148 if (scm_is_bool (numb))
149 number_visibility = ly_scm2bool (numb);
150 else if (numb == ly_symbol2scm ("if-no-beam"))
151 number_visibility = !par_beam;
153 Grob *commonx = common_refpoint_of_array (columns, me, X_AXIS);
154 commonx = commonx->common_refpoint (sp->get_bound (LEFT), X_AXIS);
155 commonx = commonx->common_refpoint (sp->get_bound (RIGHT), X_AXIS);
157 Direction dir = get_grob_direction (me);
159 Drul_array<Item *> bounds;
160 bounds[LEFT] = get_x_bound_item (me, LEFT, dir);
161 bounds[RIGHT] = get_x_bound_item (me, RIGHT, dir);
163 Drul_array<bool> connect_to_other;
168 x_span[d] = robust_relative_extent (bounds[d], commonx, X_AXIS)[d];
169 Direction break_dir = bounds[d]->break_status_dir ();
170 Spanner *orig_spanner = dynamic_cast<Spanner *> (me->original_);
173 && (me->get_break_index () - break_dir < orig_spanner->broken_intos_.size ()));
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?
194 x_span[d] = robust_relative_extent (bounds[d], commonx, X_AXIS) [LEFT] - 1.0;
197 while (flip (&d) != LEFT);
199 Real w = x_span.length ();
200 SCM number = me->get_property ("text");
202 Output_def *pap = me->get_layout ();
204 if (scm_is_string (number) && number_visibility)
206 SCM properties = Font_interface::text_font_alist_chain (me);
207 SCM snum = Text_interface::interpret_markup (pap->self_scm (),
209 num = *unsmob_stencil (snum);
210 num.align_to (X_AXIS, CENTER);
211 num.translate_axis (w / 2, X_AXIS);
212 num.align_to (Y_AXIS, CENTER);
214 num.translate_axis ((ry - ly) / 2, Y_AXIS);
216 mol.add_stencil (num);
220 No bracket when it would be smaller than the number.
222 TODO: should use GAP in calculation too.
224 if (bracket_visibility && number_visibility
225 && mol.extent (X_AXIS).length () > w)
226 bracket_visibility = false;
228 if (bracket_visibility)
232 if (!num.extent (X_AXIS).is_empty ())
233 gap = num.extent (X_AXIS).length () + 1.0;
235 Drul_array<Real> zero (0, 0);
236 Real ss = Staff_symbol_referencer::staff_space (me);
237 Drul_array<Real> height
238 = robust_scm2drul (me->get_property ("edge-height"), zero);
239 Drul_array<Real> flare
240 = robust_scm2drul (me->get_property ("bracket-flare"), zero);
241 Drul_array<Real> shorten
242 = robust_scm2drul (me->get_property ("shorten-pair"), zero);
243 Drul_array<Stencil> edge_stencils;
245 scale_drul (&height, -ss * dir);
246 scale_drul (&flare, ss);
247 scale_drul (&shorten, ss);
250 if (connect_to_other[d])
256 SCM edge_text = me->get_property ("edge-text");
258 if (scm_is_pair (edge_text))
260 SCM properties = Font_interface::text_font_alist_chain (me);
261 SCM text = index_get_cell (edge_text, d);
262 if (Text_interface::is_markup (text))
264 SCM t = Text_interface::interpret_markup (pap->self_scm (), properties, text);
266 Stencil *edge_text = unsmob_stencil (t);
267 edge_text->translate_axis (x_span[d] - x_span[LEFT], X_AXIS);
268 edge_stencils[d] = *edge_text;
273 while (flip (&d) != LEFT);
275 Stencil brack = make_bracket (me, Y_AXIS,
279 0.1 = more space at right due to italics
280 TODO: use italic correction of font.
282 Interval (-0.5, 0.5) * gap + 0.1,
287 if (!edge_stencils[d].is_empty ())
288 brack.add_stencil (edge_stencils[d]);
290 while (flip (&d) != LEFT);
292 mol.add_stencil (brack);
295 mol.translate_axis (ly, Y_AXIS);
296 mol.translate_axis (x_span[LEFT]
297 - sp->get_bound (LEFT)->relative_coordinate (commonx, X_AXIS), X_AXIS);
298 return mol.smobbed_copy ();
302 should move to lookup?
304 TODO: this will fail for very short (shorter than the flare)
308 Tuplet_bracket::make_bracket (Grob *me, // for line properties.
311 Drul_array<Real> height,
313 Drul_array<Real> flare,
314 Drul_array<Real> shorten)
316 Drul_array<Offset> corners (Offset (0, 0), dz);
318 Real length = dz.length ();
319 Drul_array<Offset> gap_corners;
321 Axis bracket_axis = other_axis (protusion_axis);
323 Drul_array<Offset> straight_corners = corners;
327 straight_corners[d] += -d * shorten[d] / length * dz;
328 while (flip (&d) != LEFT)
332 gap = Interval (0, 0);
334 gap_corners[d] = (dz * 0.5) + gap[d] / length * dz;
335 while (flip (&d) != LEFT)
338 Drul_array<Offset> flare_corners = straight_corners;
341 flare_corners[d][bracket_axis] = straight_corners[d][bracket_axis];
342 flare_corners[d][protusion_axis] += height[d];
343 straight_corners[d][bracket_axis] += -d * flare[d];
345 while (flip (&d) != LEFT);
350 m.add_stencil (Line_interface::line (me, straight_corners[d],
353 m.add_stencil (Line_interface::line (me, straight_corners[d],
356 while (flip (&d) != LEFT);
362 Tuplet_bracket::get_bounds (Grob *me, Grob **left, Grob **right)
364 extract_grob_set (me, "note-columns", columns);
366 while (l < columns.size () && Note_column::has_rests (columns[l]))
369 int r = columns.size ()- 1;
370 while (r >= l && Note_column::has_rests (columns[r]))
383 use first -> last note for slope, and then correct for disturbing
386 Tuplet_bracket::calc_position_and_height (Grob *me_grob, Real *offset, Real *dy)
388 Spanner *me = dynamic_cast<Spanner *> (me_grob);
390 extract_grob_set (me, "note-columns", columns);
391 extract_grob_set (me, "tuplets", tuplets);
393 Grob *commony = common_refpoint_of_array (columns, me, Y_AXIS);
394 commony = common_refpoint_of_array (tuplets, commony, Y_AXIS);
395 if (Grob *st = Staff_symbol_referencer::get_staff_symbol (me))
396 commony = st->common_refpoint (commony, Y_AXIS);
398 Grob *commonx = common_refpoint_of_array (columns, me, X_AXIS);
399 commonx = common_refpoint_of_array (tuplets, commonx, Y_AXIS);
400 commonx = commonx->common_refpoint (me->get_bound (LEFT), X_AXIS);
401 commonx = commonx->common_refpoint (me->get_bound (RIGHT), X_AXIS);
404 if (Grob *st = Staff_symbol_referencer::get_staff_symbol (me))
406 staff = st->extent (commony, Y_AXIS);
407 Real pad = robust_scm2double (me->get_property ("staff-padding"), 0.5);
411 Direction dir = get_grob_direction (me);
414 Use outer non-rest columns to determine slope
418 get_bounds (me, &left_col, &right_col);
419 if (left_col && right_col)
421 Interval rv = right_col->extent (commony, Y_AXIS);
422 Interval lv = left_col->extent (commony, Y_AXIS);
425 Real graphical_dy = rv[dir] - lv[dir];
427 Slice ls = Note_column::head_positions_interval (left_col);
428 Slice rs = Note_column::head_positions_interval (right_col);
431 musical_dy[UP] = rs[UP] - ls[UP];
432 musical_dy[DOWN] = rs[DOWN] - ls[DOWN];
433 if (sign (musical_dy[UP]) != sign (musical_dy[DOWN]))
435 else if (sign (graphical_dy) != sign (musical_dy[DOWN]))
443 *offset = -dir * infinity_f;
445 Item *lgr = get_x_bound_item (me, LEFT, dir);
446 Item *rgr = get_x_bound_item (me, RIGHT, dir);
447 Real x0 = robust_relative_extent (lgr, commonx, X_AXIS)[LEFT];
448 Real x1 = robust_relative_extent (rgr, commonx, X_AXIS)[RIGHT];
450 Array<Offset> points;
451 points.push (Offset (x0 - x0, staff[dir]));
452 points.push (Offset (x1 - x0, staff[dir]));
454 for (int i = 0; i < columns.size (); i++)
456 Interval note_ext = columns[i]->extent (commony, Y_AXIS);
457 Real notey = note_ext[dir] - me->relative_coordinate (commony, Y_AXIS);
459 Real x = columns[i]->relative_coordinate (commonx, X_AXIS) - x0;
460 points.push (Offset (x, notey));
464 This is a slight hack. We compute two encompass points from the
465 bbox of the smaller tuplets.
467 We assume that the smaller bracket is 1.0 space high.
469 Real ss = Staff_symbol_referencer::staff_space (me);
470 for (int i = 0; i < tuplets.size (); i++)
472 Interval tuplet_x (tuplets[i]->extent (commonx, X_AXIS));
473 Interval tuplet_y (tuplets[i]->extent (commony, Y_AXIS));
476 Real lp = scm_to_double (tuplets[i]->get_property ("left-position"));
477 Real rp = scm_to_double (tuplets[i]->get_property ("right-position"));
478 Real other_dy = rp - lp;
483 = tuplet_y.linear_combination (d * sign (other_dy));
487 Let's not take padding into account for nested tuplets.
488 the edges can come very close to the stems, likewise for
491 Drul_array<Real> my_height
492 = robust_scm2drul (me->get_property ("edge-height"),
494 if (dynamic_cast<Spanner *> (tuplets[i])->get_bound (d)
495 == me->get_bound (d))
496 y += dir * my_height[d];
499 points.push (Offset (tuplet_x[d] - x0, y));
501 while (flip (&d) != LEFT);
504 Real factor = (columns.size () > 1) ? 1 / (x1 - x0) : 1.0;
505 for (int i = 0; i < points.size (); i++)
507 Real x = points[i][X_AXIS];
508 Real tuplety = (*dy) * x * factor;
510 if (points[i][Y_AXIS] * dir > (*offset + tuplety) * dir)
511 *offset = points[i][Y_AXIS] - tuplety;
514 *offset += scm_to_double (me->get_property ("padding")) * dir;
517 horizontal brackets should not collide with staff lines.
519 Kind of pointless since we put them outside the staff anyway, but
520 let's leave code for the future when possibly allow them to move
521 into the staff once again.
524 && fabs (*offset) < ss * Staff_symbol_referencer::staff_radius (me))
526 // quantize, then do collision check.
529 *offset = rint (*offset);
530 if (Staff_symbol_referencer::on_staffline (me, (int) rint (*offset)))
538 We depend on the beams if there are any.
540 MAKE_SCHEME_CALLBACK (Tuplet_bracket, before_line_breaking, 1);
542 Tuplet_bracket::before_line_breaking (SCM smob)
544 Grob *me = unsmob_grob (smob);
545 extract_grob_set (me, "note-columns", columns);
547 for (int i = columns.size (); i--;)
549 Grob *s = Note_column::get_stem (columns[i]);
550 Grob *b = s ? Stem::get_beam (s) : 0;
552 me->add_dependency (b);
554 return SCM_UNSPECIFIED;
557 MAKE_SCHEME_CALLBACK (Tuplet_bracket, after_line_breaking, 1);
560 Tuplet_bracket::after_line_breaking (SCM smob)
562 Grob *me = unsmob_grob (smob);
563 extract_grob_set (me, "note-columns", columns);
565 Direction dir = get_grob_direction (me);
568 dir = Tuplet_bracket::get_default_dir (me);
569 set_grob_direction (me, dir);
572 bool equally_long = false;
573 Grob *par_beam = parallel_beam (me, columns, &equally_long);
576 We follow the beam only if there is one, and we are next to it.
581 || get_grob_direction (par_beam) != dir)
582 calc_position_and_height (me, &offset, &dy);
585 SCM ps = par_beam->get_property ("positions");
587 Real lp = scm_to_double (scm_car (ps));
588 Real rp = scm_to_double (scm_cdr (ps));
593 offset = lp + dir * (0.5 + scm_to_double (me->get_property ("padding")));
597 SCM lp = me->get_property ("left-position");
598 SCM rp = me->get_property ("right-position");
600 if (scm_is_number (lp) && !scm_is_number (rp))
601 rp = scm_from_double (scm_to_double (lp) + dy);
602 else if (scm_is_number (rp) && !scm_is_number (lp))
603 lp = scm_from_double (scm_to_double (rp) - dy);
604 else if (!scm_is_number (rp) && !scm_is_number (lp))
606 lp = scm_from_double (offset);
607 rp = scm_from_double (offset + dy);
610 me->set_property ("left-position", lp);
611 me->set_property ("right-position", rp);
613 return SCM_UNSPECIFIED;
620 Tuplet_bracket::get_default_dir (Grob *me)
622 Drul_array<int> dirs (0, 0);
623 extract_grob_set (me, "note-columns", columns);
624 for (int i = 0; i < columns.size (); i++)
626 Grob *nc = columns[i];
627 Direction d = Note_column::dir (nc);
632 return dirs[UP] >= dirs[DOWN] ? UP : DOWN;
636 Tuplet_bracket::add_column (Grob *me, Item *n)
638 Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-columns"), n);
639 me->add_dependency (n);
641 add_bound_item (dynamic_cast<Spanner *> (me), n);
645 Tuplet_bracket::add_tuplet_bracket (Grob *me, Grob *bracket)
647 Pointer_group_interface::add_grob (me, ly_symbol2scm ("tuplets"), bracket);
648 me->add_dependency (bracket);
651 ADD_INTERFACE (Tuplet_bracket,
652 "tuplet-bracket-interface",
653 "A bracket with a number in the middle, used for tuplets. "
654 "When the bracket spans a line break, the value of "
655 "@code{break-overshoot} determines how far it extends "
657 "At a line break, the markups in the @code{edge-text} are printed "
662 "bracket-visibility "