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_);
172 && (me->get_break_index () - break_dir < orig_spanner->broken_intos_.size ()));
174 if (connect_to_other[d])
176 Interval overshoot (robust_scm2drul (me->get_property ("break-overshoot"),
177 Interval (-0.5, 0.0)));
180 x_span[d] += d * overshoot[d];
182 x_span[d] = robust_relative_extent (bounds[d], commonx, X_AXIS)[RIGHT]
186 && (columns.is_empty ()
187 || (bounds[d]->get_column ()
188 != dynamic_cast<Item *> (columns.top ())->get_column ())))
191 TODO: make padding tunable?
193 x_span[d] = robust_relative_extent (bounds[d], commonx, X_AXIS) [LEFT] - 1.0;
196 while (flip (&d) != LEFT);
198 Real w = x_span.length ();
199 SCM number = me->get_property ("text");
201 Output_def *pap = me->get_layout ();
203 if (scm_is_string (number) && number_visibility)
205 SCM properties = Font_interface::text_font_alist_chain (me);
206 SCM snum = Text_interface::interpret_markup (pap->self_scm (),
208 num = *unsmob_stencil (snum);
209 num.align_to (X_AXIS, CENTER);
210 num.translate_axis (w / 2, X_AXIS);
211 num.align_to (Y_AXIS, CENTER);
213 num.translate_axis ((ry - ly) / 2, Y_AXIS);
215 mol.add_stencil (num);
219 No bracket when it would be smaller than the number.
221 TODO: should use GAP in calculation too.
223 if (bracket_visibility && number_visibility
224 && mol.extent (X_AXIS).length () > w)
225 bracket_visibility = false;
227 if (bracket_visibility)
231 if (!num.extent (X_AXIS).is_empty ())
232 gap = num.extent (X_AXIS).length () + 1.0;
234 Drul_array<Real> zero (0, 0);
235 Real ss = Staff_symbol_referencer::staff_space (me);
236 Drul_array<Real> height
237 = robust_scm2drul (me->get_property ("edge-height"), zero);
238 Drul_array<Real> flare
239 = robust_scm2drul (me->get_property ("bracket-flare"), zero);
240 Drul_array<Real> shorten
241 = robust_scm2drul (me->get_property ("shorten-pair"), zero);
242 Drul_array<Stencil> edge_stencils;
244 scale_drul (&height, -ss * dir);
245 scale_drul (&flare, ss);
246 scale_drul (&shorten, ss);
249 if (connect_to_other[d])
255 SCM edge_text = me->get_property ("edge-text");
257 if (scm_is_pair (edge_text))
259 SCM properties = Font_interface::text_font_alist_chain (me);
260 SCM text = index_get_cell (edge_text, d);
261 if (Text_interface::is_markup (text))
263 SCM t = Text_interface::interpret_markup (pap->self_scm (), properties, text);
265 Stencil *edge_text = unsmob_stencil (t);
266 edge_text->translate_axis (x_span[d] - x_span[LEFT], X_AXIS);
267 edge_stencils[d] = *edge_text;
272 while (flip (&d) != LEFT);
274 Stencil brack = make_bracket (me, Y_AXIS,
278 0.1 = more space at right due to italics
279 TODO: use italic correction of font.
281 Interval (-0.5, 0.5) * gap + 0.1,
286 if (!edge_stencils[d].is_empty ())
287 brack.add_stencil (edge_stencils[d]);
289 while (flip (&d) != LEFT);
291 mol.add_stencil (brack);
294 mol.translate_axis (ly, Y_AXIS);
295 mol.translate_axis (x_span[LEFT]
296 - sp->get_bound (LEFT)->relative_coordinate (commonx, X_AXIS), X_AXIS);
297 return mol.smobbed_copy ();
301 should move to lookup?
303 TODO: this will fail for very short (shorter than the flare)
307 Tuplet_bracket::make_bracket (Grob *me, // for line properties.
310 Drul_array<Real> height,
312 Drul_array<Real> flare,
313 Drul_array<Real> shorten)
315 Drul_array<Offset> corners (Offset (0, 0), dz);
317 Real length = dz.length ();
318 Drul_array<Offset> gap_corners;
320 Axis bracket_axis = other_axis (protusion_axis);
322 Drul_array<Offset> straight_corners = corners;
326 straight_corners[d] += -d * shorten[d] / length * dz;
327 while (flip (&d) != LEFT)
331 gap = Interval (0, 0);
333 gap_corners[d] = (dz * 0.5) + gap[d] / length * dz;
334 while (flip (&d) != LEFT)
337 Drul_array<Offset> flare_corners = straight_corners;
340 flare_corners[d][bracket_axis] = straight_corners[d][bracket_axis];
341 flare_corners[d][protusion_axis] += height[d];
342 straight_corners[d][bracket_axis] += -d * flare[d];
344 while (flip (&d) != LEFT);
349 m.add_stencil (Line_interface::line (me, straight_corners[d],
352 m.add_stencil (Line_interface::line (me, straight_corners[d],
355 while (flip (&d) != LEFT);
361 Tuplet_bracket::get_bounds (Grob *me, Grob **left, Grob **right)
363 extract_grob_set (me, "note-columns", columns);
365 while (l < columns.size () && Note_column::has_rests (columns[l]))
368 int r = columns.size ()- 1;
369 while (r >= l && Note_column::has_rests (columns[r]))
382 use first -> last note for slope, and then correct for disturbing
385 Tuplet_bracket::calc_position_and_height (Grob *me_grob, Real *offset, Real *dy)
387 Spanner *me = dynamic_cast<Spanner *> (me_grob);
389 extract_grob_set (me, "note-columns", columns);
390 extract_grob_set (me, "tuplets", tuplets);
392 Grob *commony = common_refpoint_of_array (columns, me, Y_AXIS);
393 commony = common_refpoint_of_array (tuplets, commony, Y_AXIS);
394 if (Grob *st = Staff_symbol_referencer::get_staff_symbol (me))
395 commony = st->common_refpoint (commony, Y_AXIS);
397 Grob *commonx = common_refpoint_of_array (columns, me, X_AXIS);
398 commonx = common_refpoint_of_array (tuplets, commonx, Y_AXIS);
399 commonx = commonx->common_refpoint (me->get_bound (LEFT), X_AXIS);
400 commonx = commonx->common_refpoint (me->get_bound (RIGHT), X_AXIS);
403 if (Grob *st = Staff_symbol_referencer::get_staff_symbol (me))
405 staff = st->extent (commony, Y_AXIS);
406 Real pad = robust_scm2double (me->get_property ("staff-padding"), 0.5);
410 Direction dir = get_grob_direction (me);
413 Use outer non-rest columns to determine slope
417 get_bounds (me, &left_col, &right_col);
418 if (left_col && right_col)
420 Interval rv = right_col->extent (commony, Y_AXIS);
421 Interval lv = left_col->extent (commony, Y_AXIS);
424 Real graphical_dy = rv[dir] - lv[dir];
426 Slice ls = Note_column::head_positions_interval (left_col);
427 Slice rs = Note_column::head_positions_interval (right_col);
430 musical_dy[UP] = rs[UP] - ls[UP];
431 musical_dy[DOWN] = rs[DOWN] - ls[DOWN];
432 if (sign (musical_dy[UP]) != sign (musical_dy[DOWN]))
434 else if (sign (graphical_dy) != sign (musical_dy[DOWN]))
442 *offset = -dir * infinity_f;
444 Item *lgr = get_x_bound_item (me, LEFT, dir);
445 Item *rgr = get_x_bound_item (me, RIGHT, dir);
446 Real x0 = robust_relative_extent (lgr, commonx, X_AXIS)[LEFT];
447 Real x1 = robust_relative_extent (rgr, commonx, X_AXIS)[RIGHT];
449 Array<Offset> points;
450 points.push (Offset (x0 - x0, staff[dir]));
451 points.push (Offset (x1 - x0, staff[dir]));
453 for (int i = 0; i < columns.size (); i++)
455 Interval note_ext = columns[i]->extent (commony, Y_AXIS);
456 Real notey = note_ext[dir] - me->relative_coordinate (commony, Y_AXIS);
458 Real x = columns[i]->relative_coordinate (commonx, X_AXIS) - x0;
459 points.push (Offset (x, notey));
463 This is a slight hack. We compute two encompass points from the
464 bbox of the smaller tuplets.
466 We assume that the smaller bracket is 1.0 space high.
468 Real ss = Staff_symbol_referencer::staff_space (me);
469 for (int i = 0; i < tuplets.size (); i++)
471 Interval tuplet_x (tuplets[i]->extent (commonx, X_AXIS));
472 Interval tuplet_y (tuplets[i]->extent (commony, Y_AXIS));
475 Real lp = scm_to_double (tuplets[i]->get_property ("left-position"));
476 Real rp = scm_to_double (tuplets[i]->get_property ("right-position"));
477 Real other_dy = rp - lp;
482 = tuplet_y.linear_combination (d * sign (other_dy));
486 Let's not take padding into account for nested tuplets.
487 the edges can come very close to the stems, likewise for
490 Drul_array<Real> my_height
491 = robust_scm2drul (me->get_property ("edge-height"),
493 if (dynamic_cast<Spanner *> (tuplets[i])->get_bound (d)
494 == me->get_bound (d))
495 y += dir * my_height[d];
498 points.push (Offset (tuplet_x[d] - x0, y));
500 while (flip (&d) != LEFT);
503 Real factor = (columns.size () > 1) ? 1 / (x1 - x0) : 1.0;
504 for (int i = 0; i < points.size (); i++)
506 Real x = points[i][X_AXIS];
507 Real tuplety = (*dy) * x * factor;
509 if (points[i][Y_AXIS] * dir > (*offset + tuplety) * dir)
510 *offset = points[i][Y_AXIS] - tuplety;
513 *offset += scm_to_double (me->get_property ("padding")) * dir;
516 horizontal brackets should not collide with staff lines.
518 Kind of pointless since we put them outside the staff anyway, but
519 let's leave code for the future when possibly allow them to move
520 into the staff once again.
523 && fabs (*offset) < ss * Staff_symbol_referencer::staff_radius (me))
525 // quantize, then do collision check.
528 *offset = rint (*offset);
529 if (Staff_symbol_referencer::on_staffline (me, (int) rint (*offset)))
537 We depend on the beams if there are any.
539 MAKE_SCHEME_CALLBACK (Tuplet_bracket, before_line_breaking, 1);
541 Tuplet_bracket::before_line_breaking (SCM smob)
543 Grob *me = unsmob_grob (smob);
544 extract_grob_set (me, "note-columns", columns);
546 for (int i = columns.size (); i--;)
548 Grob *s = Note_column::get_stem (columns[i]);
549 Grob *b = s ? Stem::get_beam (s) : 0;
551 me->add_dependency (b);
553 return SCM_UNSPECIFIED;
556 MAKE_SCHEME_CALLBACK (Tuplet_bracket, after_line_breaking, 1);
559 Tuplet_bracket::after_line_breaking (SCM smob)
561 Grob *me = unsmob_grob (smob);
562 extract_grob_set (me, "note-columns", columns);
564 Direction dir = get_grob_direction (me);
567 dir = Tuplet_bracket::get_default_dir (me);
568 set_grob_direction (me, dir);
571 bool equally_long = false;
572 Grob *par_beam = parallel_beam (me, columns, &equally_long);
575 We follow the beam only if there is one, and we are next to it.
580 || get_grob_direction (par_beam) != dir)
581 calc_position_and_height (me, &offset, &dy);
584 SCM ps = par_beam->get_property ("positions");
586 Real lp = scm_to_double (scm_car (ps));
587 Real rp = scm_to_double (scm_cdr (ps));
592 offset = lp + dir * (0.5 + scm_to_double (me->get_property ("padding")));
596 SCM lp = me->get_property ("left-position");
597 SCM rp = me->get_property ("right-position");
599 if (scm_is_number (lp) && !scm_is_number (rp))
600 rp = scm_from_double (scm_to_double (lp) + dy);
601 else if (scm_is_number (rp) && !scm_is_number (lp))
602 lp = scm_from_double (scm_to_double (rp) - dy);
603 else if (!scm_is_number (rp) && !scm_is_number (lp))
605 lp = scm_from_double (offset);
606 rp = scm_from_double (offset + dy);
609 me->set_property ("left-position", lp);
610 me->set_property ("right-position", rp);
612 return SCM_UNSPECIFIED;
619 Tuplet_bracket::get_default_dir (Grob *me)
621 Drul_array<int> dirs (0, 0);
622 extract_grob_set (me, "note-columns", columns);
623 for (int i = 0; i < columns.size (); i++)
625 Grob *nc = columns[i];
626 Direction d = Note_column::dir (nc);
631 return dirs[UP] >= dirs[DOWN] ? UP : DOWN;
635 Tuplet_bracket::add_column (Grob *me, Item *n)
637 Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-columns"), n);
638 me->add_dependency (n);
640 add_bound_item (dynamic_cast<Spanner *> (me), n);
644 Tuplet_bracket::add_tuplet_bracket (Grob *me, Grob *bracket)
646 Pointer_group_interface::add_grob (me, ly_symbol2scm ("tuplets"), bracket);
647 me->add_dependency (bracket);
650 ADD_INTERFACE (Tuplet_bracket,
651 "tuplet-bracket-interface",
652 "A bracket with a number in the middle, used for tuplets. "
653 "When the bracket spans a line break, the value of "
654 "@code{break-overshoot} determines how far it extends "
656 "At a line break, the markups in the @code{edge-text} are printed "
661 "bracket-visibility "