Until now, LyricTexts and DynamicTexts had their X-parents set to
the first NoteHead in the NoteColumn. This resulted in inconsistent
alignment - placement of lyrics and dynamics depended on the order
of notes in the input:
% this was aligned differently
{ <f' g'>1\p <g' f'>\p }
\addlyrics { la la }
By using NoteColumns themselves as the X-parents, we make sure that
the input order won't matter. Since the NoteColumn contains all NoteHeads
(including suspended ones, which usually should be ignored when aligning),
as well as Flags and some other objects, we cannot use its X-extent directly -
instead, we add a function for calculating X-extent of the "main" part of the
NoteColumn, i.e. X-extent of the non-suspended NoteHeads (represented by the
NoteHead furthest away from the stem).
--- /dev/null
+\version "2.19.9"
+
+\header {
+ texidoc = "Alignment of lyrics, dynamics, textscripts and articulations
+attached to chords with suspended notes doesn't depend on input order.
+All these items are aligned on the \"main\" notehead (the one at the
+end of the stem)."
+}
+
+<<
+ \new Staff {
+ <b' c''>2 s
+ <b' c''>\f s
+ <b' c''>^"Text" s
+ <b' c''>-! s
+ }
+ \addlyrics { blah }
+ \new Staff {
+ <c'' b'>2 s
+ <c'' b'>\f s
+ <c'' b'>^"Text" s
+ <c'' b'>-! s
+ }
+ \addlyrics { blah }
+>>
{
extract_grob_set (info.grob (), "note-heads", heads);
/*
{
extract_grob_set (info.grob (), "note-heads", heads);
/*
- Spacing constraints may require dynamics to be aligned on rests,
+ Spacing constraints may require dynamics to be attached to rests,
so check for a rest if this note column has no note heads.
*/
Grob *x_parent = (heads.size ()
so check for a rest if this note column has no note heads.
*/
Grob *x_parent = (heads.size ()
: unsmob_grob (info.grob ()->get_object ("rest")));
if (x_parent)
script_->set_parent (x_parent, X_AXIS);
: unsmob_grob (info.grob ()->get_object ("rest")));
if (x_parent)
script_->set_parent (x_parent, X_AXIS);
static Grob *accidentals (Grob *me);
static Slice head_positions_interval (Grob *me);
static Grob *first_head (Grob *me);
static Grob *accidentals (Grob *me);
static Slice head_positions_interval (Grob *me);
static Grob *first_head (Grob *me);
+ static Interval calc_main_heads_extent (Grob *me);
static Grob *get_rest (Grob *me);
static void set_stem (Grob *me, Grob *);
static void add_head (Grob *me, Grob *);
static Grob *get_rest (Grob *me);
static void set_stem (Grob *me, Grob *);
static void add_head (Grob *me, Grob *);
- text_->set_parent (head, X_AXIS);
+ text_->set_parent (head->get_parent(X_AXIS), X_AXIS);
if (melisma_busy (voice)
&& !to_boolean (get_property ("ignoreMelismata")))
text_->set_property ("self-alignment-X",
if (melisma_busy (voice)
&& !to_boolean (get_property ("ignoreMelismata")))
text_->set_property ("self-alignment-X",
return st ? Stem::first_head (st) : 0;
}
return st ? Stem::first_head (st) : 0;
}
+/*
+ Return extent of the noteheads in the "main column",
+ i.e. excluding any suspended noteheads.
+*/
+Interval
+Note_column::calc_main_heads_extent (Grob *me)
+{
+ if (get_stem (me))
+ return first_head (me)->extent (me, X_AXIS);
+ else
+ {
+ // no stems => no suspended noteheads.
+ extract_grob_set (me, "note-heads", heads);
+ if (heads.size())
+ return heads[0]->extent (me, X_AXIS);
+ else
+ return Interval (0, 0);
+ }
+}
+
/*
Return the first AccidentalPlacement grob that we find in a note-head.
*/
/*
Return the first AccidentalPlacement grob that we find in a note-head.
*/
he = Paper_column::get_interface_extent
(him, ly_symbol2scm ("note-column-interface"), a);
else
he = Paper_column::get_interface_extent
(him, ly_symbol2scm ("note-column-interface"), a);
else
- he = him->extent (him, a);
+ {
+ if (ly_scm2bool(me->internal_get_property (ly_symbol2scm ("X-align-on-main-noteheads")))
+ && Note_column::has_interface (him))
+ he = Note_column::calc_main_heads_extent(him);
+ else
+ he = him->extent (him, a);
+ }
SCM sym = (a == X_AXIS) ? ly_symbol2scm ("self-alignment-X")
: ly_symbol2scm ("self-alignment-Y");
SCM sym = (a == X_AXIS) ? ly_symbol2scm ("self-alignment-X")
: ly_symbol2scm ("self-alignment-Y");
/* properties */
"self-alignment-X "
"self-alignment-Y "
/* properties */
"self-alignment-X "
"self-alignment-Y "
+ "X-align-on-main-noteheads "
+ (X-align-on-main-noteheads ,boolean? "If true, this grob will
+ignore suspended noteheads when aligning itself on NoteColumn.")
(X-extent ,number-pair? "Extent (size) in the X@tie{}direction,
measured in staff-space units, relative to object's reference point.")
(X-offset ,number? "The horizontal amount that this object is
(X-extent ,number-pair? "Extent (size) in the X@tie{}direction,
measured in staff-space units, relative to object's reference point.")
(X-offset ,number? "The horizontal amount that this object is
(stencil . ,ly:text-interface::print)
(vertical-skylines . ,grob::always-vertical-skylines-from-stencil)
(Y-extent . ,grob::always-Y-extent-from-stencil)
(stencil . ,ly:text-interface::print)
(vertical-skylines . ,grob::always-vertical-skylines-from-stencil)
(Y-extent . ,grob::always-Y-extent-from-stencil)
+ (X-align-on-main-noteheads . #t)
(X-offset . ,ly:self-alignment-interface::aligned-on-x-parent)
(Y-offset . ,(scale-by-font-size -0.6)) ; center on an 'm'
(meta . ((class . Item)
(X-offset . ,ly:self-alignment-interface::aligned-on-x-parent)
(Y-offset . ,(scale-by-font-size -0.6)) ; center on an 'm'
(meta . ((class . Item)
(word-space . 0.6)
(skyline-horizontal-padding . 0.1)
(vertical-skylines . ,grob::always-vertical-skylines-from-stencil)
(word-space . 0.6)
(skyline-horizontal-padding . 0.1)
(vertical-skylines . ,grob::always-vertical-skylines-from-stencil)
+ (X-align-on-main-noteheads . #t)
(X-offset . ,ly:self-alignment-interface::aligned-on-x-parent)
(Y-extent . ,grob::always-Y-extent-from-stencil)
(meta . ((class . Item)
(X-offset . ,ly:self-alignment-interface::aligned-on-x-parent)
(Y-extent . ,grob::always-Y-extent-from-stencil)
(meta . ((class . Item)