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))
405 staff = st->extent (commony, Y_AXIS);
407 Direction dir = get_grob_direction (me);
410 Use outer non-rest columns to determine slope
414 get_bounds (me, &left_col, &right_col);
415 if (left_col && right_col)
417 Interval rv = right_col->extent (commony, Y_AXIS);
418 Interval lv = left_col->extent (commony, Y_AXIS);
421 Real graphical_dy = rv[dir] - lv[dir];
423 Slice ls = Note_column::head_positions_interval (left_col);
424 Slice rs = Note_column::head_positions_interval (right_col);
427 musical_dy[UP] = rs[UP] - ls[UP];
428 musical_dy[DOWN] = rs[DOWN] - ls[DOWN];
429 if (sign (musical_dy[UP]) != sign (musical_dy[DOWN]))
431 else if (sign (graphical_dy) != sign (musical_dy[DOWN]))
439 *offset = -dir * infinity_f;
441 Item *lgr = get_x_bound_item (me, LEFT, dir);
442 Item *rgr = get_x_bound_item (me, RIGHT, dir);
443 Real x0 = robust_relative_extent (lgr, commonx, X_AXIS)[LEFT];
444 Real x1 = robust_relative_extent (rgr, commonx, X_AXIS)[RIGHT];
446 Array<Offset> points;
447 points.push (Offset (x0 - x0, staff[dir]));
448 points.push (Offset (x1 - x0, staff[dir]));
450 for (int i = 0; i < columns.size (); i++)
452 Interval note_ext = columns[i]->extent (commony, Y_AXIS);
453 Real notey = note_ext[dir] - me->relative_coordinate (commony, Y_AXIS);
455 Real x = columns[i]->relative_coordinate (commonx, X_AXIS) - x0;
456 points.push (Offset (x, notey));
460 This is a slight hack. We compute two encompass points from the
461 bbox of the smaller tuplets.
463 We assume that the smaller bracket is 1.0 space high.
465 Real ss = Staff_symbol_referencer::staff_space (me);
466 for (int i = 0; i < tuplets.size (); i++)
468 Interval tuplet_x (tuplets[i]->extent (commonx, X_AXIS));
469 Interval tuplet_y (tuplets[i]->extent (commony, Y_AXIS));
472 Real lp = scm_to_double (tuplets[i]->get_property ("left-position"));
473 Real rp = scm_to_double (tuplets[i]->get_property ("right-position"));
474 Real other_dy = rp - lp;
479 = tuplet_y.linear_combination (d * sign (other_dy));
483 Let's not take padding into account for nested tuplets.
484 the edges can come very close to the stems, likewise for
487 Drul_array<Real> my_height
488 = robust_scm2drul (me->get_property ("edge-height"),
490 if (dynamic_cast<Spanner *> (tuplets[i])->get_bound (d)
491 == me->get_bound (d))
492 y += dir * my_height[d];
495 points.push (Offset (tuplet_x[d] - x0, y));
497 while (flip (&d) != LEFT);
500 Real factor = (columns.size () > 1) ? 1 / (x1 - x0) : 1.0;
501 for (int i = 0; i < points.size (); i++)
503 Real x = points[i][X_AXIS];
504 Real tuplety = (*dy) * x * factor;
506 if (points[i][Y_AXIS] * dir > (*offset + tuplety) * dir)
507 *offset = points[i][Y_AXIS] - tuplety;
510 *offset += scm_to_double (me->get_property ("padding")) * dir;
513 horizontal brackets should not collide with staff lines.
515 Kind of pointless since we put them outside the staff anyway, but
516 let's leave code for the future when possibly allow them to move
517 into the staff once again.
520 && fabs (*offset) < ss * Staff_symbol_referencer::staff_radius (me))
522 // quantize, then do collision check.
525 *offset = rint (*offset);
526 if (Staff_symbol_referencer::on_staffline (me, (int) rint (*offset)))
534 We depend on the beams if there are any.
536 MAKE_SCHEME_CALLBACK (Tuplet_bracket, before_line_breaking, 1);
538 Tuplet_bracket::before_line_breaking (SCM smob)
540 Grob *me = unsmob_grob (smob);
541 extract_grob_set (me, "note-columns", columns);
543 for (int i = columns.size (); i--;)
545 Grob *s = Note_column::get_stem (columns[i]);
546 Grob *b = s ? Stem::get_beam (s) : 0;
548 me->add_dependency (b);
550 return SCM_UNSPECIFIED;
553 MAKE_SCHEME_CALLBACK (Tuplet_bracket, after_line_breaking, 1);
556 Tuplet_bracket::after_line_breaking (SCM smob)
558 Grob *me = unsmob_grob (smob);
559 extract_grob_set (me, "note-columns", columns);
561 Direction dir = get_grob_direction (me);
564 dir = Tuplet_bracket::get_default_dir (me);
565 set_grob_direction (me, dir);
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")));
593 SCM lp = me->get_property ("left-position");
594 SCM rp = me->get_property ("right-position");
596 if (scm_is_number (lp) && !scm_is_number (rp))
597 rp = scm_from_double (scm_to_double (lp) + dy);
598 else if (scm_is_number (rp) && !scm_is_number (lp))
599 lp = scm_from_double (scm_to_double (rp) - dy);
600 else if (!scm_is_number (rp) && !scm_is_number (lp))
602 lp = scm_from_double (offset);
603 rp = scm_from_double (offset + dy);
606 me->set_property ("left-position", lp);
607 me->set_property ("right-position", rp);
609 return SCM_UNSPECIFIED;
616 Tuplet_bracket::get_default_dir (Grob *me)
618 Drul_array<int> dirs (0, 0);
619 extract_grob_set (me, "note-columns", columns);
620 for (int i = 0; i < columns.size (); i++)
622 Grob *nc = columns[i];
623 Direction d = Note_column::dir (nc);
628 return dirs[UP] >= dirs[DOWN] ? UP : DOWN;
632 Tuplet_bracket::add_column (Grob *me, Item *n)
634 Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-columns"), n);
635 me->add_dependency (n);
637 add_bound_item (dynamic_cast<Spanner *> (me), n);
641 Tuplet_bracket::add_tuplet_bracket (Grob *me, Grob *bracket)
643 Pointer_group_interface::add_grob (me, ly_symbol2scm ("tuplets"), bracket);
644 me->add_dependency (bracket);
647 ADD_INTERFACE (Tuplet_bracket,
648 "tuplet-bracket-interface",
649 "A bracket with a number in the middle, used for tuplets. "
650 "When the bracket spans a line break, the value of "
651 "@code{break-overshoot} determines how far it extends "
653 "At a line break, the markups in the @code{edge-text} are printed "
656 "note-columns bracket-flare edge-height shorten-pair "
657 "tuplets edge-text break-overshoot "
658 "padding left-position right-position bracket-visibility "
659 "number-visibility thickness direction");