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]
194 else if (d == RIGHT &&
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 (), properties, number);
216 num = *unsmob_stencil (snum);
217 num.align_to (X_AXIS, CENTER);
218 num.translate_axis (w / 2, X_AXIS);
219 num.align_to (Y_AXIS, CENTER);
221 num.translate_axis ((ry - ly) / 2, Y_AXIS);
223 mol.add_stencil (num);
227 No bracket when it would be smaller than the number.
229 TODO: should use GAP in calculation too.
231 if (bracket_visibility && number_visibility
232 && mol.extent (X_AXIS).length () > w)
234 bracket_visibility = false;
237 if (bracket_visibility)
241 if (!num.extent (X_AXIS).is_empty ())
242 gap = num.extent (X_AXIS).length () + 1.0;
244 Drul_array<Real> zero (0,0);
245 Real ss = Staff_symbol_referencer::staff_space (me);
246 Drul_array<Real> height
247 = robust_scm2drul (me->get_property ("edge-height"), zero);
248 Drul_array<Real> flare
249 = robust_scm2drul (me->get_property ("bracket-flare"), zero);
250 Drul_array<Real> shorten
251 = robust_scm2drul (me->get_property ("shorten-pair"), zero);
252 Drul_array<Stencil> edge_stencils;
254 scale_drul (&height, -ss * dir);
255 scale_drul (&flare, ss);
256 scale_drul (&shorten, ss);
259 if (connect_to_other[d])
265 SCM edge_text = me->get_property ("edge-text");
267 if (scm_is_pair (edge_text))
269 SCM properties = Font_interface::text_font_alist_chain (me);
270 SCM text = index_get_cell (edge_text, d);
271 if (Text_interface::is_markup (text))
273 SCM t = Text_interface::interpret_markup (pap->self_scm (), properties,
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"), Interval (0,0));
511 if (dynamic_cast<Spanner*> (tuplets[i])->get_bound (d)
512 == me->get_bound (d))
514 y += dir * my_height[d];
518 points.push (Offset (tuplet_x[d] - x0, y));
520 while (flip (&d) != LEFT);
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);
578 Tuplet_bracket::after_line_breaking (SCM smob)
580 Grob *me = unsmob_grob (smob);
581 extract_grob_set (me, "note-columns", columns);
583 Direction dir = get_grob_direction (me);
586 dir = Tuplet_bracket::get_default_dir (me);
587 set_grob_direction (me, dir);
590 bool equally_long = false;
591 Grob *par_beam = parallel_beam (me, columns, &equally_long);
594 We follow the beam only if there is one, and we are next to it.
599 || get_grob_direction (par_beam) != dir)
601 calc_position_and_height (me, &offset, &dy);
605 SCM ps = par_beam->get_property ("positions");
607 Real lp = scm_to_double (scm_car (ps));
608 Real rp = scm_to_double (scm_cdr (ps));
613 offset = lp + dir * (0.5 + scm_to_double (me->get_property ("padding")));
617 SCM lp = me->get_property ("left-position");
618 SCM rp = me->get_property ("right-position");
620 if (scm_is_number (lp) && !scm_is_number (rp))
622 rp = scm_from_double (scm_to_double (lp) + dy);
624 else if (scm_is_number (rp) && !scm_is_number (lp))
626 lp = scm_from_double (scm_to_double (rp) - dy);
628 else if (!scm_is_number (rp) && !scm_is_number (lp))
630 lp = scm_from_double (offset);
631 rp = scm_from_double (offset + dy);
634 me->set_property ("left-position", lp);
635 me->set_property ("right-position", rp);
637 return SCM_UNSPECIFIED;
644 Tuplet_bracket::get_default_dir (Grob *me)
646 Drul_array<int> dirs (0, 0);
647 extract_grob_set (me, "note-columns", columns);
648 for (int i = 0 ; i < columns.size (); i++)
650 Grob *nc = columns[i];
651 Direction d = Note_column::dir (nc);
656 return dirs[UP] >= dirs[DOWN] ? UP : DOWN;
660 Tuplet_bracket::add_column (Grob *me, Item *n)
662 Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-columns"), n);
663 me->add_dependency (n);
665 add_bound_item (dynamic_cast<Spanner *> (me), n);
669 Tuplet_bracket::add_tuplet_bracket (Grob *me, Grob *bracket)
671 Pointer_group_interface::add_grob (me, ly_symbol2scm ("tuplets"), bracket);
672 me->add_dependency (bracket);
677 ADD_INTERFACE (Tuplet_bracket,
678 "tuplet-bracket-interface",
679 "A bracket with a number in the middle, used for tuplets. "
680 "When the bracket spans a line break, the value of "
681 "@code{break-overshoot} determines how far it extends "
683 "At a line break, the markups in the @code{edge-text} are printed "
686 "note-columns bracket-flare edge-height shorten-pair "
687 "tuplets edge-text break-overshoot "
688 "padding left-position right-position bracket-visibility "
689 "number-visibility thickness direction");