Support stacked (multi-level) lyric lines in GABC syllables - #1765
Open
lbssousa wants to merge 19 commits into
Open
Support stacked (multi-level) lyric lines in GABC syllables#1765lbssousa wants to merge 19 commits into
lbssousa wants to merge 19 commits into
Conversation
Recognize | as a new lyric-line separator (LYRIC_CUT), and accumulate what follows each one into a gregorio_lyric_line list (extra_lyrics) attached to the syllable, alongside the existing level-1 text. A leading space on a line marks the start of a new word there.
Give each extra lyric line its own running word-boundary state, closed whenever a syllable's stack doesn't reach that level (it's shallower there, or has no stack at all — even a syllable with no text on any level, such as a bare divisio) or when a line explicitly starts a new word with a leading space. A level that is present but left empty does not close its word: the word just continues on the next syllable that carries text there. close_stacked_word_at_level marks the end on word_end_line, the most recent line with actual text at that level, since the syllable that detects the level as unreached may be several syllables past the one that last carried text on it.
Mirror the main line's post-parse rebuild — forced-center detection, character rebuilding, and first-word styling — for each extra lyric line, so every level of a stack gets its own centering independent of the others.
Drop the extra_lyrics parameter from gregorio_add_syllable and assign it directly on the new syllable afterward, the same way the parser already sets translation centering. Keeps the constructor's parameter list from growing a third character-ish parameter next to first_character and first_translation_character, which would be easy to transpose by accident.
Write each extra lyric line back as |text, emitting a leading space only where the previous syllable actually carried that same line. That's the one case where omitting the space would make the round trip read as a word continuing instead of a new one starting. A syllable with no text at all already closes every stacked word on its own, so there's no need to look further back than the immediately preceding syllable.
Write each extra lyric line as a \GreWriteLyricLine call inside the syllable's eighth argument, alongside the translation. Extracted write_text_pair, the "text or empty braces" pattern write_text already had, so the two share it instead of duplicating it. Styles are always written in full for extra lines: the fixed-style optimization that write_text otherwise applies only concerns the main lyric line.
Give each additional lyric line (level 2+) its own entry in syllables[sid].levels, populated by scan_syllables from the box \GreWriteLyricLine emits and by set_lyric_line_dash (called from that same macro) with whether the line ends a word here. level_edges finds a level's text within its zero-width outer box, for the spacing and hyphenation logic that consumes this data.
Each additional lyric line has its own word position, independent of the main line, so give it its own minimum inter-syllable distance in adjust_syllablefinalskip (interwordspacetext where the line ends a word here, none otherwise). add_level_hyphen mirrors add_hyphen for a stacked line's zero-width outer box. syllable_spacing hyphenates every line the same way it already hyphenates the main one, iterating to a fixed point since widening one line can open a gap elsewhere that then needs its own hyphen.
Declare the vertical separation between two stacked lyric lines as a scalable dimension, following the font-relative style already used by spacelinestext, and register it as a valid target for \grechangenextscorelinedim alongside the other per-line distances.
This is the entry point GregorioTeX emits for each additional lyric line (level 2+) of a syllable, invoked from the same argument of \GreSyllable that writes the main line. It mirrors the main line's centering logic (syllable/vowel/first-letter, or a forced center), then raises the result by lyricstackseparation times the level's depth and tags the box with \gre@attrid@part so gregoriotex-syllable.lua can find it via scan_syllables.
… end compute_line_statistics now tracks max_lyric_level, the deepest additional lyric line present anywhere in a system, using the same part_lyric_line_base offset gregoriotex-syllable.lua already tags those boxes with. adjust_additional_spaces turns that into lyric_stack_extra, the depth the stack needs beyond the main lyrics line, and lowers the translation (and shifts each stacked line's own box) accordingly. everything_raise deliberately excludes lyric_stack_extra: the deepest line is already positioned by its own \GreWriteLyricLine raise, so counting that distance again there would shift the whole system instead of just growing its depth. add_eol_hyphen gives each stacked line's last syllable the same end-of-line hyphen treatment as the main line, since a word can continue past the line break independently on any level.
adjust_glue's baselineskip/lineskip choice is driven by the previous line's depth, but that depth may include lyric_stack_extra — room the stacked lyrics genuinely need, not tall notation baselineskip already budgets for. Counting it in the lineskiplimit check could tip an otherwise-normal transition into \lineskip and collapse the gap. forgive_depth carves that portion out of the check while still adding it back into the glue's width, so only the stack's own extra depth is reserved. adjust_additional_spaces now returns its lyric_stack_extra, and post_linebreak threads it through as prev_stack_extra for the next line's adjust_glue call.
lbssousa
marked this pull request as ready for review
July 31, 2026 11:43
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #374.
Adds support for multiple stacked lines of lyrics under a single line of
notes, written by separating the lines with
|inside a syllable's text:The text before the first
|is the main lyric line. Each subsequent|starts a new line further below the staff. This is meant for cases like litanies,
psalm tones, or any score where several verses/invocations share the same
melody and are traditionally printed stacked under one staff instead of
repeating the notes for each one.
This proposed syntax is heavily based on the one introduced in
Source & Summit online GABC editor.
Syntax rules
following the normal centering rules (including forced centering with
{}, usable separately per line).following the same rule as the main line: a leading space starts a new
word. Without one, two consecutive syllables' lines are the same word,
even where the main line's word already ended. A word only ends where
the following syllable's stack doesn't reach that deep at all (a plain
syllable, or a shallower stack). A line that's present but
deliberately left empty (
ca||ci(h), or a trailing empty line at astack's end) does not end the word, it continues on the next syllable
that carries text there.
|is now reserved in syllable text; a literal pipe must be escaped as$|. GABC files written by Gregorio itself already escape|, soregenerated files are unaffected.