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, 1.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 properties = Font_interface::text_font_alist_chain (me);
266 SCM edge_text = me->get_property ("edge-text");
268 SCM text = index_get_cell (edge_text, d);
269 if (Text_interface::is_markup (text))
271 SCM t = Text_interface::interpret_markup (pap->self_scm (), properties,
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;
280 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);
301 mol.add_stencil (brack);
304 mol.translate_axis (ly, Y_AXIS);
305 mol.translate_axis (x_span[LEFT]
306 - sp->get_bound (LEFT)->relative_coordinate (commonx, X_AXIS), X_AXIS);
307 return mol.smobbed_copy ();
311 should move to lookup?
313 TODO: this will fail for very short (shorter than the flare)
317 Tuplet_bracket::make_bracket (Grob *me, // for line properties.
320 Drul_array<Real> height,
322 Drul_array<Real> flare,
323 Drul_array<Real> shorten)
325 Drul_array<Offset> corners (Offset (0, 0), dz);
327 Real length = dz.length ();
328 Drul_array<Offset> gap_corners;
330 Axis bracket_axis = other_axis (protusion_axis);
332 Drul_array<Offset> straight_corners = corners;
337 straight_corners[d] += -d * shorten[d] / length * dz;
339 while (flip (&d) != LEFT);
342 gap = Interval (0, 0);
345 gap_corners[d] = (dz * 0.5) + gap[d] / length * dz;
347 while (flip (&d) != LEFT);
349 Drul_array<Offset> flare_corners = straight_corners;
352 flare_corners[d][bracket_axis] = straight_corners[d][bracket_axis];
353 flare_corners[d][protusion_axis] += height[d];
354 straight_corners[d][bracket_axis] += -d * flare[d];
356 while (flip (&d) != LEFT);
361 m.add_stencil (Line_interface::line (me, straight_corners[d],
364 m.add_stencil (Line_interface::line (me, straight_corners[d],
367 while (flip (&d) != LEFT);
373 Tuplet_bracket::get_bounds (Grob *me, Grob **left, Grob **right)
375 extract_grob_set (me, "note-columns", columns);
377 while (l < columns.size () && Note_column::has_rests (columns[l]))
380 int r = columns.size ()- 1;
381 while (r >= l && Note_column::has_rests (columns[r]))
395 use first -> last note for slope, and then correct for disturbing
398 Tuplet_bracket::calc_position_and_height (Grob *me_grob, Real *offset, Real *dy)
400 Spanner *me = dynamic_cast<Spanner*> (me_grob);
402 extract_grob_set (me, "note-columns", columns);
403 extract_grob_set (me, "tuplets", tuplets);
405 Grob *commony = common_refpoint_of_array (columns, me, Y_AXIS);
406 commony = common_refpoint_of_array (tuplets, commony, Y_AXIS);
407 if (Grob *st = Staff_symbol_referencer::get_staff_symbol (me))
409 commony = st->common_refpoint (commony, Y_AXIS);
412 Grob *commonx = common_refpoint_of_array (columns, me, X_AXIS);
413 commonx = common_refpoint_of_array (tuplets, commonx, Y_AXIS);
414 commonx = commonx->common_refpoint (me->get_bound (LEFT), X_AXIS);
415 commonx = commonx->common_refpoint (me->get_bound (RIGHT), X_AXIS);
418 if (Grob *st = Staff_symbol_referencer::get_staff_symbol (me))
419 staff = st->extent (commony, Y_AXIS);
421 Direction dir = get_grob_direction (me);
424 Use outer non-rest columns to determine slope
428 get_bounds (me, &left_col, &right_col);
429 if (left_col && right_col)
431 Interval rv = right_col->extent (commony, Y_AXIS);
432 Interval lv = left_col->extent (commony, Y_AXIS);
435 Real graphical_dy = rv[dir] - lv[dir];
437 Slice ls = Note_column::head_positions_interval (left_col);
438 Slice rs = Note_column::head_positions_interval (right_col);
441 musical_dy[UP] = rs[UP] - ls[UP];
442 musical_dy[DOWN] = rs[DOWN] - ls[DOWN];
443 if (sign (musical_dy[UP]) != sign (musical_dy[DOWN]))
445 else if (sign (graphical_dy) != sign (musical_dy[DOWN]))
453 *offset = -dir * infinity_f;
455 Item *lgr = get_x_bound_item (me, LEFT, dir);
456 Item *rgr = get_x_bound_item (me, RIGHT, dir);
457 Real x0 = robust_relative_extent (lgr, commonx, X_AXIS)[LEFT];
458 Real x1 = robust_relative_extent (rgr, commonx, X_AXIS)[RIGHT];
463 Real factor = columns.size () > 1 ? 1 / (x1 - x0) : 1.0;
465 Array<Offset> points;
466 points.push (Offset (x0, staff[dir]));
467 points.push (Offset (x1, staff[dir]));
469 for (int i = 0; i < columns.size (); i++)
471 Interval note_ext = columns[i]->extent (commony, Y_AXIS);
472 Real notey = note_ext[dir] - me->relative_coordinate (commony, Y_AXIS);
474 Real x = columns[i]->relative_coordinate (commonx, X_AXIS) - x0;
475 points.push (Offset (x, notey));
479 This is a slight hack. We compute two encompass points from the
480 bbox of the smaller tuplets.
482 We assume that the smaller bracket is 1.0 space high.
484 Real ss = Staff_symbol_referencer::staff_space (me);
485 for (int i = 0; i < tuplets.size (); i++)
487 Interval tuplet_x (tuplets[i]->extent (commonx, X_AXIS));
488 Interval tuplet_y (tuplets[i]->extent (commony, Y_AXIS));
491 Real lp = scm_to_double (tuplets[i]->get_property ("left-position"));
492 Real rp = scm_to_double (tuplets[i]->get_property ("right-position"));
493 Real other_dy = rp - lp;
498 tuplet_y.linear_combination (d * sign (other_dy));
502 Let's not take padding into account for nested tuplets.
503 the edges can come very close to the stems, likewise for
506 Drul_array<Real> my_height
507 = robust_scm2drul (me->get_property ("edge-height"), Interval (0,0));
508 if (dynamic_cast<Spanner*> (tuplets[i])->get_bound (d)
509 == me->get_bound (d))
511 y += dir * my_height[d];
515 points.push (Offset (tuplet_x[d] - x0, y));
517 while (flip (&d) != LEFT);
520 for (int i = 0; i < points.size (); i++)
522 Real x = points[i][X_AXIS];
523 Real tuplety = *dy * x * factor;
525 if (points[i][Y_AXIS] * dir > (*offset + tuplety) * dir)
526 *offset = points[i][Y_AXIS] - tuplety;
529 *offset += scm_to_double (me->get_property ("padding")) * dir;
532 horizontal brackets should not collide with staff lines.
534 Kind of pointless since we put them outside the staff anyway, but
535 let's leave code for the future when possibly allow them to move
536 into the staff once again.
539 fabs (*offset) < ss * Staff_symbol_referencer::staff_radius (me))
541 // quantize, then do collision check.
544 *offset = rint (*offset);
545 if (Staff_symbol_referencer::on_staffline (me, (int) rint (*offset)))
553 We depend on the beams if there are any.
555 MAKE_SCHEME_CALLBACK (Tuplet_bracket, before_line_breaking, 1);
557 Tuplet_bracket::before_line_breaking (SCM smob)
559 Grob *me = unsmob_grob (smob);
560 extract_grob_set (me, "note-columns", columns);
562 for (int i = columns.size (); i--;)
564 Grob *s = Note_column::get_stem (columns[i]);
565 Grob *b = s ? Stem::get_beam (s) : 0;
567 me->add_dependency (b);
569 return SCM_UNSPECIFIED;
572 MAKE_SCHEME_CALLBACK (Tuplet_bracket, after_line_breaking, 1);
575 Tuplet_bracket::after_line_breaking (SCM smob)
577 Grob *me = unsmob_grob (smob);
578 extract_grob_set (me, "note-columns", columns);
580 Direction dir = get_grob_direction (me);
583 dir = Tuplet_bracket::get_default_dir (me);
584 set_grob_direction (me, dir);
587 bool equally_long = false;
588 Grob *par_beam = parallel_beam (me, columns, &equally_long);
591 We follow the beam only if there is one, and we are next to it.
596 || get_grob_direction (par_beam) != dir)
598 calc_position_and_height (me, &offset, &dy);
602 SCM ps = par_beam->get_property ("positions");
604 Real lp = scm_to_double (scm_car (ps));
605 Real rp = scm_to_double (scm_cdr (ps));
610 offset = lp + dir * (0.5 + scm_to_double (me->get_property ("padding")));
614 SCM lp = me->get_property ("left-position");
615 SCM rp = me->get_property ("right-position");
617 if (scm_is_number (lp) && !scm_is_number (rp))
619 rp = scm_from_double (scm_to_double (lp) + dy);
621 else if (scm_is_number (rp) && !scm_is_number (lp))
623 lp = scm_from_double (scm_to_double (rp) - dy);
625 else if (!scm_is_number (rp) && !scm_is_number (lp))
627 lp = scm_from_double (offset);
628 rp = scm_from_double (offset + dy);
631 me->set_property ("left-position", lp);
632 me->set_property ("right-position", rp);
634 return SCM_UNSPECIFIED;
641 Tuplet_bracket::get_default_dir (Grob *me)
643 Drul_array<int> dirs (0, 0);
644 extract_grob_set (me, "note-columns", columns);
645 for (int i = 0 ; i < columns.size (); i++)
647 Grob *nc = columns[i];
648 Direction d = Note_column::dir (nc);
653 return dirs[UP] >= dirs[DOWN] ? UP : DOWN;
657 Tuplet_bracket::add_column (Grob *me, Item *n)
659 Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-columns"), n);
660 me->add_dependency (n);
662 add_bound_item (dynamic_cast<Spanner *> (me), n);
666 Tuplet_bracket::add_tuplet_bracket (Grob *me, Grob *bracket)
668 Pointer_group_interface::add_grob (me, ly_symbol2scm ("tuplets"), bracket);
669 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 "
683 "note-columns bracket-flare edge-height shorten-pair "
684 "tuplets edge-text break-overshoot "
685 "padding left-position right-position bracket-visibility "
686 "number-visibility thickness direction");