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 Drul_array<Grob*> stems (Note_column::get_stem (cols[0]),
71 Note_column::get_stem (cols.top ()));
73 if (dynamic_cast<Item*> (stems[RIGHT])->get_column ()
74 != me->get_bound (RIGHT)->get_column())
77 Drul_array<Grob*> beams;
80 beams[d] = stems[d] ? Stem::get_beam (stems[d]) : 0;
81 } while (flip (&d) != LEFT);
83 *equally_long = false;
84 if (! (beams[LEFT] && (beams[LEFT] == beams[RIGHT]) && !me->is_broken ()))
87 extract_grob_set (beams[LEFT], "stems", beam_stems);
88 if (beam_stems.size () == 0)
90 programming_error ("beam under tuplet bracket has no stems");
95 *equally_long = (beam_stems[0] == stems[LEFT] && beam_stems.top () == stems[RIGHT]);
102 in the case that there is no bracket, but there is a (single) beam,
103 follow beam precisely for determining tuplet number location.
105 MAKE_SCHEME_CALLBACK (Tuplet_bracket, print, 1);
107 Tuplet_bracket::print (SCM smob)
109 Spanner *me = unsmob_spanner (smob);
111 extract_grob_set (me, "note-columns", columns);
114 SCM lp = me->get_property ("left-position");
115 SCM rp = me->get_property ("right-position");
117 if (!scm_is_number (rp) || !scm_is_number (lp))
120 UGH. dependency tracking!
122 extract_grob_set (me, "tuplets", tuplets);
123 for (int i = 0; i < tuplets.size (); i++)
124 Tuplet_bracket::print (tuplets[i]->self_scm ());
126 after_line_breaking (smob);
130 Real ly = robust_scm2double (me->get_property ("left-position"), 0);
131 Real ry = robust_scm2double (me->get_property ("right-position"), 0);
133 bool equally_long = false;
134 Grob *par_beam = parallel_beam (me, columns, &equally_long);
135 Spanner *sp = dynamic_cast<Spanner *> (me);
137 bool bracket_visibility = !(par_beam && equally_long);
138 bool number_visibility = true;
141 Fixme: the type of this prop is sucky.
143 SCM bracket = me->get_property ("bracket-visibility");
144 if (scm_is_bool (bracket))
145 bracket_visibility = ly_scm2bool (bracket);
146 else if (bracket == ly_symbol2scm ("if-no-beam"))
147 bracket_visibility = !par_beam;
149 SCM numb = me->get_property ("number-visibility");
150 if (scm_is_bool (numb))
151 number_visibility = ly_scm2bool (numb);
152 else if (numb == ly_symbol2scm ("if-no-beam"))
153 number_visibility = !par_beam;
155 Grob *commonx = common_refpoint_of_array (columns, me, X_AXIS);
156 commonx = commonx->common_refpoint (sp->get_bound (LEFT), X_AXIS);
157 commonx = commonx->common_refpoint (sp->get_bound (RIGHT), X_AXIS);
159 Direction dir = get_grob_direction (me);
161 Drul_array<Item *> bounds;
162 bounds[LEFT] = get_x_bound_item (me, LEFT, dir);
163 bounds[RIGHT] = get_x_bound_item (me, RIGHT, dir);
165 Drul_array<bool> connect_to_other;
170 x_span[d] = robust_relative_extent (bounds[d], commonx, X_AXIS)[d];
171 Direction break_dir = bounds[d]->break_status_dir ();
172 Spanner *orig_spanner = dynamic_cast<Spanner *> (me->original_);
174 int neighbor_idx = me->get_break_index () - break_dir;
177 UGH. dependency handling.
181 && neighbor_idx < orig_spanner->broken_intos_.size ())
183 Grob *neighbor = orig_spanner->broken_intos_[neighbor_idx];
185 // ugh, should inspect callback?
186 Tuplet_bracket::after_line_breaking (neighbor->self_scm ());
191 && (neighbor_idx < orig_spanner->broken_intos_.size ()
192 && neighbor_idx >= 0)
193 && orig_spanner->broken_intos_[neighbor_idx]->is_live ());
196 if (connect_to_other[d])
198 Interval overshoot (robust_scm2drul (me->get_property ("break-overshoot"),
199 Interval (-0.5, 0.0)));
202 x_span[d] += d * overshoot[d];
204 x_span[d] = robust_relative_extent (bounds[d], commonx, X_AXIS)[RIGHT]
208 && (columns.is_empty ()
209 || (bounds[d]->get_column ()
210 != dynamic_cast<Item *> (columns.top ())->get_column ())))
213 TODO: make padding tunable?
217 if (bounds[d]->break_status_dir ())
220 x_span[d] = robust_relative_extent (bounds[d], commonx, X_AXIS) [LEFT] - padding;
223 while (flip (&d) != LEFT);
225 Real w = x_span.length ();
226 SCM number = me->get_property ("text");
228 Output_def *pap = me->get_layout ();
230 if (scm_is_string (number) && number_visibility)
232 SCM properties = Font_interface::text_font_alist_chain (me);
233 SCM snum = Text_interface::interpret_markup (pap->self_scm (),
235 num = *unsmob_stencil (snum);
236 num.align_to (X_AXIS, CENTER);
237 num.translate_axis (w / 2, X_AXIS);
238 num.align_to (Y_AXIS, CENTER);
240 num.translate_axis ((ry - ly) / 2, Y_AXIS);
242 mol.add_stencil (num);
246 No bracket when it would be smaller than the number.
248 TODO: should use GAP in calculation too.
250 if (bracket_visibility && number_visibility
251 && mol.extent (X_AXIS).length () > w)
252 bracket_visibility = false;
254 if (bracket_visibility)
258 if (!num.extent (X_AXIS).is_empty ())
259 gap = num.extent (X_AXIS).length () + 1.0;
261 Drul_array<Real> zero (0, 0);
262 Real ss = Staff_symbol_referencer::staff_space (me);
263 Drul_array<Real> height
264 = robust_scm2drul (me->get_property ("edge-height"), zero);
265 Drul_array<Real> flare
266 = robust_scm2drul (me->get_property ("bracket-flare"), zero);
267 Drul_array<Real> shorten
268 = robust_scm2drul (me->get_property ("shorten-pair"), zero);
269 Drul_array<Stencil> edge_stencils;
271 scale_drul (&height, -ss * dir);
272 scale_drul (&flare, ss);
273 scale_drul (&shorten, ss);
276 if (connect_to_other[d])
282 SCM edge_text = me->get_property ("edge-text");
284 if (scm_is_pair (edge_text))
286 SCM properties = Font_interface::text_font_alist_chain (me);
287 SCM text = index_get_cell (edge_text, d);
288 if (Text_interface::is_markup (text))
290 SCM t = Text_interface::interpret_markup (pap->self_scm (), properties, text);
292 Stencil *edge_text = unsmob_stencil (t);
293 edge_text->translate_axis (x_span[d] - x_span[LEFT], X_AXIS);
294 edge_stencils[d] = *edge_text;
299 while (flip (&d) != LEFT);
301 Stencil brack = make_bracket (me, Y_AXIS,
305 0.1 = more space at right due to italics
306 TODO: use italic correction of font.
308 Interval (-0.5, 0.5) * gap + 0.1,
313 if (!edge_stencils[d].is_empty ())
314 brack.add_stencil (edge_stencils[d]);
316 while (flip (&d) != LEFT);
318 mol.add_stencil (brack);
321 mol.translate_axis (ly, Y_AXIS);
322 mol.translate_axis (x_span[LEFT]
323 - sp->get_bound (LEFT)->relative_coordinate (commonx, X_AXIS), X_AXIS);
324 return mol.smobbed_copy ();
328 should move to lookup?
330 TODO: this will fail for very short (shorter than the flare)
334 Tuplet_bracket::make_bracket (Grob *me, // for line properties.
337 Drul_array<Real> height,
339 Drul_array<Real> flare,
340 Drul_array<Real> shorten)
342 Drul_array<Offset> corners (Offset (0, 0), dz);
344 Real length = dz.length ();
345 Drul_array<Offset> gap_corners;
347 Axis bracket_axis = other_axis (protusion_axis);
349 Drul_array<Offset> straight_corners = corners;
353 straight_corners[d] += -d * shorten[d] / length * dz;
354 while (flip (&d) != LEFT)
358 gap = Interval (0, 0);
360 gap_corners[d] = (dz * 0.5) + gap[d] / length * dz;
361 while (flip (&d) != LEFT)
364 Drul_array<Offset> flare_corners = straight_corners;
367 flare_corners[d][bracket_axis] = straight_corners[d][bracket_axis];
368 flare_corners[d][protusion_axis] += height[d];
369 straight_corners[d][bracket_axis] += -d * flare[d];
371 while (flip (&d) != LEFT);
376 m.add_stencil (Line_interface::line (me, straight_corners[d],
379 m.add_stencil (Line_interface::line (me, straight_corners[d],
382 while (flip (&d) != LEFT);
388 Tuplet_bracket::get_bounds (Grob *me, Grob **left, Grob **right)
390 extract_grob_set (me, "note-columns", columns);
392 while (l < columns.size () && Note_column::has_rests (columns[l]))
395 int r = columns.size ()- 1;
396 while (r >= l && Note_column::has_rests (columns[r]))
409 use first -> last note for slope, and then correct for disturbing
412 Tuplet_bracket::calc_position_and_height (Grob *me_grob, Real *offset, Real *dy)
414 Spanner *me = dynamic_cast<Spanner *> (me_grob);
416 extract_grob_set (me, "note-columns", columns);
417 extract_grob_set (me, "tuplets", tuplets);
419 Grob *commony = common_refpoint_of_array (columns, me, Y_AXIS);
420 commony = common_refpoint_of_array (tuplets, commony, Y_AXIS);
421 if (Grob *st = Staff_symbol_referencer::get_staff_symbol (me))
422 commony = st->common_refpoint (commony, Y_AXIS);
424 Grob *commonx = common_refpoint_of_array (columns, me, X_AXIS);
425 commonx = common_refpoint_of_array (tuplets, commonx, Y_AXIS);
426 commonx = commonx->common_refpoint (me->get_bound (LEFT), X_AXIS);
427 commonx = commonx->common_refpoint (me->get_bound (RIGHT), X_AXIS);
430 if (Grob *st = Staff_symbol_referencer::get_staff_symbol (me))
432 staff = st->extent (commony, Y_AXIS);
433 Real pad = robust_scm2double (me->get_property ("staff-padding"), 0.5);
437 Direction dir = get_grob_direction (me);
440 Use outer non-rest columns to determine slope
444 get_bounds (me, &left_col, &right_col);
445 if (left_col && right_col)
447 Interval rv = right_col->extent (commony, Y_AXIS);
448 Interval lv = left_col->extent (commony, Y_AXIS);
451 Real graphical_dy = rv[dir] - lv[dir];
453 Slice ls = Note_column::head_positions_interval (left_col);
454 Slice rs = Note_column::head_positions_interval (right_col);
457 musical_dy[UP] = rs[UP] - ls[UP];
458 musical_dy[DOWN] = rs[DOWN] - ls[DOWN];
459 if (sign (musical_dy[UP]) != sign (musical_dy[DOWN]))
461 else if (sign (graphical_dy) != sign (musical_dy[DOWN]))
469 *offset = -dir * infinity_f;
471 Item *lgr = get_x_bound_item (me, LEFT, dir);
472 Item *rgr = get_x_bound_item (me, RIGHT, dir);
473 Real x0 = robust_relative_extent (lgr, commonx, X_AXIS)[LEFT];
474 Real x1 = robust_relative_extent (rgr, commonx, X_AXIS)[RIGHT];
476 Array<Offset> points;
477 points.push (Offset (x0 - x0, staff[dir]));
478 points.push (Offset (x1 - x0, staff[dir]));
480 for (int i = 0; i < columns.size (); i++)
482 Interval note_ext = columns[i]->extent (commony, Y_AXIS);
483 Real notey = note_ext[dir] - me->relative_coordinate (commony, Y_AXIS);
485 Real x = columns[i]->relative_coordinate (commonx, X_AXIS) - x0;
486 points.push (Offset (x, notey));
490 This is a slight hack. We compute two encompass points from the
491 bbox of the smaller tuplets.
493 We assume that the smaller bracket is 1.0 space high.
495 Real ss = Staff_symbol_referencer::staff_space (me);
496 for (int i = 0; i < tuplets.size (); i++)
498 Interval tuplet_x (tuplets[i]->extent (commonx, X_AXIS));
499 Interval tuplet_y (tuplets[i]->extent (commony, Y_AXIS));
502 Real lp = scm_to_double (tuplets[i]->get_property ("left-position"));
503 Real rp = scm_to_double (tuplets[i]->get_property ("right-position"));
504 Real other_dy = rp - lp;
509 = tuplet_y.linear_combination (d * sign (other_dy));
513 Let's not take padding into account for nested tuplets.
514 the edges can come very close to the stems, likewise for
517 Drul_array<Real> my_height
518 = robust_scm2drul (me->get_property ("edge-height"),
520 if (dynamic_cast<Spanner *> (tuplets[i])->get_bound (d)
521 == me->get_bound (d))
522 y += dir * my_height[d];
525 points.push (Offset (tuplet_x[d] - x0, y));
527 while (flip (&d) != LEFT);
530 Real factor = (columns.size () > 1) ? 1 / (x1 - x0) : 1.0;
531 for (int i = 0; i < points.size (); i++)
533 Real x = points[i][X_AXIS];
534 Real tuplety = (*dy) * x * factor;
536 if (points[i][Y_AXIS] * dir > (*offset + tuplety) * dir)
537 *offset = points[i][Y_AXIS] - tuplety;
540 *offset += scm_to_double (me->get_property ("padding")) * dir;
543 horizontal brackets should not collide with staff lines.
545 Kind of pointless since we put them outside the staff anyway, but
546 let's leave code for the future when possibly allow them to move
547 into the staff once again.
550 && fabs (*offset) < ss * Staff_symbol_referencer::staff_radius (me))
552 // quantize, then do collision check.
555 *offset = rint (*offset);
556 if (Staff_symbol_referencer::on_staffline (me, (int) rint (*offset)))
564 We depend on the beams if there are any.
566 MAKE_SCHEME_CALLBACK (Tuplet_bracket, before_line_breaking, 1);
568 Tuplet_bracket::before_line_breaking (SCM smob)
570 Grob *me = unsmob_grob (smob);
571 extract_grob_set (me, "note-columns", columns);
573 for (int i = columns.size (); i--;)
575 Grob *s = Note_column::get_stem (columns[i]);
576 Grob *b = s ? Stem::get_beam (s) : 0;
578 me->add_dependency (b);
580 return SCM_UNSPECIFIED;
583 MAKE_SCHEME_CALLBACK (Tuplet_bracket, after_line_breaking, 1);
585 Tuplet_bracket::after_line_breaking (SCM smob)
587 Grob *me = unsmob_grob (smob);
588 extract_grob_set (me, "note-columns", columns);
590 if (columns.is_empty())
593 return SCM_UNSPECIFIED;
596 Direction dir = get_grob_direction (me);
599 dir = Tuplet_bracket::get_default_dir (me);
600 set_grob_direction (me, dir);
603 bool equally_long = false;
604 Grob *par_beam = parallel_beam (me, columns, &equally_long);
607 We follow the beam only if there is one, and we are next to it.
612 || get_grob_direction (par_beam) != dir)
613 calc_position_and_height (me, &offset, &dy);
616 SCM ps = par_beam->get_property ("positions");
618 Real lp = scm_to_double (scm_car (ps));
619 Real rp = scm_to_double (scm_cdr (ps));
624 offset = lp + dir * (0.5 + scm_to_double (me->get_property ("padding")));
628 SCM lp = me->get_property ("left-position");
629 SCM rp = me->get_property ("right-position");
631 if (scm_is_number (lp) && !scm_is_number (rp))
632 rp = scm_from_double (scm_to_double (lp) + dy);
633 else if (scm_is_number (rp) && !scm_is_number (lp))
634 lp = scm_from_double (scm_to_double (rp) - dy);
635 else if (!scm_is_number (rp) && !scm_is_number (lp))
637 lp = scm_from_double (offset);
638 rp = scm_from_double (offset + dy);
641 me->set_property ("left-position", lp);
642 me->set_property ("right-position", rp);
644 return SCM_UNSPECIFIED;
651 Tuplet_bracket::get_default_dir (Grob *me)
653 Drul_array<int> dirs (0, 0);
654 extract_grob_set (me, "note-columns", columns);
655 for (int i = 0; i < columns.size (); i++)
657 Grob *nc = columns[i];
658 Direction d = Note_column::dir (nc);
663 return dirs[UP] >= dirs[DOWN] ? UP : DOWN;
667 Tuplet_bracket::add_column (Grob *me, Item *n)
669 Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-columns"), n);
670 me->add_dependency (n);
672 add_bound_item (dynamic_cast<Spanner *> (me), n);
676 Tuplet_bracket::add_tuplet_bracket (Grob *me, Grob *bracket)
678 Pointer_group_interface::add_grob (me, ly_symbol2scm ("tuplets"), bracket);
679 me->add_dependency (bracket);
682 ADD_INTERFACE (Tuplet_bracket,
683 "tuplet-bracket-interface",
684 "A bracket with a number in the middle, used for tuplets. "
685 "When the bracket spans a line break, the value of "
686 "@code{break-overshoot} determines how far it extends "
688 "At a line break, the markups in the @code{edge-text} are printed "
693 "bracket-visibility "