Merge branch 'fret-diagram-details'
[lilypond/csorensen.git] / lily / tuplet-bracket.cc
blobdde38da22d9fc984a029a6c6b48ca81a5a79207a
1 /*
2 tuplet-bracket.cc -- implement Tuplet_bracket
4 source file of the GNU LilyPond music typesetter
6 (c) 1997--2007 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 TODO: make padding tunable?
237 Real padding = 1.0;
239 if (bounds[d]->break_status_dir ())
240 padding = 0.0;
242 x_span[d]
243 = robust_relative_extent (bounds[d], commonx, X_AXIS) [LEFT]
244 - padding;
247 while (flip (&d) != LEFT);
251 x_span -= me->get_bound (LEFT)->relative_coordinate (commonx, X_AXIS);
252 return scm_list_2 (ly_offset2scm (Offset (x_span[LEFT], positions[LEFT])),
253 ly_offset2scm (Offset (x_span[RIGHT], positions[RIGHT])));
257 TODO:
259 in the case that there is no bracket, but there is a (single) beam,
260 follow beam precisely for determining tuplet number location.
262 MAKE_SCHEME_CALLBACK (Tuplet_bracket, print, 1);
264 Tuplet_bracket::print (SCM smob)
266 Spanner *me = unsmob_spanner (smob);
267 Stencil mol;
269 extract_grob_set (me, "note-columns", columns);
270 bool equally_long = false;
271 Grob *par_beam = parallel_beam (me, columns, &equally_long);
273 bool bracket_visibility = !(par_beam && equally_long);
275 Fixme: the type of this prop is sucky.
277 SCM bracket = me->get_property ("bracket-visibility");
278 if (scm_is_bool (bracket))
279 bracket_visibility = ly_scm2bool (bracket);
280 else if (bracket == ly_symbol2scm ("if-no-beam"))
281 bracket_visibility = !par_beam;
283 /* Don't print a tuplet bracket and number if no control-points were calculated */
284 SCM cpoints = me->get_property ("control-points");
285 if (scm_ilength (cpoints) < 2)
287 me->suicide ();
288 return SCM_EOL;
290 /* if the tuplet does not span any time, i.e. a single-note tuplet, hide
291 the bracket, but still let the number be displayed */
292 if (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)))
295 bracket_visibility = false;
298 Drul_array<Offset> points;
299 points[LEFT] = ly_scm2offset (scm_car (cpoints));
300 points[RIGHT] = ly_scm2offset (scm_cadr (cpoints));
302 Interval x_span (points[LEFT][X_AXIS], points[RIGHT][X_AXIS]);
303 Drul_array<Real> positions (points[LEFT][Y_AXIS], points[RIGHT][Y_AXIS]);
305 Output_def *pap = me->layout ();
307 Grob *number_grob = unsmob_grob (me->get_object ("tuplet-number"));
310 No bracket when it would be smaller than the number.
312 Real gap = 0.;
313 if (bracket_visibility && number_grob)
315 Interval ext = number_grob->extent (number_grob, X_AXIS);
316 if (!ext.is_empty ())
318 gap = ext.length () + 1.0;
320 if (0.75 * x_span.length () < gap)
321 bracket_visibility = false;
325 if (bracket_visibility)
327 Drul_array<Real> zero (0, 0);
328 Real ss = Staff_symbol_referencer::staff_space (me);
329 Drul_array<Real> height
330 = robust_scm2drul (me->get_property ("edge-height"), zero);
331 Drul_array<Real> flare
332 = robust_scm2drul (me->get_property ("bracket-flare"), zero);
333 Drul_array<Real> shorten
334 = robust_scm2drul (me->get_property ("shorten-pair"), zero);
335 Drul_array<Stencil> edge_stencils;
337 Direction dir = get_grob_direction (me);
339 scale_drul (&height, -ss * dir);
340 scale_drul (&flare, ss);
341 scale_drul (&shorten, ss);
343 Drul_array<bool> connect_to_other =
344 robust_scm2booldrul (me->get_property ("connect-to-neighbor"),
345 Drul_array<bool> (false, false));
347 Direction d = LEFT;
350 if (connect_to_other[d])
352 height[d] = 0.0;
353 flare[d] = 0.0;
354 shorten[d] = 0.0;
356 SCM edge_text = me->get_property ("edge-text");
358 if (scm_is_pair (edge_text))
360 SCM properties = Font_interface::text_font_alist_chain (me);
361 SCM text = index_get_cell (edge_text, d);
362 if (Text_interface::is_markup (text))
364 SCM t = Text_interface::interpret_markup (pap->self_scm (),
365 properties, text);
367 Stencil *edge_text = unsmob_stencil (t);
368 edge_text->translate_axis (x_span[d] - x_span[LEFT], X_AXIS);
369 edge_stencils[d] = *edge_text;
374 while (flip (&d) != LEFT);
376 Stencil brack = make_bracket (me, Y_AXIS,
377 points[RIGHT] - points[LEFT],
378 height,
380 0.1 = more space at right due to italics
381 TODO: use italic correction of font.
383 Interval (-0.5, 0.5) * gap + 0.1,
384 flare, shorten);
388 if (!edge_stencils[d].is_empty ())
389 brack.add_stencil (edge_stencils[d]);
391 while (flip (&d) != LEFT);
393 mol.add_stencil (brack);
396 mol.translate (points[LEFT]);
397 return mol.smobbed_copy ();
401 should move to lookup?
403 TODO: this will fail for very short (shorter than the flare)
404 brackets.
406 Stencil
407 Tuplet_bracket::make_bracket (Grob *me, // for line properties.
408 Axis protusion_axis,
409 Offset dz,
410 Drul_array<Real> height,
411 Interval gap,
412 Drul_array<Real> flare,
413 Drul_array<Real> shorten)
415 Drul_array<Offset> corners (Offset (0, 0), dz);
417 Real length = dz.length ();
418 Drul_array<Offset> gap_corners;
420 Axis bracket_axis = other_axis (protusion_axis);
422 Drul_array<Offset> straight_corners = corners;
424 Direction d = LEFT;
426 straight_corners[d] += -d * shorten[d] / length * dz;
427 while (flip (&d) != LEFT);
429 if (!gap.is_empty ())
432 gap_corners[d] = (dz * 0.5) + gap[d] / length * dz;
433 while (flip (&d) != LEFT);
436 Drul_array<Offset> flare_corners = straight_corners;
439 flare_corners[d][bracket_axis] = straight_corners[d][bracket_axis];
440 flare_corners[d][protusion_axis] += height[d];
441 straight_corners[d][bracket_axis] += -d * flare[d];
443 while (flip (&d) != LEFT);
445 Stencil m;
448 if (!gap.is_empty ())
449 m.add_stencil (Line_interface::line (me, straight_corners[d],
450 gap_corners[d]));
452 m.add_stencil (Line_interface::line (me, straight_corners[d],
453 flare_corners[d]));
456 while (flip (&d) != LEFT);
458 if (gap.is_empty ())
459 m.add_stencil (Line_interface::line (me, straight_corners[LEFT],
460 straight_corners[RIGHT]));
462 return m;
465 void
466 Tuplet_bracket::get_bounds (Grob *me, Grob **left, Grob **right)
468 extract_grob_set (me, "note-columns", columns);
469 vsize l = 0;
470 while (l < columns.size () && Note_column::has_rests (columns[l]))
471 l++;
473 vsize r = columns.size ();
474 while (r > l && Note_column::has_rests (columns[r-1]))
475 r--;
477 *left = *right = 0;
479 if (l < r)
481 *left = columns[l];
482 *right = columns[r-1];
487 use first -> last note for slope, and then correct for disturbing
488 notes in between. */
489 void
490 Tuplet_bracket::calc_position_and_height (Grob *me_grob, Real *offset, Real *dy)
492 Spanner *me = dynamic_cast<Spanner *> (me_grob);
494 extract_grob_set (me, "note-columns", columns);
495 extract_grob_set (me, "tuplets", tuplets);
497 Grob *commony = common_refpoint_of_array (columns, me, Y_AXIS);
498 commony = common_refpoint_of_array (tuplets, commony, Y_AXIS);
499 if (Grob *st = Staff_symbol_referencer::get_staff_symbol (me))
500 commony = st->common_refpoint (commony, Y_AXIS);
501 Real my_offset = me->relative_coordinate (commony, Y_AXIS);
503 Grob *commonx = get_common_x (me);
504 commonx = common_refpoint_of_array (tuplets, commonx, Y_AXIS);
506 Interval staff;
507 Grob *st = Staff_symbol_referencer::get_staff_symbol (me);
509 /* staff-padding doesn't work correctly on cross-staff tuplets
510 because it only considers one staff symbol. Until this works,
511 disable it. */
512 if (st && !to_boolean (me->get_property ("cross-staff")))
514 Real pad = robust_scm2double (me->get_property ("staff-padding"), -1.0);
515 if (pad >= 0.0)
517 staff = st->extent (commony, Y_AXIS) - my_offset;
518 staff.widen (pad);
522 Direction dir = get_grob_direction (me);
524 bool equally_long = false;
525 Grob *par_beam = parallel_beam (me, columns, &equally_long);
527 Item *lgr = get_x_bound_item (me, LEFT, dir);
528 Item *rgr = get_x_bound_item (me, RIGHT, dir);
529 Real x0 = robust_relative_extent (lgr, commonx, X_AXIS)[LEFT];
530 Real x1 = robust_relative_extent (rgr, commonx, X_AXIS)[RIGHT];
531 bool follow_beam = par_beam
532 && get_grob_direction (par_beam) == dir
533 && ! to_boolean (par_beam->get_property ("knee"));
535 vector<Offset> points;
536 if (columns.size ()
537 && follow_beam
538 && Note_column::get_stem (columns[0])
539 && Note_column::get_stem (columns.back ()))
542 trigger set_stem_ends
544 (void) par_beam->get_property ("quantized-positions");
546 Drul_array<Grob *> stems (Note_column::get_stem (columns[0]),
547 Note_column::get_stem (columns.back ()));
549 Real ss = 0.5 * Staff_symbol_referencer::staff_space (me);
550 Real lp = ss * robust_scm2double (stems[LEFT]->get_property ("stem-end-position"), 0.0)
551 + stems[LEFT]->get_parent (Y_AXIS)->relative_coordinate (commony, Y_AXIS);
552 Real rp = ss * robust_scm2double (stems[RIGHT]->get_property ("stem-end-position"), 0.0)
553 + stems[RIGHT]->get_parent (Y_AXIS)->relative_coordinate (commony, Y_AXIS);
555 *dy = rp - lp;
556 points.push_back (Offset (stems[LEFT]->relative_coordinate (commonx, X_AXIS) - x0, lp));
557 points.push_back (Offset (stems[RIGHT]->relative_coordinate (commonx, X_AXIS) - x0, rp));
559 else
562 Use outer non-rest columns to determine slope
564 Grob *left_col = 0;
565 Grob *right_col = 0;
566 get_bounds (me, &left_col, &right_col);
567 if (left_col && right_col)
569 Interval rv = Note_column::cross_staff_extent (right_col, commony);
570 Interval lv = Note_column::cross_staff_extent (left_col, commony);
571 rv.unite (staff);
572 lv.unite (staff);
574 Real graphical_dy = rv[dir] - lv[dir];
576 Slice ls = Note_column::head_positions_interval (left_col);
577 Slice rs = Note_column::head_positions_interval (right_col);
579 Interval musical_dy;
580 musical_dy[UP] = rs[UP] - ls[UP];
581 musical_dy[DOWN] = rs[DOWN] - ls[DOWN];
582 if (sign (musical_dy[UP]) != sign (musical_dy[DOWN]))
583 *dy = 0.0;
584 else if (sign (graphical_dy) != sign (musical_dy[DOWN]))
585 *dy = 0.0;
586 else
587 *dy = graphical_dy;
589 else
590 *dy = 0;
592 for (vsize i = 0; i < columns.size (); i++)
594 Interval note_ext = Note_column::cross_staff_extent (columns[i], commony);
595 Real x = columns[i]->relative_coordinate (commonx, X_AXIS) - x0;
597 points.push_back (Offset (x, note_ext[dir]));
601 if (!follow_beam)
603 points.push_back (Offset (x0 - x0, staff[dir]));
604 points.push_back (Offset (x1 - x0, staff[dir]));
608 This is a slight hack. We compute two encompass points from the
609 bbox of the smaller tuplets.
611 We assume that the smaller bracket is 1.0 space high.
613 Real ss = Staff_symbol_referencer::staff_space (me);
614 for (vsize i = 0; i < tuplets.size (); i++)
616 Interval tuplet_x (tuplets[i]->extent (commonx, X_AXIS));
617 Interval tuplet_y (tuplets[i]->extent (commony, Y_AXIS));
619 if (!tuplets[i]->is_live ())
620 continue;
622 Direction d = LEFT;
623 Drul_array<Real> positions = robust_scm2interval (tuplets[i]->get_property ("positions"),
624 Interval (0,0));
627 Real other_dy = positions[RIGHT] - positions[LEFT];
631 Real y
632 = tuplet_y.linear_combination (d * sign (other_dy));
635 We don't take padding into account for nested tuplets.
636 the edges can come very close to the stems, likewise for
637 nested tuplets?
640 points.push_back (Offset (tuplet_x[d] - x0, y));
642 while (flip (&d) != LEFT);
645 *offset = -dir * infinity_f;
646 Real factor = (columns.size () > 1) ? 1 / (x1 - x0) : 1.0;
647 for (vsize i = 0; i < points.size (); i++)
649 Real x = points[i][X_AXIS];
650 Real tuplety = (*dy) * x * factor + my_offset;
652 if (points[i][Y_AXIS] * dir > (*offset + tuplety) * dir)
653 *offset = points[i][Y_AXIS] - tuplety;
656 *offset += scm_to_double (me->get_property ("padding")) * dir;
659 horizontal brackets should not collide with staff lines.
661 Kind of pointless since we put them outside the staff anyway, but
662 let's leave code for the future when possibly allow them to move
663 into the staff once again.
665 This doesn't seem to support cross-staff tuplets atm.
667 if (*dy == 0
668 && fabs (*offset) < ss * Staff_symbol_referencer::staff_radius (me))
670 // quantize, then do collision check.
671 *offset *= 2 / ss;
673 *offset = rint (*offset);
674 if (Staff_symbol_referencer::on_line (me, (int) rint (*offset)))
675 *offset += dir;
677 *offset *= 0.5 * ss;
682 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_direction, 1);
684 Tuplet_bracket::calc_direction (SCM smob)
686 Grob *me = unsmob_grob (smob);
687 Direction dir = Tuplet_bracket::get_default_dir (me);
688 return scm_from_int (dir);
691 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_positions, 1);
693 Tuplet_bracket::calc_positions (SCM smob)
695 Spanner *me = unsmob_spanner (smob);
697 Real dy = 0.0;
698 Real offset = 0.0;
699 calc_position_and_height (me, &offset, &dy);
701 SCM x = scm_cons (scm_from_double (offset),
702 scm_from_double (offset + dy));
704 return x;
708 similar to beam ?
710 Direction
711 Tuplet_bracket::get_default_dir (Grob *me)
713 Drul_array<int> dirs (0, 0);
714 extract_grob_set (me, "note-columns", columns);
715 for (vsize i = 0; i < columns.size (); i++)
717 Grob *nc = columns[i];
718 Direction d = Note_column::dir (nc);
719 if (d)
720 dirs[d]++;
723 return dirs[UP] >= dirs[DOWN] ? UP : DOWN;
726 void
727 Tuplet_bracket::add_column (Grob *me, Item *n)
729 Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-columns"), n);
730 add_bound_item (dynamic_cast<Spanner *> (me), n);
733 void
734 Tuplet_bracket::add_tuplet_bracket (Grob *me, Grob *bracket)
736 Pointer_group_interface::add_grob (me, ly_symbol2scm ("tuplets"), bracket);
739 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_cross_staff, 1);
741 Tuplet_bracket::calc_cross_staff (SCM smob)
743 Grob *me = unsmob_grob (smob);
744 Grob *staff_symbol = 0;
745 extract_grob_set (me, "note-columns", cols);
746 bool equally_long = false;
747 Grob *par_beam = parallel_beam (me, cols, &equally_long);
749 if (par_beam)
750 return par_beam->get_property ("cross-staff");
752 for (vsize i = 0; i < cols.size (); i++)
754 Grob *stem = unsmob_grob (cols[i]->get_object ("stem"));
755 if (!stem)
756 continue;
758 if (to_boolean (stem->get_property ("cross-staff")))
759 return SCM_BOOL_T;
761 Grob *stem_staff = Staff_symbol_referencer::get_staff_symbol (stem);
762 if (staff_symbol && (stem_staff != staff_symbol))
763 return SCM_BOOL_T;
764 staff_symbol = stem_staff;
766 return SCM_BOOL_F;
769 ADD_INTERFACE (Tuplet_bracket,
770 "A bracket with a number in the middle, used for tuplets."
771 " When the bracket spans a line break, the value of"
772 " @code{break-overshoot} determines how far it extends"
773 " beyond the staff. At a line break, the markups in the"
774 " @code{edge-text} are printed at the edges.",
776 /* properties */
777 "bracket-flare "
778 "bracket-visibility "
779 "break-overshoot "
780 "connect-to-neighbor "
781 "control-points "
782 "direction "
783 "edge-height "
784 "edge-text "
785 "gap "
786 "positions "
787 "note-columns "
788 "padding "
789 "tuplet-number "
790 "shorten-pair "
791 "staff-padding "
792 "thickness "
793 "tuplets "