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 ());
76 if (s2 != me->get_bound (RIGHT))
79 Grob *b1 = s1 ? Stem::get_beam (s1) : 0;
80 Grob *b2 = s2 ? Stem::get_beam (s2) : 0;
82 *equally_long = false;
83 if (! (b1 && (b1 == b2) && !me->is_broken ()))
86 extract_grob_set (b1, "stems", beam_stems);
87 if (beam_stems.size () == 0)
89 programming_error ("beam under tuplet bracket has no stems");
94 *equally_long = (beam_stems[0] == s1 && beam_stems.top () == s2);
101 in the case that there is no bracket, but there is a (single) beam,
102 follow beam precisely for determining tuplet number location.
104 MAKE_SCHEME_CALLBACK (Tuplet_bracket, print, 1);
106 Tuplet_bracket::print (SCM smob)
108 Spanner *me = unsmob_spanner (smob);
110 extract_grob_set (me, "note-columns", columns);
113 SCM lp = me->get_property ("left-position");
114 SCM rp = me->get_property ("right-position");
116 if (!scm_is_number (rp) || !scm_is_number (lp))
119 UGH. dependency tracking!
121 extract_grob_set (me, "tuplets", tuplets);
122 for (int i = 0; i < tuplets.size (); i++)
123 Tuplet_bracket::print (tuplets[i]->self_scm ());
125 after_line_breaking (smob);
129 Real ly = robust_scm2double (me->get_property ("left-position"), 0);
130 Real ry = robust_scm2double (me->get_property ("right-position"), 0);
132 bool equally_long = false;
133 Grob *par_beam = parallel_beam (me, columns, &equally_long);
135 Spanner *sp = dynamic_cast<Spanner *> (me);
137 bool bracket_visibility = !(par_beam && equally_long);
138 bool number_visibility = true;
141 Fixme: the type of this prop is sucky.
143 SCM bracket = me->get_property ("bracket-visibility");
144 if (scm_is_bool (bracket))
146 bracket_visibility = ly_scm2bool (bracket);
148 else if (bracket == ly_symbol2scm ("if-no-beam"))
149 bracket_visibility = !par_beam;
151 SCM numb = me->get_property ("number-visibility");
152 if (scm_is_bool (numb))
154 number_visibility = ly_scm2bool (numb);
156 else if (numb == ly_symbol2scm ("if-no-beam"))
157 number_visibility = !par_beam;
159 Grob *commonx = common_refpoint_of_array (columns, me, X_AXIS);
160 commonx = commonx->common_refpoint (sp->get_bound (LEFT), X_AXIS);
161 commonx = commonx->common_refpoint (sp->get_bound (RIGHT), X_AXIS);
163 Direction dir = get_grob_direction (me);
165 Drul_array<Item *> bounds;
166 bounds[LEFT] = get_x_bound_item (me, LEFT, dir);
167 bounds[RIGHT] = get_x_bound_item (me, RIGHT, dir);
169 Drul_array<bool> connect_to_other;
174 x_span[d] = robust_relative_extent (bounds[d], commonx, X_AXIS)[d];
175 Direction break_dir = bounds[d]->break_status_dir ();
176 Spanner *orig_spanner = dynamic_cast<Spanner *> (me->original_);
179 && (me->get_break_index () - break_dir < orig_spanner->broken_intos_.size ()));
181 if (connect_to_other[d])
183 Interval overshoot (robust_scm2drul (me->get_property ("break-overshoot"),
184 Interval (-0.5, 0.0)));
187 x_span[d] += d * overshoot[d];
189 x_span[d] = robust_relative_extent (bounds[d], commonx, X_AXIS)[RIGHT]
193 && (columns.is_empty ()
194 || (bounds[d]->get_column ()
195 != dynamic_cast<Item *> (columns.top ())->get_column ())))
198 TODO: make padding tunable?
200 x_span[d] = robust_relative_extent (bounds[d], commonx, X_AXIS) [LEFT] - 1.0;
203 while (flip (&d) != LEFT);
205 Real w = x_span.length ();
206 SCM number = me->get_property ("text");
208 Output_def *pap = me->get_layout ();
210 if (scm_is_string (number) && number_visibility)
212 SCM properties = Font_interface::text_font_alist_chain (me);
213 SCM snum = Text_interface::interpret_markup (pap->self_scm (),
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 edge_text = me->get_property ("edge-text");
266 if (scm_is_pair (edge_text))
268 SCM properties = Font_interface::text_font_alist_chain (me);
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, text);
274 Stencil *edge_text = unsmob_stencil (t);
275 edge_text->translate_axis (x_span[d] - x_span[LEFT], X_AXIS);
276 edge_stencils[d] = *edge_text;
281 while (flip (&d) != LEFT);
283 Stencil brack = make_bracket (me, Y_AXIS,
287 0.1 = more space at right due to italics
288 TODO: use italic correction of font.
290 Interval (-0.5, 0.5) * gap + 0.1,
295 if (!edge_stencils[d].is_empty ())
296 brack.add_stencil (edge_stencils[d]);
298 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]))
393 use first -> last note for slope, and then correct for disturbing
396 Tuplet_bracket::calc_position_and_height (Grob *me_grob, Real *offset, Real *dy)
398 Spanner *me = dynamic_cast<Spanner *> (me_grob);
400 extract_grob_set (me, "note-columns", columns);
401 extract_grob_set (me, "tuplets", tuplets);
403 Grob *commony = common_refpoint_of_array (columns, me, Y_AXIS);
404 commony = common_refpoint_of_array (tuplets, commony, Y_AXIS);
405 if (Grob *st = Staff_symbol_referencer::get_staff_symbol (me))
407 commony = st->common_refpoint (commony, Y_AXIS);
410 Grob *commonx = common_refpoint_of_array (columns, me, X_AXIS);
411 commonx = common_refpoint_of_array (tuplets, commonx, Y_AXIS);
412 commonx = commonx->common_refpoint (me->get_bound (LEFT), X_AXIS);
413 commonx = commonx->common_refpoint (me->get_bound (RIGHT), X_AXIS);
416 if (Grob *st = Staff_symbol_referencer::get_staff_symbol (me))
417 staff = st->extent (commony, Y_AXIS);
419 Direction dir = get_grob_direction (me);
422 Use outer non-rest columns to determine slope
426 get_bounds (me, &left_col, &right_col);
427 if (left_col && right_col)
429 Interval rv = right_col->extent (commony, Y_AXIS);
430 Interval lv = left_col->extent (commony, Y_AXIS);
433 Real graphical_dy = rv[dir] - lv[dir];
435 Slice ls = Note_column::head_positions_interval (left_col);
436 Slice rs = Note_column::head_positions_interval (right_col);
439 musical_dy[UP] = rs[UP] - ls[UP];
440 musical_dy[DOWN] = rs[DOWN] - ls[DOWN];
441 if (sign (musical_dy[UP]) != sign (musical_dy[DOWN]))
443 else if (sign (graphical_dy) != sign (musical_dy[DOWN]))
451 *offset = -dir * infinity_f;
453 Item *lgr = get_x_bound_item (me, LEFT, dir);
454 Item *rgr = get_x_bound_item (me, RIGHT, dir);
455 Real x0 = robust_relative_extent (lgr, commonx, X_AXIS)[LEFT];
456 Real x1 = robust_relative_extent (rgr, commonx, X_AXIS)[RIGHT];
461 Real factor = columns.size () > 1 ? 1 / (x1 - x0) : 1.0;
463 Array<Offset> points;
464 points.push (Offset (x0, staff[dir]));
465 points.push (Offset (x1, staff[dir]));
467 for (int i = 0; i < columns.size (); i++)
469 Interval note_ext = columns[i]->extent (commony, Y_AXIS);
470 Real notey = note_ext[dir] - me->relative_coordinate (commony, Y_AXIS);
472 Real x = columns[i]->relative_coordinate (commonx, X_AXIS) - x0;
473 points.push (Offset (x, notey));
477 This is a slight hack. We compute two encompass points from the
478 bbox of the smaller tuplets.
480 We assume that the smaller bracket is 1.0 space high.
482 Real ss = Staff_symbol_referencer::staff_space (me);
483 for (int i = 0; i < tuplets.size (); i++)
485 Interval tuplet_x (tuplets[i]->extent (commonx, X_AXIS));
486 Interval tuplet_y (tuplets[i]->extent (commony, Y_AXIS));
489 Real lp = scm_to_double (tuplets[i]->get_property ("left-position"));
490 Real rp = scm_to_double (tuplets[i]->get_property ("right-position"));
491 Real other_dy = rp - lp;
496 = tuplet_y.linear_combination (d * sign (other_dy));
500 Let's not take padding into account for nested tuplets.
501 the edges can come very close to the stems, likewise for
504 Drul_array<Real> my_height
505 = robust_scm2drul (me->get_property ("edge-height"),
507 if (dynamic_cast<Spanner *> (tuplets[i])->get_bound (d)
508 == me->get_bound (d))
509 y += dir * my_height[d];
512 points.push (Offset (tuplet_x[d] - x0, y));
514 while (flip (&d) != LEFT);
517 for (int i = 0; i < points.size (); i++)
519 Real x = points[i][X_AXIS];
520 Real tuplety = *dy * x * factor;
522 if (points[i][Y_AXIS] * dir > (*offset + tuplety) * dir)
523 *offset = points[i][Y_AXIS] - tuplety * sign (*dy) * dir;
526 *offset += scm_to_double (me->get_property ("padding")) * dir;
529 horizontal brackets should not collide with staff lines.
531 Kind of pointless since we put them outside the staff anyway, but
532 let's leave code for the future when possibly allow them to move
533 into the staff once again.
536 && fabs (*offset) < ss * Staff_symbol_referencer::staff_radius (me))
538 // quantize, then do collision check.
541 *offset = rint (*offset);
542 if (Staff_symbol_referencer::on_staffline (me, (int) rint (*offset)))
550 We depend on the beams if there are any.
552 MAKE_SCHEME_CALLBACK (Tuplet_bracket, before_line_breaking, 1);
554 Tuplet_bracket::before_line_breaking (SCM smob)
556 Grob *me = unsmob_grob (smob);
557 extract_grob_set (me, "note-columns", columns);
559 for (int i = columns.size (); i--;)
561 Grob *s = Note_column::get_stem (columns[i]);
562 Grob *b = s ? Stem::get_beam (s) : 0;
564 me->add_dependency (b);
566 return SCM_UNSPECIFIED;
569 MAKE_SCHEME_CALLBACK (Tuplet_bracket, after_line_breaking, 1);
572 Tuplet_bracket::after_line_breaking (SCM smob)
574 Grob *me = unsmob_grob (smob);
575 extract_grob_set (me, "note-columns", columns);
577 Direction dir = get_grob_direction (me);
580 dir = Tuplet_bracket::get_default_dir (me);
581 set_grob_direction (me, dir);
584 bool equally_long = false;
585 Grob *par_beam = parallel_beam (me, columns, &equally_long);
588 We follow the beam only if there is one, and we are next to it.
593 || get_grob_direction (par_beam) != dir)
595 calc_position_and_height (me, &offset, &dy);
599 SCM ps = par_beam->get_property ("positions");
601 Real lp = scm_to_double (scm_car (ps));
602 Real rp = scm_to_double (scm_cdr (ps));
607 offset = lp + dir * (0.5 + scm_to_double (me->get_property ("padding")));
611 SCM lp = me->get_property ("left-position");
612 SCM rp = me->get_property ("right-position");
614 if (scm_is_number (lp) && !scm_is_number (rp))
616 rp = scm_from_double (scm_to_double (lp) + dy);
618 else if (scm_is_number (rp) && !scm_is_number (lp))
620 lp = scm_from_double (scm_to_double (rp) - dy);
622 else if (!scm_is_number (rp) && !scm_is_number (lp))
624 lp = scm_from_double (offset);
625 rp = scm_from_double (offset + dy);
628 me->set_property ("left-position", lp);
629 me->set_property ("right-position", rp);
631 return SCM_UNSPECIFIED;
638 Tuplet_bracket::get_default_dir (Grob *me)
640 Drul_array<int> dirs (0, 0);
641 extract_grob_set (me, "note-columns", columns);
642 for (int i = 0; i < columns.size (); i++)
644 Grob *nc = columns[i];
645 Direction d = Note_column::dir (nc);
650 return dirs[UP] >= dirs[DOWN] ? UP : DOWN;
654 Tuplet_bracket::add_column (Grob *me, Item *n)
656 Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-columns"), n);
657 me->add_dependency (n);
659 add_bound_item (dynamic_cast<Spanner *> (me), n);
663 Tuplet_bracket::add_tuplet_bracket (Grob *me, Grob *bracket)
665 Pointer_group_interface::add_grob (me, ly_symbol2scm ("tuplets"), bracket);
666 me->add_dependency (bracket);
670 ADD_INTERFACE (Tuplet_bracket,
671 "tuplet-bracket-interface",
672 "A bracket with a number in the middle, used for tuplets. "
673 "When the bracket spans a line break, the value of "
674 "@code{break-overshoot} determines how far it extends "
676 "At a line break, the markups in the @code{edge-text} are printed "
679 "note-columns bracket-flare edge-height shorten-pair "
680 "tuplets edge-text break-overshoot "
681 "padding left-position right-position bracket-visibility "
682 "number-visibility thickness direction");