Repository navigation
fix(text): center 'middle' text on its cap height and measure capHeight without padding - #721
Merged
Merged
Conversation
…ht without padding verticalAlign 'middle' now centers the band from the first line's cap height to the last line's baseline, using only font metrics and the line count, so a label no longer moves when its text changes. The font atlas generator subtracts the distance field padding when it measures capHeight, and the shipped default font's capHeight is corrected, so 'capline' puts the top of a capital on its anchor. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HszxXvkXYYmRTFwJkgyq7Y
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
10 tasks done
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.
Summary
Implements
design/text-cap-height-centering.md(Kind: Defect) and deletes the design doc.verticalAlign: 'middle'centers the cap-height band. It used to center the string's own rendered glyphs (getInkBounds), so a label moved when its text changed: "LEVEL 1" and "QUIT" sat at different heights, and a lowercase label jumped when a "g" appeared. Now'middle'centers the band from the first line's cap line to the last line's baseline:offset = -(capHeight * size + lastBaseline) / 2. This reads only font metrics and the line count. It's the same box as CSStext-box-trimwithtext-box-edge: cap alphabeticand Figma's vertical trim, and what TextMesh Pro callsCaplinealignment. Forge's own'capline'is a different thing: it's a top anchor, not centering.getInkBounds/InkBoundsare deleted.createButton,createDropdownandcreateTooltiplabels pick this up with no code change.capHeightis measured without the distance-field padding.computeCapHeightinscripts/generate-font-atlas.mjsreturned the reference capital'splaneBounds.top. That value includes msdf-bmfont-xml's padding:distanceRange >> 1px on each side. The generator now subtractsMath.floor(distanceRange / 2) / fontSize, which mirrors that integer padding. In the shipped default font (distanceRange 16, size 42), "H" spans 0.881 to -0.190 em, socapHeightdrops from 0.881 to 0.690 em. As a result'capline'now puts the top of a capital exactly on its anchor.Changes from the design (the code moved on since it was written)
documentation-site/static/fonts/default/too. It no longer exists, because docs demos now import the package'sfonts/defaultexport (fix(text): load font atlases from explicit metrics and image URLs #713). Onlyassets/fonts/default/default.jsonchanged.capHeightwas edited by hand, not regenerated. msdf-bmfont-xml and the source TTF aren't available in this environment. The value is a deterministic function of data already in the file (H's top, distanceRange, size), and H's bottom of exactly -8/42 confirms the padding. The PNG and glyph data would not change.ascender/descenderkeep the padding, and the generator comment now says why. Outlines, glows and shadows draw into the padding, so padded bounds are the right outer bounds for'top'/'bottom'.capHeightis a typographic metric. The doc only scoped'top'/'bottom'out without saying why; this records the reason.textCenteredAt/titleBaseline/menu-button code lives outside this repo, so it isn't touched here. In this repo, theui-main-menudocs demo's "CONTINUE - ACT II" tag label used'capline'to fake centering. It now uses'middle'.Solution-reviewer verdict
REVISE, meaning the direction is right with small fixes. All of them are applied:
Math.floor(distanceRange / 2)to match msdf-bmfont-xml'sdistanceRange >> 1.ascender/descenderpadded, and reword the generator comment so the difference is intentional..mjsruns its CLI.verticalAlignsuites and rewrite the e2e scene's "centers this glyph's own ink" comment.'capline'shift its own wording in the changelog.Decisions taken from the design's open questions
The design lists none. DL-1 (redefine
'middle'rather than add a new value) and DL-2 (fix the metric in the generator) are implemented as written.Tests
shape-text.test.ts: tests for one line, three lines, an "A" vs. "x" pair sharing a baseline, and an empty string. The ink-centering tests are removed.text-effects-overlap: the synthetic atlas'scapHeightis now the glyph's ink top, andglyphCenterWorldYis derived from it instead of hard-coded0.Related issue(s)
Design doc
design/text-cap-height-centering.md(deleted by this PR).Verification checklist
npm run check-typespasses with 0 errorsnpm testpasses (1941 tests)npm run lintpasses with 0 errorsnpm run cspellpasses with 0 errorsnpm run check-exportspassesindex.ts: no API surface changed/documentation-site/docs/docsis updated:text/rendering-text.md(what each vertical alignment anchors to) andtext/loading-a-font-atlas.md(padded plane bounds vs.capHeight)npm run build, then docstypecheckandbuild, then served the site and screenshotted text, ui-button, ui-main-menu, ui-toggle, ui-dropdown, ui-slider, ui-progress-bar, ui-anchors, ui-nested-resize, layout-groups, ui-world-space-canvas, ui-canvas-group and game-states. No page errors. Labels are centered, and'capline'touches the top of the "A" in the text demo.text-effects-overlap.spec.ts: 7/7 pass locally (run against the container's Chromium build)npm run prettierreports warnings only in files this PR doesn't touch; they're also there ondev.Changelog
## [Unreleased]→#### Changed: one for'middle'semantics, one for thecapHeightcorrection and the resulting'capline'shift (~0.19 em × size).🤖 Generated with Claude Code
https://claude.ai/code/session_01HszxXvkXYYmRTFwJkgyq7Y
Generated by Claude Code