Nitpick: ly:spanner-bound grob name slur -> spanner.
[lilypond.git] / lily / tuplet-bracket.cc
blob4ddb09245ee236f98a17c5e7cb359f8b8711ffa2
1 /*
2 tuplet-bracket.cc -- implement Tuplet_bracket
4 source file of the GNU LilyPond music typesetter
6 (c) 1997--2009 Jan Nieuwenhuizen <janneke@gnu.org>
7 Han-Wen Nienhuys <hanwen@xs4all.nl>
8 */
11 TODO:
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
22 graphical center.
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"
35 #include "beam.hh"
36 #include "warn.hh"
37 #include "output-def.hh"
38 #include "font-interface.hh"
39 #include "text-interface.hh"
40 #include "stem.hh"
41 #include "note-column.hh"
42 #include "pointer-group-interface.hh"
43 #include "directional-element-interface.hh"
44 #include "spanner.hh"
45 #include "staff-symbol-referencer.hh"
46 #include "lookup.hh"
47 #include "paper-column.hh"
48 #include "moment.hh"
50 static Item *
51 get_x_bound_item (Grob *me_grob, Direction hdir, Direction my_dir)
53 Spanner *me = dynamic_cast<Spanner *> (me_grob);
54 Item *g = me->get_bound (hdir);
55 if (Note_column::has_interface (g)
56 && Note_column::get_stem (g)
57 && Note_column::dir (g) == my_dir)
58 g = Note_column::get_stem (g);
60 return g;
64 void
65 flatten_number_pair_property (Grob *me,
66 Direction xdir, SCM sym)
68 Drul_array<Real> zero (0, 0);
69 Drul_array<Real> pair
70 = robust_scm2drul (me->internal_get_property (sym), zero);
71 pair[xdir] = 0.0;
73 me->set_property (sym, ly_interval2scm (pair));
78 Return beam that encompasses the span of the tuplet bracket.
80 Grob *
81 Tuplet_bracket::parallel_beam (Grob *me_grob, vector<Grob*> const &cols,
82 bool *equally_long)
84 Spanner *me = dynamic_cast<Spanner *> (me_grob);
86 if (me->get_bound (LEFT)->break_status_dir ()
87 || me->get_bound (RIGHT)->break_status_dir ())
88 return 0;
90 Drul_array<Grob*> stems (Note_column::get_stem (cols[0]),
91 Note_column::get_stem (cols.back ()));
93 if (!stems[RIGHT]
94 || !stems[LEFT]
95 || (dynamic_cast<Item*> (stems[RIGHT])->get_column ()
96 != me->get_bound (RIGHT)->get_column ()))
97 return 0;
99 Drul_array<Grob*> beams;
100 Direction d = LEFT;
101 do {
102 beams[d] = stems[d] ? Stem::get_beam (stems[d]) : 0;
103 } while (flip (&d) != LEFT);
105 *equally_long = false;
106 if (! (beams[LEFT] && (beams[LEFT] == beams[RIGHT]) && !me->is_broken ()))
107 return 0;
109 extract_grob_set (beams[LEFT], "stems", beam_stems);
110 if (beam_stems.size () == 0)
112 programming_error ("beam under tuplet bracket has no stems");
113 *equally_long = 0;
114 return 0;
117 *equally_long =
118 (beam_stems[0] == stems[LEFT]
119 && beam_stems.back () == stems[RIGHT]);
120 return beams[LEFT];
124 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_connect_to_neighbors,1);
126 Tuplet_bracket::calc_connect_to_neighbors (SCM smob)
128 Spanner *me = unsmob_spanner (smob);
130 Direction dir = get_grob_direction (me);
131 Drul_array<Item *> bounds (get_x_bound_item (me, LEFT, dir),
132 get_x_bound_item (me, RIGHT, dir));
134 Drul_array<bool> connect_to_other (false, false);
135 Direction d = LEFT;
138 Direction break_dir = bounds[d]->break_status_dir ();
139 Spanner *orig_spanner = dynamic_cast<Spanner*> (me->original ());
140 vsize neighbor_idx = me->get_break_index () - break_dir;
141 if (break_dir
142 && d == RIGHT
143 && neighbor_idx < orig_spanner->broken_intos_.size ())
145 Grob *neighbor = orig_spanner->broken_intos_[neighbor_idx];
147 /* trigger possible suicide*/
148 (void) neighbor->get_property ("positions");
151 connect_to_other[d]
152 = (break_dir
153 && neighbor_idx < orig_spanner->broken_intos_.size ()
154 && orig_spanner->broken_intos_[neighbor_idx]->is_live ());
156 while (flip (&d) != LEFT);
159 if (connect_to_other[LEFT] || connect_to_other[RIGHT])
160 return scm_cons (scm_from_bool (connect_to_other[LEFT]),
161 scm_from_bool (connect_to_other[RIGHT]));
163 return SCM_EOL;
166 Grob*
167 Tuplet_bracket::get_common_x (Spanner *me)
169 extract_grob_set (me, "note-columns", columns);
171 Grob * commonx = common_refpoint_of_array (columns, me, X_AXIS);
172 commonx = commonx->common_refpoint (me->get_bound (LEFT), X_AXIS);
173 commonx = commonx->common_refpoint (me->get_bound (RIGHT), X_AXIS);
175 return commonx;
178 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_control_points,1)
180 Tuplet_bracket::calc_control_points (SCM smob)
182 Spanner *me = unsmob_spanner (smob);
184 extract_grob_set (me, "note-columns", columns);
186 SCM scm_positions = me->get_property ("positions");
187 if (!me->is_live ())
188 return SCM_EOL;
190 if (!scm_is_pair (scm_positions))
191 programming_error ("Positions should be number pair");
193 Drul_array<Real> positions
194 = robust_scm2drul (scm_positions, Drul_array<Real> (0,0));
196 Grob *commonx = get_common_x (me);
197 Direction dir = get_grob_direction (me);
199 Drul_array<Item *> bounds;
200 bounds[LEFT] = get_x_bound_item (me, LEFT, dir);
201 bounds[RIGHT] = get_x_bound_item (me, RIGHT, dir);
203 Drul_array<bool> connect_to_other =
204 robust_scm2booldrul (me->get_property ("connect-to-neighbor"),
205 Drul_array<bool> (false, false));
208 Interval x_span;
209 Direction d = LEFT;
212 x_span[d] = robust_relative_extent (bounds[d], commonx, X_AXIS)[d];
214 if (connect_to_other[d])
216 Interval overshoot (robust_scm2drul (me->get_property ("break-overshoot"),
217 Interval (-0.5, 0.0)));
219 if (d == RIGHT)
220 x_span[d] += d * overshoot[d];
221 else
222 x_span[d] = robust_relative_extent (bounds[d], commonx, X_AXIS)[RIGHT]
223 - overshoot[LEFT];
226 else if (d == RIGHT
227 && (columns.empty ()
228 || (bounds[d]->get_column ()
229 != dynamic_cast<Item *> (columns.back ())->get_column ())))
232 We're connecting to a column, for the last bit of a broken
233 fullLength bracket.
235 Real padding =
236 robust_scm2double(me->get_property("full-length-padding"), 1.0);
238 if (bounds[d]->break_status_dir ())
239 padding = 0.0;
241 Real coord = bounds[d]->relative_coordinate(commonx, X_AXIS);
242 if (to_boolean (me->get_property ("full-length-to-extent")))
243 coord = robust_relative_extent(bounds[d], commonx, X_AXIS)[LEFT];
245 coord = max (coord, x_span[LEFT]);
247 x_span[d] = coord - padding;
250 while (flip (&d) != LEFT);
254 x_span -= me->get_bound (LEFT)->relative_coordinate (commonx, X_AXIS);
255 return scm_list_2 (ly_offset2scm (Offset (x_span[LEFT], positions[LEFT])),
256 ly_offset2scm (Offset (x_span[RIGHT], positions[RIGHT])));
260 TODO:
262 in the case that there is no bracket, but there is a (single) beam,
263 follow beam precisely for determining tuplet number location.
265 MAKE_SCHEME_CALLBACK (Tuplet_bracket, print, 1);
267 Tuplet_bracket::print (SCM smob)
269 Spanner *me = unsmob_spanner (smob);
270 Stencil mol;
272 extract_grob_set (me, "note-columns", columns);
273 bool equally_long = false;
274 Grob *par_beam = parallel_beam (me, columns, &equally_long);
276 bool bracket_visibility = !(par_beam && equally_long);
278 Fixme: the type of this prop is sucky.
280 SCM bracket = me->get_property ("bracket-visibility");
281 if (scm_is_bool (bracket))
282 bracket_visibility = ly_scm2bool (bracket);
283 else if (bracket == ly_symbol2scm ("if-no-beam"))
284 bracket_visibility = !par_beam;
286 /* Don't print a tuplet bracket and number if no control-points were calculated */
287 SCM cpoints = me->get_property ("control-points");
288 if (scm_ilength (cpoints) < 2)
290 me->suicide ();
291 return SCM_EOL;
293 /* if the tuplet does not span any time, i.e. a single-note tuplet, hide
294 the bracket, but still let the number be displayed */
295 if (robust_scm2moment (me->get_bound (LEFT)->get_column ()->get_property ("when"), Moment (0))
296 == robust_scm2moment (me->get_bound (RIGHT)->get_column ()->get_property ("when"), Moment (0)))
298 bracket_visibility = false;
301 Drul_array<Offset> points;
302 points[LEFT] = ly_scm2offset (scm_car (cpoints));
303 points[RIGHT] = ly_scm2offset (scm_cadr (cpoints));
305 Interval x_span (points[LEFT][X_AXIS], points[RIGHT][X_AXIS]);
306 Drul_array<Real> positions (points[LEFT][Y_AXIS], points[RIGHT][Y_AXIS]);
308 Output_def *pap = me->layout ();
310 Grob *number_grob = unsmob_grob (me->get_object ("tuplet-number"));
313 No bracket when it would be smaller than the number.
315 Real gap = 0.;
316 if (bracket_visibility && number_grob)
318 Interval ext = number_grob->extent (number_grob, X_AXIS);
319 if (!ext.is_empty ())
321 gap = ext.length () + 1.0;
323 if (0.75 * x_span.length () < gap)
324 bracket_visibility = false;
328 if (bracket_visibility)
330 Drul_array<Real> zero (0, 0);
331 Real ss = Staff_symbol_referencer::staff_space (me);
332 Drul_array<Real> height
333 = robust_scm2drul (me->get_property ("edge-height"), zero);
334 Drul_array<Real> flare
335 = robust_scm2drul (me->get_property ("bracket-flare"), zero);
336 Drul_array<Real> shorten
337 = robust_scm2drul (me->get_property ("shorten-pair"), zero);
338 Drul_array<Stencil> edge_stencils;
340 Direction dir = get_grob_direction (me);
342 scale_drul (&height, -ss * dir);
343 scale_drul (&flare, ss);
344 scale_drul (&shorten, ss);
346 Drul_array<bool> connect_to_other =
347 robust_scm2booldrul (me->get_property ("connect-to-neighbor"),
348 Drul_array<bool> (false, false));
350 Direction d = LEFT;
353 if (connect_to_other[d])
355 height[d] = 0.0;
356 flare[d] = 0.0;
357 shorten[d] = 0.0;
359 SCM edge_text = me->get_property ("edge-text");
361 if (scm_is_pair (edge_text))
363 SCM properties = Font_interface::text_font_alist_chain (me);
364 SCM text = index_get_cell (edge_text, d);
365 if (Text_interface::is_markup (text))
367 SCM t = Text_interface::interpret_markup (pap->self_scm (),
368 properties, text);
370 Stencil *edge_text = unsmob_stencil (t);
371 edge_text->translate_axis (x_span[d] - x_span[LEFT], X_AXIS);
372 edge_stencils[d] = *edge_text;
377 while (flip (&d) != LEFT);
379 Stencil brack = make_bracket (me, Y_AXIS,
380 points[RIGHT] - points[LEFT],
381 height,
383 0.1 = more space at right due to italics
384 TODO: use italic correction of font.
386 Interval (-0.5, 0.5) * gap + 0.1,
387 flare, shorten);
391 if (!edge_stencils[d].is_empty ())
392 brack.add_stencil (edge_stencils[d]);
394 while (flip (&d) != LEFT);
396 mol.add_stencil (brack);
399 mol.translate (points[LEFT]);
400 return mol.smobbed_copy ();
404 should move to lookup?
406 TODO: this will fail for very short (shorter than the flare)
407 brackets.
409 Stencil
410 Tuplet_bracket::make_bracket (Grob *me, // for line properties.
411 Axis protusion_axis,
412 Offset dz,
413 Drul_array<Real> height,
414 Interval gap,
415 Drul_array<Real> flare,
416 Drul_array<Real> shorten)
418 Drul_array<Offset> corners (Offset (0, 0), dz);
420 Real length = dz.length ();
421 Drul_array<Offset> gap_corners;
423 Axis bracket_axis = other_axis (protusion_axis);
425 Drul_array<Offset> straight_corners = corners;
427 Direction d = LEFT;
429 straight_corners[d] += -d * shorten[d] / length * dz;
430 while (flip (&d) != LEFT);
432 if (!gap.is_empty ())
435 gap_corners[d] = (dz * 0.5) + gap[d] / length * dz;
436 while (flip (&d) != LEFT);
439 Drul_array<Offset> flare_corners = straight_corners;
442 flare_corners[d][bracket_axis] = straight_corners[d][bracket_axis];
443 flare_corners[d][protusion_axis] += height[d];
444 straight_corners[d][bracket_axis] += -d * flare[d];
446 while (flip (&d) != LEFT);
448 Stencil m;
451 if (!gap.is_empty ())
452 m.add_stencil (Line_interface::line (me, straight_corners[d],
453 gap_corners[d]));
455 m.add_stencil (Line_interface::line (me, straight_corners[d],
456 flare_corners[d]));
459 while (flip (&d) != LEFT);
461 if (gap.is_empty ())
462 m.add_stencil (Line_interface::line (me, straight_corners[LEFT],
463 straight_corners[RIGHT]));
465 return m;
468 void
469 Tuplet_bracket::get_bounds (Grob *me, Grob **left, Grob **right)
471 extract_grob_set (me, "note-columns", columns);
472 vsize l = 0;
473 while (l < columns.size () && Note_column::has_rests (columns[l]))
474 l++;
476 vsize r = columns.size ();
477 while (r > l && Note_column::has_rests (columns[r-1]))
478 r--;
480 *left = *right = 0;
482 if (l < r)
484 *left = columns[l];
485 *right = columns[r-1];
490 use first -> last note for slope, and then correct for disturbing
491 notes in between. */
492 void
493 Tuplet_bracket::calc_position_and_height (Grob *me_grob, Real *offset, Real *dy)
495 Spanner *me = dynamic_cast<Spanner *> (me_grob);
497 extract_grob_set (me, "note-columns", columns);
498 extract_grob_set (me, "tuplets", tuplets);
500 Grob *commony = common_refpoint_of_array (columns, me, Y_AXIS);
501 commony = common_refpoint_of_array (tuplets, commony, Y_AXIS);
502 if (Grob *st = Staff_symbol_referencer::get_staff_symbol (me))
503 commony = st->common_refpoint (commony, Y_AXIS);
504 Real my_offset = me->relative_coordinate (commony, Y_AXIS);
506 Grob *commonx = get_common_x (me);
507 commonx = common_refpoint_of_array (tuplets, commonx, Y_AXIS);
509 Interval staff;
510 Grob *st = Staff_symbol_referencer::get_staff_symbol (me);
512 /* staff-padding doesn't work correctly on cross-staff tuplets
513 because it only considers one staff symbol. Until this works,
514 disable it. */
515 if (st && !to_boolean (me->get_property ("cross-staff")))
517 Real pad = robust_scm2double (me->get_property ("staff-padding"), -1.0);
518 if (pad >= 0.0)
520 staff = st->extent (commony, Y_AXIS) - my_offset;
521 staff.widen (pad);
525 Direction dir = get_grob_direction (me);
527 bool equally_long = false;
528 Grob *par_beam = parallel_beam (me, columns, &equally_long);
530 Item *lgr = get_x_bound_item (me, LEFT, dir);
531 Item *rgr = get_x_bound_item (me, RIGHT, dir);
532 Real x0 = robust_relative_extent (lgr, commonx, X_AXIS)[LEFT];
533 Real x1 = robust_relative_extent (rgr, commonx, X_AXIS)[RIGHT];
534 bool follow_beam = par_beam
535 && get_grob_direction (par_beam) == dir
536 && ! to_boolean (par_beam->get_property ("knee"));
538 vector<Offset> points;
539 if (columns.size ()
540 && follow_beam
541 && Note_column::get_stem (columns[0])
542 && Note_column::get_stem (columns.back ()))
545 trigger set_stem_ends
547 (void) par_beam->get_property ("quantized-positions");
549 Drul_array<Grob *> stems (Note_column::get_stem (columns[0]),
550 Note_column::get_stem (columns.back ()));
552 Real ss = 0.5 * Staff_symbol_referencer::staff_space (me);
553 Real lp = ss * robust_scm2double (stems[LEFT]->get_property ("stem-end-position"), 0.0)
554 + stems[LEFT]->get_parent (Y_AXIS)->relative_coordinate (commony, Y_AXIS);
555 Real rp = ss * robust_scm2double (stems[RIGHT]->get_property ("stem-end-position"), 0.0)
556 + stems[RIGHT]->get_parent (Y_AXIS)->relative_coordinate (commony, Y_AXIS);
558 *dy = rp - lp;
559 points.push_back (Offset (stems[LEFT]->relative_coordinate (commonx, X_AXIS) - x0, lp));
560 points.push_back (Offset (stems[RIGHT]->relative_coordinate (commonx, X_AXIS) - x0, rp));
562 else
565 Use outer non-rest columns to determine slope
567 Grob *left_col = 0;
568 Grob *right_col = 0;
569 get_bounds (me, &left_col, &right_col);
570 if (left_col && right_col)
572 Interval rv = Note_column::cross_staff_extent (right_col, commony);
573 Interval lv = Note_column::cross_staff_extent (left_col, commony);
574 rv.unite (staff);
575 lv.unite (staff);
577 Real graphical_dy = rv[dir] - lv[dir];
579 Slice ls = Note_column::head_positions_interval (left_col);
580 Slice rs = Note_column::head_positions_interval (right_col);
582 Interval musical_dy;
583 musical_dy[UP] = rs[UP] - ls[UP];
584 musical_dy[DOWN] = rs[DOWN] - ls[DOWN];
585 if (sign (musical_dy[UP]) != sign (musical_dy[DOWN]))
586 *dy = 0.0;
587 else if (sign (graphical_dy) != sign (musical_dy[DOWN]))
588 *dy = 0.0;
589 else
590 *dy = graphical_dy;
592 else
593 *dy = 0;
595 for (vsize i = 0; i < columns.size (); i++)
597 Interval note_ext = Note_column::cross_staff_extent (columns[i], commony);
598 Real x = columns[i]->relative_coordinate (commonx, X_AXIS) - x0;
600 points.push_back (Offset (x, note_ext[dir]));
604 if (!follow_beam)
606 points.push_back (Offset (x0 - x0, staff[dir]));
607 points.push_back (Offset (x1 - x0, staff[dir]));
611 This is a slight hack. We compute two encompass points from the
612 bbox of the smaller tuplets.
614 We assume that the smaller bracket is 1.0 space high.
616 Real ss = Staff_symbol_referencer::staff_space (me);
617 for (vsize i = 0; i < tuplets.size (); i++)
619 Interval tuplet_x (tuplets[i]->extent (commonx, X_AXIS));
620 Interval tuplet_y (tuplets[i]->extent (commony, Y_AXIS));
622 if (!tuplets[i]->is_live ())
623 continue;
625 Direction d = LEFT;
626 Drul_array<Real> positions = robust_scm2interval (tuplets[i]->get_property ("positions"),
627 Interval (0,0));
630 Real other_dy = positions[RIGHT] - positions[LEFT];
634 Real y
635 = tuplet_y.linear_combination (d * sign (other_dy));
638 We don't take padding into account for nested tuplets.
639 the edges can come very close to the stems, likewise for
640 nested tuplets?
643 points.push_back (Offset (tuplet_x[d] - x0, y));
645 while (flip (&d) != LEFT);
648 *offset = -dir * infinity_f;
649 Real factor = (columns.size () > 1) ? 1 / (x1 - x0) : 1.0;
650 for (vsize i = 0; i < points.size (); i++)
652 Real x = points[i][X_AXIS];
653 Real tuplety = (*dy) * x * factor + my_offset;
655 if (points[i][Y_AXIS] * dir > (*offset + tuplety) * dir)
656 *offset = points[i][Y_AXIS] - tuplety;
659 *offset += scm_to_double (me->get_property ("padding")) * dir;
662 horizontal brackets should not collide with staff lines.
664 Kind of pointless since we put them outside the staff anyway, but
665 let's leave code for the future when possibly allow them to move
666 into the staff once again.
668 This doesn't seem to support cross-staff tuplets atm.
670 if (*dy == 0
671 && fabs (*offset) < ss * Staff_symbol_referencer::staff_radius (me))
673 // quantize, then do collision check.
674 *offset *= 2 / ss;
676 *offset = rint (*offset);
677 if (Staff_symbol_referencer::on_line (me, (int) rint (*offset)))
678 *offset += dir;
680 *offset *= 0.5 * ss;
685 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_direction, 1);
687 Tuplet_bracket::calc_direction (SCM smob)
689 Grob *me = unsmob_grob (smob);
690 Direction dir = Tuplet_bracket::get_default_dir (me);
691 return scm_from_int (dir);
694 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_positions, 1);
696 Tuplet_bracket::calc_positions (SCM smob)
698 Spanner *me = unsmob_spanner (smob);
700 Real dy = 0.0;
701 Real offset = 0.0;
702 calc_position_and_height (me, &offset, &dy);
704 SCM x = scm_cons (scm_from_double (offset),
705 scm_from_double (offset + dy));
707 return x;
711 similar to beam ?
713 Direction
714 Tuplet_bracket::get_default_dir (Grob *me)
716 Drul_array<int> dirs (0, 0);
717 extract_grob_set (me, "note-columns", columns);
718 for (vsize i = 0; i < columns.size (); i++)
720 Grob *nc = columns[i];
721 Direction d = Note_column::dir (nc);
722 if (d)
723 dirs[d]++;
726 return dirs[UP] >= dirs[DOWN] ? UP : DOWN;
729 void
730 Tuplet_bracket::add_column (Grob *me, Item *n)
732 Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-columns"), n);
733 add_bound_item (dynamic_cast<Spanner *> (me), n);
736 void
737 Tuplet_bracket::add_tuplet_bracket (Grob *me, Grob *bracket)
739 Pointer_group_interface::add_grob (me, ly_symbol2scm ("tuplets"), bracket);
742 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_cross_staff, 1);
744 Tuplet_bracket::calc_cross_staff (SCM smob)
746 Grob *me = unsmob_grob (smob);
747 Grob *staff_symbol = 0;
748 extract_grob_set (me, "note-columns", cols);
749 bool equally_long = false;
750 Grob *par_beam = parallel_beam (me, cols, &equally_long);
752 if (par_beam)
753 return par_beam->get_property ("cross-staff");
755 for (vsize i = 0; i < cols.size (); i++)
757 Grob *stem = unsmob_grob (cols[i]->get_object ("stem"));
758 if (!stem)
759 continue;
761 if (to_boolean (stem->get_property ("cross-staff")))
762 return SCM_BOOL_T;
764 Grob *stem_staff = Staff_symbol_referencer::get_staff_symbol (stem);
765 if (staff_symbol && (stem_staff != staff_symbol))
766 return SCM_BOOL_T;
767 staff_symbol = stem_staff;
769 return SCM_BOOL_F;
772 ADD_INTERFACE (Tuplet_bracket,
773 "A bracket with a number in the middle, used for tuplets."
774 " When the bracket spans a line break, the value of"
775 " @code{break-overshoot} determines how far it extends"
776 " beyond the staff. At a line break, the markups in the"
777 " @code{edge-text} are printed at the edges.",
779 /* properties */
780 "bracket-flare "
781 "bracket-visibility "
782 "break-overshoot "
783 "connect-to-neighbor "
784 "control-points "
785 "direction "
786 "edge-height "
787 "edge-text "
788 "full-length-padding "
789 "full-length-to-extent "
790 "gap "
791 "positions "
792 "note-columns "
793 "padding "
794 "tuplet-number "
795 "shorten-pair "
796 "staff-padding "
797 "thickness "
798 "tuplets "