Skip to content

[WIP][Companion for #195384] [web_paragraph] Clusterless layout & wrapping edge cases - #195386

Closed
Rusino wants to merge 3 commits into
flutter:masterfrom
Rusino:companion-pr-195384
Closed

Rusino wants to merge 3 commits into
flutter:masterfrom
Rusino:companion-pr-195384

Conversation

@Rusino

@Rusino Rusino commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

Companion Draft PR for #195384 (diff of just the 2 companion commits on top of 990aa341cb0):

  • Commit 1 (90406c4ecf7): 6 unit tests in paragraph_edge_cases_test.dart (all 6 fail on 990aa341cb0 on Chrome 145: 3 StateError crashes on lines.first, 2 line boundary mismatches [0, 5) vs [0, 4) at width: 35, and 1 AssertionError at wrapper.dart:101).
  • Commit 2 (1efa853646b): Fixes in layout.dart and wrapper.dart (all 52 tests in paragraph_edge_cases_test.dart pass).

Chrome gives some invisible characters, such as a soft hyphen (U+00AD)
or a zero width space (U+200B), no text cluster. Their code units were
left mapped to cluster 0, so a text range that included one could map
to a cluster range that went backwards. Laying out a paragraph that
ends with one of these characters failed the assertion in
_TextClusterMapping.toTextRange.

Map each of those code units to the cluster before it, or to the first
cluster at the start of the text. A cluster range that starts at such a
code unit then starts at the next cluster, so a span that starts with
one covers only its own clusters. A cluster range that ends at one at
the start of the text ends before the first cluster.

Bidi regions and span slices that contain only these characters have
no clusters, so they add no bidi run and no block to the line. A line's
text range starts where the previous line ended, or at 0, so a line
that starts with one of these characters still contains it.

Co-authored-by: Julia Lavrova <[email protected]>
@github-actions github-actions Bot added engine flutter/engine related. See also e: labels. platform-web Web applications specifically team-web Owned by Web platform team labels Oct 27, 2026
@dbebawy dbebawy mentioned this pull request Oct 28, 2026
10 of 11 tasks
Add contract tests for paragraphs containing only clusterless characters,
soft line breaks on clusterless code units before directional marks, and
clusterless characters following trailing spaces. Without the fix, all 6
new tests fail (3 StateError crashes on lines.first, 2 line boundary
mismatches [0, 5) vs [0, 4), and 1 AssertionError at wrapper.dart:101).

Assisted-by: Gemini Pro Next
Check allClusters.length == 1 in TextLayout.wrapText and lines.isEmpty in
TextLayout._calculatePaintBounds so non-empty paragraphs containing only
clusterless characters (such as U+00AD, U+200B, or U+200F) get empty-text
metrics and Rect.zero paintBounds instead of crashing on lines.first.

Scan codeUnitFlags from the previous cluster's end to the current cluster's
start in TextWrapper._isSoftLineBreak so soft line breaks that land on
unmapped clusterless code units before directional marks are not missed, and
use hardlineTextRange.start in TextLayout.addLine so clusterless characters
after trailing spaces are included in allTextRange. Without the fix, the 6
new tests in paragraph_edge_cases_test.dart fail (3 StateError crashes on
lines.first, 2 line boundary mismatches [0, 5) vs [0, 4), and 1 AssertionError at
wrapper.dart:101).

Assisted-by: Gemini Pro Next
@Rusino
Rusino force-pushed the companion-pr-195384 branch from 2393e83 to 1efa853 Compare October 28, 2026 17:19
@Rusino Rusino closed this Oct 30, 2026
dbebawy added a commit to dbebawy/flutter that referenced this pull request Nov 2, 2026
…r#195384)

Chrome gives some invisible characters, such as a soft hyphen (U+00AD),
a zero width space (U+200B) or a right-to-left mark (U+200F), no text
cluster. WebParagraph left their code units mapped to cluster 0, so a
text range that includes one can map to the wrong clusters. On master:

* Laying out a paragraph that ends with a soft hyphen or zero width
space fails the assertion in `_TextClusterMapping.toTextRange`.
* Laying out a paragraph whose only text is one of these characters
throws a `StateError`.
* Wrapping text where a space is followed by one of these characters,
such as `'ab \u200F\u200Fcd'`, fails an assertion in
`TextWrapper.breakLines`.
* `getLineNumberAt` fails an assertion for a line that starts with one
of these characters.
* A right-to-left mark at the start of an LTR paragraph makes the first
letter count twice in the line width.
* A styled span that contains only one of these characters adds an empty
box to `getBoxesForRange`.

This maps each of those code units to the cluster before it, or to the
first cluster at the start of the text, so a range that starts or ends
at one of them does not take a cluster next to it. Bidi regions and span
slices with no clusters add no bidi run and no block to the line, and a
line's text range starts where the previous line ended and ends where
the next line starts. A paragraph with no clusters is laid out like
empty text, and the line breaker also finds a soft line break that lands
on one of these characters.

This was the first commit of flutter#195366 (soft hyphens in WebParagraph, for
flutter#193506). @Rusino found three of these cases while reviewing that PR,
wrote the fixes and tests in flutter#195377, and suggested landing this part on
its own first. Her two commits from flutter#195386 add the rest: paragraphs
with only these characters, and line breaks next to them.

## Testing

`paragraph_edge_cases_test.dart` has a new group for these characters.
Eleven of its tests fail on master. The two tests for a soft line break
before a direction mark after a zero width space pass on master with
Chrome 145 on my machine, so they may depend on the Chrome version.

I ran the `chrome-dart2js-webparagraph-ui` suite locally on macOS with
`felt test --gcs-prod`. Everything passes except "Paragraph with
different locales for the same language" and "paint overflows" in
`paragraph_test.dart`, which fail the same way on master on my machine.

## Pre-launch Checklist

- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [AI contribution guidelines] and understand my
responsibilities, or I am not using AI tools.
- [x] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [x] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [x] I signed the [CLA].
- [ ] I listed at least one issue that this PR fixes in the description
above.
- [x] I updated/added relevant in-code documentation (doc comments with
`///`).
- [x] If this PR introduces a new feature or capability, I created and
linked a website documentation issue or PR in [flutter/website] (or
verified none is needed).
- [x] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [x] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [ ] All existing and new tests are passing.

<!-- Links -->
[Contributor Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview
[AI contribution guidelines]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#ai-contribution-guidelines
[Tree Hygiene]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
[test-exempt]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests
[Flutter Style Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md
[Features we expect every widget to implement]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement
[CLA]: https://cla.developers.google.com/
[flutter/website]: https://github.com/flutter/website
[breaking change policy]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes
[Data Driven Fixes]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md

---------

Co-authored-by: Julia Lavrova <[email protected]>
gmackall pushed a commit to gmackall/flutter that referenced this pull request Nov 3, 2026
…r#195384)

Chrome gives some invisible characters, such as a soft hyphen (U+00AD),
a zero width space (U+200B) or a right-to-left mark (U+200F), no text
cluster. WebParagraph left their code units mapped to cluster 0, so a
text range that includes one can map to the wrong clusters. On master:

* Laying out a paragraph that ends with a soft hyphen or zero width
space fails the assertion in `_TextClusterMapping.toTextRange`.
* Laying out a paragraph whose only text is one of these characters
throws a `StateError`.
* Wrapping text where a space is followed by one of these characters,
such as `'ab \u200F\u200Fcd'`, fails an assertion in
`TextWrapper.breakLines`.
* `getLineNumberAt` fails an assertion for a line that starts with one
of these characters.
* A right-to-left mark at the start of an LTR paragraph makes the first
letter count twice in the line width.
* A styled span that contains only one of these characters adds an empty
box to `getBoxesForRange`.

This maps each of those code units to the cluster before it, or to the
first cluster at the start of the text, so a range that starts or ends
at one of them does not take a cluster next to it. Bidi regions and span
slices with no clusters add no bidi run and no block to the line, and a
line's text range starts where the previous line ended and ends where
the next line starts. A paragraph with no clusters is laid out like
empty text, and the line breaker also finds a soft line break that lands
on one of these characters.

This was the first commit of flutter#195366 (soft hyphens in WebParagraph, for
flutter#193506). @Rusino found three of these cases while reviewing that PR,
wrote the fixes and tests in flutter#195377, and suggested landing this part on
its own first. Her two commits from flutter#195386 add the rest: paragraphs
with only these characters, and line breaks next to them.

## Testing

`paragraph_edge_cases_test.dart` has a new group for these characters.
Eleven of its tests fail on master. The two tests for a soft line break
before a direction mark after a zero width space pass on master with
Chrome 145 on my machine, so they may depend on the Chrome version.

I ran the `chrome-dart2js-webparagraph-ui` suite locally on macOS with
`felt test --gcs-prod`. Everything passes except "Paragraph with
different locales for the same language" and "paint overflows" in
`paragraph_test.dart`, which fail the same way on master on my machine.

## Pre-launch Checklist

- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [AI contribution guidelines] and understand my
responsibilities, or I am not using AI tools.
- [x] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [x] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [x] I signed the [CLA].
- [ ] I listed at least one issue that this PR fixes in the description
above.
- [x] I updated/added relevant in-code documentation (doc comments with
`///`).
- [x] If this PR introduces a new feature or capability, I created and
linked a website documentation issue or PR in [flutter/website] (or
verified none is needed).
- [x] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [x] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [ ] All existing and new tests are passing.

<!-- Links -->
[Contributor Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview
[AI contribution guidelines]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#ai-contribution-guidelines
[Tree Hygiene]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
[test-exempt]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests
[Flutter Style Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md
[Features we expect every widget to implement]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement
[CLA]: https://cla.developers.google.com/
[flutter/website]: https://github.com/flutter/website
[breaking change policy]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes
[Data Driven Fixes]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md

---------

Co-authored-by: Julia Lavrova <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

engine flutter/engine related. See also e: labels. platform-web Web applications specifically team-web Owned by Web platform team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants