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 Tuplet brackets are normally not broken, but we shouldn't crash if
164 commonx = commonx->common_refpoint (sp->get_bound (LEFT), X_AXIS);
165 commonx = commonx->common_refpoint (sp->get_bound (RIGHT), X_AXIS);
167 Direction dir = get_grob_direction (me);
169 Drul_array<Item *> bounds;
170 bounds[LEFT] = get_x_bound_item (me, LEFT, dir);
171 bounds[RIGHT] = get_x_bound_item (me, RIGHT, dir);
177 x_span[d] = robust_relative_extent (bounds[d], commonx, X_AXIS)[d];
179 if (bounds[d]->break_status_dir())
181 Interval overshoot (robust_scm2drul (me->get_property ("break-overshoot"),
184 x_span[d] += d * overshoot[d];
187 while (flip (&d) != LEFT);
189 Real w = x_span.length();
190 SCM number = me->get_property ("text");
192 Output_def *pap = me->get_layout ();
194 if (scm_is_string (number) && number_visibility)
196 SCM properties = Font_interface::text_font_alist_chain (me);
197 SCM snum = Text_interface::interpret_markup (pap->self_scm (), properties, number);
198 num = *unsmob_stencil (snum);
199 num.align_to (X_AXIS, CENTER);
200 num.translate_axis (w / 2, X_AXIS);
201 num.align_to (Y_AXIS, CENTER);
203 num.translate_axis ((ry - ly) / 2, Y_AXIS);
205 mol.add_stencil (num);
209 No bracket when it would be smaller than the number.
211 TODO: should use GAP in calculation too.
213 if (bracket_visibility && number_visibility
214 && mol.extent (X_AXIS).length () > w)
216 bracket_visibility = false;
219 if (bracket_visibility)
223 if (!num.extent (X_AXIS).is_empty ())
224 gap = num.extent (X_AXIS).length () + 1.0;
226 Drul_array<Real> zero (0,0);
227 Real ss = Staff_symbol_referencer::staff_space (me);
228 Drul_array<Real> height
229 = robust_scm2drul (me->get_property ("edge-height"), zero);
230 Drul_array<Real> flare
231 = robust_scm2drul (me->get_property ("bracket-flare"), zero);
232 Drul_array<Real> shorten
233 = robust_scm2drul (me->get_property ("shorten-pair"), zero);
235 scale_drul (&height, -ss * dir);
236 scale_drul (&flare, ss);
237 scale_drul (&shorten, ss);
242 if (bounds[d]->break_status_dir ())
249 while (flip (&d) != LEFT);
252 Stencil brack = make_bracket (me, Y_AXIS,
256 0.1 = more space at right due to italics
257 TODO: use italic correction of font.
259 Interval (-0.5, 0.5) * gap + 0.1,
264 if (bounds[d]->break_status_dir ())
266 SCM properties = Font_interface::text_font_alist_chain (me);
267 SCM edge_text = me->get_property ("edge-text");
269 SCM text = index_get_cell (edge_text, d);
270 if (Text_interface::is_markup (text))
272 SCM t = Text_interface::interpret_markup (pap->self_scm (), properties,
275 Stencil *edge_text = unsmob_stencil (t);
276 edge_text->translate_axis (x_span[d] - x_span[LEFT], X_AXIS);
277 mol.add_stencil (*edge_text);
281 while (flip (&d) != LEFT);
284 mol.add_stencil (brack);
287 mol.translate_axis (ly, Y_AXIS);
288 mol.translate_axis (x_span[LEFT]
289 - sp->get_bound (LEFT)->relative_coordinate (commonx, X_AXIS), X_AXIS);
290 return mol.smobbed_copy ();
294 should move to lookup?
296 TODO: this will fail for very short (shorter than the flare)
300 Tuplet_bracket::make_bracket (Grob *me, // for line properties.
303 Drul_array<Real> height,
305 Drul_array<Real> flare,
306 Drul_array<Real> shorten)
308 Drul_array<Offset> corners (Offset (0, 0), dz);
310 Real length = dz.length ();
311 Drul_array<Offset> gap_corners;
313 Axis bracket_axis = other_axis (protusion_axis);
315 Drul_array<Offset> straight_corners = corners;
320 straight_corners[d] += -d * shorten[d] / length * dz;
322 while (flip (&d) != LEFT);
325 gap = Interval (0, 0);
328 gap_corners[d] = (dz * 0.5) + gap[d] / length * dz;
330 while (flip (&d) != LEFT);
332 Drul_array<Offset> flare_corners = straight_corners;
335 flare_corners[d][bracket_axis] = straight_corners[d][bracket_axis];
336 flare_corners[d][protusion_axis] += height[d];
337 straight_corners[d][bracket_axis] += -d * flare[d];
339 while (flip (&d) != LEFT);
344 m.add_stencil (Line_interface::line (me, straight_corners[d],
347 m.add_stencil (Line_interface::line (me, straight_corners[d],
350 while (flip (&d) != LEFT);
356 Tuplet_bracket::get_bounds (Grob *me, Grob **left, Grob **right)
358 extract_grob_set (me, "note-columns", columns);
360 while (l < columns.size () && Note_column::has_rests (columns[l]))
363 int r = columns.size ()- 1;
364 while (r >= l && Note_column::has_rests (columns[r]))
378 use first -> last note for slope, and then correct for disturbing
381 Tuplet_bracket::calc_position_and_height (Grob *me_grob, Real *offset, Real *dy)
383 Spanner *me = dynamic_cast<Spanner*> (me_grob);
385 extract_grob_set (me, "note-columns", columns);
386 extract_grob_set (me, "tuplets", tuplets);
388 Grob *commony = common_refpoint_of_array (columns, me, Y_AXIS);
389 commony = common_refpoint_of_array (tuplets, commony, Y_AXIS);
390 Grob *commonx = common_refpoint_of_array (columns, me, X_AXIS);
391 commonx = common_refpoint_of_array (tuplets, commonx, Y_AXIS);
394 if (Grob *st = Staff_symbol_referencer::get_staff_symbol (me))
395 staff = st->extent (commony, Y_AXIS);
397 Direction dir = get_grob_direction (me);
400 Use outer non-rest columns to determine slope
404 get_bounds (me, &left_col, &right_col);
405 if (left_col && right_col)
407 Interval rv = right_col->extent (commony, Y_AXIS);
408 Interval lv = left_col->extent (commony, Y_AXIS);
411 Real graphical_dy = rv[dir] - lv[dir];
413 Slice ls = Note_column::head_positions_interval (left_col);
414 Slice rs = Note_column::head_positions_interval (right_col);
417 musical_dy[UP] = rs[UP] - ls[UP];
418 musical_dy[DOWN] = rs[DOWN] - ls[DOWN];
419 if (sign (musical_dy[UP]) != sign (musical_dy[DOWN]))
421 else if (sign (graphical_dy) != sign (musical_dy[DOWN]))
429 *offset = -dir * infinity_f;
431 if (!columns.size ())
434 Item *lgr = get_x_bound_item (me, LEFT, dir);
435 Item *rgr = get_x_bound_item (me, RIGHT, dir);
436 Real x0 = robust_relative_extent (lgr, commonx, X_AXIS)[LEFT];
437 Real x1 = robust_relative_extent (rgr, commonx, X_AXIS)[RIGHT];
442 Real factor = columns.size () > 1 ? 1 / (x1 - x0) : 1.0;
444 Array<Offset> points;
445 for (int i = 0; i < columns.size (); i++)
447 Interval note_ext = columns[i]->extent (commony, Y_AXIS);
448 note_ext.unite (staff);
449 Real notey = note_ext[dir] - me->relative_coordinate (commony, Y_AXIS);
451 Real x = columns[i]->relative_coordinate (commonx, X_AXIS) - x0;
452 points.push (Offset (x, notey));
456 This is a slight hack. We compute two encompass points from the
457 bbox of the smaller tuplets.
459 We assume that the smaller bracket is 1.0 space high.
462 Real ss = Staff_symbol_referencer::staff_space (me);
463 for (int i = 0; i < tuplets.size (); i++)
465 Interval tuplet_x (tuplets[i]->extent (commonx, X_AXIS));
466 Interval tuplet_y (tuplets[i]->extent (commony, Y_AXIS));
469 Real lp = scm_to_double (tuplets[i]->get_property ("left-position"));
470 Real rp = scm_to_double (tuplets[i]->get_property ("right-position"));
471 Real other_dy = rp - lp;
476 tuplet_y.linear_combination (d * sign (other_dy));
480 Let's not take padding into account for nested tuplets.
481 the edges can come very close to the stems, likewise for
484 Drul_array<Real> my_height
485 = robust_scm2drul (me->get_property ("edge-height"), Interval (0,0));
486 if (dynamic_cast<Spanner*> (tuplets[i])->get_bound (d)
487 == me->get_bound (d))
489 y += dir * my_height[d];
493 points.push (Offset (tuplet_x[d] - x0, y));
495 while (flip (&d) != LEFT);
498 for (int i = 0; i < points.size (); i++)
500 Real x = points[i][X_AXIS];
501 Real tuplety = *dy * x * factor;
503 if (points[i][Y_AXIS] * dir > (*offset + tuplety) * dir)
504 *offset = points[i][Y_AXIS] - tuplety;
507 *offset += scm_to_double (me->get_property ("padding")) * dir;
510 horizontal brackets should not collide with staff lines.
512 Kind of pointless since we put them outside the staff anyway, but
513 let's leave code for the future when possibly allow them to move
514 into the staff once again.
517 fabs (*offset) < ss * Staff_symbol_referencer::staff_radius (me))
519 // quantize, then do collision check.
522 *offset = rint (*offset);
523 if (Staff_symbol_referencer::on_staffline (me, (int) rint (*offset)))
531 We depend on the beams if there are any.
533 MAKE_SCHEME_CALLBACK (Tuplet_bracket, before_line_breaking, 1);
535 Tuplet_bracket::before_line_breaking (SCM smob)
537 Grob *me = unsmob_grob (smob);
538 extract_grob_set (me, "note-columns", columns);
540 for (int i = columns.size (); i--;)
542 Grob *s = Note_column::get_stem (columns[i]);
543 Grob *b = s ? Stem::get_beam (s) : 0;
545 me->add_dependency (b);
547 return SCM_UNSPECIFIED;
550 MAKE_SCHEME_CALLBACK (Tuplet_bracket, after_line_breaking, 1);
553 Tuplet_bracket::after_line_breaking (SCM smob)
555 Grob *me = unsmob_grob (smob);
556 extract_grob_set (me, "note-columns", columns);
558 if (!columns.size ())
561 return SCM_UNSPECIFIED;
563 if (dynamic_cast<Spanner *> (me)->is_broken ())
565 me->warning (_ ("removing tuplet bracket across linebreak"));
567 return SCM_UNSPECIFIED;
570 Direction dir = get_grob_direction (me);
573 dir = Tuplet_bracket::get_default_dir (me);
574 set_grob_direction (me, dir);
577 bool equally_long = false;
578 Grob *par_beam = parallel_beam (me, columns, &equally_long);
581 We follow the beam only if there is one, and we are next to it.
586 || get_grob_direction (par_beam) != dir)
588 calc_position_and_height (me, &offset, &dy);
592 SCM ps = par_beam->get_property ("positions");
594 Real lp = scm_to_double (scm_car (ps));
595 Real rp = scm_to_double (scm_cdr (ps));
600 offset = lp + dir * (0.5 + scm_to_double (me->get_property ("padding")));
604 SCM lp = me->get_property ("left-position");
605 SCM rp = me->get_property ("right-position");
607 if (scm_is_number (lp) && !scm_is_number (rp))
609 rp = scm_from_double (scm_to_double (lp) + dy);
611 else if (scm_is_number (rp) && !scm_is_number (lp))
613 lp = scm_from_double (scm_to_double (rp) - dy);
615 else if (!scm_is_number (rp) && !scm_is_number (lp))
617 lp = scm_from_double (offset);
618 rp = scm_from_double (offset + dy);
621 me->set_property ("left-position", lp);
622 me->set_property ("right-position", rp);
624 return SCM_UNSPECIFIED;
631 Tuplet_bracket::get_default_dir (Grob *me)
633 Drul_array<int> dirs (0, 0);
634 extract_grob_set (me, "note-columns", columns);
635 for (int i = 0 ; i < columns.size (); i++)
637 Grob *nc = columns[i];
638 Direction d = Note_column::dir (nc);
643 return dirs[UP] >= dirs[DOWN] ? UP : DOWN;
647 Tuplet_bracket::add_column (Grob *me, Item *n)
649 Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-columns"), n);
650 me->add_dependency (n);
652 add_bound_item (dynamic_cast<Spanner *> (me), n);
656 Tuplet_bracket::add_tuplet_bracket (Grob *me, Grob *bracket)
658 Pointer_group_interface::add_grob (me, ly_symbol2scm ("tuplets"), bracket);
659 me->add_dependency (bracket);
664 ADD_INTERFACE (Tuplet_bracket,
665 "tuplet-bracket-interface",
666 "A bracket with a number in the middle, used for tuplets. "
667 "When the bracket spans a line break, the value of "
668 "@code{break-overshoot} determines how far it extends beyond the staff. "
669 "At a line break, the markups in the @code{edge-text} are printed "
672 "note-columns bracket-flare edge-height shorten-pair "
673 "tuplets edge-text break-overshoot "
674 "padding left-position right-position bracket-visibility "
675 "number-visibility thickness direction");