Measure card text against the real faces instead of a guess - #9
Merged
Conversation
The card gate asked repo_card how wide its own text was. Check and drawing shared a single table, so the two agreed with each other whatever that table said. The table guessed a width from a character's class and handed every lowercase letter one number, which under-measures 86 of the 95 printable ASCII characters against the widest face a reader's machine can resolve. The card here drew past the right rule in DejaVu Sans under a green suite. Widths now come from face-metrics.json, measured off the font files with fontTools and checked in beside the renderer. The renderer wraps to the per-character maximum across those faces. The gate loads the same file and rebuilds each measured face on its own, then asks whether the line fits in that face, so the two can now disagree. A binding check holds the renderer's table at or above every measured advance, and putting the class guess back fails on the first lowercase m. The gate also reads the width of every line the wrapper hands back rather than only asking whether text was cut. A token longer than the budget got through before. The wrapper is greedy, so it leaves that token alone on its line, the joined text still equals the source, and nothing complains while the drawing runs off the page. The control fixture carries that shape now. Re-rendered and checked against Hanken Grotesk, Segoe UI, Arial, DejaVu Sans, Conso, Consolas, Cascadia Mono and DejaVu Sans Mono. Nothing on the card reaches the rule at x=916 in any of them. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The suite asserts the receipt reports exactly the gates it named, in the order it ran them. Two gates arrived and the expected list did not move, so the assertion failed on the first new name. A gate appearing or vanishing unannounced is what that assertion exists to catch, so the list says the new names rather than the check being loosened. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.
What was wrong
The card gate measured text by calling the renderer's own width table, so the
check and the drawing agreed with each other no matter what that table said.
The table guessed a width from a character's class and handed every lowercase
letter one number. Against the widest face a reader's machine can resolve it
under-measures 86 of the 95 printable ASCII characters, and the card in this
repository drew past the right rule in DejaVu Sans while the suite stayed
green.
How it is fixed
face-metrics.jsonholds advance widths measured off the font files withfontTools, rounded up, with the SHA-256 of every face recorded beside them.
The renderer wraps to the per-character maximum across those faces. The gate
loads the same file and rebuilds each measured face on its own, then asks
whether the line fits in that face. The two can now disagree.
A binding check asserts, per character, that the renderer's table sits at or
above every measured advance. Reverting to the class guess fails it on the
first lowercase m, so no fixture string has to carry that regression.
The greedy-wrap hole
A check that only asks whether text was cut misses a single token longer than
the budget. The wrapper is greedy, so it leaves that token alone on its line,
the joined text still equals the source, and the drawing runs off the page
with nothing red. The gate now reads the width of every line the wrapper hands
back, and the control fixture carries a row of that shape.
Verification
Re-rendered, then measured again by a separate script that walks the font
files a second time rather than trusting either table: Hanken Grotesk, Segoe
UI, Arial, DejaVu Sans, Conso, Consolas, Cascadia Mono, DejaVu Sans Mono.
Nothing on the card reaches the rule at x=916 in any of them.
🤖 Generated with Claude Code