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);
113 Drul_array<Real> positions
114 = ly_scm2realdrul (me->get_property ("positions"));
115 Real dy = positions[RIGHT] - positions[LEFT];
116 bool equally_long = false;
117 Grob *par_beam = parallel_beam (me, columns, &equally_long);
118 Spanner *sp = dynamic_cast<Spanner *> (me);
120 bool bracket_visibility = !(par_beam && equally_long);
121 bool number_visibility = true;
124 Fixme: the type of this prop is sucky.
126 SCM bracket = me->get_property ("bracket-visibility");
127 if (scm_is_bool (bracket))
128 bracket_visibility = ly_scm2bool (bracket);
129 else if (bracket == ly_symbol2scm ("if-no-beam"))
130 bracket_visibility = !par_beam;
132 SCM numb = me->get_property ("number-visibility");
133 if (scm_is_bool (numb))
134 number_visibility = ly_scm2bool (numb);
135 else if (numb == ly_symbol2scm ("if-no-beam"))
136 number_visibility = !par_beam;
138 Grob *commonx = common_refpoint_of_array (columns, me, X_AXIS);
139 commonx = commonx->common_refpoint (sp->get_bound (LEFT), X_AXIS);
140 commonx = commonx->common_refpoint (sp->get_bound (RIGHT), X_AXIS);
142 Direction dir = get_grob_direction (me);
144 Drul_array<Item *> bounds;
145 bounds[LEFT] = get_x_bound_item (me, LEFT, dir);
146 bounds[RIGHT] = get_x_bound_item (me, RIGHT, dir);
148 Drul_array<bool> connect_to_other;
153 x_span[d] = robust_relative_extent (bounds[d], commonx, X_AXIS)[d];
154 Direction break_dir = bounds[d]->break_status_dir ();
155 Spanner *orig_spanner = dynamic_cast<Spanner *> (me->original ());
157 int neighbor_idx = me->get_break_index () - break_dir;
161 && neighbor_idx < orig_spanner->broken_intos_.size ())
163 Grob *neighbor = orig_spanner->broken_intos_[neighbor_idx];
165 /* trigger possible suicide*/
166 (void) neighbor->get_property ("positions");
171 && (neighbor_idx < orig_spanner->broken_intos_.size ()
172 && neighbor_idx >= 0)
173 && orig_spanner->broken_intos_[neighbor_idx]->is_live ());
176 if (connect_to_other[d])
178 Interval overshoot (robust_scm2drul (me->get_property ("break-overshoot"),
179 Interval (-0.5, 0.0)));
182 x_span[d] += d * overshoot[d];
184 x_span[d] = robust_relative_extent (bounds[d], commonx, X_AXIS)[RIGHT]
188 && (columns.is_empty ()
189 || (bounds[d]->get_column ()
190 != dynamic_cast<Item *> (columns.top ())->get_column ())))
193 TODO: make padding tunable?
197 if (bounds[d]->break_status_dir ())
200 x_span[d] = robust_relative_extent (bounds[d], commonx, X_AXIS) [LEFT] - padding;
203 while (flip (&d) != LEFT);
205 Real w = x_span.length ();
206 SCM number = me->get_property ("text");
208 Output_def *pap = me->layout ();
210 if (scm_is_string (number) && number_visibility)
212 SCM properties = Font_interface::text_font_alist_chain (me);
213 SCM snum = Text_interface::interpret_markup (pap->self_scm (),
215 num = *unsmob_stencil (snum);
216 num.align_to (X_AXIS, CENTER);
217 num.translate_axis (w / 2, X_AXIS);
218 num.align_to (Y_AXIS, CENTER);
220 num.translate_axis (dy / 2, Y_AXIS);
222 mol.add_stencil (num);
226 No bracket when it would be smaller than the number.
229 if (bracket_visibility && number_visibility)
231 if (!num.extent (X_AXIS).is_empty ())
232 gap = num.extent (X_AXIS).length () + 1.0;
234 if (w - gap < w / 4.0)
235 bracket_visibility = false;
238 if (bracket_visibility)
240 Drul_array<Real> zero (0, 0);
241 Real ss = Staff_symbol_referencer::staff_space (me);
242 Drul_array<Real> height
243 = robust_scm2drul (me->get_property ("edge-height"), zero);
244 Drul_array<Real> flare
245 = robust_scm2drul (me->get_property ("bracket-flare"), zero);
246 Drul_array<Real> shorten
247 = robust_scm2drul (me->get_property ("shorten-pair"), zero);
248 Drul_array<Stencil> edge_stencils;
250 scale_drul (&height, -ss * dir);
251 scale_drul (&flare, ss);
252 scale_drul (&shorten, ss);
255 if (connect_to_other[d])
261 SCM edge_text = me->get_property ("edge-text");
263 if (scm_is_pair (edge_text))
265 SCM properties = Font_interface::text_font_alist_chain (me);
266 SCM text = index_get_cell (edge_text, d);
267 if (Text_interface::is_markup (text))
269 SCM t = Text_interface::interpret_markup (pap->self_scm (),
272 Stencil *edge_text = unsmob_stencil (t);
273 edge_text->translate_axis (x_span[d] - x_span[LEFT], X_AXIS);
274 edge_stencils[d] = *edge_text;
279 while (flip (&d) != LEFT);
281 Stencil brack = make_bracket (me, Y_AXIS,
285 0.1 = more space at right due to italics
286 TODO: use italic correction of font.
288 Interval (-0.5, 0.5) * gap + 0.1,
293 if (!edge_stencils[d].is_empty ())
294 brack.add_stencil (edge_stencils[d]);
296 while (flip (&d) != LEFT);
298 mol.add_stencil (brack);
301 mol.translate_axis (positions[LEFT], Y_AXIS);
302 mol.translate_axis (x_span[LEFT]
303 - sp->get_bound (LEFT)->relative_coordinate (commonx, X_AXIS), X_AXIS);
304 return mol.smobbed_copy ();
308 should move to lookup?
310 TODO: this will fail for very short (shorter than the flare)
314 Tuplet_bracket::make_bracket (Grob *me, // for line properties.
317 Drul_array<Real> height,
319 Drul_array<Real> flare,
320 Drul_array<Real> shorten)
322 Drul_array<Offset> corners (Offset (0, 0), dz);
324 Real length = dz.length ();
325 Drul_array<Offset> gap_corners;
327 Axis bracket_axis = other_axis (protusion_axis);
329 Drul_array<Offset> straight_corners = corners;
333 straight_corners[d] += -d * shorten[d] / length * dz;
334 while (flip (&d) != LEFT)
338 gap = Interval (0, 0);
340 gap_corners[d] = (dz * 0.5) + gap[d] / length * dz;
341 while (flip (&d) != LEFT)
344 Drul_array<Offset> flare_corners = straight_corners;
347 flare_corners[d][bracket_axis] = straight_corners[d][bracket_axis];
348 flare_corners[d][protusion_axis] += height[d];
349 straight_corners[d][bracket_axis] += -d * flare[d];
351 while (flip (&d) != LEFT);
356 m.add_stencil (Line_interface::line (me, straight_corners[d],
359 m.add_stencil (Line_interface::line (me, straight_corners[d],
362 while (flip (&d) != LEFT);
368 Tuplet_bracket::get_bounds (Grob *me, Grob **left, Grob **right)
370 extract_grob_set (me, "note-columns", columns);
372 while (l < columns.size () && Note_column::has_rests (columns[l]))
375 int r = columns.size ()- 1;
376 while (r >= l && Note_column::has_rests (columns[r]))
389 use first -> last note for slope, and then correct for disturbing
392 Tuplet_bracket::calc_position_and_height (Grob *me_grob, Real *offset, Real *dy)
394 Spanner *me = dynamic_cast<Spanner *> (me_grob);
396 extract_grob_set (me, "note-columns", columns);
397 extract_grob_set (me, "tuplets", tuplets);
399 Grob *commony = common_refpoint_of_array (columns, me, Y_AXIS);
400 commony = common_refpoint_of_array (tuplets, commony, Y_AXIS);
401 if (Grob *st = Staff_symbol_referencer::get_staff_symbol (me))
402 commony = st->common_refpoint (commony, Y_AXIS);
404 Grob *commonx = common_refpoint_of_array (columns, me, X_AXIS);
405 commonx = common_refpoint_of_array (tuplets, commonx, Y_AXIS);
406 commonx = commonx->common_refpoint (me->get_bound (LEFT), X_AXIS);
407 commonx = commonx->common_refpoint (me->get_bound (RIGHT), X_AXIS);
410 if (Grob *st = Staff_symbol_referencer::get_staff_symbol (me))
412 staff = st->extent (commony, Y_AXIS);
413 Real pad = robust_scm2double (me->get_property ("staff-padding"), 0.5);
417 Direction dir = get_grob_direction (me);
420 Use outer non-rest columns to determine slope
424 get_bounds (me, &left_col, &right_col);
425 if (left_col && right_col)
427 Interval rv = right_col->extent (commony, Y_AXIS);
428 Interval lv = left_col->extent (commony, Y_AXIS);
431 Real graphical_dy = rv[dir] - lv[dir];
433 Slice ls = Note_column::head_positions_interval (left_col);
434 Slice rs = Note_column::head_positions_interval (right_col);
437 musical_dy[UP] = rs[UP] - ls[UP];
438 musical_dy[DOWN] = rs[DOWN] - ls[DOWN];
439 if (sign (musical_dy[UP]) != sign (musical_dy[DOWN]))
441 else if (sign (graphical_dy) != sign (musical_dy[DOWN]))
449 *offset = -dir * infinity_f;
451 Item *lgr = get_x_bound_item (me, LEFT, dir);
452 Item *rgr = get_x_bound_item (me, RIGHT, dir);
453 Real x0 = robust_relative_extent (lgr, commonx, X_AXIS)[LEFT];
454 Real x1 = robust_relative_extent (rgr, commonx, X_AXIS)[RIGHT];
456 Array<Offset> points;
457 points.push (Offset (x0 - x0, staff[dir]));
458 points.push (Offset (x1 - x0, staff[dir]));
460 for (int i = 0; i < columns.size (); i++)
462 Interval note_ext = columns[i]->extent (commony, Y_AXIS);
463 Real notey = note_ext[dir] - me->relative_coordinate (commony, Y_AXIS);
465 Real x = columns[i]->relative_coordinate (commonx, X_AXIS) - x0;
466 points.push (Offset (x, notey));
470 This is a slight hack. We compute two encompass points from the
471 bbox of the smaller tuplets.
473 We assume that the smaller bracket is 1.0 space high.
475 Real ss = Staff_symbol_referencer::staff_space (me);
476 for (int i = 0; i < tuplets.size (); i++)
478 Interval tuplet_x (tuplets[i]->extent (commonx, X_AXIS));
479 Interval tuplet_y (tuplets[i]->extent (commony, Y_AXIS));
482 Drul_array<Real> positions = ly_scm2realdrul (tuplets[i]->get_property ("positions"));
485 Real other_dy = positions[RIGHT] - positions[LEFT];
490 = tuplet_y.linear_combination (d * sign (other_dy));
494 Let's not take padding into account for nested tuplets.
495 the edges can come very close to the stems, likewise for
498 Drul_array<Real> my_height
499 = robust_scm2drul (me->get_property ("edge-height"),
501 if (dynamic_cast<Spanner *> (tuplets[i])->get_bound (d)
502 == me->get_bound (d))
503 y += dir * my_height[d];
506 points.push (Offset (tuplet_x[d] - x0, y));
508 while (flip (&d) != LEFT);
511 Real factor = (columns.size () > 1) ? 1 / (x1 - x0) : 1.0;
512 for (int i = 0; i < points.size (); i++)
514 Real x = points[i][X_AXIS];
515 Real tuplety = (*dy) * x * factor;
517 if (points[i][Y_AXIS] * dir > (*offset + tuplety) * dir)
518 *offset = points[i][Y_AXIS] - tuplety;
521 *offset += scm_to_double (me->get_property ("padding")) * dir;
524 horizontal brackets should not collide with staff lines.
526 Kind of pointless since we put them outside the staff anyway, but
527 let's leave code for the future when possibly allow them to move
528 into the staff once again.
531 && fabs (*offset) < ss * Staff_symbol_referencer::staff_radius (me))
533 // quantize, then do collision check.
536 *offset = rint (*offset);
537 if (Staff_symbol_referencer::on_staffline (me, (int) rint (*offset)))
545 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_direction, 1);
547 Tuplet_bracket::calc_direction (SCM smob)
549 Grob *me = unsmob_grob (smob);
550 Direction dir = Tuplet_bracket::get_default_dir (me);
551 return scm_from_int (dir);
554 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_positions, 1);
556 Tuplet_bracket::calc_positions (SCM smob)
558 Grob *me = unsmob_grob (smob);
559 extract_grob_set (me, "note-columns", columns);
561 if (columns.is_empty())
564 return scm_cons (scm_from_double (0),
565 scm_from_double (0));
568 Direction dir = get_grob_direction (me);
569 bool equally_long = false;
570 Grob *par_beam = parallel_beam (me, columns, &equally_long);
573 We follow the beam only if there is one, and we are next to it.
578 || get_grob_direction (par_beam) != dir)
579 calc_position_and_height (me, &offset, &dy);
582 SCM ps = par_beam->get_property ("positions");
584 Real lp = scm_to_double (scm_car (ps));
585 Real rp = scm_to_double (scm_cdr (ps));
590 offset = lp + dir * (0.5 + scm_to_double (me->get_property ("padding")));
595 SCM x = scm_cons (scm_from_double (offset),
596 scm_from_double (offset + dy));
605 Tuplet_bracket::get_default_dir (Grob *me)
607 Drul_array<int> dirs (0, 0);
608 extract_grob_set (me, "note-columns", columns);
609 for (int i = 0; i < columns.size (); i++)
611 Grob *nc = columns[i];
612 Direction d = Note_column::dir (nc);
617 return dirs[UP] >= dirs[DOWN] ? UP : DOWN;
621 Tuplet_bracket::add_column (Grob *me, Item *n)
623 Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-columns"), n);
624 add_bound_item (dynamic_cast<Spanner *> (me), n);
628 Tuplet_bracket::add_tuplet_bracket (Grob *me, Grob *bracket)
630 Pointer_group_interface::add_grob (me, ly_symbol2scm ("tuplets"), bracket);
633 ADD_INTERFACE (Tuplet_bracket,
634 "tuplet-bracket-interface",
635 "A bracket with a number in the middle, used for tuplets. "
636 "When the bracket spans a line break, the value of "
637 "@code{break-overshoot} determines how far it extends "
639 "At a line break, the markups in the @code{edge-text} are printed "
645 "bracket-visibility "