You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Text runs can be measured narrower than their shaped line width, forcing mid-word breaks in flex rows #181
A flex-row item holding a <Text> whose content is a nested inline <Text> run can be laid out narrower than the line its breaker actually produces. When the shaped width exceeds the measured box by even a fraction of a point, the breaker falls back to character-level breaks — e.g. Mumbai renders as Mumba / i, and Mumbai, renders as Mumbai / ,.
Root cause
Two measurement paths disagree:
Intrinsic sizing: measure_intrinsic_width (engine/src/layout/mod.rs) measures text via FontContext::measure_string — a per-character hmtx-width sum plus a 0.01 epsilon.
Line breaking for styled runs: break_runs_into_lines → measure_styled_chars (engine/src/text/mod.rs) resolves the concrete family per character and shapes contiguous runs with rustybuzz, then sums the shaped cluster widths.
For Mumbai in IBM Plex Serif 10pt the shaped width is ~39.13pt while measure_string reports ~39.03pt (+0.01 epsilon → ~39.04pt box). The styled breaker sees the final i exceed max_width and character-breaks it.
Plain string content does not hit this: break_into_lines → measure_chars takes the same per-character path measure_string uses whenever the font stack lists more than one family, so box and breaker agree. The discrepancy only surfaces for styled runs — any <Text> containing nested <Text> children — because runs always go through measure_styled_chars' shaping path even when the family list resolves to a single concrete font.
The trailing text lays out as Mumba / i. Emitting the string directly (no nested <Text>) keeps it on one line. Which strings split depends on the font — Bengaluru also splits under IBM Plex Serif, while Noto Serif is self-consistent for these strings.
Suggested fix direction
Measure run-structured text with the same measure_styled_chars shaping path the breaker uses (or clamp the breaker's available width to the same measure), so a content-measured box can never end up narrower than the shaped line it must hold. Short of that, the breaker's character-level fallback could refuse to split a word when no break opportunity exists within the line.
Found while investigating reactive-resume/reactive-resume#3586 — happy to provide the laid-out ElementInfo dumps (w=39.04 box vs the two forced lines) if useful.
Thanks for the detailed report and the measurements, they made this quick to pin down. Fixed in #184, which is on main and ships in the next release.
It turned out plain text hit it too, not just nested runs: with a single font family the plain path also shapes. Intrinsic width now uses the same shaped measurement as the line breaker, so a content-sized box always holds its line. "Mumbai", "Mumbai," and "Bengaluru" in IBM Plex Serif now stay on one line either way.
It also fixed a second bug in the same code: text with a hard break (like <span>…</span><br><span>…</span>) was measured as all its lines end to end instead of its widest line.
Activity
danmolitor commentedon Oct 6, 2026
Thanks for the detailed report and the measurements, they made this quick to pin down. Fixed in #184, which is on main and ships in the next release.
It turned out plain text hit it too, not just nested runs: with a single font family the plain path also shapes. Intrinsic width now uses the same shaped measurement as the line breaker, so a content-sized box always holds its line. "Mumbai", "Mumbai," and "Bengaluru" in IBM Plex Serif now stay on one line either way.
It also fixed a second bug in the same code: text with a hard break (like
<span>…</span><br><span>…</span>) was measured as all its lines end to end instead of its widest line.I'll close this once the release is out.
Also, if you get a chance and wouldn't mind filling this out - https://www.formepdf.com/production - that'd be awesome. Thanks!
danmolitor commentedon Oct 6, 2026
Shipped in 0.28.0 (now on npm, PyPI and crates.io). See the 0.28.0 entry in CHANGELOG.md.
santhiprakash commentedon Oct 9, 2026
@/tmp/forme_reply.md