2 tuplet-bracket.cc -- implement Tuplet_bracket
4 source file of the GNU LilyPond music typesetter
6 (c) 1997--2006 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 "output-def.hh"
38 #include "font-interface.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);
63 flatten_number_pair_property (Grob *me,
64 Direction xdir, SCM sym)
66 Drul_array<Real> zero (0, 0);
67 Drul_array<Real> pair = robust_scm2drul (me->internal_get_property (sym), zero);
70 me->internal_set_property (sym, ly_interval2scm (pair));
75 Tuplet_bracket::parallel_beam (Grob *me_grob, vector<Grob*> const &cols, bool *equally_long)
77 Spanner *me = dynamic_cast<Spanner *> (me_grob);
79 if (me->get_bound (LEFT)->break_status_dir ()
80 || me->get_bound (RIGHT)->break_status_dir ())
83 Drul_array<Grob*> stems (Note_column::get_stem (cols[0]),
84 Note_column::get_stem (cols.back ()));
86 if (dynamic_cast<Item*> (stems[RIGHT])->get_column ()
87 != me->get_bound (RIGHT)->get_column())
90 Drul_array<Grob*> beams;
93 beams[d] = stems[d] ? Stem::get_beam (stems[d]) : 0;
94 } while (flip (&d) != LEFT);
96 *equally_long = false;
97 if (! (beams[LEFT] && (beams[LEFT] == beams[RIGHT]) && !me->is_broken ()))
100 extract_grob_set (beams[LEFT], "stems", beam_stems);
101 if (beam_stems.size () == 0)
103 programming_error ("beam under tuplet bracket has no stems");
108 *equally_long = (beam_stems[0] == stems[LEFT] && beam_stems.back () == stems[RIGHT]);
113 MAKE_SCHEME_CALLBACK(Tuplet_bracket,calc_connect_to_neighbors,1);
115 Tuplet_bracket::calc_connect_to_neighbors (SCM smob)
117 Spanner *me = unsmob_spanner (smob);
119 Direction dir = get_grob_direction (me);
120 Drul_array<Item *> bounds (get_x_bound_item (me, LEFT, dir),
121 get_x_bound_item (me, RIGHT, dir));
123 Drul_array<bool> connect_to_other (false, false);
127 Direction break_dir = bounds[d]->break_status_dir ();
129 Spanner *orig_spanner = dynamic_cast<Spanner*> (me->original ());
131 vsize neighbor_idx = me->get_break_index () - break_dir;
134 && neighbor_idx < orig_spanner->broken_intos_.size ())
136 Grob *neighbor = orig_spanner->broken_intos_[neighbor_idx];
138 /* trigger possible suicide*/
139 (void) neighbor->get_property ("positions");
144 && neighbor_idx < orig_spanner->broken_intos_.size ()
145 && orig_spanner->broken_intos_[neighbor_idx]->is_live ());
147 while (flip (&d) != LEFT);
150 if (connect_to_other[LEFT] || connect_to_other[RIGHT])
151 return scm_cons (scm_from_bool (connect_to_other[LEFT]),
152 scm_from_bool (connect_to_other[RIGHT]));
158 Tuplet_bracket::get_common_x (Spanner *me)
160 extract_grob_set (me, "note-columns", columns);
162 Grob * commonx = common_refpoint_of_array (columns, me, X_AXIS);
163 commonx = commonx->common_refpoint (me->get_bound (LEFT), X_AXIS);
164 commonx = commonx->common_refpoint (me->get_bound (RIGHT), X_AXIS);
169 MAKE_SCHEME_CALLBACK(Tuplet_bracket,calc_control_points,1)
171 Tuplet_bracket::calc_control_points (SCM smob)
173 Spanner *me = unsmob_spanner (smob);
175 extract_grob_set (me, "note-columns", columns);
177 Drul_array<Real> positions
178 = ly_scm2realdrul (me->get_property ("positions"));
180 Grob *commonx = get_common_x (me);
181 Direction dir = get_grob_direction (me);
183 Drul_array<Item *> bounds;
184 bounds[LEFT] = get_x_bound_item (me, LEFT, dir);
185 bounds[RIGHT] = get_x_bound_item (me, RIGHT, dir);
187 Drul_array<bool> connect_to_other =
188 robust_scm2booldrul (me->get_property ("connect-to-neighbor"),
189 Drul_array<bool> (false, false));
196 x_span[d] = robust_relative_extent (bounds[d], commonx, X_AXIS)[d];
198 if (connect_to_other[d])
200 Interval overshoot (robust_scm2drul (me->get_property ("break-overshoot"),
201 Interval (-0.5, 0.0)));
204 x_span[d] += d * overshoot[d];
206 x_span[d] = robust_relative_extent (bounds[d], commonx, X_AXIS)[RIGHT]
211 || (bounds[d]->get_column ()
212 != dynamic_cast<Item *> (columns.back ())->get_column ())))
215 TODO: make padding tunable?
219 if (bounds[d]->break_status_dir ())
222 x_span[d] = robust_relative_extent (bounds[d], commonx, X_AXIS) [LEFT] - padding;
225 while (flip (&d) != LEFT);
229 x_span -= me->get_bound (LEFT)->relative_coordinate (commonx, X_AXIS);
230 return scm_list_2 (ly_offset2scm (Offset (x_span[LEFT], positions[LEFT])),
231 ly_offset2scm (Offset (x_span[RIGHT], positions[RIGHT])));
237 in the case that there is no bracket, but there is a (single) beam,
238 follow beam precisely for determining tuplet number location.
240 MAKE_SCHEME_CALLBACK (Tuplet_bracket, print, 1);
242 Tuplet_bracket::print (SCM smob)
244 Spanner *me = unsmob_spanner (smob);
247 extract_grob_set (me, "note-columns", columns);
248 bool equally_long = false;
249 Grob *par_beam = parallel_beam (me, columns, &equally_long);
251 bool bracket_visibility = !(par_beam && equally_long);
253 Fixme: the type of this prop is sucky.
255 SCM bracket = me->get_property ("bracket-visibility");
256 if (scm_is_bool (bracket))
257 bracket_visibility = ly_scm2bool (bracket);
258 else if (bracket == ly_symbol2scm ("if-no-beam"))
259 bracket_visibility = !par_beam;
262 SCM cpoints = me->get_property ("control-points");
263 if (scm_ilength (cpoints) < 2)
269 Drul_array<Offset> points;
270 points[LEFT] = ly_scm2offset (scm_car (cpoints));
271 points[RIGHT] = ly_scm2offset (scm_cadr (cpoints));
273 Interval x_span (points[LEFT][X_AXIS], points[RIGHT][X_AXIS]);
274 Drul_array<Real> positions (points[LEFT][Y_AXIS], points[RIGHT][Y_AXIS]);
278 Output_def *pap = me->layout ();
280 Grob *number_grob = unsmob_grob (me->get_object ("tuplet-number"));
283 No bracket when it would be smaller than the number.
286 if (bracket_visibility && number_grob)
288 Interval ext = number_grob->extent (number_grob, X_AXIS);
289 if (!ext.is_empty ())
291 gap = ext.length () + 1.0;
293 if (0.75 * x_span.length () < gap)
294 bracket_visibility = false;
298 if (bracket_visibility)
300 Drul_array<Real> zero (0, 0);
301 Real ss = Staff_symbol_referencer::staff_space (me);
302 Drul_array<Real> height
303 = robust_scm2drul (me->get_property ("edge-height"), zero);
304 Drul_array<Real> flare
305 = robust_scm2drul (me->get_property ("bracket-flare"), zero);
306 Drul_array<Real> shorten
307 = robust_scm2drul (me->get_property ("shorten-pair"), zero);
308 Drul_array<Stencil> edge_stencils;
310 Direction dir = get_grob_direction (me);
312 scale_drul (&height, -ss * dir);
313 scale_drul (&flare, ss);
314 scale_drul (&shorten, ss);
316 Drul_array<bool> connect_to_other =
317 robust_scm2booldrul (me->get_property ("connect-to-neighbor"),
318 Drul_array<bool> (false, false));
323 if (connect_to_other[d])
329 SCM edge_text = me->get_property ("edge-text");
331 if (scm_is_pair (edge_text))
333 SCM properties = Font_interface::text_font_alist_chain (me);
334 SCM text = index_get_cell (edge_text, d);
335 if (Text_interface::is_markup (text))
337 SCM t = Text_interface::interpret_markup (pap->self_scm (),
340 Stencil *edge_text = unsmob_stencil (t);
341 edge_text->translate_axis (x_span[d] - x_span[LEFT], X_AXIS);
342 edge_stencils[d] = *edge_text;
347 while (flip (&d) != LEFT);
349 Stencil brack = make_bracket (me, Y_AXIS,
350 points[RIGHT] - points[LEFT],
353 0.1 = more space at right due to italics
354 TODO: use italic correction of font.
356 Interval (-0.5, 0.5) * gap + 0.1,
361 if (!edge_stencils[d].is_empty ())
362 brack.add_stencil (edge_stencils[d]);
364 while (flip (&d) != LEFT);
366 mol.add_stencil (brack);
369 mol.translate (points[LEFT]);
370 return mol.smobbed_copy ();
374 should move to lookup?
376 TODO: this will fail for very short (shorter than the flare)
380 Tuplet_bracket::make_bracket (Grob *me, // for line properties.
383 Drul_array<Real> height,
385 Drul_array<Real> flare,
386 Drul_array<Real> shorten)
388 Drul_array<Offset> corners (Offset (0, 0), dz);
390 Real length = dz.length ();
391 Drul_array<Offset> gap_corners;
393 Axis bracket_axis = other_axis (protusion_axis);
395 Drul_array<Offset> straight_corners = corners;
399 straight_corners[d] += -d * shorten[d] / length * dz;
400 while (flip (&d) != LEFT)
404 gap = Interval (0, 0);
406 gap_corners[d] = (dz * 0.5) + gap[d] / length * dz;
407 while (flip (&d) != LEFT)
410 Drul_array<Offset> flare_corners = straight_corners;
413 flare_corners[d][bracket_axis] = straight_corners[d][bracket_axis];
414 flare_corners[d][protusion_axis] += height[d];
415 straight_corners[d][bracket_axis] += -d * flare[d];
417 while (flip (&d) != LEFT);
422 m.add_stencil (Line_interface::line (me, straight_corners[d],
425 m.add_stencil (Line_interface::line (me, straight_corners[d],
428 while (flip (&d) != LEFT);
434 Tuplet_bracket::get_bounds (Grob *me, Grob **left, Grob **right)
436 extract_grob_set (me, "note-columns", columns);
438 while (l < columns.size () && Note_column::has_rests (columns[l]))
441 vsize r = columns.size () - 1;
442 while (r >= l && Note_column::has_rests (columns[r]))
455 use first -> last note for slope, and then correct for disturbing
458 Tuplet_bracket::calc_position_and_height (Grob *me_grob, Real *offset, Real *dy)
460 Spanner *me = dynamic_cast<Spanner *> (me_grob);
462 extract_grob_set (me, "note-columns", columns);
463 extract_grob_set (me, "tuplets", tuplets);
465 Grob *commony = common_refpoint_of_array (columns, me, Y_AXIS);
466 commony = common_refpoint_of_array (tuplets, commony, Y_AXIS);
467 if (Grob *st = Staff_symbol_referencer::get_staff_symbol (me))
468 commony = st->common_refpoint (commony, Y_AXIS);
470 Grob *commonx = get_common_x (me);
471 commonx = common_refpoint_of_array (tuplets, commonx, Y_AXIS);
474 if (Grob *st = Staff_symbol_referencer::get_staff_symbol (me))
476 Real pad = robust_scm2double (me->get_property ("staff-padding"), -1.0);
479 staff = st->extent (commony, Y_AXIS);
484 Direction dir = get_grob_direction (me);
487 Use outer non-rest columns to determine slope
491 get_bounds (me, &left_col, &right_col);
492 if (left_col && right_col)
494 Interval rv = right_col->extent (commony, Y_AXIS);
495 Interval lv = left_col->extent (commony, Y_AXIS);
498 Real graphical_dy = rv[dir] - lv[dir];
500 Slice ls = Note_column::head_positions_interval (left_col);
501 Slice rs = Note_column::head_positions_interval (right_col);
504 musical_dy[UP] = rs[UP] - ls[UP];
505 musical_dy[DOWN] = rs[DOWN] - ls[DOWN];
506 if (sign (musical_dy[UP]) != sign (musical_dy[DOWN]))
508 else if (sign (graphical_dy) != sign (musical_dy[DOWN]))
516 *offset = -dir * infinity_f;
518 Item *lgr = get_x_bound_item (me, LEFT, dir);
519 Item *rgr = get_x_bound_item (me, RIGHT, dir);
520 Real x0 = robust_relative_extent (lgr, commonx, X_AXIS)[LEFT];
521 Real x1 = robust_relative_extent (rgr, commonx, X_AXIS)[RIGHT];
523 vector<Offset> points;
524 points.push_back (Offset (x0 - x0, staff[dir]));
525 points.push_back (Offset (x1 - x0, staff[dir]));
527 for (vsize i = 0; i < columns.size (); i++)
529 Interval note_ext = columns[i]->extent (commony, Y_AXIS);
530 Real notey = note_ext[dir] - me->relative_coordinate (commony, Y_AXIS);
532 Real x = columns[i]->relative_coordinate (commonx, X_AXIS) - x0;
533 points.push_back (Offset (x, notey));
537 This is a slight hack. We compute two encompass points from the
538 bbox of the smaller tuplets.
540 We assume that the smaller bracket is 1.0 space high.
542 Real ss = Staff_symbol_referencer::staff_space (me);
543 for (vsize i = 0; i < tuplets.size (); i++)
545 Interval tuplet_x (tuplets[i]->extent (commonx, X_AXIS));
546 Interval tuplet_y (tuplets[i]->extent (commony, Y_AXIS));
549 Drul_array<Real> positions = ly_scm2realdrul (tuplets[i]->get_property ("positions"));
552 Real other_dy = positions[RIGHT] - positions[LEFT];
557 = tuplet_y.linear_combination (d * sign (other_dy));
561 Let's not take padding into account for nested tuplets.
562 the edges can come very close to the stems, likewise for
565 Drul_array<Real> my_height
566 = robust_scm2drul (me->get_property ("edge-height"),
568 if (dynamic_cast<Spanner *> (tuplets[i])->get_bound (d)
569 == me->get_bound (d))
570 y += dir * my_height[d];
573 points.push_back (Offset (tuplet_x[d] - x0, y));
575 while (flip (&d) != LEFT);
578 Real factor = (columns.size () > 1) ? 1 / (x1 - x0) : 1.0;
579 for (vsize i = 0; i < points.size (); i++)
581 Real x = points[i][X_AXIS];
582 Real tuplety = (*dy) * x * factor;
584 if (points[i][Y_AXIS] * dir > (*offset + tuplety) * dir)
585 *offset = points[i][Y_AXIS] - tuplety;
588 *offset += scm_to_double (me->get_property ("padding")) * dir;
591 horizontal brackets should not collide with staff lines.
593 Kind of pointless since we put them outside the staff anyway, but
594 let's leave code for the future when possibly allow them to move
595 into the staff once again.
598 && fabs (*offset) < ss * Staff_symbol_referencer::staff_radius (me))
600 // quantize, then do collision check.
603 *offset = rint (*offset);
604 if (Staff_symbol_referencer::on_line (me, (int) rint (*offset)))
612 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_direction, 1);
614 Tuplet_bracket::calc_direction (SCM smob)
616 Grob *me = unsmob_grob (smob);
617 Direction dir = Tuplet_bracket::get_default_dir (me);
618 return scm_from_int (dir);
621 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_positions, 1);
623 Tuplet_bracket::calc_positions (SCM smob)
625 Grob *me = unsmob_grob (smob);
626 extract_grob_set (me, "note-columns", columns);
628 Direction dir = get_grob_direction (me);
629 bool equally_long = false;
630 Grob *par_beam = parallel_beam (me, columns, &equally_long);
633 We follow the beam only if there is one, and we are next to it.
638 || get_grob_direction (par_beam) != dir)
639 calc_position_and_height (me, &offset, &dy);
642 SCM ps = par_beam->get_property ("positions");
644 Real lp = scm_to_double (scm_car (ps));
645 Real rp = scm_to_double (scm_cdr (ps));
647 Real ss = Staff_symbol_referencer::staff_space (me);
649 offset = lp + dir * (0.5 + scm_to_double (me->get_property ("padding")));
657 SCM x = scm_cons (scm_from_double (offset),
658 scm_from_double (offset + dy));
667 Tuplet_bracket::get_default_dir (Grob *me)
669 Drul_array<int> dirs (0, 0);
670 extract_grob_set (me, "note-columns", columns);
671 for (vsize i = 0; i < columns.size (); i++)
673 Grob *nc = columns[i];
674 Direction d = Note_column::dir (nc);
679 return dirs[UP] >= dirs[DOWN] ? UP : DOWN;
683 Tuplet_bracket::add_column (Grob *me, Item *n)
685 Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-columns"), n);
686 add_bound_item (dynamic_cast<Spanner *> (me), n);
690 Tuplet_bracket::add_tuplet_bracket (Grob *me, Grob *bracket)
692 Pointer_group_interface::add_grob (me, ly_symbol2scm ("tuplets"), bracket);
695 ADD_INTERFACE (Tuplet_bracket,
696 "tuplet-bracket-interface",
697 "A bracket with a number in the middle, used for tuplets. "
698 "When the bracket spans a line break, the value of "
699 "@code{break-overshoot} determines how far it extends "
701 "At a line break, the markups in the @code{edge-text} are printed "
707 "bracket-visibility "
709 "connect-to-neighbor "