Skip to content

Fix #186 — honour the §11.4.2 font directives - #202

Merged
sbeitzel merged 1 commit into
developfrom
feature/fix_186
Oct 3, 2026
Merged

sbeitzel merged 1 commit into
developfrom
feature/fix_186

Conversation

@sbeitzel

@sbeitzel sbeitzel commented Oct 3, 2026

Copy link
Copy Markdown
Owner

Fix #186

Decisions (from the discussion on this issue)

  • Defaults: today's look. Libertinus Serif at the current sizes, so a document with no font directive renders byte-identically.
  • Scale: text drawn with the staff (chord symbols, annotations, lyrics, a tempo marking inside the music) scales with %%ceolkit:scale. Page text (title, subtitle, composer, info, header tempo, W: words) is in absolute points.
  • Bold: Libertinus Serif Bold and Bold Italic are bundled, so bold requests render bold with no system fonts.
  • House style: a new SVGRenderConfig.textFonts: [TextFontRole: FontSpec] sits under the document's directives. For example, "everything default, but composers in Zapf Chancery"; a %%composerfont in the ABC still wins.
  • Severity: a substitution is an info note while system lookup is off. It is a warning only when the system was searched and nothing matched.

What changed

Model / parser

  • New TextFontRole (one case per §11.4.2 directive) and FontSpec(name?, size?), carried as CeolKitDirective.font(role, spec).
  • Scoping: in the file header the directive applies to the whole file; in a tune header or body, to that tune (§4.23).
  • Accepted forms: <font name> [<size>], a quoted name, and * to keep the current face (abcm2ps). A size of zero or below, a non-numeric size, extra words after the size, or an unterminated quote produces .invalidFontDirective and the directive is dropped.

Renderer

  • Per-tune TextStyles, layered: defaults, then config, then file directives, then tune directives. A spec overrides only the half (face or size) it states. Faces resolve through Font provider: use host-registered and system fonts instead of bundling more faces #190's FontProvider.
  • Byte-identical without directives: a role with no named face draws through exactly the existing code.
  • Layout follows the text:
  • Drawing: text is outlined from the resolved face itself. In fontFace mode, <text> gets that face's family plus font-weight/font-style. The bold @font-face rules are embedded only in documents that use them.
  • Diagnostics: FontResolution diagnostics are reported once per directive, at the directive's source line.
  • Bundled fonts: Libertinus Serif Bold and Bold Italic, from the 7.040 release (Libertinus-7.040.zip, sha256 2cce0850…). Its Regular and Italic are byte-identical to the faces already bundled, so all four come from one build. The existing OFL licence file covers the family. Adds about 0.6 MB.

Behaviour change worth knowing

The §14.4 Canzonetta fixture sets %%titlefont Times-Bold 32, %%vocalfont Times-Roman 14 and similar, which are now honoured. It also depends on abcm2ps's %%scale 0.7, which shrinks the whole page and which CeolKit does not implement. The larger lyrics widen the columns, so the tune now breaks into four systems on two pages instead of three on one.

  • Its bracket and bar-line checks now run per page.
  • Its snapshot was re-recorded after I checked the rendered page.
  • No other snapshot changed.

Not done (documented in CONFORMANCE.md)

  • The $1–$4 font switches inside strings. setfont-n is parsed and scoped only.
  • partsfont and textfont: P: labels and %%text aren't printed yet.
  • %%font declarations and abcm2ps's box/class= arguments.
  • abcm2ps's %%scale.

Upgrading (for the release notes)

  • CeolKitFonts.Face has two new cases, .libertinusSerifBold and .libertinusSerifBoldItalic. An exhaustive switch over it needs updating. register() and install(into:) now include them.
  • New DiagnosticCode.invalidFontDirective.
  • Documents that use font directives now render in those fonts and sizes, which can change their line and page breaks.

Tests

  • FontDirectiveTests (parser):

    • every directive is recognised;
    • scope in the file header, tune header and body;
    • quoted names, *, fractional sizes;
    • five malformed payloads;
    • FontSpec layering.
  • FontDirectiveRenderingTests (11):

    • defaults unchanged;
    • each role's size (10 cases), with * keeping the italic default;
    • scaling of staff text versus page text;
    • Times-Bold drawn in the bundled bold face (libertinusSerifBold-g…);
    • a host-registered face drawn when named;
    • a tune header overriding the file header;
    • config house style, with a directive overriding it;
    • a bigger title pushing the rows below down;
    • bigger chord and lyric fonts enlarging their bands;
    • bold embedded only when used;
    • one info-level substitution note at the directive line.

    With directive handling disabled, 10 of the 11 fail (the defaults test should, and does, still pass).

  • Visual: Monsieur de la Miranda (%%wordsfont Courier-Bold 16) prints its words in Libertinus Serif Bold 16, and ckprobe reports [info] line 2: Courier-Bold not found; using LibertinusSerif-Bold.

  • swift test: all tests pass on macOS. The Linux swift:6.3 container passes 1,437 tests.

🤖 Generated with Claude Code

Every font directive was reported as unsupported and dropped, so all text
was Libertinus Serif at sizes the renderer fixed.

Parser
- TextFontRole (titlefont … setfont-4) and FontSpec(name?, size?) in the
  model, carried as CeolKitDirective.font. File header → file-global;
  tune header and body → tune-global.
- `<font name> [<size>]`, a quoted name for families with spaces, and `*`
  to keep the face in force (abcm2ps). Anything else is reported as
  .invalidFontDirective and dropped.

Renderer
- Each tune's text styles are resolved from CeolKit's defaults, then
  SVGRenderConfig.textFonts (new: a host house style), then file
  directives, then tune directives, each half of a spec overriding only
  what it states. Faces resolve through the #190 FontProvider.
- With no directive a role has no face and is drawn by the existing
  code, so documents without font directives are byte-identical (the
  snapshots confirm it).
- Staff text (gchord, annotation, vocal, an inline tempo) scales with
  %%ceolkit:scale; page text (title, subtitle, composer, info, header
  tempo, words) is in absolute points.
- Layout follows the text: title rows grow past their default size;
  chord/annotation and lyric bands take their line height and
  ascent/descent from the style; words lines scale; chord symbols,
  annotations and syllables are measured in their own face for spacing.
- Libertinus Serif Bold and Bold Italic (v7.040, the same release as the
  bundled Regular/Italic, OFL) are bundled, so bold requests render bold
  without system fonts. fontFace mode embeds them only when used.
- Substitutions are reported once per directive, at the directive: info
  while system lookup is off, warning when it was searched and nothing
  answered.

The §14.4 Canzonetta sets 32 pt titles and 14 pt lyrics and relies on
abcm2ps's %%scale 0.7, which CeolKit doesn't implement, so it now runs to
two pages. Its structural checks are now per page, and its snapshot was
re-recorded after a visual check.

Not done: $1–$4 in-string switches (setfont-n is parsed only); partsfont
and textfont have no printed text to style yet. CONFORMANCE.md documents
all of this.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@sbeitzel
sbeitzel merged commit b6975b5 into develop Oct 3, 2026
2 checks passed
@sbeitzel
sbeitzel deleted the feature/fix_186 branch October 4, 2026 04:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant