Skip to content

Glyph fonts: renderable trim indicators, and vertical metrics on IGlyphFont - #68

Merged
mstrobel merged 1 commit into
developfrom
feat/glyph-trim-indicator-metrics
Aug 5, 2026
Merged

Glyph fonts: renderable trim indicators, and vertical metrics on IGlyphFont#68
mstrobel merged 1 commit into
developfrom
feat/glyph-trim-indicator-metrics

Conversation

@mstrobel

@mstrobel mstrobel commented Aug 5, 2026

Copy link
Copy Markdown
Owner

A FIGlet-trimmed line could render its trim indicator as nothing at all — no bundled face has a glyph for U+2026, and the formatter budgeted for an ellipsis the face could not draw. Fixing that exposed a second defect underneath it.

Renderable trim indicators

IGlyphFont.HasGlyph(uint) is a default interface member, so external implementors stay source-compatible. ShadowedFont and DecoratedFont forward to Inner — inheriting the optimistic default would silently reinstate the bug behind any decorator.

TextFormatter picks the indicator per face: the configured Ellipsis if the face can draw it, else "...", else the ellipsis re-sourced to GlyphSource.Default so the terminal's own font draws it. A mismatched indicator is a defect in taste; an invisible one is a defect in correctness.

U+2026 art is authored for eight bundled faces. miniwi and phm-leds ship a truncated required block whose positional layout an appended codetag would corrupt, so they use the derived form, guarded by two table-integrity tests.

Vertical metrics — the defect the fallback created

Runs align to the band's Bottom row, so a one-cell indicator beside a multi-row face landed in the descender row — one row low on standard/small/mini/slant, two on big. Invisible until now because a band whose runs share one face shares their metrics; the mixed-source fallback is the first construct that doesn't.

IGlyphFont gains Baseline, Ascender, Descender, parsed from FLF header field 1.

Baseline is a COUNT of rows from the top through the baseline row — never a 0-based index. The index reading renders correctly on ansi-shadow (7/7) and wrong everywhere else, so contract, remarks and tests all name the distinction, and Baseline_IsARowCount_NotARowIndex pins it against painted ink rather than the header.

VerticalTextAlignment.Baseline is declared last so default stays Bottom; only the last-resort indicator opts in.

The decorators are not pure forwards

baseline descent
ShadowedFont Inner's, unchanged (glyph at the anchor, shadow below) grows by the offset
DecoratedFont Above shifts down one unchanged
DecoratedFont Below unchanged deepens by one

All three bundled DecoratedFont instances wrap MonospaceFont, where a single-row inner face makes every reading agree — no shipped configuration could have caught this, so it was derived from the painting code rather than from behaviour.

Two pre-existing quirks were found, documented in <remarks>, and left alone: ShadowedFont with a negative row offset paints outside the box Measure reports, and DecoratedFont draws a Below decoration at a hard-coded row + 1.

Verification

Red-first: the fallback test failed Expected: [4] / Actual: [5] on a 6/5 face — the descender row exactly.

Rendering 689 (+51) · UI 3521 · Drawing 484 · Core 1071 — all green, re-run after rebasing onto develop.

🤖 Generated with Claude Code

https://claude.ai/code/session_01A4i5CZSjkU1TbAgiYBomhL

…yphFont`.

A FIGlet-trimmed line could render its trim indicator as nothing at all: no
bundled face has a glyph for U+2026, and the formatter budgeted for an ellipsis
the face could not draw.

`IGlyphFont.HasGlyph(uint)` is a default interface member so external
implementors stay source-compatible, and honest for pass-through faces that hand
the cluster to the terminal. `ShadowedFont` and `DecoratedFont` forward to
`Inner` — inheriting the optimistic default would silently reinstate the bug
behind any decorator.

`TextFormatter` now chooses the indicator per face: the configured `Ellipsis` if
the face can draw every codepoint of it, else `"..."`, else the configured
ellipsis re-sourced to `GlyphSource.Default` so the terminal's own font draws it.
A mismatched indicator is a defect in taste; an invisible one is a defect in
correctness. `U+2026` art is authored for eight bundled faces; `miniwi` and
`phm-leds` ship a truncated required block, whose positional layout would be
corrupted by an appended codetag, so they fall back to the derived form.

That last-resort path then exposed a second defect. Runs align to the band's
BOTTOM row, so a one-cell indicator beside a multi-row face landed in the
DESCENDER row — one row low on standard/small/mini/slant, two on big. It was
invisible until now because a band whose runs share one face shares their
metrics, and the mixed-source fallback is the first construct that does not.

So `IGlyphFont` gains `Baseline`, `Ascender` and `Descender`, parsed from FLF
header field 1. `Baseline` is a COUNT of rows from the top through the baseline
row, never a 0-based index — the index reading renders correctly on ansi-shadow
(7/7) and wrong on every other face, so both the contract and the tests name the
distinction explicitly. `VerticalTextAlignment.Baseline` is declared last, so
`default` remains `Bottom`, and only the last-resort indicator opts into it.

The decorators are not pure forwards here, and the painting code decides it:
`ShadowedFont` paints the glyph at the caller's anchor and the shadow below, so
its baseline is `Inner`'s and only its descent grows; `DecoratedFont` shifts the
baseline down for `Above` and deepens the descent for `Below`. All three bundled
`DecoratedFont` instances wrap `MonospaceFont`, where every reading agrees — so
no shipped configuration could have caught it.

Rendering 679, UI 3509, Drawing 460 — all green.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A4i5CZSjkU1TbAgiYBomhL
@mstrobel
mstrobel merged commit eda8137 into develop Aug 5, 2026
3 checks passed
@mstrobel
mstrobel deleted the feat/glyph-trim-indicator-metrics branch August 5, 2026 20:55
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