Skip to content

fix: preserve integer text advances and inside stroke geometry - #40

Merged
owjs3901 merged 2 commits into
mainfrom
integration/line-box-displacement
Sep 12, 2026
Merged

fix: preserve integer text advances and inside stroke geometry#40
owjs3901 merged 2 commits into
mainfrom
integration/line-box-displacement

Conversation

@owjs3901

Copy link
Copy Markdown
Contributor

The narrowest width of a screen diverged from Figma's own PNG six times worse than the widest one. Two separate defects were compounding; both are fixed here. Measured on landing against Figma's reference PNG:

Screen Width Before After
landing-833-3640 360 11.41% 4.99%
landing-833-3322 992 3.10% 2.47%
landing-832-2975 1920 1.84% 1.50%

The rendered height now equals the design height at all three widths. Thresholds moved down only.

1. The line advance was a ratio

Figma advances each line by round(fontSize x ratio); the browser accumulates the fraction. A ratio structurally cannot express an integer advance across several font sizes, which is why the sign of the error flipped with font size (38/18/28 too tall, 52/36 too short) and every uniform correction traded one width for another. The advance is now emitted as a whole-pixel length, and all seven measured text blocks match their design height exactly.

A pixel advance is only valid for the size it was computed from, so the resolved font size travels with it. A percentage line height whose font size is missing or bound to a mode-dependent variable is refused with a typed error naming the node or text style, rather than emitted as a number that would be wrong at another size.

2. An inside stroke was taking layout space

Figma's inside stroke consumes no layout space. With box-sizing: border-box a CSS border eats into the content box, and the existing padding compensation cannot absorb it when the design's own padding is narrower than the stroke - padding cannot go negative. Every bordered auto-layout container therefore grew by twice its stroke weight and pushed everything below it down by exactly 2px, at every width.

That case now paints inward with outline at a negative offset, which takes no layout space, and the padding compensation stands down for it. The decision keys on stroke alignment, layout mode, and stroke weight against padding - never on a viewport width - so it survives a design redrawn at another size.

Corpus and provenance

Twenty-four plugin goldens change, each enumerated with its reason in docs/line-box-displacement.md. Forty-seven of the forty-eight line-height changes are the ratio-to-integer conversion (1.6 to 26px); the remaining one is a node whose typography token previously suppressed its own resolved metrics. The corpus consistency test and manifest checksums are unchanged in force.

Source map and provenance follow the emission: outline/outlineOffset carry their stroke provenance, and a rich-text wrapper's own advance is attributed to the segment whose text follows it.

Verification

Re-measured independently from this branch with a binary built here (a9b96f24df75, SHA-256 1859B639...), not from the worker's report: python scripts/acquire.py landing then node scripts/render.mjs exits 0 against the tightened thresholds.

Gates: fmt 0, clippy --locked --workspace --all-targets --all-features -D warnings 0, cargo test --workspace 1056 passed / 0 failed / 2 ignored, insta --check no drift, stdio_smoke 2 passed.

@owjs3901
owjs3901 merged commit 757dc4a into main Sep 12, 2026
8 checks passed
@owjs3901
owjs3901 deleted the integration/line-box-displacement branch September 12, 2026 15:09
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