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)
70 Spanner *me = dynamic_cast<Spanner *> (me_grob);
72 if (me->get_bound (LEFT)->break_status_dir ()
73 || me->get_bound (RIGHT)->break_status_dir ())
76 Grob *s1 = Note_column::get_stem (cols[0]);
77 Grob *s2 = Note_column::get_stem (cols.top ());
79 Grob *b1 = s1 ? Stem::get_beam (s1) : 0;
80 Grob *b2 = s2 ? Stem::get_beam (s2) : 0;
83 *equally_long = false;
84 if (! (b1 && (b1 == b2) && !me->is_broken ()))
87 extract_grob_set (b1, "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] == s1 && beam_stems.top () == s2);
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 Grob *me = unsmob_grob (smob);
111 extract_grob_set (me, "note-columns", columns);
113 if (!columns.size ())
114 return mol.smobbed_copy ();
117 SCM lp = me->get_property ("left-position");
118 SCM rp = me->get_property ("right-position");
120 if (!scm_is_number (rp) || !scm_is_number (lp))
123 UGH. dependency tracking!
125 extract_grob_set (me, "tuplets", tuplets);
126 for (int i = 0; i < tuplets.size (); i++)
127 Tuplet_bracket::print (tuplets[i]->self_scm());
129 after_line_breaking (smob);
133 Real ly = robust_scm2double (me->get_property ("left-position"), 0);
134 Real ry = robust_scm2double (me->get_property ("right-position"), 0);
136 bool equally_long = false;
137 Grob *par_beam = parallel_beam (me, columns, &equally_long);
139 Spanner *sp = dynamic_cast<Spanner *> (me);
141 bool bracket_visibility = !(par_beam && equally_long);
142 bool number_visibility = true;
145 Fixme: the type of this prop is sucky.
147 SCM bracket = me->get_property ("bracket-visibility");
148 if (scm_is_bool (bracket))
150 bracket_visibility = ly_scm2bool (bracket);
152 else if (bracket == ly_symbol2scm ("if-no-beam"))
153 bracket_visibility = !par_beam;
155 SCM numb = me->get_property ("number-visibility");
156 if (scm_is_bool (numb))
158 number_visibility = ly_scm2bool (numb);
160 else if (numb == ly_symbol2scm ("if-no-beam"))
161 number_visibility = !par_beam;
163 Grob *commonx = columns[0]->common_refpoint (columns.top (), X_AXIS);
166 commonx = commonx->common_refpoint (sp->get_bound (LEFT), X_AXIS);
167 commonx = commonx->common_refpoint (sp->get_bound (RIGHT), X_AXIS);
169 Direction dir = get_grob_direction (me);
171 Drul_array<Item *> bounds;
172 bounds[LEFT] = get_x_bound_item (me, LEFT, dir);
173 bounds[RIGHT] = get_x_bound_item (me, RIGHT, dir);
179 x_span[d] = robust_relative_extent (bounds[d], commonx, X_AXIS)[d];
181 if (bounds[d]->break_status_dir())
183 Interval overshoot (robust_scm2drul (me->get_property ("break-overshoot"),
184 Interval (-0.5, 1.0)));
187 x_span[d] += d * overshoot[d];
189 x_span[d] = robust_relative_extent(bounds[d], commonx, X_AXIS)[RIGHT]
193 while (flip (&d) != LEFT);
195 Real w = x_span.length();
196 SCM number = me->get_property ("text");
198 Output_def *pap = me->get_layout ();
200 if (scm_is_string (number) && number_visibility)
202 SCM properties = Font_interface::text_font_alist_chain (me);
203 SCM snum = Text_interface::interpret_markup (pap->self_scm (), properties, number);
204 num = *unsmob_stencil (snum);
205 num.align_to (X_AXIS, CENTER);
206 num.translate_axis (w / 2, X_AXIS);
207 num.align_to (Y_AXIS, CENTER);
209 num.translate_axis ((ry - ly) / 2, Y_AXIS);
211 mol.add_stencil (num);
215 No bracket when it would be smaller than the number.
217 TODO: should use GAP in calculation too.
219 if (bracket_visibility && number_visibility
220 && mol.extent (X_AXIS).length () > w)
222 bracket_visibility = false;
225 if (bracket_visibility)
229 if (!num.extent (X_AXIS).is_empty ())
230 gap = num.extent (X_AXIS).length () + 1.0;
232 Drul_array<Real> zero (0,0);
233 Real ss = Staff_symbol_referencer::staff_space (me);
234 Drul_array<Real> height
235 = robust_scm2drul (me->get_property ("edge-height"), zero);
236 Drul_array<Real> flare
237 = robust_scm2drul (me->get_property ("bracket-flare"), zero);
238 Drul_array<Real> shorten
239 = robust_scm2drul (me->get_property ("shorten-pair"), zero);
241 scale_drul (&height, -ss * dir);
242 scale_drul (&flare, ss);
243 scale_drul (&shorten, ss);
248 if (bounds[d]->break_status_dir ())
255 while (flip (&d) != LEFT);
258 Stencil brack = make_bracket (me, Y_AXIS,
262 0.1 = more space at right due to italics
263 TODO: use italic correction of font.
265 Interval (-0.5, 0.5) * gap + 0.1,
270 if (bounds[d]->break_status_dir ())
272 SCM properties = Font_interface::text_font_alist_chain (me);
273 SCM edge_text = me->get_property ("edge-text");
275 SCM text = index_get_cell (edge_text, d);
276 if (Text_interface::is_markup (text))
278 SCM t = Text_interface::interpret_markup (pap->self_scm (), properties,
281 Stencil *edge_text = unsmob_stencil (t);
282 edge_text->translate_axis (x_span[d] - x_span[LEFT], X_AXIS);
283 mol.add_stencil (*edge_text);
287 while (flip (&d) != LEFT);
290 mol.add_stencil (brack);
293 mol.translate_axis (ly, Y_AXIS);
294 mol.translate_axis (x_span[LEFT]
295 - sp->get_bound (LEFT)->relative_coordinate (commonx, X_AXIS), X_AXIS);
296 return mol.smobbed_copy ();
300 should move to lookup?
302 TODO: this will fail for very short (shorter than the flare)
306 Tuplet_bracket::make_bracket (Grob *me, // for line properties.
309 Drul_array<Real> height,
311 Drul_array<Real> flare,
312 Drul_array<Real> shorten)
314 Drul_array<Offset> corners (Offset (0, 0), dz);
316 Real length = dz.length ();
317 Drul_array<Offset> gap_corners;
319 Axis bracket_axis = other_axis (protusion_axis);
321 Drul_array<Offset> straight_corners = corners;
326 straight_corners[d] += -d * shorten[d] / length * dz;
328 while (flip (&d) != LEFT);
331 gap = Interval (0, 0);
334 gap_corners[d] = (dz * 0.5) + gap[d] / length * dz;
336 while (flip (&d) != LEFT);
338 Drul_array<Offset> flare_corners = straight_corners;
341 flare_corners[d][bracket_axis] = straight_corners[d][bracket_axis];
342 flare_corners[d][protusion_axis] += height[d];
343 straight_corners[d][bracket_axis] += -d * flare[d];
345 while (flip (&d) != LEFT);
350 m.add_stencil (Line_interface::line (me, straight_corners[d],
353 m.add_stencil (Line_interface::line (me, straight_corners[d],
356 while (flip (&d) != LEFT);
362 Tuplet_bracket::get_bounds (Grob *me, Grob **left, Grob **right)
364 extract_grob_set (me, "note-columns", columns);
366 while (l < columns.size () && Note_column::has_rests (columns[l]))
369 int r = columns.size ()- 1;
370 while (r >= l && Note_column::has_rests (columns[r]))
384 use first -> last note for slope, and then correct for disturbing
387 Tuplet_bracket::calc_position_and_height (Grob *me_grob, Real *offset, Real *dy)
389 Spanner *me = dynamic_cast<Spanner*> (me_grob);
391 extract_grob_set (me, "note-columns", columns);
392 extract_grob_set (me, "tuplets", tuplets);
394 Grob *commony = common_refpoint_of_array (columns, me, Y_AXIS);
395 commony = common_refpoint_of_array (tuplets, commony, Y_AXIS);
396 Grob *commonx = common_refpoint_of_array (columns, me, X_AXIS);
397 commonx = common_refpoint_of_array (tuplets, commonx, Y_AXIS);
398 commonx = commonx->common_refpoint (me->get_bound (LEFT), X_AXIS);
399 commonx = commonx->common_refpoint (me->get_bound (RIGHT), X_AXIS);
403 if (Grob *st = Staff_symbol_referencer::get_staff_symbol (me))
404 staff = st->extent (commony, Y_AXIS);
406 Direction dir = get_grob_direction (me);
409 Use outer non-rest columns to determine slope
413 get_bounds (me, &left_col, &right_col);
414 if (left_col && right_col)
416 Interval rv = right_col->extent (commony, Y_AXIS);
417 Interval lv = left_col->extent (commony, Y_AXIS);
420 Real graphical_dy = rv[dir] - lv[dir];
422 Slice ls = Note_column::head_positions_interval (left_col);
423 Slice rs = Note_column::head_positions_interval (right_col);
426 musical_dy[UP] = rs[UP] - ls[UP];
427 musical_dy[DOWN] = rs[DOWN] - ls[DOWN];
428 if (sign (musical_dy[UP]) != sign (musical_dy[DOWN]))
430 else if (sign (graphical_dy) != sign (musical_dy[DOWN]))
438 *offset = -dir * infinity_f;
440 if (!columns.size ())
443 Item *lgr = get_x_bound_item (me, LEFT, dir);
444 Item *rgr = get_x_bound_item (me, RIGHT, dir);
445 Real x0 = robust_relative_extent (lgr, commonx, X_AXIS)[LEFT];
446 Real x1 = robust_relative_extent (rgr, commonx, X_AXIS)[RIGHT];
451 Real factor = columns.size () > 1 ? 1 / (x1 - x0) : 1.0;
453 Array<Offset> points;
454 for (int i = 0; i < columns.size (); i++)
456 Interval note_ext = columns[i]->extent (commony, Y_AXIS);
457 note_ext.unite (staff);
458 Real notey = note_ext[dir] - me->relative_coordinate (commony, Y_AXIS);
460 Real x = columns[i]->relative_coordinate (commonx, X_AXIS) - x0;
461 points.push (Offset (x, notey));
465 This is a slight hack. We compute two encompass points from the
466 bbox of the smaller tuplets.
468 We assume that the smaller bracket is 1.0 space high.
471 Real ss = Staff_symbol_referencer::staff_space (me);
472 for (int i = 0; i < tuplets.size (); i++)
474 Interval tuplet_x (tuplets[i]->extent (commonx, X_AXIS));
475 Interval tuplet_y (tuplets[i]->extent (commony, Y_AXIS));
478 Real lp = scm_to_double (tuplets[i]->get_property ("left-position"));
479 Real rp = scm_to_double (tuplets[i]->get_property ("right-position"));
480 Real other_dy = rp - lp;
485 tuplet_y.linear_combination (d * sign (other_dy));
489 Let's not take padding into account for nested tuplets.
490 the edges can come very close to the stems, likewise for
493 Drul_array<Real> my_height
494 = robust_scm2drul (me->get_property ("edge-height"), Interval (0,0));
495 if (dynamic_cast<Spanner*> (tuplets[i])->get_bound (d)
496 == me->get_bound (d))
498 y += dir * my_height[d];
502 points.push (Offset (tuplet_x[d] - x0, y));
504 while (flip (&d) != LEFT);
507 for (int i = 0; i < points.size (); i++)
509 Real x = points[i][X_AXIS];
510 Real tuplety = *dy * x * factor;
512 if (points[i][Y_AXIS] * dir > (*offset + tuplety) * dir)
513 *offset = points[i][Y_AXIS] - tuplety;
516 *offset += scm_to_double (me->get_property ("padding")) * dir;
519 horizontal brackets should not collide with staff lines.
521 Kind of pointless since we put them outside the staff anyway, but
522 let's leave code for the future when possibly allow them to move
523 into the staff once again.
526 fabs (*offset) < ss * Staff_symbol_referencer::staff_radius (me))
528 // quantize, then do collision check.
531 *offset = rint (*offset);
532 if (Staff_symbol_referencer::on_staffline (me, (int) rint (*offset)))
540 We depend on the beams if there are any.
542 MAKE_SCHEME_CALLBACK (Tuplet_bracket, before_line_breaking, 1);
544 Tuplet_bracket::before_line_breaking (SCM smob)
546 Grob *me = unsmob_grob (smob);
547 extract_grob_set (me, "note-columns", columns);
549 for (int i = columns.size (); i--;)
551 Grob *s = Note_column::get_stem (columns[i]);
552 Grob *b = s ? Stem::get_beam (s) : 0;
554 me->add_dependency (b);
556 return SCM_UNSPECIFIED;
559 MAKE_SCHEME_CALLBACK (Tuplet_bracket, after_line_breaking, 1);
562 Tuplet_bracket::after_line_breaking (SCM smob)
564 Grob *me = unsmob_grob (smob);
565 extract_grob_set (me, "note-columns", columns);
567 if (!columns.size ())
570 return SCM_UNSPECIFIED;
572 if (dynamic_cast<Spanner *> (me)->is_broken ())
574 me->warning (_ ("removing tuplet bracket across linebreak"));
576 return SCM_UNSPECIFIED;
579 Direction dir = get_grob_direction (me);
582 dir = Tuplet_bracket::get_default_dir (me);
583 set_grob_direction (me, dir);
586 bool equally_long = false;
587 Grob *par_beam = parallel_beam (me, columns, &equally_long);
590 We follow the beam only if there is one, and we are next to it.
595 || get_grob_direction (par_beam) != dir)
597 calc_position_and_height (me, &offset, &dy);
601 SCM ps = par_beam->get_property ("positions");
603 Real lp = scm_to_double (scm_car (ps));
604 Real rp = scm_to_double (scm_cdr (ps));
609 offset = lp + dir * (0.5 + scm_to_double (me->get_property ("padding")));
613 SCM lp = me->get_property ("left-position");
614 SCM rp = me->get_property ("right-position");
616 if (scm_is_number (lp) && !scm_is_number (rp))
618 rp = scm_from_double (scm_to_double (lp) + dy);
620 else if (scm_is_number (rp) && !scm_is_number (lp))
622 lp = scm_from_double (scm_to_double (rp) - dy);
624 else if (!scm_is_number (rp) && !scm_is_number (lp))
626 lp = scm_from_double (offset);
627 rp = scm_from_double (offset + dy);
630 me->set_property ("left-position", lp);
631 me->set_property ("right-position", rp);
633 return SCM_UNSPECIFIED;
640 Tuplet_bracket::get_default_dir (Grob *me)
642 Drul_array<int> dirs (0, 0);
643 extract_grob_set (me, "note-columns", columns);
644 for (int i = 0 ; i < columns.size (); i++)
646 Grob *nc = columns[i];
647 Direction d = Note_column::dir (nc);
652 return dirs[UP] >= dirs[DOWN] ? UP : DOWN;
656 Tuplet_bracket::add_column (Grob *me, Item *n)
658 Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-columns"), n);
659 me->add_dependency (n);
661 add_bound_item (dynamic_cast<Spanner *> (me), n);
665 Tuplet_bracket::add_tuplet_bracket (Grob *me, Grob *bracket)
667 Pointer_group_interface::add_grob (me, ly_symbol2scm ("tuplets"), bracket);
668 me->add_dependency (bracket);
673 ADD_INTERFACE (Tuplet_bracket,
674 "tuplet-bracket-interface",
675 "A bracket with a number in the middle, used for tuplets. "
676 "When the bracket spans a line break, the value of "
677 "@code{break-overshoot} determines how far it extends "
679 "At a line break, the markups in the @code{edge-text} are printed "
682 "note-columns bracket-flare edge-height shorten-pair "
683 "tuplets edge-text break-overshoot "
684 "padding left-position right-position bracket-visibility "
685 "number-visibility thickness direction");