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);
389 commonx = commonx->common_refpoint (me->get_bound (LEFT), X_AXIS);
390 commonx = commonx->common_refpoint (me->get_bound (RIGHT), X_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 "
670 "At a line break, the markups in the @code{edge-text} are printed "
673 "note-columns bracket-flare edge-height shorten-pair "
674 "tuplets edge-text break-overshoot "
675 "padding left-position right-position bracket-visibility "
676 "number-visibility thickness direction");