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 ());
77 if (s2 != me->get_bound (RIGHT))
80 Grob *b1 = s1 ? Stem::get_beam (s1) : 0;
81 Grob *b2 = s2 ? Stem::get_beam (s2) : 0;
84 *equally_long = false;
85 if (! (b1 && (b1 == b2) && !me->is_broken ()))
88 extract_grob_set (b1, "stems", beam_stems);
89 if (beam_stems.size () == 0)
91 programming_error ("beam under tuplet bracket has no stems");
96 *equally_long = (beam_stems[0] == s1 && beam_stems.top () == s2);
103 in the case that there is no bracket, but there is a (single) beam,
104 follow beam precisely for determining tuplet number location.
106 MAKE_SCHEME_CALLBACK (Tuplet_bracket, print, 1);
108 Tuplet_bracket::print (SCM smob)
110 Spanner *me = unsmob_spanner (smob);
112 extract_grob_set (me, "note-columns", columns);
115 SCM lp = me->get_property ("left-position");
116 SCM rp = me->get_property ("right-position");
118 if (!scm_is_number (rp) || !scm_is_number (lp))
121 UGH. dependency tracking!
123 extract_grob_set (me, "tuplets", tuplets);
124 for (int i = 0; i < tuplets.size (); i++)
125 Tuplet_bracket::print (tuplets[i]->self_scm());
127 after_line_breaking (smob);
131 Real ly = robust_scm2double (me->get_property ("left-position"), 0);
132 Real ry = robust_scm2double (me->get_property ("right-position"), 0);
134 bool equally_long = false;
135 Grob *par_beam = parallel_beam (me, columns, &equally_long);
137 Spanner *sp = dynamic_cast<Spanner *> (me);
139 bool bracket_visibility = !(par_beam && equally_long);
140 bool number_visibility = true;
143 Fixme: the type of this prop is sucky.
145 SCM bracket = me->get_property ("bracket-visibility");
146 if (scm_is_bool (bracket))
148 bracket_visibility = ly_scm2bool (bracket);
150 else if (bracket == ly_symbol2scm ("if-no-beam"))
151 bracket_visibility = !par_beam;
153 SCM numb = me->get_property ("number-visibility");
154 if (scm_is_bool (numb))
156 number_visibility = ly_scm2bool (numb);
158 else if (numb == ly_symbol2scm ("if-no-beam"))
159 number_visibility = !par_beam;
161 Grob *commonx = common_refpoint_of_array (columns, me, X_AXIS);
162 commonx = commonx->common_refpoint (sp->get_bound (LEFT), X_AXIS);
163 commonx = commonx->common_refpoint (sp->get_bound (RIGHT), X_AXIS);
165 Direction dir = get_grob_direction (me);
167 Drul_array<Item *> bounds;
168 bounds[LEFT] = get_x_bound_item (me, LEFT, dir);
169 bounds[RIGHT] = get_x_bound_item (me, RIGHT, dir);
171 Drul_array<bool> connect_to_other;
176 x_span[d] = robust_relative_extent (bounds[d], commonx, X_AXIS)[d];
177 Direction break_dir = bounds[d]->break_status_dir ();
178 Spanner *orig_spanner = dynamic_cast<Spanner*> (me->original_);
181 && (me->get_break_index() - break_dir < orig_spanner->broken_intos_.size()));
183 if (connect_to_other[d])
185 Interval overshoot (robust_scm2drul (me->get_property ("break-overshoot"),
186 Interval (-0.5, 0.0)));
189 x_span[d] += d * overshoot[d];
191 x_span[d] = robust_relative_extent(bounds[d], commonx, X_AXIS)[RIGHT]
195 && (columns.is_empty ()
196 || (bounds[d]->get_column ()
197 != dynamic_cast<Item*> (columns.top())->get_column ())))
200 TODO: make padding tunable?
202 x_span[d] = robust_relative_extent (bounds[d], commonx, X_AXIS) [LEFT] - 1.0;
205 while (flip (&d) != LEFT);
207 Real w = x_span.length();
208 SCM number = me->get_property ("text");
210 Output_def *pap = me->get_layout ();
212 if (scm_is_string (number) && number_visibility)
214 SCM properties = Font_interface::text_font_alist_chain (me);
215 SCM snum = Text_interface::interpret_markup (pap->self_scm (),
217 num = *unsmob_stencil (snum);
218 num.align_to (X_AXIS, CENTER);
219 num.translate_axis (w / 2, X_AXIS);
220 num.align_to (Y_AXIS, CENTER);
222 num.translate_axis ((ry - ly) / 2, Y_AXIS);
224 mol.add_stencil (num);
228 No bracket when it would be smaller than the number.
230 TODO: should use GAP in calculation too.
232 if (bracket_visibility && number_visibility
233 && mol.extent (X_AXIS).length () > w)
235 bracket_visibility = false;
238 if (bracket_visibility)
242 if (!num.extent (X_AXIS).is_empty ())
243 gap = num.extent (X_AXIS).length () + 1.0;
245 Drul_array<Real> zero (0, 0);
246 Real ss = Staff_symbol_referencer::staff_space (me);
247 Drul_array<Real> height
248 = robust_scm2drul (me->get_property ("edge-height"), zero);
249 Drul_array<Real> flare
250 = robust_scm2drul (me->get_property ("bracket-flare"), zero);
251 Drul_array<Real> shorten
252 = robust_scm2drul (me->get_property ("shorten-pair"), zero);
253 Drul_array<Stencil> edge_stencils;
255 scale_drul (&height, -ss * dir);
256 scale_drul (&flare, ss);
257 scale_drul (&shorten, ss);
260 if (connect_to_other[d])
266 SCM edge_text = me->get_property ("edge-text");
268 if (scm_is_pair (edge_text))
270 SCM properties = Font_interface::text_font_alist_chain (me);
271 SCM text = index_get_cell (edge_text, d);
272 if (Text_interface::is_markup (text))
274 SCM t = Text_interface::interpret_markup (pap->self_scm (), properties, text);
276 Stencil *edge_text = unsmob_stencil (t);
277 edge_text->translate_axis (x_span[d] - x_span[LEFT], X_AXIS);
278 edge_stencils[d] = *edge_text;
283 while (flip (&d) != LEFT);
286 Stencil brack = make_bracket (me, Y_AXIS,
290 0.1 = more space at right due to italics
291 TODO: use italic correction of font.
293 Interval (-0.5, 0.5) * gap + 0.1,
298 if (!edge_stencils[d].is_empty ())
299 brack.add_stencil (edge_stencils[d]);
301 while (flip (&d) != LEFT);
304 mol.add_stencil (brack);
307 mol.translate_axis (ly, Y_AXIS);
308 mol.translate_axis (x_span[LEFT]
309 - sp->get_bound (LEFT)->relative_coordinate (commonx, X_AXIS), X_AXIS);
310 return mol.smobbed_copy ();
314 should move to lookup?
316 TODO: this will fail for very short (shorter than the flare)
320 Tuplet_bracket::make_bracket (Grob *me, // for line properties.
323 Drul_array<Real> height,
325 Drul_array<Real> flare,
326 Drul_array<Real> shorten)
328 Drul_array<Offset> corners (Offset (0, 0), dz);
330 Real length = dz.length ();
331 Drul_array<Offset> gap_corners;
333 Axis bracket_axis = other_axis (protusion_axis);
335 Drul_array<Offset> straight_corners = corners;
340 straight_corners[d] += -d * shorten[d] / length * dz;
342 while (flip (&d) != LEFT);
345 gap = Interval (0, 0);
348 gap_corners[d] = (dz * 0.5) + gap[d] / length * dz;
350 while (flip (&d) != LEFT);
352 Drul_array<Offset> flare_corners = straight_corners;
355 flare_corners[d][bracket_axis] = straight_corners[d][bracket_axis];
356 flare_corners[d][protusion_axis] += height[d];
357 straight_corners[d][bracket_axis] += -d * flare[d];
359 while (flip (&d) != LEFT);
364 m.add_stencil (Line_interface::line (me, straight_corners[d],
367 m.add_stencil (Line_interface::line (me, straight_corners[d],
370 while (flip (&d) != LEFT);
376 Tuplet_bracket::get_bounds (Grob *me, Grob **left, Grob **right)
378 extract_grob_set (me, "note-columns", columns);
380 while (l < columns.size () && Note_column::has_rests (columns[l]))
383 int r = columns.size ()- 1;
384 while (r >= l && Note_column::has_rests (columns[r]))
398 use first -> last note for slope, and then correct for disturbing
401 Tuplet_bracket::calc_position_and_height (Grob *me_grob, Real *offset, Real *dy)
403 Spanner *me = dynamic_cast<Spanner*> (me_grob);
405 extract_grob_set (me, "note-columns", columns);
406 extract_grob_set (me, "tuplets", tuplets);
408 Grob *commony = common_refpoint_of_array (columns, me, Y_AXIS);
409 commony = common_refpoint_of_array (tuplets, commony, Y_AXIS);
410 if (Grob *st = Staff_symbol_referencer::get_staff_symbol (me))
412 commony = st->common_refpoint (commony, Y_AXIS);
415 Grob *commonx = common_refpoint_of_array (columns, me, X_AXIS);
416 commonx = common_refpoint_of_array (tuplets, commonx, Y_AXIS);
417 commonx = commonx->common_refpoint (me->get_bound (LEFT), X_AXIS);
418 commonx = commonx->common_refpoint (me->get_bound (RIGHT), X_AXIS);
421 if (Grob *st = Staff_symbol_referencer::get_staff_symbol (me))
422 staff = st->extent (commony, Y_AXIS);
424 Direction dir = get_grob_direction (me);
427 Use outer non-rest columns to determine slope
431 get_bounds (me, &left_col, &right_col);
432 if (left_col && right_col)
434 Interval rv = right_col->extent (commony, Y_AXIS);
435 Interval lv = left_col->extent (commony, Y_AXIS);
438 Real graphical_dy = rv[dir] - lv[dir];
440 Slice ls = Note_column::head_positions_interval (left_col);
441 Slice rs = Note_column::head_positions_interval (right_col);
444 musical_dy[UP] = rs[UP] - ls[UP];
445 musical_dy[DOWN] = rs[DOWN] - ls[DOWN];
446 if (sign (musical_dy[UP]) != sign (musical_dy[DOWN]))
448 else if (sign (graphical_dy) != sign (musical_dy[DOWN]))
456 *offset = -dir * infinity_f;
458 Item *lgr = get_x_bound_item (me, LEFT, dir);
459 Item *rgr = get_x_bound_item (me, RIGHT, dir);
460 Real x0 = robust_relative_extent (lgr, commonx, X_AXIS)[LEFT];
461 Real x1 = robust_relative_extent (rgr, commonx, X_AXIS)[RIGHT];
466 Real factor = columns.size () > 1 ? 1 / (x1 - x0) : 1.0;
468 Array<Offset> points;
469 points.push (Offset (x0, staff[dir]));
470 points.push (Offset (x1, 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))
515 y += dir * my_height[d];
519 points.push (Offset (tuplet_x[d] - x0, y));
521 while (flip (&d) != LEFT);
524 for (int i = 0; i < points.size (); i++)
526 Real x = points[i][X_AXIS];
527 Real tuplety = *dy * x * factor;
529 if (points[i][Y_AXIS] * dir > (*offset + tuplety) * dir)
530 *offset = points[i][Y_AXIS] - tuplety * sign (*dy) * dir;
533 *offset += scm_to_double (me->get_property ("padding")) * dir;
536 horizontal brackets should not collide with staff lines.
538 Kind of pointless since we put them outside the staff anyway, but
539 let's leave code for the future when possibly allow them to move
540 into the staff once again.
543 && fabs (*offset) < ss * Staff_symbol_referencer::staff_radius (me))
545 // quantize, then do collision check.
548 *offset = rint (*offset);
549 if (Staff_symbol_referencer::on_staffline (me, (int) rint (*offset)))
557 We depend on the beams if there are any.
559 MAKE_SCHEME_CALLBACK (Tuplet_bracket, before_line_breaking, 1);
561 Tuplet_bracket::before_line_breaking (SCM smob)
563 Grob *me = unsmob_grob (smob);
564 extract_grob_set (me, "note-columns", columns);
566 for (int i = columns.size (); i--;)
568 Grob *s = Note_column::get_stem (columns[i]);
569 Grob *b = s ? Stem::get_beam (s) : 0;
571 me->add_dependency (b);
573 return SCM_UNSPECIFIED;
576 MAKE_SCHEME_CALLBACK (Tuplet_bracket, after_line_breaking, 1);
579 Tuplet_bracket::after_line_breaking (SCM smob)
581 Grob *me = unsmob_grob (smob);
582 extract_grob_set (me, "note-columns", columns);
584 Direction dir = get_grob_direction (me);
587 dir = Tuplet_bracket::get_default_dir (me);
588 set_grob_direction (me, dir);
591 bool equally_long = false;
592 Grob *par_beam = parallel_beam (me, columns, &equally_long);
595 We follow the beam only if there is one, and we are next to it.
600 || get_grob_direction (par_beam) != dir)
602 calc_position_and_height (me, &offset, &dy);
606 SCM ps = par_beam->get_property ("positions");
608 Real lp = scm_to_double (scm_car (ps));
609 Real rp = scm_to_double (scm_cdr (ps));
614 offset = lp + dir * (0.5 + scm_to_double (me->get_property ("padding")));
618 SCM lp = me->get_property ("left-position");
619 SCM rp = me->get_property ("right-position");
621 if (scm_is_number (lp) && !scm_is_number (rp))
623 rp = scm_from_double (scm_to_double (lp) + dy);
625 else if (scm_is_number (rp) && !scm_is_number (lp))
627 lp = scm_from_double (scm_to_double (rp) - dy);
629 else if (!scm_is_number (rp) && !scm_is_number (lp))
631 lp = scm_from_double (offset);
632 rp = scm_from_double (offset + dy);
635 me->set_property ("left-position", lp);
636 me->set_property ("right-position", rp);
638 return SCM_UNSPECIFIED;
645 Tuplet_bracket::get_default_dir (Grob *me)
647 Drul_array<int> dirs (0, 0);
648 extract_grob_set (me, "note-columns", columns);
649 for (int i = 0 ; i < columns.size (); i++)
651 Grob *nc = columns[i];
652 Direction d = Note_column::dir (nc);
657 return dirs[UP] >= dirs[DOWN] ? UP : DOWN;
661 Tuplet_bracket::add_column (Grob *me, Item *n)
663 Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-columns"), n);
664 me->add_dependency (n);
666 add_bound_item (dynamic_cast<Spanner *> (me), n);
670 Tuplet_bracket::add_tuplet_bracket (Grob *me, Grob *bracket)
672 Pointer_group_interface::add_grob (me, ly_symbol2scm ("tuplets"), bracket);
673 me->add_dependency (bracket);
678 ADD_INTERFACE (Tuplet_bracket,
679 "tuplet-bracket-interface",
680 "A bracket with a number in the middle, used for tuplets. "
681 "When the bracket spans a line break, the value of "
682 "@code{break-overshoot} determines how far it extends "
684 "At a line break, the markups in the @code{edge-text} are printed "
687 "note-columns bracket-flare edge-height shorten-pair "
688 "tuplets edge-text break-overshoot "
689 "padding left-position right-position bracket-visibility "
690 "number-visibility thickness direction");