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, Link_array<Grob> const &cols, bool *equally_long)
70 Grob *s1 = Note_column::get_stem (cols[0]);
71 Grob *s2 = Note_column::get_stem (cols.top ());
73 Grob *b1 = s1 ? Stem::get_beam (s1) : 0;
74 Grob *b2 = s2 ? Stem::get_beam (s2) : 0;
76 Spanner *sp = dynamic_cast<Spanner *> (me);
78 *equally_long = false;
79 if (! (b1 && (b1 == b2) && !sp->is_broken ()))
82 extract_grob_set (b1, "stems", beam_stems);
83 if (beam_stems.size () == 0)
85 programming_error ("beam under tuplet bracket has no stems");
90 *equally_long = (beam_stems[0] == s1 && beam_stems.top () == s2);
97 in the case that there is no bracket, but there is a (single) beam,
98 follow beam precisely for determining tuplet number location.
100 MAKE_SCHEME_CALLBACK (Tuplet_bracket, print, 1);
102 Tuplet_bracket::print (SCM smob)
104 Grob *me = unsmob_grob (smob);
106 extract_grob_set (me, "note-columns", columns);
108 if (!columns.size ())
109 return mol.smobbed_copy ();
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))
145 bracket_visibility = ly_scm2bool (bracket);
147 else if (bracket == ly_symbol2scm ("if-no-beam"))
148 bracket_visibility = !par_beam;
150 SCM numb = me->get_property ("number-visibility");
151 if (scm_is_bool (numb))
153 number_visibility = ly_scm2bool (numb);
155 else if (numb == ly_symbol2scm ("if-no-beam"))
156 number_visibility = !par_beam;
158 Grob *commonx = columns[0]->common_refpoint (columns.top (), X_AXIS);
161 commonx = commonx->common_refpoint (sp->get_bound (LEFT), X_AXIS);
162 commonx = commonx->common_refpoint (sp->get_bound (RIGHT), X_AXIS);
164 Direction dir = get_grob_direction (me);
166 Drul_array<Item *> bounds;
167 bounds[LEFT] = get_x_bound_item (me, LEFT, dir);
168 bounds[RIGHT] = get_x_bound_item (me, RIGHT, dir);
174 x_span[d] = robust_relative_extent (bounds[d], commonx, X_AXIS)[d];
176 if (bounds[d]->break_status_dir())
178 Interval overshoot (robust_scm2drul (me->get_property ("break-overshoot"),
181 x_span[d] += d * overshoot[d];
184 while (flip (&d) != LEFT);
186 Real w = x_span.length();
187 SCM number = me->get_property ("text");
189 Output_def *pap = me->get_layout ();
191 if (scm_is_string (number) && number_visibility)
193 SCM properties = Font_interface::text_font_alist_chain (me);
194 SCM snum = Text_interface::interpret_markup (pap->self_scm (), properties, number);
195 num = *unsmob_stencil (snum);
196 num.align_to (X_AXIS, CENTER);
197 num.translate_axis (w / 2, X_AXIS);
198 num.align_to (Y_AXIS, CENTER);
200 num.translate_axis ((ry - ly) / 2, Y_AXIS);
202 mol.add_stencil (num);
206 No bracket when it would be smaller than the number.
208 TODO: should use GAP in calculation too.
210 if (bracket_visibility && number_visibility
211 && mol.extent (X_AXIS).length () > w)
213 bracket_visibility = false;
216 if (bracket_visibility)
220 if (!num.extent (X_AXIS).is_empty ())
221 gap = num.extent (X_AXIS).length () + 1.0;
223 Drul_array<Real> zero (0,0);
224 Real ss = Staff_symbol_referencer::staff_space (me);
225 Drul_array<Real> height
226 = robust_scm2drul (me->get_property ("edge-height"), zero);
227 Drul_array<Real> flare
228 = robust_scm2drul (me->get_property ("bracket-flare"), zero);
229 Drul_array<Real> shorten
230 = robust_scm2drul (me->get_property ("shorten-pair"), zero);
232 scale_drul (&height, -ss * dir);
233 scale_drul (&flare, ss);
234 scale_drul (&shorten, ss);
239 if (bounds[d]->break_status_dir ())
246 while (flip (&d) != LEFT);
249 Stencil brack = make_bracket (me, Y_AXIS,
253 0.1 = more space at right due to italics
254 TODO: use italic correction of font.
256 Interval (-0.5, 0.5) * gap + 0.1,
261 if (bounds[d]->break_status_dir ())
263 SCM properties = Font_interface::text_font_alist_chain (me);
264 SCM edge_text = me->get_property ("edge-text");
266 SCM text = index_get_cell (edge_text, d);
267 if (Text_interface::is_markup (text))
269 SCM t = Text_interface::interpret_markup (pap->self_scm (), properties,
272 Stencil *edge_text = unsmob_stencil (t);
273 edge_text->translate_axis (x_span[d] - x_span[LEFT], X_AXIS);
274 mol.add_stencil (*edge_text);
278 while (flip (&d) != LEFT);
281 mol.add_stencil (brack);
284 mol.translate_axis (ly, Y_AXIS);
285 mol.translate_axis (x_span[LEFT]
286 - sp->get_bound (LEFT)->relative_coordinate (commonx, X_AXIS), X_AXIS);
287 return mol.smobbed_copy ();
291 should move to lookup?
293 TODO: this will fail for very short (shorter than the flare)
297 Tuplet_bracket::make_bracket (Grob *me, // for line properties.
300 Drul_array<Real> height,
302 Drul_array<Real> flare,
303 Drul_array<Real> shorten)
305 Drul_array<Offset> corners (Offset (0, 0), dz);
307 Real length = dz.length ();
308 Drul_array<Offset> gap_corners;
310 Axis bracket_axis = other_axis (protusion_axis);
312 Drul_array<Offset> straight_corners = corners;
317 straight_corners[d] += -d * shorten[d] / length * dz;
319 while (flip (&d) != LEFT);
322 gap = Interval (0, 0);
325 gap_corners[d] = (dz * 0.5) + gap[d] / length * dz;
327 while (flip (&d) != LEFT);
329 Drul_array<Offset> flare_corners = straight_corners;
332 flare_corners[d][bracket_axis] = straight_corners[d][bracket_axis];
333 flare_corners[d][protusion_axis] += height[d];
334 straight_corners[d][bracket_axis] += -d * flare[d];
336 while (flip (&d) != LEFT);
341 m.add_stencil (Line_interface::line (me, straight_corners[d],
344 m.add_stencil (Line_interface::line (me, straight_corners[d],
347 while (flip (&d) != LEFT);
353 Tuplet_bracket::get_bounds (Grob *me, Grob **left, Grob **right)
355 extract_grob_set (me, "note-columns", columns);
357 while (l < columns.size () && Note_column::has_rests (columns[l]))
360 int r = columns.size ()- 1;
361 while (r >= l && Note_column::has_rests (columns[r]))
375 use first -> last note for slope, and then correct for disturbing
378 Tuplet_bracket::calc_position_and_height (Grob *me_grob, Real *offset, Real *dy)
380 Spanner *me = dynamic_cast<Spanner*> (me_grob);
382 extract_grob_set (me, "note-columns", columns);
383 extract_grob_set (me, "tuplets", tuplets);
385 Grob *commony = common_refpoint_of_array (columns, me, Y_AXIS);
386 commony = common_refpoint_of_array (tuplets, commony, Y_AXIS);
387 Grob *commonx = common_refpoint_of_array (columns, me, X_AXIS);
388 commonx = common_refpoint_of_array (tuplets, commonx, Y_AXIS);
391 if (Grob *st = Staff_symbol_referencer::get_staff_symbol (me))
392 staff = st->extent (commony, Y_AXIS);
394 Direction dir = get_grob_direction (me);
397 Use outer non-rest columns to determine slope
401 get_bounds (me, &left_col, &right_col);
402 if (left_col && right_col)
404 Interval rv = right_col->extent (commony, Y_AXIS);
405 Interval lv = left_col->extent (commony, Y_AXIS);
408 Real graphical_dy = rv[dir] - lv[dir];
410 Slice ls = Note_column::head_positions_interval (left_col);
411 Slice rs = Note_column::head_positions_interval (right_col);
414 musical_dy[UP] = rs[UP] - ls[UP];
415 musical_dy[DOWN] = rs[DOWN] - ls[DOWN];
416 if (sign (musical_dy[UP]) != sign (musical_dy[DOWN]))
418 else if (sign (graphical_dy) != sign (musical_dy[DOWN]))
426 *offset = -dir * infinity_f;
428 if (!columns.size ())
431 Item *lgr = get_x_bound_item (me, LEFT, dir);
432 Item *rgr = get_x_bound_item (me, RIGHT, dir);
433 Real x0 = robust_relative_extent (lgr, commonx, X_AXIS)[LEFT];
434 Real x1 = robust_relative_extent (rgr, commonx, X_AXIS)[RIGHT];
439 Real factor = columns.size () > 1 ? 1 / (x1 - x0) : 1.0;
441 Array<Offset> points;
442 for (int i = 0; i < columns.size (); i++)
444 Interval note_ext = columns[i]->extent (commony, Y_AXIS);
445 note_ext.unite (staff);
446 Real notey = note_ext[dir] - me->relative_coordinate (commony, Y_AXIS);
448 Real x = columns[i]->relative_coordinate (commonx, X_AXIS) - x0;
449 points.push (Offset (x, notey));
453 This is a slight hack. We compute two encompass points from the
454 bbox of the smaller tuplets.
456 We assume that the smaller bracket is 1.0 space high.
459 Real ss = Staff_symbol_referencer::staff_space (me);
460 for (int i = 0; i < tuplets.size (); i++)
462 Interval tuplet_x (tuplets[i]->extent (commonx, X_AXIS));
463 Interval tuplet_y (tuplets[i]->extent (commony, Y_AXIS));
466 Real lp = scm_to_double (tuplets[i]->get_property ("left-position"));
467 Real rp = scm_to_double (tuplets[i]->get_property ("right-position"));
468 Real other_dy = rp - lp;
473 tuplet_y.linear_combination (d * sign (other_dy));
477 Let's not take padding into account for nested tuplets.
478 the edges can come very close to the stems, likewise for
481 Drul_array<Real> my_height
482 = robust_scm2drul (me->get_property ("edge-height"), Interval (0,0));
483 if (dynamic_cast<Spanner*> (tuplets[i])->get_bound (d)
484 == me->get_bound (d))
486 y += dir * my_height[d];
490 points.push (Offset (tuplet_x[d] - x0, y));
492 while (flip (&d) != LEFT);
495 for (int i = 0; i < points.size (); i++)
497 Real x = points[i][X_AXIS];
498 Real tuplety = *dy * x * factor;
500 if (points[i][Y_AXIS] * dir > (*offset + tuplety) * dir)
501 *offset = points[i][Y_AXIS] - tuplety;
504 *offset += scm_to_double (me->get_property ("padding")) * dir;
507 horizontal brackets should not collide with staff lines.
509 Kind of pointless since we put them outside the staff anyway, but
510 let's leave code for the future when possibly allow them to move
511 into the staff once again.
514 fabs (*offset) < ss * Staff_symbol_referencer::staff_radius (me))
516 // quantize, then do collision check.
519 *offset = rint (*offset);
520 if (Staff_symbol_referencer::on_staffline (me, (int) rint (*offset)))
528 We depend on the beams if there are any.
530 MAKE_SCHEME_CALLBACK (Tuplet_bracket, before_line_breaking, 1);
532 Tuplet_bracket::before_line_breaking (SCM smob)
534 Grob *me = unsmob_grob (smob);
535 extract_grob_set (me, "note-columns", columns);
537 for (int i = columns.size (); i--;)
539 Grob *s = Note_column::get_stem (columns[i]);
540 Grob *b = s ? Stem::get_beam (s) : 0;
542 me->add_dependency (b);
544 return SCM_UNSPECIFIED;
547 MAKE_SCHEME_CALLBACK (Tuplet_bracket, after_line_breaking, 1);
550 Tuplet_bracket::after_line_breaking (SCM smob)
552 Grob *me = unsmob_grob (smob);
553 extract_grob_set (me, "note-columns", columns);
555 if (!columns.size ())
558 return SCM_UNSPECIFIED;
560 if (dynamic_cast<Spanner *> (me)->is_broken ())
562 me->warning (_ ("removing tuplet bracket across linebreak"));
564 return SCM_UNSPECIFIED;
567 Direction dir = get_grob_direction (me);
570 dir = Tuplet_bracket::get_default_dir (me);
571 set_grob_direction (me, dir);
574 bool equally_long = false;
575 Grob *par_beam = parallel_beam (me, columns, &equally_long);
578 We follow the beam only if there is one, and we are next to it.
583 || get_grob_direction (par_beam) != dir)
585 calc_position_and_height (me, &offset, &dy);
589 SCM ps = par_beam->get_property ("positions");
591 Real lp = scm_to_double (scm_car (ps));
592 Real rp = scm_to_double (scm_cdr (ps));
597 offset = lp + dir * (0.5 + scm_to_double (me->get_property ("padding")));
601 SCM lp = me->get_property ("left-position");
602 SCM rp = me->get_property ("right-position");
604 if (scm_is_number (lp) && !scm_is_number (rp))
606 rp = scm_from_double (scm_to_double (lp) + dy);
608 else if (scm_is_number (rp) && !scm_is_number (lp))
610 lp = scm_from_double (scm_to_double (rp) - dy);
612 else if (!scm_is_number (rp) && !scm_is_number (lp))
614 lp = scm_from_double (offset);
615 rp = scm_from_double (offset + dy);
618 me->set_property ("left-position", lp);
619 me->set_property ("right-position", rp);
621 return SCM_UNSPECIFIED;
628 Tuplet_bracket::get_default_dir (Grob *me)
630 Drul_array<int> dirs (0, 0);
631 extract_grob_set (me, "note-columns", columns);
632 for (int i = 0 ; i < columns.size (); i++)
634 Grob *nc = columns[i];
635 Direction d = Note_column::dir (nc);
640 return dirs[UP] >= dirs[DOWN] ? UP : DOWN;
644 Tuplet_bracket::add_column (Grob *me, Item *n)
646 Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-columns"), n);
647 me->add_dependency (n);
649 add_bound_item (dynamic_cast<Spanner *> (me), n);
653 Tuplet_bracket::add_tuplet_bracket (Grob *me, Grob *bracket)
655 Pointer_group_interface::add_grob (me, ly_symbol2scm ("tuplets"), bracket);
656 me->add_dependency (bracket);
661 ADD_INTERFACE (Tuplet_bracket,
662 "tuplet-bracket-interface",
663 "A bracket with a number in the middle, used for tuplets. "
664 "When the bracket spans a line break, the value of "
665 "@code{break-overshoot} determines how far it extends beyond the staff. "
666 "At a line break, the markups in the @code{edge-text} are printed "
669 "note-columns bracket-flare edge-height shorten-pair "
670 "tuplets edge-text break-overshoot "
671 "padding left-position right-position bracket-visibility "
672 "number-visibility thickness direction");