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)
58 g = Note_column::get_stem (g);
65 Tuplet_bracket::parallel_beam (Grob *me_grob, Link_array<Grob> const &cols, bool *equally_long)
67 Spanner *me = dynamic_cast<Spanner *> (me_grob);
69 if (me->get_bound (LEFT)->break_status_dir ()
70 || me->get_bound (RIGHT)->break_status_dir ())
73 Grob *s1 = Note_column::get_stem (cols[0]);
74 Grob *s2 = Note_column::get_stem (cols.top ());
77 if (s2 != me->get_bound (RIGHT))
80 Grob *b1 = s1 ? Stem::get_beam (s1) : 0;
81 Grob *b2 = s2 ? Stem::get_beam (s2) : 0;
84 *equally_long = false;
85 if (! (b1 && (b1 == b2) && !me->is_broken ()))
88 extract_grob_set (b1, "stems", beam_stems);
89 if (beam_stems.size () == 0)
91 programming_error ("beam under tuplet bracket has no stems");
96 *equally_long = (beam_stems[0] == s1 && beam_stems.top () == s2);
103 in the case that there is no bracket, but there is a (single) beam,
104 follow beam precisely for determining tuplet number location.
106 MAKE_SCHEME_CALLBACK (Tuplet_bracket, print, 1);
108 Tuplet_bracket::print (SCM smob)
110 Spanner *me = unsmob_spanner (smob);
112 extract_grob_set (me, "note-columns", columns);
115 SCM lp = me->get_property ("left-position");
116 SCM rp = me->get_property ("right-position");
118 if (!scm_is_number (rp) || !scm_is_number (lp))
121 UGH. dependency tracking!
123 extract_grob_set (me, "tuplets", tuplets);
124 for (int i = 0; i < tuplets.size (); i++)
125 Tuplet_bracket::print (tuplets[i]->self_scm());
127 after_line_breaking (smob);
131 Real ly = robust_scm2double (me->get_property ("left-position"), 0);
132 Real ry = robust_scm2double (me->get_property ("right-position"), 0);
134 bool equally_long = false;
135 Grob *par_beam = parallel_beam (me, columns, &equally_long);
137 Spanner *sp = dynamic_cast<Spanner *> (me);
139 bool bracket_visibility = !(par_beam && equally_long);
140 bool number_visibility = true;
143 Fixme: the type of this prop is sucky.
145 SCM bracket = me->get_property ("bracket-visibility");
146 if (scm_is_bool (bracket))
148 bracket_visibility = ly_scm2bool (bracket);
150 else if (bracket == ly_symbol2scm ("if-no-beam"))
151 bracket_visibility = !par_beam;
153 SCM numb = me->get_property ("number-visibility");
154 if (scm_is_bool (numb))
156 number_visibility = ly_scm2bool (numb);
158 else if (numb == ly_symbol2scm ("if-no-beam"))
159 number_visibility = !par_beam;
161 Grob *commonx = common_refpoint_of_array (columns, me, X_AXIS);
162 commonx = commonx->common_refpoint (sp->get_bound (LEFT), X_AXIS);
163 commonx = commonx->common_refpoint (sp->get_bound (RIGHT), X_AXIS);
165 Direction dir = get_grob_direction (me);
167 Drul_array<Item *> bounds;
168 bounds[LEFT] = get_x_bound_item (me, LEFT, dir);
169 bounds[RIGHT] = get_x_bound_item (me, RIGHT, dir);
171 Drul_array<bool> connect_to_other;
176 x_span[d] = robust_relative_extent (bounds[d], commonx, X_AXIS)[d];
177 Direction break_dir = bounds[d]->break_status_dir ();
180 && (me->get_break_index() - break_dir < me->broken_intos_.size()));
182 if (connect_to_other[d])
184 Interval overshoot (robust_scm2drul (me->get_property ("break-overshoot"),
185 Interval (-0.5, 1.0)));
188 x_span[d] += d * overshoot[d];
190 x_span[d] = robust_relative_extent(bounds[d], commonx, X_AXIS)[RIGHT]
193 else if (d == RIGHT &&
195 || bounds[d] != columns.top()))
199 TODO: make padding tunable?
201 x_span[d] = robust_relative_extent (bounds[d], commonx, X_AXIS) [LEFT] - 1.0;
204 while (flip (&d) != LEFT);
206 Real w = x_span.length();
207 SCM number = me->get_property ("text");
209 Output_def *pap = me->get_layout ();
211 if (scm_is_string (number) && number_visibility)
213 SCM properties = Font_interface::text_font_alist_chain (me);
214 SCM snum = Text_interface::interpret_markup (pap->self_scm (), properties, number);
215 num = *unsmob_stencil (snum);
216 num.align_to (X_AXIS, CENTER);
217 num.translate_axis (w / 2, X_AXIS);
218 num.align_to (Y_AXIS, CENTER);
220 num.translate_axis ((ry - ly) / 2, Y_AXIS);
222 mol.add_stencil (num);
226 No bracket when it would be smaller than the number.
228 TODO: should use GAP in calculation too.
230 if (bracket_visibility && number_visibility
231 && mol.extent (X_AXIS).length () > w)
233 bracket_visibility = false;
236 if (bracket_visibility)
240 if (!num.extent (X_AXIS).is_empty ())
241 gap = num.extent (X_AXIS).length () + 1.0;
243 Drul_array<Real> zero (0,0);
244 Real ss = Staff_symbol_referencer::staff_space (me);
245 Drul_array<Real> height
246 = robust_scm2drul (me->get_property ("edge-height"), zero);
247 Drul_array<Real> flare
248 = robust_scm2drul (me->get_property ("bracket-flare"), zero);
249 Drul_array<Real> shorten
250 = robust_scm2drul (me->get_property ("shorten-pair"), zero);
251 Drul_array<Stencil> edge_stencils;
253 scale_drul (&height, -ss * dir);
254 scale_drul (&flare, ss);
255 scale_drul (&shorten, ss);
258 if (connect_to_other[d])
264 SCM properties = Font_interface::text_font_alist_chain (me);
265 SCM edge_text = me->get_property ("edge-text");
267 SCM text = index_get_cell (edge_text, d);
268 if (Text_interface::is_markup (text))
270 SCM t = Text_interface::interpret_markup (pap->self_scm (), properties,
273 Stencil *edge_text = unsmob_stencil (t);
274 edge_text->translate_axis (x_span[d] - x_span[LEFT], X_AXIS);
275 edge_stencils[d] = *edge_text;
279 while (flip (&d) != LEFT);
282 Stencil brack = make_bracket (me, Y_AXIS,
286 0.1 = more space at right due to italics
287 TODO: use italic correction of font.
289 Interval (-0.5, 0.5) * gap + 0.1,
294 if (!edge_stencils[d].is_empty ())
295 brack.add_stencil (edge_stencils[d]);
297 while (flip (&d) != LEFT);
300 mol.add_stencil (brack);
303 mol.translate_axis (ly, Y_AXIS);
304 mol.translate_axis (x_span[LEFT]
305 - sp->get_bound (LEFT)->relative_coordinate (commonx, X_AXIS), X_AXIS);
306 return mol.smobbed_copy ();
310 should move to lookup?
312 TODO: this will fail for very short (shorter than the flare)
316 Tuplet_bracket::make_bracket (Grob *me, // for line properties.
319 Drul_array<Real> height,
321 Drul_array<Real> flare,
322 Drul_array<Real> shorten)
324 Drul_array<Offset> corners (Offset (0, 0), dz);
326 Real length = dz.length ();
327 Drul_array<Offset> gap_corners;
329 Axis bracket_axis = other_axis (protusion_axis);
331 Drul_array<Offset> straight_corners = corners;
336 straight_corners[d] += -d * shorten[d] / length * dz;
338 while (flip (&d) != LEFT);
341 gap = Interval (0, 0);
344 gap_corners[d] = (dz * 0.5) + gap[d] / length * dz;
346 while (flip (&d) != LEFT);
348 Drul_array<Offset> flare_corners = straight_corners;
351 flare_corners[d][bracket_axis] = straight_corners[d][bracket_axis];
352 flare_corners[d][protusion_axis] += height[d];
353 straight_corners[d][bracket_axis] += -d * flare[d];
355 while (flip (&d) != LEFT);
360 m.add_stencil (Line_interface::line (me, straight_corners[d],
363 m.add_stencil (Line_interface::line (me, straight_corners[d],
366 while (flip (&d) != LEFT);
372 Tuplet_bracket::get_bounds (Grob *me, Grob **left, Grob **right)
374 extract_grob_set (me, "note-columns", columns);
376 while (l < columns.size () && Note_column::has_rests (columns[l]))
379 int r = columns.size ()- 1;
380 while (r >= l && Note_column::has_rests (columns[r]))
394 use first -> last note for slope, and then correct for disturbing
397 Tuplet_bracket::calc_position_and_height (Grob *me_grob, Real *offset, Real *dy)
399 Spanner *me = dynamic_cast<Spanner*> (me_grob);
401 extract_grob_set (me, "note-columns", columns);
402 extract_grob_set (me, "tuplets", tuplets);
404 Grob *commony = common_refpoint_of_array (columns, me, Y_AXIS);
405 commony = common_refpoint_of_array (tuplets, commony, Y_AXIS);
406 if (Grob *st = Staff_symbol_referencer::get_staff_symbol (me))
408 commony = st->common_refpoint (commony, Y_AXIS);
411 Grob *commonx = common_refpoint_of_array (columns, me, X_AXIS);
412 commonx = common_refpoint_of_array (tuplets, commonx, Y_AXIS);
413 commonx = commonx->common_refpoint (me->get_bound (LEFT), X_AXIS);
414 commonx = commonx->common_refpoint (me->get_bound (RIGHT), X_AXIS);
417 if (Grob *st = Staff_symbol_referencer::get_staff_symbol (me))
418 staff = st->extent (commony, Y_AXIS);
420 Direction dir = get_grob_direction (me);
423 Use outer non-rest columns to determine slope
427 get_bounds (me, &left_col, &right_col);
428 if (left_col && right_col)
430 Interval rv = right_col->extent (commony, Y_AXIS);
431 Interval lv = left_col->extent (commony, Y_AXIS);
434 Real graphical_dy = rv[dir] - lv[dir];
436 Slice ls = Note_column::head_positions_interval (left_col);
437 Slice rs = Note_column::head_positions_interval (right_col);
440 musical_dy[UP] = rs[UP] - ls[UP];
441 musical_dy[DOWN] = rs[DOWN] - ls[DOWN];
442 if (sign (musical_dy[UP]) != sign (musical_dy[DOWN]))
444 else if (sign (graphical_dy) != sign (musical_dy[DOWN]))
452 *offset = -dir * infinity_f;
454 Item *lgr = get_x_bound_item (me, LEFT, dir);
455 Item *rgr = get_x_bound_item (me, RIGHT, dir);
456 Real x0 = robust_relative_extent (lgr, commonx, X_AXIS)[LEFT];
457 Real x1 = robust_relative_extent (rgr, commonx, X_AXIS)[RIGHT];
462 Real factor = columns.size () > 1 ? 1 / (x1 - x0) : 1.0;
464 Array<Offset> points;
465 points.push (Offset (x0, staff[dir]));
466 points.push (Offset (x1, staff[dir]));
468 for (int i = 0; i < columns.size (); i++)
470 Interval note_ext = columns[i]->extent (commony, Y_AXIS);
471 Real notey = note_ext[dir] - me->relative_coordinate (commony, Y_AXIS);
473 Real x = columns[i]->relative_coordinate (commonx, X_AXIS) - x0;
474 points.push (Offset (x, notey));
478 This is a slight hack. We compute two encompass points from the
479 bbox of the smaller tuplets.
481 We assume that the smaller bracket is 1.0 space high.
483 Real ss = Staff_symbol_referencer::staff_space (me);
484 for (int i = 0; i < tuplets.size (); i++)
486 Interval tuplet_x (tuplets[i]->extent (commonx, X_AXIS));
487 Interval tuplet_y (tuplets[i]->extent (commony, Y_AXIS));
490 Real lp = scm_to_double (tuplets[i]->get_property ("left-position"));
491 Real rp = scm_to_double (tuplets[i]->get_property ("right-position"));
492 Real other_dy = rp - lp;
497 tuplet_y.linear_combination (d * sign (other_dy));
501 Let's not take padding into account for nested tuplets.
502 the edges can come very close to the stems, likewise for
505 Drul_array<Real> my_height
506 = robust_scm2drul (me->get_property ("edge-height"), Interval (0,0));
507 if (dynamic_cast<Spanner*> (tuplets[i])->get_bound (d)
508 == me->get_bound (d))
510 y += dir * my_height[d];
514 points.push (Offset (tuplet_x[d] - x0, y));
516 while (flip (&d) != LEFT);
519 for (int i = 0; i < points.size (); i++)
521 Real x = points[i][X_AXIS];
522 Real tuplety = *dy * x * factor;
524 if (points[i][Y_AXIS] * dir > (*offset + tuplety) * dir)
525 *offset = points[i][Y_AXIS] - tuplety;
528 *offset += scm_to_double (me->get_property ("padding")) * dir;
531 horizontal brackets should not collide with staff lines.
533 Kind of pointless since we put them outside the staff anyway, but
534 let's leave code for the future when possibly allow them to move
535 into the staff once again.
538 fabs (*offset) < ss * Staff_symbol_referencer::staff_radius (me))
540 // quantize, then do collision check.
543 *offset = rint (*offset);
544 if (Staff_symbol_referencer::on_staffline (me, (int) rint (*offset)))
552 We depend on the beams if there are any.
554 MAKE_SCHEME_CALLBACK (Tuplet_bracket, before_line_breaking, 1);
556 Tuplet_bracket::before_line_breaking (SCM smob)
558 Grob *me = unsmob_grob (smob);
559 extract_grob_set (me, "note-columns", columns);
561 for (int i = columns.size (); i--;)
563 Grob *s = Note_column::get_stem (columns[i]);
564 Grob *b = s ? Stem::get_beam (s) : 0;
566 me->add_dependency (b);
568 return SCM_UNSPECIFIED;
571 MAKE_SCHEME_CALLBACK (Tuplet_bracket, after_line_breaking, 1);
574 Tuplet_bracket::after_line_breaking (SCM smob)
576 Grob *me = unsmob_grob (smob);
577 extract_grob_set (me, "note-columns", columns);
579 Direction dir = get_grob_direction (me);
582 dir = Tuplet_bracket::get_default_dir (me);
583 set_grob_direction (me, dir);
586 bool equally_long = false;
587 Grob *par_beam = parallel_beam (me, columns, &equally_long);
590 We follow the beam only if there is one, and we are next to it.
595 || get_grob_direction (par_beam) != dir)
597 calc_position_and_height (me, &offset, &dy);
601 SCM ps = par_beam->get_property ("positions");
603 Real lp = scm_to_double (scm_car (ps));
604 Real rp = scm_to_double (scm_cdr (ps));
609 offset = lp + dir * (0.5 + scm_to_double (me->get_property ("padding")));
613 SCM lp = me->get_property ("left-position");
614 SCM rp = me->get_property ("right-position");
616 if (scm_is_number (lp) && !scm_is_number (rp))
618 rp = scm_from_double (scm_to_double (lp) + dy);
620 else if (scm_is_number (rp) && !scm_is_number (lp))
622 lp = scm_from_double (scm_to_double (rp) - dy);
624 else if (!scm_is_number (rp) && !scm_is_number (lp))
626 lp = scm_from_double (offset);
627 rp = scm_from_double (offset + dy);
630 me->set_property ("left-position", lp);
631 me->set_property ("right-position", rp);
633 return SCM_UNSPECIFIED;
640 Tuplet_bracket::get_default_dir (Grob *me)
642 Drul_array<int> dirs (0, 0);
643 extract_grob_set (me, "note-columns", columns);
644 for (int i = 0 ; i < columns.size (); i++)
646 Grob *nc = columns[i];
647 Direction d = Note_column::dir (nc);
652 return dirs[UP] >= dirs[DOWN] ? UP : DOWN;
656 Tuplet_bracket::add_column (Grob *me, Item *n)
658 Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-columns"), n);
659 me->add_dependency (n);
661 add_bound_item (dynamic_cast<Spanner *> (me), n);
665 Tuplet_bracket::add_tuplet_bracket (Grob *me, Grob *bracket)
667 Pointer_group_interface::add_grob (me, ly_symbol2scm ("tuplets"), bracket);
668 me->add_dependency (bracket);
673 ADD_INTERFACE (Tuplet_bracket,
674 "tuplet-bracket-interface",
675 "A bracket with a number in the middle, used for tuplets. "
676 "When the bracket spans a line break, the value of "
677 "@code{break-overshoot} determines how far it extends "
679 "At a line break, the markups in the @code{edge-text} are printed "
682 "note-columns bracket-flare edge-height shorten-pair "
683 "tuplets edge-text break-overshoot "
684 "padding left-position right-position bracket-visibility "
685 "number-visibility thickness direction");