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?
214 if (bounds[d]->break_status_dir ())
217 x_span[d] = robust_relative_extent (bounds[d], commonx, X_AXIS) [LEFT] - padding;
220 while (flip (&d) != LEFT);
222 Real w = x_span.length ();
223 SCM number = me->get_property ("text");
225 Output_def *pap = me->get_layout ();
227 if (scm_is_string (number) && number_visibility)
229 SCM properties = Font_interface::text_font_alist_chain (me);
230 SCM snum = Text_interface::interpret_markup (pap->self_scm (),
232 num = *unsmob_stencil (snum);
233 num.align_to (X_AXIS, CENTER);
234 num.translate_axis (w / 2, X_AXIS);
235 num.align_to (Y_AXIS, CENTER);
237 num.translate_axis ((ry - ly) / 2, Y_AXIS);
239 mol.add_stencil (num);
243 No bracket when it would be smaller than the number.
245 TODO: should use GAP in calculation too.
247 if (bracket_visibility && number_visibility
248 && mol.extent (X_AXIS).length () > w)
249 bracket_visibility = false;
251 if (bracket_visibility)
255 if (!num.extent (X_AXIS).is_empty ())
256 gap = num.extent (X_AXIS).length () + 1.0;
258 Drul_array<Real> zero (0, 0);
259 Real ss = Staff_symbol_referencer::staff_space (me);
260 Drul_array<Real> height
261 = robust_scm2drul (me->get_property ("edge-height"), zero);
262 Drul_array<Real> flare
263 = robust_scm2drul (me->get_property ("bracket-flare"), zero);
264 Drul_array<Real> shorten
265 = robust_scm2drul (me->get_property ("shorten-pair"), zero);
266 Drul_array<Stencil> edge_stencils;
268 scale_drul (&height, -ss * dir);
269 scale_drul (&flare, ss);
270 scale_drul (&shorten, ss);
273 if (connect_to_other[d])
279 SCM edge_text = me->get_property ("edge-text");
281 if (scm_is_pair (edge_text))
283 SCM properties = Font_interface::text_font_alist_chain (me);
284 SCM text = index_get_cell (edge_text, d);
285 if (Text_interface::is_markup (text))
287 SCM t = Text_interface::interpret_markup (pap->self_scm (), properties, text);
289 Stencil *edge_text = unsmob_stencil (t);
290 edge_text->translate_axis (x_span[d] - x_span[LEFT], X_AXIS);
291 edge_stencils[d] = *edge_text;
296 while (flip (&d) != LEFT);
298 Stencil brack = make_bracket (me, Y_AXIS,
302 0.1 = more space at right due to italics
303 TODO: use italic correction of font.
305 Interval (-0.5, 0.5) * gap + 0.1,
310 if (!edge_stencils[d].is_empty ())
311 brack.add_stencil (edge_stencils[d]);
313 while (flip (&d) != LEFT);
315 mol.add_stencil (brack);
318 mol.translate_axis (ly, Y_AXIS);
319 mol.translate_axis (x_span[LEFT]
320 - sp->get_bound (LEFT)->relative_coordinate (commonx, X_AXIS), X_AXIS);
321 return mol.smobbed_copy ();
325 should move to lookup?
327 TODO: this will fail for very short (shorter than the flare)
331 Tuplet_bracket::make_bracket (Grob *me, // for line properties.
334 Drul_array<Real> height,
336 Drul_array<Real> flare,
337 Drul_array<Real> shorten)
339 Drul_array<Offset> corners (Offset (0, 0), dz);
341 Real length = dz.length ();
342 Drul_array<Offset> gap_corners;
344 Axis bracket_axis = other_axis (protusion_axis);
346 Drul_array<Offset> straight_corners = corners;
350 straight_corners[d] += -d * shorten[d] / length * dz;
351 while (flip (&d) != LEFT)
355 gap = Interval (0, 0);
357 gap_corners[d] = (dz * 0.5) + gap[d] / length * dz;
358 while (flip (&d) != LEFT)
361 Drul_array<Offset> flare_corners = straight_corners;
364 flare_corners[d][bracket_axis] = straight_corners[d][bracket_axis];
365 flare_corners[d][protusion_axis] += height[d];
366 straight_corners[d][bracket_axis] += -d * flare[d];
368 while (flip (&d) != LEFT);
373 m.add_stencil (Line_interface::line (me, straight_corners[d],
376 m.add_stencil (Line_interface::line (me, straight_corners[d],
379 while (flip (&d) != LEFT);
385 Tuplet_bracket::get_bounds (Grob *me, Grob **left, Grob **right)
387 extract_grob_set (me, "note-columns", columns);
389 while (l < columns.size () && Note_column::has_rests (columns[l]))
392 int r = columns.size ()- 1;
393 while (r >= l && Note_column::has_rests (columns[r]))
406 use first -> last note for slope, and then correct for disturbing
409 Tuplet_bracket::calc_position_and_height (Grob *me_grob, Real *offset, Real *dy)
411 Spanner *me = dynamic_cast<Spanner *> (me_grob);
413 extract_grob_set (me, "note-columns", columns);
414 extract_grob_set (me, "tuplets", tuplets);
416 Grob *commony = common_refpoint_of_array (columns, me, Y_AXIS);
417 commony = common_refpoint_of_array (tuplets, commony, Y_AXIS);
418 if (Grob *st = Staff_symbol_referencer::get_staff_symbol (me))
419 commony = st->common_refpoint (commony, Y_AXIS);
421 Grob *commonx = common_refpoint_of_array (columns, me, X_AXIS);
422 commonx = common_refpoint_of_array (tuplets, commonx, Y_AXIS);
423 commonx = commonx->common_refpoint (me->get_bound (LEFT), X_AXIS);
424 commonx = commonx->common_refpoint (me->get_bound (RIGHT), X_AXIS);
427 if (Grob *st = Staff_symbol_referencer::get_staff_symbol (me))
429 staff = st->extent (commony, Y_AXIS);
430 Real pad = robust_scm2double (me->get_property ("staff-padding"), 0.5);
434 Direction dir = get_grob_direction (me);
437 Use outer non-rest columns to determine slope
441 get_bounds (me, &left_col, &right_col);
442 if (left_col && right_col)
444 Interval rv = right_col->extent (commony, Y_AXIS);
445 Interval lv = left_col->extent (commony, Y_AXIS);
448 Real graphical_dy = rv[dir] - lv[dir];
450 Slice ls = Note_column::head_positions_interval (left_col);
451 Slice rs = Note_column::head_positions_interval (right_col);
454 musical_dy[UP] = rs[UP] - ls[UP];
455 musical_dy[DOWN] = rs[DOWN] - ls[DOWN];
456 if (sign (musical_dy[UP]) != sign (musical_dy[DOWN]))
458 else if (sign (graphical_dy) != sign (musical_dy[DOWN]))
466 *offset = -dir * infinity_f;
468 Item *lgr = get_x_bound_item (me, LEFT, dir);
469 Item *rgr = get_x_bound_item (me, RIGHT, dir);
470 Real x0 = robust_relative_extent (lgr, commonx, X_AXIS)[LEFT];
471 Real x1 = robust_relative_extent (rgr, commonx, X_AXIS)[RIGHT];
473 Array<Offset> points;
474 points.push (Offset (x0 - x0, staff[dir]));
475 points.push (Offset (x1 - x0, staff[dir]));
477 for (int i = 0; i < columns.size (); i++)
479 Interval note_ext = columns[i]->extent (commony, Y_AXIS);
480 Real notey = note_ext[dir] - me->relative_coordinate (commony, Y_AXIS);
482 Real x = columns[i]->relative_coordinate (commonx, X_AXIS) - x0;
483 points.push (Offset (x, notey));
487 This is a slight hack. We compute two encompass points from the
488 bbox of the smaller tuplets.
490 We assume that the smaller bracket is 1.0 space high.
492 Real ss = Staff_symbol_referencer::staff_space (me);
493 for (int i = 0; i < tuplets.size (); i++)
495 Interval tuplet_x (tuplets[i]->extent (commonx, X_AXIS));
496 Interval tuplet_y (tuplets[i]->extent (commony, Y_AXIS));
499 Real lp = scm_to_double (tuplets[i]->get_property ("left-position"));
500 Real rp = scm_to_double (tuplets[i]->get_property ("right-position"));
501 Real other_dy = rp - lp;
506 = tuplet_y.linear_combination (d * sign (other_dy));
510 Let's not take padding into account for nested tuplets.
511 the edges can come very close to the stems, likewise for
514 Drul_array<Real> my_height
515 = robust_scm2drul (me->get_property ("edge-height"),
517 if (dynamic_cast<Spanner *> (tuplets[i])->get_bound (d)
518 == me->get_bound (d))
519 y += dir * my_height[d];
522 points.push (Offset (tuplet_x[d] - x0, y));
524 while (flip (&d) != LEFT);
527 Real factor = (columns.size () > 1) ? 1 / (x1 - x0) : 1.0;
528 for (int i = 0; i < points.size (); i++)
530 Real x = points[i][X_AXIS];
531 Real tuplety = (*dy) * x * factor;
533 if (points[i][Y_AXIS] * dir > (*offset + tuplety) * dir)
534 *offset = points[i][Y_AXIS] - tuplety;
537 *offset += scm_to_double (me->get_property ("padding")) * dir;
540 horizontal brackets should not collide with staff lines.
542 Kind of pointless since we put them outside the staff anyway, but
543 let's leave code for the future when possibly allow them to move
544 into the staff once again.
547 && fabs (*offset) < ss * Staff_symbol_referencer::staff_radius (me))
549 // quantize, then do collision check.
552 *offset = rint (*offset);
553 if (Staff_symbol_referencer::on_staffline (me, (int) rint (*offset)))
561 We depend on the beams if there are any.
563 MAKE_SCHEME_CALLBACK (Tuplet_bracket, before_line_breaking, 1);
565 Tuplet_bracket::before_line_breaking (SCM smob)
567 Grob *me = unsmob_grob (smob);
568 extract_grob_set (me, "note-columns", columns);
570 for (int i = columns.size (); i--;)
572 Grob *s = Note_column::get_stem (columns[i]);
573 Grob *b = s ? Stem::get_beam (s) : 0;
575 me->add_dependency (b);
577 return SCM_UNSPECIFIED;
580 MAKE_SCHEME_CALLBACK (Tuplet_bracket, after_line_breaking, 1);
582 Tuplet_bracket::after_line_breaking (SCM smob)
584 Grob *me = unsmob_grob (smob);
585 extract_grob_set (me, "note-columns", columns);
587 if (columns.is_empty())
590 return SCM_UNSPECIFIED;
593 Direction dir = get_grob_direction (me);
596 dir = Tuplet_bracket::get_default_dir (me);
597 set_grob_direction (me, dir);
600 bool equally_long = false;
601 Grob *par_beam = parallel_beam (me, columns, &equally_long);
604 We follow the beam only if there is one, and we are next to it.
609 || get_grob_direction (par_beam) != dir)
610 calc_position_and_height (me, &offset, &dy);
613 SCM ps = par_beam->get_property ("positions");
615 Real lp = scm_to_double (scm_car (ps));
616 Real rp = scm_to_double (scm_cdr (ps));
621 offset = lp + dir * (0.5 + scm_to_double (me->get_property ("padding")));
625 SCM lp = me->get_property ("left-position");
626 SCM rp = me->get_property ("right-position");
628 if (scm_is_number (lp) && !scm_is_number (rp))
629 rp = scm_from_double (scm_to_double (lp) + dy);
630 else if (scm_is_number (rp) && !scm_is_number (lp))
631 lp = scm_from_double (scm_to_double (rp) - dy);
632 else if (!scm_is_number (rp) && !scm_is_number (lp))
634 lp = scm_from_double (offset);
635 rp = scm_from_double (offset + dy);
638 me->set_property ("left-position", lp);
639 me->set_property ("right-position", rp);
641 return SCM_UNSPECIFIED;
648 Tuplet_bracket::get_default_dir (Grob *me)
650 Drul_array<int> dirs (0, 0);
651 extract_grob_set (me, "note-columns", columns);
652 for (int i = 0; i < columns.size (); i++)
654 Grob *nc = columns[i];
655 Direction d = Note_column::dir (nc);
660 return dirs[UP] >= dirs[DOWN] ? UP : DOWN;
664 Tuplet_bracket::add_column (Grob *me, Item *n)
666 Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-columns"), n);
667 me->add_dependency (n);
669 add_bound_item (dynamic_cast<Spanner *> (me), n);
673 Tuplet_bracket::add_tuplet_bracket (Grob *me, Grob *bracket)
675 Pointer_group_interface::add_grob (me, ly_symbol2scm ("tuplets"), bracket);
676 me->add_dependency (bracket);
679 ADD_INTERFACE (Tuplet_bracket,
680 "tuplet-bracket-interface",
681 "A bracket with a number in the middle, used for tuplets. "
682 "When the bracket spans a line break, the value of "
683 "@code{break-overshoot} determines how far it extends "
685 "At a line break, the markups in the @code{edge-text} are printed "
690 "bracket-visibility "