Skip to content

[web_paragraph] Render soft hyphens - #195366

Open
dbebawy wants to merge 1 commit into
flutter:masterfrom
dbebawy:webparagraph-soft-hyphens-193506
Open

dbebawy wants to merge 1 commit into
flutter:masterfrom
dbebawy:webparagraph-soft-hyphens-193506

Conversation

@dbebawy

@dbebawy dbebawy commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

Draws a hyphen at the end of a line that breaks at a soft hyphen (U+00AD) in WebParagraph, so Hyphens works there the way it does on native, CanvasKit and Skwasm.

@Rusino, I know you still have WebParagraph PRs open in the same files (#195122, #193774). I am happy to wait for those and rebase, or to change the approach.

Approach

I tried to match SkParagraph's current behavior, including the soft hyphen fixes that are now in Flutter:

  • 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. With Hyphens.hidden nothing is drawn, and the soft hyphen is still a break opportunity.
  • A soft hyphen break only fits if the hyphen fits too. If it does not, the line breaks at the last earlier break that fits, and the text after that break is scanned again for the next line. If there is no earlier break, the hyphen overflows. A soft hyphen right after a space breaks like the space, so it does not need room for a hyphen.
  • minIntrinsicWidth and longestLine include the hyphen. maxIntrinsicWidth and LineMetrics.width do not.
  • The hyphen takes the soft hyphen's style, so it gets the same color, background and decorations. It is shaped in the direction of the text it ends, and placed at the end of the line in the paragraph's direction, like the ellipsis.
  • Selection boxes, getGlyphInfoAt and hit testing for the soft hyphen use the drawn hyphen. Hit testing the hyphen gives the position after the soft hyphen, with upstream affinity so it stays on the line with the hyphen.

Chrome gives the soft hyphen no text cluster, so the wrapper checks the code unit just before each soft line break. The hyphen is a SoftHyphenBlock, a TextBlock that owns its own clusters and covers the soft hyphen's text range, so painting, backgrounds and decorations work without changes.

Characters that Chrome gives no text cluster, such as U+00AD and U+200B, are handled by #195384, which has landed. This PR is now rebased on it and has one commit.

@Rusino reviewed this and found five edge cases (#195377). Three were fixed in #195384, and the other two are folded in here: a soft hyphen right after a space, and the affinity when hit testing the hyphen.

Whether a line has an ellipsis comes from TextLayout.ellipsisClusters, which is not cleared between layouts on master, so a hyphen can be left out after an earlier, narrower layout added an ellipsis. #195122 resets it in wrapText, which fixes that here too.

The hyphen string is "-" for now. Once ParagraphStyle.hyphenCharacter (#195345, for #189617) lands, WebParagraphStyle.softHyphenString can return it.

Testing

test/webparagraph/paragraph_soft_hyphen_test.dart has 33 tests that use the FlutterTest font, most of them ported from SkParagraph's soft hyphen tests, plus one golden.

The golden, with Hyphens.manual on the left and Hyphens.hidden on the right:

"inter-national" with Hyphens.manual, and "inter national" with Hyphens.hidden, both underlined

I ran the chrome-dart2js-webparagraph-ui suite locally on macOS with Chrome 145 and the prebuilt artifacts (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.

From the review

  • Like SkParagraph, an LTR word in an RTL paragraph shows as "-inter", with the hyphen at the left end of the line, so every renderer matches. Browsers put it next to the text instead. If that changes later, SkParagraph and WebParagraph would change together.
  • The hyphen is measured and painted with the run's style, so it gets the run's letter spacing, like the ellipsis. SkParagraph does not apply letter spacing to it.

Other differences from SkParagraph that I left alone: the soft hyphen itself has no cluster here, so it gets no letter spacing in the line width, and getGlyphInfoAt returns null for a soft hyphen in the middle of a line, where SkParagraph returns a zero-width box. Selection boxes are not merged, so selecting "inter-" gives two boxes here and one in SkParagraph. Hit testing the hyphen now gives upstream affinity in SkParagraph too, since Skia CL 1406081 (in Flutter through #195711). SkParagraph also skips line breaking for a single run without spaces that fits the width, so on native, minIntrinsicWidth for "inter­national" measured at infinite width is the whole word. This PR splits at the soft hyphen there, so WebParagraph's minIntrinsicWidth can be smaller than native for one-word text.

Things on master that I noticed but did not change: ellipsize runs on every line that overflows rather than only the last one, and minIntrinsicWidth depends on the layout width for words that are too long for the line.

Fixes #193506.

Pre-launch Checklist

@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 26, 2026
@Rusino

Rusino commented Oct 26, 2026 via email

Copy link
Copy Markdown
Contributor

@dbebawy
dbebawy force-pushed the webparagraph-soft-hyphens-193506 branch from 8f0ff93 to 6bbb470 Compare October 27, 2026 01:09
@dbebawy

dbebawy commented Oct 27, 2026

Copy link
Copy Markdown
Contributor Author

Thank you for the review, and for writing the fixes and tests. That made them easy to take.

I folded in all five, with you as co-author on both commits. Fixes 1 to 3 went into the first commit and fixes 4 and 5 into the soft hyphen commit, and the code is the same as #195377. The one change is that the U+200F test moved to paragraph_edge_cases_test.dart with the first commit. It compares against the width of "abc" there, since that file does not load FlutterTest.

I also opened #195384 with just the first commit, so it can land first. This PR is stacked on it for now.

Locally, the webparagraph suite passes for both, except the two paragraph_test.dart tests that also fail on master on my machine. Seven of the edge case tests fail on master.

On the affinity in fix 5: from reading skparagraph, I think TextLine::getGlyphPositionAtCoordinate returns downstream for a hit on the hyphen or the ellipsis, and the engine passes that through, so native may land on the next line there today. I have not checked it on a device. Would it be worth changing skparagraph to upstream there as well?

@Rusino

Rusino commented Oct 27, 2026 via email

Copy link
Copy Markdown
Contributor

@dbebawy
dbebawy force-pushed the webparagraph-soft-hyphens-193506 branch from 6bbb470 to 88b5e70 Compare October 28, 2026 16:42
@dbebawy

dbebawy commented Oct 28, 2026

Copy link
Copy Markdown
Contributor Author

Thank you for checking, and for finding the ellipsis case too. I put up https://skia-review.googlesource.com/c/skia/+/1406081, which makes a hit on the hyphen or the ellipsis return upstream, with tests for both, and added you as a reviewer.

I also rebased this PR onto #195384, which now includes your commits from #195386.

@dbebawy
dbebawy force-pushed the webparagraph-soft-hyphens-193506 branch from 88b5e70 to bc11571 Compare October 30, 2026 13:51
hubot pushed a commit to google/skia that referenced this pull request Oct 30, 2026
TextLine::getGlyphPositionAtCoordinate returns the end of the line's
text for a hit on the ellipsis or on a rendered soft hyphen, but with
downstream affinity. For a soft hyphen that position is also the start
of the next line, and for an ellipsis it's past the line's text, so:

- getClosestUTF16GlyphInfoAt returns the next line's first glyph for a
  hit on the hyphen, and no glyph at all for a hit on the ellipsis.
- Flutter's Paragraph.getLineBoundary puts a hit on the hyphen on the
  next line.

Return upstream instead, as for a hit past the end of any other line.
For "inter­national" broken after the soft hyphen, a hit on the
hyphen now returns {6, upstream}, and its glyph is the soft hyphen
[5, 6) instead of the "n" [6, 7). For "abcdef" ellipsized to "abc...",
a hit on the ellipsis returns {3, upstream} and the glyph "c" [2, 3).

This came up in flutter/flutter#195366, where
WebParagraph now returns upstream here too.

[email protected], [email protected]

Bug: flutter/flutter#189234
Change-Id: If514d73dd39fd4a6cb17633d6818e496ade07ca8
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/1406081
Commit-Queue: Julia Lavrova <[email protected]>
Reviewed-by: Julia Lavrova <[email protected]>
Reviewed-by: Alexis Cruz-Ayala <[email protected]>
@dbebawy
dbebawy force-pushed the webparagraph-soft-hyphens-193506 branch from bc11571 to 11db239 Compare November 2, 2026 19:13
@dbebawy
dbebawy marked this pull request as ready for review November 2, 2026 19:13
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]>

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request implements support for soft hyphens (U+00AD) in the web paragraph layout engine, introducing line-breaking fallback logic, hyphen shaping, and query support. The feedback identifies a potential out-of-bounds error in softHyphenBefore when softHyphenOffset is negative, and suggests adding a bounds check to prevent a layout crash.

Comment on lines +177 to +180
final int softHyphenOffset = allClusters[clusterIndex].start - 1;
if (paragraph.text.codeUnitAt(softHyphenOffset) != 0x00AD) {
return null;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

The softHyphenOffset can be negative (e.g., if allClusters[clusterIndex].start is 0), which will cause paragraph.text.codeUnitAt(softHyphenOffset) to throw a RangeError and crash the layout process. Adding a bounds check for softHyphenOffset ensures defensive robustness.

Suggested change
final int softHyphenOffset = allClusters[clusterIndex].start - 1;
if (paragraph.text.codeUnitAt(softHyphenOffset) != 0x00AD) {
return null;
}
final int softHyphenOffset = allClusters[clusterIndex].start - 1;
if (softHyphenOffset < 0 || softHyphenOffset >= paragraph.text.length) {
return null;
}
if (paragraph.text.codeUnitAt(softHyphenOffset) != 0x00AD) {
return null;
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I think this cannot happen here. allClusters is sorted by start and its clusters are not empty, and the check above returns null for clusterIndex <= 0, so the cluster starts at 1 or later. I am happy to add the check anyway if a reviewer prefers it.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Agreed — 0 < clusterIndex < allClusters.length - 1 (before the artificial end-of-text cluster) and non-empty sorted clusters guarantee 1 <= allClusters[clusterIndex].start <= paragraph.text.length, so no extra bounds check is needed.

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]>
@dbebawy

dbebawy commented Nov 3, 2026

Copy link
Copy Markdown
Contributor Author

@Rusino @mdebbar Now that #195384 has landed, this is rebased to the one soft hyphen commit and ready for review. Could one of you add CICD when you have a chance?

@Rusino Rusino added the CICD Run CI/CD label Nov 3, 2026
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. A soft hyphen right after a space breaks
  like the space, so it does not need room for a hyphen.
* 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. Hit testing the hyphen gives the position after the
  soft hyphen with upstream affinity, so it stays on the hyphen's line.

Fixes flutter#193506.

Co-authored-by: Julia Lavrova <[email protected]>
@dbebawy
dbebawy force-pushed the webparagraph-soft-hyphens-193506 branch from 11db239 to c3d96d6 Compare November 3, 2026 19:42
@flutter-dashboard flutter-dashboard Bot removed the CICD Run CI/CD label Nov 3, 2026

@Rusino Rusino left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM! Thanks for rebasing onto #195384 and folding in the post-space soft-hyphen and upstream hit-test affinity fixes (as well as landing Skia CL 1406081).

Verified on the WebParagraph test corpus (0 regressions; fixes 3 existing soft-hyphen tests in paragraph_builder_test.dart and text_painter_test.dart).

@Rusino Rusino added the CICD Run CI/CD label Nov 3, 2026
@flutter-dashboard

Copy link
Copy Markdown

Golden file changes have been found for this pull request. Click here to view and triage (e.g. because this is an intentional change).

If you are still iterating on this change and are not ready to resolve the images on the Flutter Gold dashboard, consider marking this PR as a draft pull request above. You will still be able to view image results on the dashboard, commenting will be silenced, and the check will not try to resolve itself until marked ready for review.

For more guidance, visit Writing a golden file test for package:flutter.

Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing.

Changes reported for pull request #195366 at sha c3d96d6

@flutter-dashboard flutter-dashboard Bot added the will affect goldens Changes to golden files label Nov 3, 2026
@dbebawy

dbebawy commented Nov 4, 2026

Copy link
Copy Markdown
Contributor Author

@Rusino Thank you for the review! Gold has one new image, engine.web_paragraph.soft_hyphen, from the new test. It shows 'inter-' / 'national' with Hyphens.manual and no hyphen with Hyphens.hidden, which I think is expected. Could you triage it when you have a chance?

chingjun pushed a commit to chingjun/flutter that referenced this pull request Nov 7, 2026
Adds `hyphenCharacter` to `dart:ui`'s `ParagraphStyle`, so apps can
choose the string drawn at the end of a line that breaks at a soft
hyphen (U+00AD) instead of always "-". If it is null, "-" is drawn as
before. The empty string draws nothing, and the line still breaks there.

The engine passes it to Skia's `ParagraphStyle::setSoftHyphenString`,
which already reserves the string's width when breaking lines and
includes it in selection boxes. Skwasm gets a
`paragraphStyle_setSoftHyphenString` binding for the same call, and
CanvasKit sets the `softHyphenString` property that rolled in with
flutter#195339.

This is the additive `dart:ui` part of flutter#189617. The name and shape
follow the API question there, and I am happy to change them. The
framework side (`TextStyle.getParagraphStyle`, `TextPainter`,
`RenderParagraph`, `RichText` and `Text`) is ready as a follow-up PR,
along with a breaking change page for flutter/website, since a new
`getParagraphStyle` parameter breaks classes that override it. I will
open both once this lands.

WebParagraph stores the value but does not draw soft hyphens yet.
flutter#195366 adds soft hyphens there for flutter#193506, and can use this value once
both land.

Part of flutter#189617.

## 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].
- [x] 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
`///`).
- [ ] 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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CICD Run CI/CD engine flutter/engine related. See also e: labels. platform-web Web applications specifically team-web Owned by Web platform team will affect goldens Changes to golden files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[web] Render soft hyphens (Hyphens API) on WebParagraph

2 participants