Skip to content

fix: Prevent text layouting to break punctuation on adjacent spans - #4060

Merged
spydon merged 5 commits into
mainfrom
luan.fix-text-layout-punctuation
Sep 28, 2026
Merged

spydon merged 5 commits into
mainfrom
luan.fix-text-layout-punctuation

Conversation

@luanpotter

@luanpotter luanpotter commented Sep 27, 2026 •

Copy link
Copy Markdown
Member

Description

Prevent text layouting to break punctuation on adjacent spans.

Currently any node gap is a potential break-point for line layouting; since a (for example) **foo**. breaks into two nodes, the period might endup on the next line, which is a terrible layout decision.

This changes the text layouting engine to look ahead to next groups if they are "glued" (no whitespace) to determine where to break the runs.

Note that this uses the existing line break rule, which only allows literal spaces (.split(' ') on _PlainTextLayoutBuilder), which we can expand on a followup to comply to the unicode standard - that is completely tangential to this PR. Upgrading flame to re-use flutter's text layouting algo is also absolutely out of scope.

Before:
image

After:
image

Checklist

  • I have followed the Contributor Guide when preparing my PR.
  • I have updated/added tests for ALL new/updated/fixed functionality.
  • I have updated/added relevant documentation in docs and added dartdoc comments with ///.
  • I have updated/added relevant examples in examples or docs.

Breaking Change?

It is technically a minor breaking on the "inner workings" but exposed TextNodeLayoutBuilder interface that I doubt anyone uses, so not flagging to reduce noise on actual user-facing migration list; the fix is also simple, just add the two new overrides.

  • Yes, this PR is a breaking change.
  • No, this PR is not a breaking change.

@luanpotter
luanpotter marked this pull request as ready for review September 27, 2026 15:47
@luanpotter
luanpotter requested a review from spydon September 27, 2026 15:47

@spydon spydon left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lgtm! Weren't we discussing before if we could just use Flutter's text layout pipeline instead of rolling our own?

@spydon
spydon merged commit ba16cf8 into main Sep 28, 2026
8 checks passed
@spydon
spydon deleted the luan.fix-text-layout-punctuation branch September 28, 2026 08:41
spydon added a commit that referenced this pull request Oct 1, 2026
The rich text document pipeline (`DocumentRoot`, `ParagraphNode`,
`HeaderNode`, and therefore `flame_markdown`) was the last place in
Flame that broke lines itself. Its layout builder split on ASCII spaces,
re-measured every tentative line with a fresh `TextPainter`, could not
wrap CJK, threw on `TextAlign.justify`, lost kerning and ligatures at
span boundaries, and needed glue heuristics for punctuation that were
patched again in #4060 after #3192 and #3939.

Since `InlineTextStyle` always renders through `TextPaint`, that
pipeline can hand the whole block to Flutter's paragraph engine without
losing any capability:

- `InlineTextNode.layoutBuilder` is replaced by `InlineSpan
toInlineSpan()`. `PlainTextNode` and `GroupTextNode` build a `TextSpan`
tree, the wrapper nodes delegate to their child.
- `TextBlockNode.format` lays out one `TextPainter` per block and
returns the new `ParagraphTextElement`, which exposes `lines` and
`lineMetrics`. Justify, CJK wrapping, hard line breaks, and shaping
across differently styled spans now work.
- `TextElement` gained a no-op `dispose()`, forwarded by `GroupElement`,
which `ParagraphTextElement` uses to release its painter.
- `TextBoxComponent` lays out a dedicated painter for wrapping and reads
lines through `ParagraphTextElement`. It previously called
`layout(maxWidth:)` on the painter shared through the `TextPaint` cache,
so the wrapped layout leaked into later `format` calls for the same
string. Per-line rendering, `align`, `growingBox` and the typewriter
effect are unchanged.

---------

Co-authored-by: Luan Nico <[email protected]>
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.

2 participants