2 This file is part of LilyPond, the GNU music typesetter.
4 Copyright (C) 1997--2012 Jan Nieuwenhuizen <janneke@gnu.org>
5 Han-Wen Nienhuys <hanwen@xs4all.nl>
7 LilyPond is free software: you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation, either version 3 of the License, or
10 (at your option) any later version.
12 LilyPond is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
17 You should have received a copy of the GNU General Public License
18 along with LilyPond. If not, see <http://www.gnu.org/licenses/>.
24 - tuplet bracket should probably be subject to the same rules as
25 beam sloping/quanting.
27 - There is no support for kneed brackets, or nested brackets.
29 - number placement for parallel beams should be much more advanced:
30 for sloped beams some extra horizontal offset must be introduced.
32 - number placement is usually done over the center note, not the
37 TODO: quantise, we don't want to collide with staff lines.
38 (or should we be above staff?)
40 todo: handle breaking elegantly.
43 #include "tuplet-bracket.hh"
44 #include "axis-group-interface.hh"
45 #include "line-interface.hh"
48 #include "output-def.hh"
49 #include "font-interface.hh"
50 #include "text-interface.hh"
52 #include "note-column.hh"
53 #include "pointer-group-interface.hh"
54 #include "directional-element-interface.hh"
57 #include "staff-symbol-referencer.hh"
59 #include "paper-column.hh"
63 get_x_bound_item (Grob *me_grob, Direction hdir, Direction my_dir)
65 Spanner *me = dynamic_cast<Spanner *> (me_grob);
66 Item *g = me->get_bound (hdir);
67 if (Note_column::has_interface (g)
68 && Note_column::get_stem (g)
69 && Note_column::dir (g) == my_dir)
70 g = Note_column::get_stem (g);
76 flatten_number_pair_property (Grob *me, Direction xdir, SCM sym)
78 Drul_array<Real> zero (0, 0);
80 = robust_scm2drul (me->internal_get_property (sym), zero);
83 me->set_property (sym, ly_interval2scm (pair));
87 Return beam that encompasses the span of the tuplet bracket.
90 Tuplet_bracket::parallel_beam (Grob *me_grob, vector<Grob *> const &cols,
93 Spanner *me = dynamic_cast<Spanner *> (me_grob);
95 if (me->get_bound (LEFT)->break_status_dir ()
96 || me->get_bound (RIGHT)->break_status_dir ())
99 Drul_array<Grob *> stems (Note_column::get_stem (cols[0]),
100 Note_column::get_stem (cols.back ()));
104 || (dynamic_cast<Item *> (stems[RIGHT])->get_column ()
105 != me->get_bound (RIGHT)->get_column ()))
108 Drul_array<Grob *> beams;
109 for (LEFT_and_RIGHT (d))
110 beams[d] = stems[d] ? Stem::get_beam (stems[d]) : 0;
112 *equally_long = false;
113 if (! (beams[LEFT] && (beams[LEFT] == beams[RIGHT]) && !me->is_broken ()))
116 extract_grob_set (beams[LEFT], "stems", beam_stems);
117 if (beam_stems.size () == 0)
119 programming_error ("beam under tuplet bracket has no stems");
125 = (beam_stems[0] == stems[LEFT]
126 && beam_stems.back () == stems[RIGHT]);
130 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_connect_to_neighbors, 1);
132 Tuplet_bracket::calc_connect_to_neighbors (SCM smob)
134 Spanner *me = unsmob_spanner (smob);
136 Direction dir = get_grob_direction (me);
137 Drul_array<Item *> bounds (get_x_bound_item (me, LEFT, dir),
138 get_x_bound_item (me, RIGHT, dir));
140 Drul_array<bool> connect_to_other (false, false);
141 for (LEFT_and_RIGHT (d))
143 Direction break_dir = bounds[d]->break_status_dir ();
144 Spanner *orig_spanner = dynamic_cast<Spanner *> (me->original ());
145 vsize neighbor_idx = me->get_break_index () - break_dir;
148 && neighbor_idx < orig_spanner->broken_intos_.size ())
150 Grob *neighbor = orig_spanner->broken_intos_[neighbor_idx];
152 /* trigger possible suicide*/
153 (void) neighbor->get_property ("positions");
158 && neighbor_idx < orig_spanner->broken_intos_.size ()
159 && orig_spanner->broken_intos_[neighbor_idx]->is_live ());
162 if (connect_to_other[LEFT] || connect_to_other[RIGHT])
163 return scm_cons (scm_from_bool (connect_to_other[LEFT]),
164 scm_from_bool (connect_to_other[RIGHT]));
170 Tuplet_bracket::get_common_x (Spanner *me)
172 extract_grob_set (me, "note-columns", columns);
174 Grob *commonx = common_refpoint_of_array (columns, me, X_AXIS);
175 commonx = commonx->common_refpoint (me->get_bound (LEFT), X_AXIS);
176 commonx = commonx->common_refpoint (me->get_bound (RIGHT), X_AXIS);
181 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_x_positions, 1)
183 Tuplet_bracket::calc_x_positions (SCM smob)
185 Spanner *me = unsmob_spanner (smob);
186 extract_grob_set (me, "note-columns", columns);
188 Grob *commonx = get_common_x (me);
189 Direction dir = get_grob_direction (me);
191 Drul_array<Item *> bounds;
192 bounds[LEFT] = get_x_bound_item (me, LEFT, dir);
193 bounds[RIGHT] = get_x_bound_item (me, RIGHT, dir);
195 Drul_array<bool> connect_to_other
196 = robust_scm2booldrul (me->get_property ("connect-to-neighbor"),
197 Drul_array<bool> (false, false));
200 for (LEFT_and_RIGHT (d))
202 x_span[d] = bounds[d]->break_status_dir ()
203 ? Axis_group_interface::generic_bound_extent (bounds[d], commonx, X_AXIS)[d]
204 : robust_relative_extent (bounds[d], commonx, X_AXIS)[d];
205 if (connect_to_other[d])
207 Interval overshoot (robust_scm2drul (me->get_property ("break-overshoot"),
208 Interval (-0.5, 0.0)));
211 x_span[d] += d * overshoot[d];
213 x_span[d] = (bounds[d]->break_status_dir ()
214 ? Axis_group_interface::generic_bound_extent (bounds[d], commonx, X_AXIS)[-d]
215 : robust_relative_extent (bounds[d], commonx, X_AXIS)[-d])
221 || (bounds[d]->get_column ()
222 != dynamic_cast<Item *> (columns.back ())->get_column ())))
225 We're connecting to a column, for the last bit of a broken
229 = robust_scm2double (me->get_property ("full-length-padding"), 1.0);
231 if (bounds[d]->break_status_dir ())
234 Real coord = bounds[d]->relative_coordinate (commonx, X_AXIS);
235 if (to_boolean (me->get_property ("full-length-to-extent")))
236 coord = robust_relative_extent (bounds[d], commonx, X_AXIS)[LEFT];
238 coord = max (coord, x_span[LEFT]);
240 x_span[d] = coord - padding;
244 return ly_interval2scm (x_span - me->get_bound (LEFT)->relative_coordinate (commonx, X_AXIS));
250 in the case that there is no bracket, but there is a (single) beam,
251 follow beam precisely for determining tuplet number location.
253 MAKE_SCHEME_CALLBACK (Tuplet_bracket, print, 1);
255 Tuplet_bracket::print (SCM smob)
257 Spanner *me = unsmob_spanner (smob);
260 extract_grob_set (me, "note-columns", columns);
261 bool equally_long = false;
262 Grob *par_beam = parallel_beam (me, columns, &equally_long);
264 bool bracket_visibility = !(par_beam && equally_long); // Flag, print/don't print tuplet bracket.
266 FIXME: The type of this prop is sucky.
268 SCM bracket_vis_prop = me->get_property ("bracket-visibility");
269 bool bracket_prop = ly_scm2bool (bracket_vis_prop); // Flag, user has set bracket-visibility prop.
270 bool bracket = (bracket_vis_prop == ly_symbol2scm ("if-no-beam"));
271 if (scm_is_bool (bracket_vis_prop))
272 bracket_visibility = bracket_prop;
274 bracket_visibility = !par_beam;
277 Don't print a tuplet bracket and number if
278 no X or Y positions were calculated.
280 SCM scm_x_span = me->get_property ("X-positions");
281 SCM scm_positions = me->get_property ("positions");
282 if (!scm_is_pair (scm_x_span) || !scm_is_pair (scm_positions))
287 /* if the tuplet does not span any time, i.e. a single-note tuplet, hide
288 the bracket, but still let the number be displayed.
289 Only do this if the user has not explicitly specified bracket-visibility = #t.
291 if (!to_boolean (bracket_vis_prop)
292 && (robust_scm2moment (me->get_bound (LEFT)->get_column ()->get_property ("when"), Moment (0))
293 == robust_scm2moment (me->get_bound (RIGHT)->get_column ()->get_property ("when"), Moment (0))))
294 bracket_visibility = false;
296 Interval x_span = robust_scm2interval (scm_x_span, Interval (0.0, 0.0));
297 Interval positions = robust_scm2interval (scm_positions, Interval (0.0, 0.0));
299 Drul_array<Offset> points;
300 for (LEFT_and_RIGHT (d))
301 points[d] = Offset (x_span[d], positions[d]);
303 Output_def *pap = me->layout ();
305 Grob *number_grob = unsmob_grob (me->get_object ("tuplet-number"));
308 Don't print the bracket when it would be smaller than the number.
309 ...Unless the user has coded bracket-visibility = #t, that is.
312 if (bracket_visibility && number_grob)
314 Interval ext = number_grob->extent (number_grob, X_AXIS);
315 if (!ext.is_empty ())
317 gap = ext.length () + 1.0;
319 if ((0.75 * x_span.length () < gap) && !bracket_prop)
320 bracket_visibility = false;
324 if (bracket_visibility)
326 Drul_array<Real> zero (0, 0);
327 Real ss = Staff_symbol_referencer::staff_space (me);
328 Drul_array<Real> height
329 = robust_scm2drul (me->get_property ("edge-height"), zero);
330 Drul_array<Real> flare
331 = robust_scm2drul (me->get_property ("bracket-flare"), zero);
332 Drul_array<Real> shorten
333 = robust_scm2drul (me->get_property ("shorten-pair"), zero);
334 Drul_array<Stencil> edge_stencils;
336 Direction dir = get_grob_direction (me);
338 scale_drul (&height, -ss * dir);
339 scale_drul (&flare, ss);
340 scale_drul (&shorten, ss);
342 Drul_array<bool> connect_to_other
343 = robust_scm2booldrul (me->get_property ("connect-to-neighbor"),
344 Drul_array<bool> (false, false));
346 for (LEFT_and_RIGHT (d))
348 if (connect_to_other[d])
354 SCM edge_text = me->get_property ("edge-text");
356 if (scm_is_pair (edge_text))
358 SCM properties = Font_interface::text_font_alist_chain (me);
359 SCM text = index_get_cell (edge_text, d);
360 if (Text_interface::is_markup (text))
363 = Text_interface::interpret_markup (pap->self_scm (),
366 Stencil *edge_text = unsmob_stencil (t);
367 edge_text->translate_axis (x_span[d] - x_span[LEFT],
369 edge_stencils[d] = *edge_text;
375 Stencil brack = make_bracket (me, Y_AXIS,
376 points[RIGHT] - points[LEFT],
379 0.1 = more space at right due to italics
380 TODO: use italic correction of font.
382 Interval (-0.5, 0.5) * gap + 0.1,
385 for (LEFT_and_RIGHT (d))
387 if (!edge_stencils[d].is_empty ())
388 brack.add_stencil (edge_stencils[d]);
391 mol.add_stencil (brack);
394 mol.translate (points[LEFT]);
395 return mol.smobbed_copy ();
399 should move to lookup?
401 TODO: this will fail for very short (shorter than the flare)
405 Tuplet_bracket::make_bracket (Grob *me, // for line properties.
406 Axis protrusion_axis,
408 Drul_array<Real> height,
410 Drul_array<Real> flare,
411 Drul_array<Real> shorten)
413 Drul_array<Offset> corners (Offset (0, 0), dz);
415 Real length = dz.length ();
416 Drul_array<Offset> gap_corners;
418 Axis bracket_axis = other_axis (protrusion_axis);
420 Drul_array<Offset> straight_corners = corners;
422 for (LEFT_and_RIGHT (d))
423 straight_corners[d] += -d * shorten[d] / length * dz;
425 if (!gap.is_empty ())
427 for (LEFT_and_RIGHT (d))
428 gap_corners[d] = (dz * 0.5) + gap[d] / length * dz;
431 Drul_array<Offset> flare_corners = straight_corners;
432 for (LEFT_and_RIGHT (d))
434 flare_corners[d][bracket_axis] = straight_corners[d][bracket_axis];
435 flare_corners[d][protrusion_axis] += height[d];
436 straight_corners[d][bracket_axis] += -d * flare[d];
440 for (LEFT_and_RIGHT (d))
442 if (!gap.is_empty ())
443 m.add_stencil (Line_interface::line (me, straight_corners[d],
446 m.add_stencil (Line_interface::line (me, straight_corners[d],
451 m.add_stencil (Line_interface::line (me, straight_corners[LEFT],
452 straight_corners[RIGHT]));
458 Tuplet_bracket::get_bounds (Grob *me, Grob **left, Grob **right)
460 extract_grob_set (me, "note-columns", columns);
462 while (l < columns.size () && Note_column::has_rests (columns[l]))
465 vsize r = columns.size ();
466 while (r > l && Note_column::has_rests (columns[r - 1]))
474 *right = columns[r - 1];
479 use first -> last note for slope, and then correct for disturbing
482 Tuplet_bracket::calc_position_and_height (Grob *me_grob, Real *offset, Real *dy)
484 Spanner *me = dynamic_cast<Spanner *> (me_grob);
486 extract_grob_set (me, "note-columns", columns);
487 extract_grob_set (me, "tuplets", tuplets);
489 Grob *commony = common_refpoint_of_array (columns, me, Y_AXIS);
490 commony = common_refpoint_of_array (tuplets, commony, Y_AXIS);
491 if (Grob *st = Staff_symbol_referencer::get_staff_symbol (me))
492 commony = st->common_refpoint (commony, Y_AXIS);
493 Real my_offset = me->relative_coordinate (commony, Y_AXIS);
495 Grob *commonx = get_common_x (me);
496 commonx = common_refpoint_of_array (tuplets, commonx, Y_AXIS);
499 Grob *st = Staff_symbol_referencer::get_staff_symbol (me);
501 /* staff-padding doesn't work correctly on cross-staff tuplets
502 because it only considers one staff symbol. Until this works,
504 if (st && !to_boolean (me->get_property ("cross-staff")))
506 Real pad = robust_scm2double (me->get_property ("staff-padding"), -1.0);
509 staff = st->extent (commony, Y_AXIS) - my_offset;
514 Direction dir = get_grob_direction (me);
516 bool equally_long = false;
517 Grob *par_beam = parallel_beam (me, columns, &equally_long);
519 Item *lgr = get_x_bound_item (me, LEFT, dir);
520 Item *rgr = get_x_bound_item (me, RIGHT, dir);
521 Real x0 = robust_relative_extent (lgr, commonx, X_AXIS)[LEFT];
522 Real x1 = robust_relative_extent (rgr, commonx, X_AXIS)[RIGHT];
523 bool follow_beam = par_beam
524 && get_grob_direction (par_beam) == dir
525 && !to_boolean (par_beam->get_property ("knee"));
527 vector<Offset> points;
530 && Note_column::get_stem (columns[0])
531 && Note_column::get_stem (columns.back ()))
533 Drul_array<Grob *> stems (Note_column::get_stem (columns[0]),
534 Note_column::get_stem (columns.back ()));
537 for (LEFT_and_RIGHT (side))
539 // Trigger setting of stem lengths if necessary.
540 if (Grob *beam = Stem::get_beam (stems[side]))
541 (void) beam->get_property ("quantized-positions");
542 poss[side] = stems[side]->extent (stems[side], Y_AXIS)[get_grob_direction (stems[side])]
543 + stems[side]->get_parent (Y_AXIS)->relative_coordinate (commony, Y_AXIS);
546 *dy = poss[RIGHT] - poss[LEFT];
547 points.push_back (Offset (stems[LEFT]->relative_coordinate (commonx, X_AXIS) - x0, poss[LEFT]));
548 points.push_back (Offset (stems[RIGHT]->relative_coordinate (commonx, X_AXIS) - x0, poss[RIGHT]));
553 Use outer non-rest columns to determine slope
557 get_bounds (me, &left_col, &right_col);
558 if (left_col && right_col)
560 Interval rv = Note_column::cross_staff_extent (right_col, commony);
561 Interval lv = Note_column::cross_staff_extent (left_col, commony);
565 Real graphical_dy = rv[dir] - lv[dir];
567 Slice ls = Note_column::head_positions_interval (left_col);
568 Slice rs = Note_column::head_positions_interval (right_col);
571 musical_dy[UP] = rs[UP] - ls[UP];
572 musical_dy[DOWN] = rs[DOWN] - ls[DOWN];
573 if (sign (musical_dy[UP]) != sign (musical_dy[DOWN]))
575 else if (sign (graphical_dy) != sign (musical_dy[DOWN]))
583 for (vsize i = 0; i < columns.size (); i++)
585 Interval note_ext = Note_column::cross_staff_extent (columns[i],
587 Real x = columns[i]->relative_coordinate (commonx, X_AXIS) - x0;
589 points.push_back (Offset (x, note_ext[dir]));
595 points.push_back (Offset (x0 - x0, staff[dir]));
596 points.push_back (Offset (x1 - x0, staff[dir]));
600 This is a slight hack. We compute two encompass points from the
601 bbox of the smaller tuplets.
603 We assume that the smaller bracket is 1.0 space high.
605 Real ss = Staff_symbol_referencer::staff_space (me);
606 for (vsize i = 0; i < tuplets.size (); i++)
608 Interval tuplet_x (tuplets[i]->extent (commonx, X_AXIS));
609 Interval tuplet_y (tuplets[i]->extent (commony, Y_AXIS));
611 if (!tuplets[i]->is_live ())
614 Drul_array<Real> positions
615 = robust_scm2interval (tuplets[i]->get_property ("positions"),
618 Real other_dy = positions[RIGHT] - positions[LEFT];
620 for (LEFT_and_RIGHT (d))
623 = tuplet_y.linear_combination (d * sign (other_dy));
626 We don't take padding into account for nested tuplets.
627 the edges can come very close to the stems, likewise for
631 points.push_back (Offset (tuplet_x[d] - x0, y));
634 // Check for number-on-bracket collisions
635 Grob *number = unsmob_grob (tuplets[i]->get_object ("tuplet-number"));
637 points.push_back (Offset (number->extent (commonx, X_AXIS).center () - x0,
638 number->extent (commony, Y_AXIS)[dir]));
641 if (to_boolean (me->get_property ("avoid-scripts"))
642 && !scm_is_number (me->get_property ("outside-staff-priority")))
644 extract_grob_set (me, "scripts", scripts);
645 for (vsize i = 0; i < scripts.size (); i++)
647 if (!scripts[i]->is_live ())
649 if (scm_is_number (scripts[i]->get_property ("outside-staff-priority")))
652 // assume that if a script is avoiding slurs, it should not get placed
653 // under a tuplet bracket
654 SCM avoid = scripts[i]->get_property ("avoid-slur");
655 if (unsmob_grob (scripts[i]->get_object ("slur"))
656 && (avoid == ly_symbol2scm ("outside")
657 || avoid == ly_symbol2scm ("around")))
660 Interval script_x (scripts[i]->extent (commonx, X_AXIS));
661 Interval script_y (scripts[i]->extent (commony, Y_AXIS));
663 points.push_back (Offset (script_x.center () - x0,
668 *offset = -dir * infinity_f;
669 Real factor = (columns.size () > 1) ? 1 / (x1 - x0) : 1.0;
670 for (vsize i = 0; i < points.size (); i++)
672 Real x = points[i][X_AXIS];
673 Real tuplety = (*dy) * x * factor + my_offset;
675 if (points[i][Y_AXIS] * dir > (*offset + tuplety) * dir)
676 *offset = points[i][Y_AXIS] - tuplety;
679 *offset += scm_to_double (me->get_property ("padding")) * dir;
682 horizontal brackets should not collide with staff lines.
684 Kind of pointless since we put them outside the staff anyway, but
685 let's leave code for the future when possibly allow them to move
686 into the staff once again.
688 This doesn't seem to support cross-staff tuplets atm.
691 && fabs (*offset) < ss * Staff_symbol_referencer::staff_radius (me))
693 // quantize, then do collision check.
696 *offset = rint (*offset);
697 if (Staff_symbol_referencer::on_line (me, (int) rint (*offset)))
704 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_direction, 1);
706 Tuplet_bracket::calc_direction (SCM smob)
708 Grob *me = unsmob_grob (smob);
709 Direction dir = Tuplet_bracket::get_default_dir (me);
710 return scm_from_int (dir);
713 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_positions, 1);
715 Tuplet_bracket::calc_positions (SCM smob)
717 Spanner *me = unsmob_spanner (smob);
721 calc_position_and_height (me, &offset, &dy);
723 SCM x = scm_cons (scm_from_double (offset),
724 scm_from_double (offset + dy));
733 Tuplet_bracket::get_default_dir (Grob *me)
735 Drul_array<int> dirs (0, 0);
736 extract_grob_set (me, "note-columns", columns);
737 for (vsize i = 0; i < columns.size (); i++)
739 Grob *nc = columns[i];
740 if (Note_column::has_rests (nc))
742 Direction d = Note_column::dir (nc);
747 if (dirs[UP] == dirs[DOWN])
751 Grob *staff = Staff_symbol_referencer::get_staff_symbol (me);
754 Interval staff_extent = staff->extent (staff, Y_AXIS);
755 Interval extremal_positions;
756 extremal_positions.set_empty ();
757 for (vsize i = 0; i < columns.size (); i++)
759 Direction d = Note_column::dir (columns[i]);
760 extremal_positions[d] = minmax (d, 1.0 * Note_column::head_positions_interval (columns[i])[d], extremal_positions[d]);
762 for (LEFT_and_RIGHT (d))
763 extremal_positions[d] = -d * (staff_extent[d] - extremal_positions[d]);
765 return extremal_positions[UP] <= extremal_positions[DOWN] ? UP : DOWN;
768 return dirs[UP] > dirs[DOWN] ? UP : DOWN;
772 Tuplet_bracket::add_column (Grob *me, Item *n)
774 Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-columns"), n);
775 add_bound_item (dynamic_cast<Spanner *> (me), n);
779 Tuplet_bracket::add_script (Grob *me, Item *s)
781 Pointer_group_interface::add_grob (me, ly_symbol2scm ("scripts"), s);
785 Tuplet_bracket::add_tuplet_bracket (Grob *me, Grob *bracket)
787 Pointer_group_interface::add_grob (me, ly_symbol2scm ("tuplets"), bracket);
790 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_cross_staff, 1);
792 Tuplet_bracket::calc_cross_staff (SCM smob)
794 Grob *me = unsmob_grob (smob);
795 extract_grob_set (me, "note-columns", cols);
796 extract_grob_set (me, "tuplets", tuplets);
798 Grob *commony = common_refpoint_of_array (cols, me, Y_AXIS);
799 commony = common_refpoint_of_array (tuplets, commony, Y_AXIS);
800 if (Grob *st = Staff_symbol_referencer::get_staff_symbol (me))
801 commony = st->common_refpoint (commony, Y_AXIS);
802 if (me->check_cross_staff (commony))
805 bool equally_long = false;
806 Grob *par_beam = parallel_beam (me, cols, &equally_long);
808 if (par_beam && to_boolean (par_beam->get_property ("cross-staff")))
811 for (vsize i = 0; i < cols.size (); i++)
813 Grob *stem = unsmob_grob (cols[i]->get_object ("stem"));
814 if (stem && to_boolean (stem->get_property ("cross-staff")))
821 ADD_INTERFACE (Tuplet_bracket,
822 "A bracket with a number in the middle, used for tuplets."
823 " When the bracket spans a line break, the value of"
824 " @code{break-overshoot} determines how far it extends"
825 " beyond the staff. At a line break, the markups in the"
826 " @code{edge-text} are printed at the edges.",
831 "bracket-visibility "
833 "connect-to-neighbor "
837 "full-length-padding "
838 "full-length-to-extent "