Repository navigation
Conversation
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.
Draws a hyphen at the end of a line that breaks at a soft hyphen (U+00AD), unless ParagraphStyle.hyphens is Hyphens.hidden. This matches SkParagraph, which CanvasKit, Skwasm and the native engine use: * The hyphen is drawn only where the line breaks at the soft hyphen: not at the end of the text, before a space or newline, or on a line with an ellipsis. * A soft hyphen break only fits if the hyphen fits too. Otherwise the line breaks at the last earlier break that fits, or the hyphen overflows if there is none. * minIntrinsicWidth and longestLine include the hyphen; maxIntrinsicWidth and LineMetrics.width do not. * The hyphen takes the soft hyphen's style, is shaped in the direction of the text it ends, and goes at the end of the line in the paragraph's direction. * Selection boxes, glyph info and hit testing for the soft hyphen use the drawn hyphen. Fixes flutter#193506.
Add contract tests for clusterless characters (U+00AD, U+200B, U+200F) at paragraph and line starts, in standalone styled spans, and before newlines, as well as soft hyphens immediately after whitespace and upstream TextAffinity when hit-testing a drawn soft hyphen. Assisted-by: Gemini 4 Argon (Medium)
Fix five edge cases in cluster mapping, line layout, and soft-hyphen wrapping: 1. In _TextClusterMapping.toClusterRange, decrement endClusterIndex when _clusters[endClusterIndex - 1].start >= end so leading clusterless code units at offset 0 map to an empty ClusterRange(0, 0) instead of ClusterRange(0, 1). 2. In TextLayout.extractBidiRuns and TextLayout.addLine, skip empty clusterRange and bidiLineSpanRange slices produced by standalone clusterless characters (such as U+200F, U+00AD, or U+200B). 3. In TextLayout.addLine, start contentTextRange and allTextRange at 0 for the first line and at lines.last.hardLineBreakRange.end for subsequent lines so leading clusterless characters do not leave gaps in WebParagraph.getLineNumberAt. 4. In _LineBuilder.markSoftLineBreak and consumePendingText, ignore softHyphen when !hasPendingText so a soft hyphen immediately after whitespace breaks at the space without reserving hyphen width or inflating minIntrinsicWidth. 5. In TextLayout.getPositionForOffset, return TextAffinity.upstream when hit-testing a SoftHyphenBlock so line-boundary and closest-glyph queries stay on the hyphenated line. Without this fix, 9 tests in paragraph_edge_cases_test.dart and paragraph_soft_hyphen_test.dart fail (+73 -7 in felt test). Assisted-by: Gemini 3.8 Flash (router)
10 of 11 tasks
9 of 11 tasks
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]>
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.
Companion draft PR stacked on top of #195366 (
8f0ff938c0b8337ccb5580e584526aa01c3773ab) by @dbebawy.Commit Structure (stacked on
8f0ff938c0b)e50f01a9c85) —[web_paragraph] Add failing soft hyphen tests:Adds 9 contract tests in
paragraph_edge_cases_test.dartandparagraph_soft_hyphen_test.dartthat fail on [web_paragraph] Render soft hyphens #195366 (+73 -7infelt test).269f171b2c4) —[web_paragraph] Fix soft hyphen edge cases:Fixes the 5 underlying edge cases in
layout.dartandwrapper.dart(all 80 tests in both files pass:+80 -0).Feel free to cherry-pick both commits, fold the changes into #195366, or take only the parts you want — I will close this draft PR once #195366 is updated.