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"),
186 x_span[d] += d * overshoot[d];
189 while (flip (&d) != LEFT);
191 Real w = x_span.length();
192 SCM number = me->get_property ("text");
194 Output_def *pap = me->get_layout ();
196 if (scm_is_string (number) && number_visibility)
198 SCM properties = Font_interface::text_font_alist_chain (me);
199 SCM snum = Text_interface::interpret_markup (pap->self_scm (), properties, number);
200 num = *unsmob_stencil (snum);
201 num.align_to (X_AXIS, CENTER);
202 num.translate_axis (w / 2, X_AXIS);
203 num.align_to (Y_AXIS, CENTER);
205 num.translate_axis ((ry - ly) / 2, Y_AXIS);
207 mol.add_stencil (num);
211 No bracket when it would be smaller than the number.
213 TODO: should use GAP in calculation too.
215 if (bracket_visibility && number_visibility
216 && mol.extent (X_AXIS).length () > w)
218 bracket_visibility = false;
221 if (bracket_visibility)
225 if (!num.extent (X_AXIS).is_empty ())
226 gap = num.extent (X_AXIS).length () + 1.0;
228 Drul_array<Real> zero (0,0);
229 Real ss = Staff_symbol_referencer::staff_space (me);
230 Drul_array<Real> height
231 = robust_scm2drul (me->get_property ("edge-height"), zero);
232 Drul_array<Real> flare
233 = robust_scm2drul (me->get_property ("bracket-flare"), zero);
234 Drul_array<Real> shorten
235 = robust_scm2drul (me->get_property ("shorten-pair"), zero);
237 scale_drul (&height, -ss * dir);
238 scale_drul (&flare, ss);
239 scale_drul (&shorten, ss);
244 if (bounds[d]->break_status_dir ())
251 while (flip (&d) != LEFT);
254 Stencil brack = make_bracket (me, Y_AXIS,
258 0.1 = more space at right due to italics
259 TODO: use italic correction of font.
261 Interval (-0.5, 0.5) * gap + 0.1,
266 if (bounds[d]->break_status_dir ())
268 SCM properties = Font_interface::text_font_alist_chain (me);
269 SCM edge_text = me->get_property ("edge-text");
271 SCM text = index_get_cell (edge_text, d);
272 if (Text_interface::is_markup (text))
274 SCM t = Text_interface::interpret_markup (pap->self_scm (), properties,
277 Stencil *edge_text = unsmob_stencil (t);
278 edge_text->translate_axis (x_span[d] - x_span[LEFT], X_AXIS);
279 mol.add_stencil (*edge_text);
283 while (flip (&d) != LEFT);
286 mol.add_stencil (brack);
289 mol.translate_axis (ly, Y_AXIS);
290 mol.translate_axis (x_span[LEFT]
291 - sp->get_bound (LEFT)->relative_coordinate (commonx, X_AXIS), X_AXIS);
292 return mol.smobbed_copy ();
296 should move to lookup?
298 TODO: this will fail for very short (shorter than the flare)
302 Tuplet_bracket::make_bracket (Grob *me, // for line properties.
305 Drul_array<Real> height,
307 Drul_array<Real> flare,
308 Drul_array<Real> shorten)
310 Drul_array<Offset> corners (Offset (0, 0), dz);
312 Real length = dz.length ();
313 Drul_array<Offset> gap_corners;
315 Axis bracket_axis = other_axis (protusion_axis);
317 Drul_array<Offset> straight_corners = corners;
322 straight_corners[d] += -d * shorten[d] / length * dz;
324 while (flip (&d) != LEFT);
327 gap = Interval (0, 0);
330 gap_corners[d] = (dz * 0.5) + gap[d] / length * dz;
332 while (flip (&d) != LEFT);
334 Drul_array<Offset> flare_corners = straight_corners;
337 flare_corners[d][bracket_axis] = straight_corners[d][bracket_axis];
338 flare_corners[d][protusion_axis] += height[d];
339 straight_corners[d][bracket_axis] += -d * flare[d];
341 while (flip (&d) != LEFT);
346 m.add_stencil (Line_interface::line (me, straight_corners[d],
349 m.add_stencil (Line_interface::line (me, straight_corners[d],
352 while (flip (&d) != LEFT);
358 Tuplet_bracket::get_bounds (Grob *me, Grob **left, Grob **right)
360 extract_grob_set (me, "note-columns", columns);
362 while (l < columns.size () && Note_column::has_rests (columns[l]))
365 int r = columns.size ()- 1;
366 while (r >= l && Note_column::has_rests (columns[r]))
380 use first -> last note for slope, and then correct for disturbing
383 Tuplet_bracket::calc_position_and_height (Grob *me_grob, Real *offset, Real *dy)
385 Spanner *me = dynamic_cast<Spanner*> (me_grob);
387 extract_grob_set (me, "note-columns", columns);
388 extract_grob_set (me, "tuplets", tuplets);
390 Grob *commony = common_refpoint_of_array (columns, me, Y_AXIS);
391 commony = common_refpoint_of_array (tuplets, commony, Y_AXIS);
392 Grob *commonx = common_refpoint_of_array (columns, me, X_AXIS);
393 commonx = common_refpoint_of_array (tuplets, commonx, Y_AXIS);
394 commonx = commonx->common_refpoint (me->get_bound (LEFT), X_AXIS);
395 commonx = commonx->common_refpoint (me->get_bound (RIGHT), X_AXIS);
399 if (Grob *st = Staff_symbol_referencer::get_staff_symbol (me))
400 staff = st->extent (commony, Y_AXIS);
402 Direction dir = get_grob_direction (me);
405 Use outer non-rest columns to determine slope
409 get_bounds (me, &left_col, &right_col);
410 if (left_col && right_col)
412 Interval rv = right_col->extent (commony, Y_AXIS);
413 Interval lv = left_col->extent (commony, Y_AXIS);
416 Real graphical_dy = rv[dir] - lv[dir];
418 Slice ls = Note_column::head_positions_interval (left_col);
419 Slice rs = Note_column::head_positions_interval (right_col);
422 musical_dy[UP] = rs[UP] - ls[UP];
423 musical_dy[DOWN] = rs[DOWN] - ls[DOWN];
424 if (sign (musical_dy[UP]) != sign (musical_dy[DOWN]))
426 else if (sign (graphical_dy) != sign (musical_dy[DOWN]))
434 *offset = -dir * infinity_f;
436 if (!columns.size ())
439 Item *lgr = get_x_bound_item (me, LEFT, dir);
440 Item *rgr = get_x_bound_item (me, RIGHT, dir);
441 Real x0 = robust_relative_extent (lgr, commonx, X_AXIS)[LEFT];
442 Real x1 = robust_relative_extent (rgr, commonx, X_AXIS)[RIGHT];
447 Real factor = columns.size () > 1 ? 1 / (x1 - x0) : 1.0;
449 Array<Offset> points;
450 for (int i = 0; i < columns.size (); i++)
452 Interval note_ext = columns[i]->extent (commony, Y_AXIS);
453 note_ext.unite (staff);
454 Real notey = note_ext[dir] - me->relative_coordinate (commony, Y_AXIS);
456 Real x = columns[i]->relative_coordinate (commonx, X_AXIS) - x0;
457 points.push (Offset (x, notey));
461 This is a slight hack. We compute two encompass points from the
462 bbox of the smaller tuplets.
464 We assume that the smaller bracket is 1.0 space high.
467 Real ss = Staff_symbol_referencer::staff_space (me);
468 for (int i = 0; i < tuplets.size (); i++)
470 Interval tuplet_x (tuplets[i]->extent (commonx, X_AXIS));
471 Interval tuplet_y (tuplets[i]->extent (commony, Y_AXIS));
474 Real lp = scm_to_double (tuplets[i]->get_property ("left-position"));
475 Real rp = scm_to_double (tuplets[i]->get_property ("right-position"));
476 Real other_dy = rp - lp;
481 tuplet_y.linear_combination (d * sign (other_dy));
485 Let's not take padding into account for nested tuplets.
486 the edges can come very close to the stems, likewise for
489 Drul_array<Real> my_height
490 = robust_scm2drul (me->get_property ("edge-height"), Interval (0,0));
491 if (dynamic_cast<Spanner*> (tuplets[i])->get_bound (d)
492 == me->get_bound (d))
494 y += dir * my_height[d];
498 points.push (Offset (tuplet_x[d] - x0, y));
500 while (flip (&d) != LEFT);
503 for (int i = 0; i < points.size (); i++)
505 Real x = points[i][X_AXIS];
506 Real tuplety = *dy * x * factor;
508 if (points[i][Y_AXIS] * dir > (*offset + tuplety) * dir)
509 *offset = points[i][Y_AXIS] - tuplety;
512 *offset += scm_to_double (me->get_property ("padding")) * dir;
515 horizontal brackets should not collide with staff lines.
517 Kind of pointless since we put them outside the staff anyway, but
518 let's leave code for the future when possibly allow them to move
519 into the staff once again.
522 fabs (*offset) < ss * Staff_symbol_referencer::staff_radius (me))
524 // quantize, then do collision check.
527 *offset = rint (*offset);
528 if (Staff_symbol_referencer::on_staffline (me, (int) rint (*offset)))
536 We depend on the beams if there are any.
538 MAKE_SCHEME_CALLBACK (Tuplet_bracket, before_line_breaking, 1);
540 Tuplet_bracket::before_line_breaking (SCM smob)
542 Grob *me = unsmob_grob (smob);
543 extract_grob_set (me, "note-columns", columns);
545 for (int i = columns.size (); i--;)
547 Grob *s = Note_column::get_stem (columns[i]);
548 Grob *b = s ? Stem::get_beam (s) : 0;
550 me->add_dependency (b);
552 return SCM_UNSPECIFIED;
555 MAKE_SCHEME_CALLBACK (Tuplet_bracket, after_line_breaking, 1);
558 Tuplet_bracket::after_line_breaking (SCM smob)
560 Grob *me = unsmob_grob (smob);
561 extract_grob_set (me, "note-columns", columns);
563 if (!columns.size ())
566 return SCM_UNSPECIFIED;
568 if (dynamic_cast<Spanner *> (me)->is_broken ())
570 me->warning (_ ("removing tuplet bracket across linebreak"));
572 return SCM_UNSPECIFIED;
575 Direction dir = get_grob_direction (me);
578 dir = Tuplet_bracket::get_default_dir (me);
579 set_grob_direction (me, dir);
582 bool equally_long = false;
583 Grob *par_beam = parallel_beam (me, columns, &equally_long);
586 We follow the beam only if there is one, and we are next to it.
591 || get_grob_direction (par_beam) != dir)
593 calc_position_and_height (me, &offset, &dy);
597 SCM ps = par_beam->get_property ("positions");
599 Real lp = scm_to_double (scm_car (ps));
600 Real rp = scm_to_double (scm_cdr (ps));
605 offset = lp + dir * (0.5 + scm_to_double (me->get_property ("padding")));
609 SCM lp = me->get_property ("left-position");
610 SCM rp = me->get_property ("right-position");
612 if (scm_is_number (lp) && !scm_is_number (rp))
614 rp = scm_from_double (scm_to_double (lp) + dy);
616 else if (scm_is_number (rp) && !scm_is_number (lp))
618 lp = scm_from_double (scm_to_double (rp) - dy);
620 else if (!scm_is_number (rp) && !scm_is_number (lp))
622 lp = scm_from_double (offset);
623 rp = scm_from_double (offset + dy);
626 me->set_property ("left-position", lp);
627 me->set_property ("right-position", rp);
629 return SCM_UNSPECIFIED;
636 Tuplet_bracket::get_default_dir (Grob *me)
638 Drul_array<int> dirs (0, 0);
639 extract_grob_set (me, "note-columns", columns);
640 for (int i = 0 ; i < columns.size (); i++)
642 Grob *nc = columns[i];
643 Direction d = Note_column::dir (nc);
648 return dirs[UP] >= dirs[DOWN] ? UP : DOWN;
652 Tuplet_bracket::add_column (Grob *me, Item *n)
654 Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-columns"), n);
655 me->add_dependency (n);
657 add_bound_item (dynamic_cast<Spanner *> (me), n);
661 Tuplet_bracket::add_tuplet_bracket (Grob *me, Grob *bracket)
663 Pointer_group_interface::add_grob (me, ly_symbol2scm ("tuplets"), bracket);
664 me->add_dependency (bracket);
669 ADD_INTERFACE (Tuplet_bracket,
670 "tuplet-bracket-interface",
671 "A bracket with a number in the middle, used for tuplets. "
672 "When the bracket spans a line break, the value of "
673 "@code{break-overshoot} determines how far it extends "
675 "At a line break, the markups in the @code{edge-text} are printed "
678 "note-columns bracket-flare edge-height shorten-pair "
679 "tuplets edge-text break-overshoot "
680 "padding left-position right-position bracket-visibility "
681 "number-visibility thickness direction");