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.
33 #include "tuplet-bracket.hh"
34 #include "line-interface.hh"
37 #include "font-interface.hh"
38 #include "output-def.hh"
39 #include "text-interface.hh"
41 #include "note-column.hh"
42 #include "pointer-group-interface.hh"
43 #include "directional-element-interface.hh"
45 #include "staff-symbol-referencer.hh"
49 get_x_bound_item (Grob *me_grob, Direction hdir, Direction my_dir)
51 Spanner *me = dynamic_cast<Spanner *> (me_grob);
52 Item *g = me->get_bound (hdir);
53 if (Note_column::has_interface (g)
54 && Note_column::get_stem (g)
55 && Note_column::dir (g) == my_dir)
56 g = Note_column::get_stem (g);
62 Tuplet_bracket::parallel_beam (Grob *me_grob, Link_array<Grob> const &cols, bool *equally_long)
64 Spanner *me = dynamic_cast<Spanner *> (me_grob);
66 if (me->get_bound (LEFT)->break_status_dir ()
67 || me->get_bound (RIGHT)->break_status_dir ())
70 Grob *s1 = Note_column::get_stem (cols[0]);
71 Grob *s2 = Note_column::get_stem (cols.top ());
73 if (s2 != me->get_bound (RIGHT))
76 Grob *b1 = s1 ? Stem::get_beam (s1) : 0;
77 Grob *b2 = s2 ? Stem::get_beam (s2) : 0;
79 *equally_long = false;
80 if (! (b1 && (b1 == b2) && !me->is_broken ()))
83 extract_grob_set (b1, "stems", beam_stems);
84 if (beam_stems.size () == 0)
86 programming_error ("beam under tuplet bracket has no stems");
91 *equally_long = (beam_stems[0] == s1 && beam_stems.top () == s2);
98 in the case that there is no bracket, but there is a (single) beam,
99 follow beam precisely for determining tuplet number location.
101 MAKE_SCHEME_CALLBACK (Tuplet_bracket, print, 1);
103 Tuplet_bracket::print (SCM smob)
105 Spanner *me = unsmob_spanner (smob);
107 extract_grob_set (me, "note-columns", columns);
110 SCM lp = me->get_property ("left-position");
111 SCM rp = me->get_property ("right-position");
113 if (!scm_is_number (rp) || !scm_is_number (lp))
116 UGH. dependency tracking!
118 extract_grob_set (me, "tuplets", tuplets);
119 for (int i = 0; i < tuplets.size (); i++)
120 Tuplet_bracket::print (tuplets[i]->self_scm ());
122 after_line_breaking (smob);
126 Real ly = robust_scm2double (me->get_property ("left-position"), 0);
127 Real ry = robust_scm2double (me->get_property ("right-position"), 0);
129 bool equally_long = false;
130 Grob *par_beam = parallel_beam (me, columns, &equally_long);
132 Spanner *sp = dynamic_cast<Spanner *> (me);
134 bool bracket_visibility = !(par_beam && equally_long);
135 bool number_visibility = true;
138 Fixme: the type of this prop is sucky.
140 SCM bracket = me->get_property ("bracket-visibility");
141 if (scm_is_bool (bracket))
142 bracket_visibility = ly_scm2bool (bracket);
143 else if (bracket == ly_symbol2scm ("if-no-beam"))
144 bracket_visibility = !par_beam;
146 SCM numb = me->get_property ("number-visibility");
147 if (scm_is_bool (numb))
148 number_visibility = ly_scm2bool (numb);
149 else if (numb == ly_symbol2scm ("if-no-beam"))
150 number_visibility = !par_beam;
152 Grob *commonx = common_refpoint_of_array (columns, me, X_AXIS);
153 commonx = commonx->common_refpoint (sp->get_bound (LEFT), X_AXIS);
154 commonx = commonx->common_refpoint (sp->get_bound (RIGHT), X_AXIS);
156 Direction dir = get_grob_direction (me);
158 Drul_array<Item *> bounds;
159 bounds[LEFT] = get_x_bound_item (me, LEFT, dir);
160 bounds[RIGHT] = get_x_bound_item (me, RIGHT, dir);
162 Drul_array<bool> connect_to_other;
167 x_span[d] = robust_relative_extent (bounds[d], commonx, X_AXIS)[d];
168 Direction break_dir = bounds[d]->break_status_dir ();
169 Spanner *orig_spanner = dynamic_cast<Spanner *> (me->original_);
171 int neighbor_idx = me->get_break_index () - break_dir;
174 UGH. dependency handling.
178 && neighbor_idx < orig_spanner->broken_intos_.size ())
180 Grob *neighbor = orig_spanner->broken_intos_[neighbor_idx];
182 // ugh, should inspect callback?
183 Tuplet_bracket::after_line_breaking (neighbor->self_scm ());
188 && (neighbor_idx < orig_spanner->broken_intos_.size ()
189 && neighbor_idx >= 0)
190 && orig_spanner->broken_intos_[neighbor_idx]->is_live ());
193 if (connect_to_other[d])
195 Interval overshoot (robust_scm2drul (me->get_property ("break-overshoot"),
196 Interval (-0.5, 0.0)));
199 x_span[d] += d * overshoot[d];
201 x_span[d] = robust_relative_extent (bounds[d], commonx, X_AXIS)[RIGHT]
205 && (columns.is_empty ()
206 || (bounds[d]->get_column ()
207 != dynamic_cast<Item *> (columns.top ())->get_column ())))
210 TODO: make padding tunable?
212 x_span[d] = robust_relative_extent (bounds[d], commonx, X_AXIS) [LEFT] - 1.0;
215 while (flip (&d) != LEFT);
217 Real w = x_span.length ();
218 SCM number = me->get_property ("text");
220 Output_def *pap = me->get_layout ();
222 if (scm_is_string (number) && number_visibility)
224 SCM properties = Font_interface::text_font_alist_chain (me);
225 SCM snum = Text_interface::interpret_markup (pap->self_scm (),
227 num = *unsmob_stencil (snum);
228 num.align_to (X_AXIS, CENTER);
229 num.translate_axis (w / 2, X_AXIS);
230 num.align_to (Y_AXIS, CENTER);
232 num.translate_axis ((ry - ly) / 2, Y_AXIS);
234 mol.add_stencil (num);
238 No bracket when it would be smaller than the number.
240 TODO: should use GAP in calculation too.
242 if (bracket_visibility && number_visibility
243 && mol.extent (X_AXIS).length () > w)
244 bracket_visibility = false;
246 if (bracket_visibility)
250 if (!num.extent (X_AXIS).is_empty ())
251 gap = num.extent (X_AXIS).length () + 1.0;
253 Drul_array<Real> zero (0, 0);
254 Real ss = Staff_symbol_referencer::staff_space (me);
255 Drul_array<Real> height
256 = robust_scm2drul (me->get_property ("edge-height"), zero);
257 Drul_array<Real> flare
258 = robust_scm2drul (me->get_property ("bracket-flare"), zero);
259 Drul_array<Real> shorten
260 = robust_scm2drul (me->get_property ("shorten-pair"), zero);
261 Drul_array<Stencil> edge_stencils;
263 scale_drul (&height, -ss * dir);
264 scale_drul (&flare, ss);
265 scale_drul (&shorten, ss);
268 if (connect_to_other[d])
274 SCM edge_text = me->get_property ("edge-text");
276 if (scm_is_pair (edge_text))
278 SCM properties = Font_interface::text_font_alist_chain (me);
279 SCM text = index_get_cell (edge_text, d);
280 if (Text_interface::is_markup (text))
282 SCM t = Text_interface::interpret_markup (pap->self_scm (), properties, text);
284 Stencil *edge_text = unsmob_stencil (t);
285 edge_text->translate_axis (x_span[d] - x_span[LEFT], X_AXIS);
286 edge_stencils[d] = *edge_text;
291 while (flip (&d) != LEFT);
293 Stencil brack = make_bracket (me, Y_AXIS,
297 0.1 = more space at right due to italics
298 TODO: use italic correction of font.
300 Interval (-0.5, 0.5) * gap + 0.1,
305 if (!edge_stencils[d].is_empty ())
306 brack.add_stencil (edge_stencils[d]);
308 while (flip (&d) != LEFT);
310 mol.add_stencil (brack);
313 mol.translate_axis (ly, Y_AXIS);
314 mol.translate_axis (x_span[LEFT]
315 - sp->get_bound (LEFT)->relative_coordinate (commonx, X_AXIS), X_AXIS);
316 return mol.smobbed_copy ();
320 should move to lookup?
322 TODO: this will fail for very short (shorter than the flare)
326 Tuplet_bracket::make_bracket (Grob *me, // for line properties.
329 Drul_array<Real> height,
331 Drul_array<Real> flare,
332 Drul_array<Real> shorten)
334 Drul_array<Offset> corners (Offset (0, 0), dz);
336 Real length = dz.length ();
337 Drul_array<Offset> gap_corners;
339 Axis bracket_axis = other_axis (protusion_axis);
341 Drul_array<Offset> straight_corners = corners;
345 straight_corners[d] += -d * shorten[d] / length * dz;
346 while (flip (&d) != LEFT)
350 gap = Interval (0, 0);
352 gap_corners[d] = (dz * 0.5) + gap[d] / length * dz;
353 while (flip (&d) != LEFT)
356 Drul_array<Offset> flare_corners = straight_corners;
359 flare_corners[d][bracket_axis] = straight_corners[d][bracket_axis];
360 flare_corners[d][protusion_axis] += height[d];
361 straight_corners[d][bracket_axis] += -d * flare[d];
363 while (flip (&d) != LEFT);
368 m.add_stencil (Line_interface::line (me, straight_corners[d],
371 m.add_stencil (Line_interface::line (me, straight_corners[d],
374 while (flip (&d) != LEFT);
380 Tuplet_bracket::get_bounds (Grob *me, Grob **left, Grob **right)
382 extract_grob_set (me, "note-columns", columns);
384 while (l < columns.size () && Note_column::has_rests (columns[l]))
387 int r = columns.size ()- 1;
388 while (r >= l && Note_column::has_rests (columns[r]))
401 use first -> last note for slope, and then correct for disturbing
404 Tuplet_bracket::calc_position_and_height (Grob *me_grob, Real *offset, Real *dy)
406 Spanner *me = dynamic_cast<Spanner *> (me_grob);
408 extract_grob_set (me, "note-columns", columns);
409 extract_grob_set (me, "tuplets", tuplets);
411 Grob *commony = common_refpoint_of_array (columns, me, Y_AXIS);
412 commony = common_refpoint_of_array (tuplets, commony, Y_AXIS);
413 if (Grob *st = Staff_symbol_referencer::get_staff_symbol (me))
414 commony = st->common_refpoint (commony, Y_AXIS);
416 Grob *commonx = common_refpoint_of_array (columns, me, X_AXIS);
417 commonx = common_refpoint_of_array (tuplets, commonx, Y_AXIS);
418 commonx = commonx->common_refpoint (me->get_bound (LEFT), X_AXIS);
419 commonx = commonx->common_refpoint (me->get_bound (RIGHT), X_AXIS);
422 if (Grob *st = Staff_symbol_referencer::get_staff_symbol (me))
424 staff = st->extent (commony, Y_AXIS);
425 Real pad = robust_scm2double (me->get_property ("staff-padding"), 0.5);
429 Direction dir = get_grob_direction (me);
432 Use outer non-rest columns to determine slope
436 get_bounds (me, &left_col, &right_col);
437 if (left_col && right_col)
439 Interval rv = right_col->extent (commony, Y_AXIS);
440 Interval lv = left_col->extent (commony, Y_AXIS);
443 Real graphical_dy = rv[dir] - lv[dir];
445 Slice ls = Note_column::head_positions_interval (left_col);
446 Slice rs = Note_column::head_positions_interval (right_col);
449 musical_dy[UP] = rs[UP] - ls[UP];
450 musical_dy[DOWN] = rs[DOWN] - ls[DOWN];
451 if (sign (musical_dy[UP]) != sign (musical_dy[DOWN]))
453 else if (sign (graphical_dy) != sign (musical_dy[DOWN]))
461 *offset = -dir * infinity_f;
463 Item *lgr = get_x_bound_item (me, LEFT, dir);
464 Item *rgr = get_x_bound_item (me, RIGHT, dir);
465 Real x0 = robust_relative_extent (lgr, commonx, X_AXIS)[LEFT];
466 Real x1 = robust_relative_extent (rgr, commonx, X_AXIS)[RIGHT];
468 Array<Offset> points;
469 points.push (Offset (x0 - x0, staff[dir]));
470 points.push (Offset (x1 - x0, staff[dir]));
472 for (int i = 0; i < columns.size (); i++)
474 Interval note_ext = columns[i]->extent (commony, Y_AXIS);
475 Real notey = note_ext[dir] - me->relative_coordinate (commony, Y_AXIS);
477 Real x = columns[i]->relative_coordinate (commonx, X_AXIS) - x0;
478 points.push (Offset (x, notey));
482 This is a slight hack. We compute two encompass points from the
483 bbox of the smaller tuplets.
485 We assume that the smaller bracket is 1.0 space high.
487 Real ss = Staff_symbol_referencer::staff_space (me);
488 for (int i = 0; i < tuplets.size (); i++)
490 Interval tuplet_x (tuplets[i]->extent (commonx, X_AXIS));
491 Interval tuplet_y (tuplets[i]->extent (commony, Y_AXIS));
494 Real lp = scm_to_double (tuplets[i]->get_property ("left-position"));
495 Real rp = scm_to_double (tuplets[i]->get_property ("right-position"));
496 Real other_dy = rp - lp;
501 = tuplet_y.linear_combination (d * sign (other_dy));
505 Let's not take padding into account for nested tuplets.
506 the edges can come very close to the stems, likewise for
509 Drul_array<Real> my_height
510 = robust_scm2drul (me->get_property ("edge-height"),
512 if (dynamic_cast<Spanner *> (tuplets[i])->get_bound (d)
513 == me->get_bound (d))
514 y += dir * my_height[d];
517 points.push (Offset (tuplet_x[d] - x0, y));
519 while (flip (&d) != LEFT);
522 Real factor = (columns.size () > 1) ? 1 / (x1 - x0) : 1.0;
523 for (int i = 0; i < points.size (); i++)
525 Real x = points[i][X_AXIS];
526 Real tuplety = (*dy) * x * factor;
528 if (points[i][Y_AXIS] * dir > (*offset + tuplety) * dir)
529 *offset = points[i][Y_AXIS] - tuplety;
532 *offset += scm_to_double (me->get_property ("padding")) * dir;
535 horizontal brackets should not collide with staff lines.
537 Kind of pointless since we put them outside the staff anyway, but
538 let's leave code for the future when possibly allow them to move
539 into the staff once again.
542 && fabs (*offset) < ss * Staff_symbol_referencer::staff_radius (me))
544 // quantize, then do collision check.
547 *offset = rint (*offset);
548 if (Staff_symbol_referencer::on_staffline (me, (int) rint (*offset)))
556 We depend on the beams if there are any.
558 MAKE_SCHEME_CALLBACK (Tuplet_bracket, before_line_breaking, 1);
560 Tuplet_bracket::before_line_breaking (SCM smob)
562 Grob *me = unsmob_grob (smob);
563 extract_grob_set (me, "note-columns", columns);
565 for (int i = columns.size (); i--;)
567 Grob *s = Note_column::get_stem (columns[i]);
568 Grob *b = s ? Stem::get_beam (s) : 0;
570 me->add_dependency (b);
572 return SCM_UNSPECIFIED;
575 MAKE_SCHEME_CALLBACK (Tuplet_bracket, after_line_breaking, 1);
577 Tuplet_bracket::after_line_breaking (SCM smob)
579 Grob *me = unsmob_grob (smob);
580 extract_grob_set (me, "note-columns", columns);
582 if (columns.is_empty())
585 return SCM_UNSPECIFIED;
588 Direction dir = get_grob_direction (me);
591 dir = Tuplet_bracket::get_default_dir (me);
592 set_grob_direction (me, dir);
595 bool equally_long = false;
596 Grob *par_beam = parallel_beam (me, columns, &equally_long);
599 We follow the beam only if there is one, and we are next to it.
604 || get_grob_direction (par_beam) != dir)
605 calc_position_and_height (me, &offset, &dy);
608 SCM ps = par_beam->get_property ("positions");
610 Real lp = scm_to_double (scm_car (ps));
611 Real rp = scm_to_double (scm_cdr (ps));
616 offset = lp + dir * (0.5 + scm_to_double (me->get_property ("padding")));
620 SCM lp = me->get_property ("left-position");
621 SCM rp = me->get_property ("right-position");
623 if (scm_is_number (lp) && !scm_is_number (rp))
624 rp = scm_from_double (scm_to_double (lp) + dy);
625 else if (scm_is_number (rp) && !scm_is_number (lp))
626 lp = scm_from_double (scm_to_double (rp) - dy);
627 else if (!scm_is_number (rp) && !scm_is_number (lp))
629 lp = scm_from_double (offset);
630 rp = scm_from_double (offset + dy);
633 me->set_property ("left-position", lp);
634 me->set_property ("right-position", rp);
636 return SCM_UNSPECIFIED;
643 Tuplet_bracket::get_default_dir (Grob *me)
645 Drul_array<int> dirs (0, 0);
646 extract_grob_set (me, "note-columns", columns);
647 for (int i = 0; i < columns.size (); i++)
649 Grob *nc = columns[i];
650 Direction d = Note_column::dir (nc);
655 return dirs[UP] >= dirs[DOWN] ? UP : DOWN;
659 Tuplet_bracket::add_column (Grob *me, Item *n)
661 Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-columns"), n);
662 me->add_dependency (n);
664 add_bound_item (dynamic_cast<Spanner *> (me), n);
668 Tuplet_bracket::add_tuplet_bracket (Grob *me, Grob *bracket)
670 Pointer_group_interface::add_grob (me, ly_symbol2scm ("tuplets"), bracket);
671 me->add_dependency (bracket);
674 ADD_INTERFACE (Tuplet_bracket,
675 "tuplet-bracket-interface",
676 "A bracket with a number in the middle, used for tuplets. "
677 "When the bracket spans a line break, the value of "
678 "@code{break-overshoot} determines how far it extends "
680 "At a line break, the markups in the @code{edge-text} are printed "
685 "bracket-visibility "