Repository navigation
Support soft hyphen (U+00AD) rendering with a Hyphens API - #185152
Conversation
|
@LongCatIsLooong — this is the draft PR per your suggestion in #18443. Would appreciate it if you could kick off Google testing to surface any regressions in internal apps. The change is intentionally minimal (one-line engine opt-in + gtest). A design doc covering this approach vs. exposing a Dart-level |
This comment was marked as resolved.
This comment was marked as resolved.
37aaa03 to
ae3aa44
Compare
|
Design doc is now posted: https://flutter.dev/go/soft-hyphens (redirect PR: flutter/website#13282; direct: https://docs.google.com/document/d/1w_d53wb-RMuwXmXIPq67JrZYG97XF1mDaKEoKBDt8-E) Tracking issue: #185154 This PR is Stage 1 of the two-stage rollout described in the doc. Stage 2 (Dart |
|
I put on the CICD label. The tests should finish in an hour or two, and then I'll start google testing. |
The skia version with your reland commit should have already been rolled into flutter: #185131 |
ae3aa44 to
fa25665
Compare
|
@LongCatIsLooong good catch — thanks. My branch base (commit 6154f75) didn't include the Skia roll in #185131, so DEPS was still pinning the pre-fix Skia SHA. I've rebased onto current upstream/master (now pinning |
|
I'm really curious if this will pass Google and customer tests or not. If so and we consider merging this, we should be certain that the phase 2 Dart solution does not need to touch Material/Cupertino, so it's not blocked by the decoupling code freeze, just in case someone shows up desperately needing to opt out. |
|
it looks like the test is failing to compile:
|
fa25665 to
2571b41
Compare
|
Pushed a fix ( friend class SkiaParagraphBuilderTests_ParagraphStrutStyle_Test;
friend class SkiaParagraphBuilderTests_RenderSoftHyphensEnabled_Test; |
|
It looks like everything (include Google testing) passed. 🎉 |
Adds a redirect for flutter.dev/go/soft-hyphens pointing to the design doc for soft hyphen (U+00AD) rendering support in Flutter. Design doc: https://docs.google.com/document/d/1w_d53wb-RMuwXmXIPq67JrZYG97XF1mDaKEoKBDt8-E Tracking issue: flutter/flutter#18443 Implementation PR: flutter/flutter#185152
|
Hi @dbebawy are you still working on this? In case you want to include this change in the next stable release, 3.47 will be cut soon (in about 2 weeks). |
|
Hi @LongCatIsLooong — yes, still on it, and I'd like to land this in 3.47 (Q3 cutoff is July 7, ~2 weeks out). Quick status so we're aligned. This PR currently only carries the engine-side opt-in (
The only thing blocking me from writing that code is design sign-off — the doc has had no reviewer feedback since that 4/23 reconciliation, and I'd rather not implement the full API and then find the shape needs to change in review on a tight clock. Could I get a gut-check from you (and/or @chunhtai @justinmc) on the reconciled design this week? If the enum + widget-param shape looks right, I can have the combined PR up within a few days, leaving runway for review before the cutoff. Happy to hop on a short sync if that's faster — or to just proceed against the doc as written and iterate in code review if you'd prefer that. Thanks! |
2571b41 to
126e8d9
Compare
|
Thanks @LongCatIsLooong! Synced to master, and CI and Google testing passed, including @Renzo-Olivares I filed #193506 for the web side and pointed the web TODOs at it, so this PR can close #18443. I have a Skwasm follow-up ready for after this lands. For CanvasKit, the Skia change that exposes the setting is in review: https://skia-review.googlesource.com/c/skia/+/1378836. While testing I also found that Skia draws the hyphen in a few spots where the line doesn't actually break at the soft hyphen, for example a soft hyphen right before a space. The fix is in review too, at https://skia-review.googlesource.com/c/skia/+/1378356, and will come in with a Skia roll once it lands. @justinmc with the material_ui proxy gone this no longer breaks flutter/packages. Could you take another look when you get a chance? |
mdebbar
left a comment
There was a problem hiding this comment.
Web changes look good to me with some nits.
| /// Defaults to [Hyphens.manual], which renders a hyphen glyph at a line break | ||
| /// that falls on a soft hyphen. [Hyphens.hidden] suppresses the glyph; the line | ||
| /// still breaks at U+00AD regardless. |
There was a problem hiding this comment.
Let's mention that this isn't supported on the web yet, so web developers aren't surprised:
| /// Defaults to [Hyphens.manual], which renders a hyphen glyph at a line break | |
| /// that falls on a soft hyphen. [Hyphens.hidden] suppresses the glyph; the line | |
| /// still breaks at U+00AD regardless. | |
| /// Defaults to [Hyphens.manual], which renders a hyphen glyph at a line break | |
| /// that falls on a soft hyphen. [Hyphens.hidden] suppresses the glyph; the line | |
| /// still breaks at U+00AD regardless. | |
| /// | |
| /// Not yet supported on the web, where the hyphen glyph is never rendered. |
| 'hyphens: ${_hyphens ?? "unspecified"}, ' | ||
| 'locale: ${_locale ?? "unspecified"}' |
There was a problem hiding this comment.
nit: native prints hyphens after locale. Let's keep the same order:
| 'hyphens: ${_hyphens ?? "unspecified"}, ' | |
| 'locale: ${_locale ?? "unspecified"}' | |
| 'locale: ${_locale ?? "unspecified"}, ' | |
| 'hyphens: ${_hyphens ?? "unspecified"}' |
| 'hyphens: ${_hyphens ?? "unspecified"}, ' | ||
| 'locale: ${_locale ?? "unspecified"}' |
There was a problem hiding this comment.
Same here:
| 'hyphens: ${_hyphens ?? "unspecified"}, ' | |
| 'locale: ${_locale ?? "unspecified"}' | |
| 'locale: ${_locale ?? "unspecified"}, ' | |
| 'hyphens: ${_hyphens ?? "unspecified"}' |
| String? ellipsis, | ||
| int? maxLines, | ||
| TextHeightBehavior? textHeightBehavior, | ||
| Hyphens? hyphens, |
There was a problem hiding this comment.
I think that if we land this PR with this change, then the flutter/packages CI will fail and it will have to be rolled back. The CI in flutter/packages runs against both master and stable branches of flutter/flutter, so any breaking change between the two will cause it to fail.
The reason why flutter/packages will fail is because material_ui has the corresponding test where this change was added (see our previous discussion earlier in this PR in https://github.com/flutter/flutter/pull/185152/changes#r3509107642).
The only way to land changes like this now is to make the change in a two-step way that is not breaking. For example, we could introduce a parallel getParagraphStyleWithHyphens and deprecate getParagraphStyle. We should probably think hard about what we want this API to look like given these constraints, though.
There was a problem hiding this comment.
I was second guessing myself and thinking maybe this isn't a breaking change because it's an optional parameter. But no, the problem is that it is implemented, not that it is called, in the Material tests.
There was a problem hiding this comment.
Ok I'm changing my mind. I now see that @LongCatIsLooong has landed flutter/packages#12728, which should be compatible with flutter/flutter both before and after this PR. The test below can also stay as it is, since it only runs off of the TextStyle that's in its version of flutter/flutter.
That said, we should cross our fingers and be ready to revert if it fails on the autoroller.
| String? ellipsis, | ||
| int? maxLines, | ||
| TextHeightBehavior? textHeightBehavior, | ||
| Hyphens? hyphens, |
There was a problem hiding this comment.
Ok I'm changing my mind. I now see that @LongCatIsLooong has landed flutter/packages#12728, which should be compatible with flutter/flutter both before and after this PR. The test below can also stay as it is, since it only runs off of the TextStyle that's in its version of flutter/flutter.
That said, we should cross our fingers and be ready to revert if it fails on the autoroller.
…ng order Applies mdebbar's review suggestions: document that the hyphen glyph isn't rendered on the web yet, and print hyphens after locale in the CanvasKit and Skwasm ParagraphStyle toString, as native does.
|
Applied @mdebbar's suggestions. The push reset the approvals and the CICD label, so could you all take another look, and could someone re-add CICD and add |
|
@dbebawy Congrats on finishing this and thank your for your perseverance! I think this feature is really worth it. |
Summary
Adds visible soft hyphen (U+00AD) rendering at line breaks, with a new
HyphensAPI to opt out. Fixes #18443 on native platforms; web rendering is tracked in #193506.A line break that falls on a soft hyphen now renders a visible
-by default. A newHyphens { manual, hidden }enum controls this:Hyphens.manual(default): render a hyphen at the break.Hyphens.hidden: don't render the hyphen. The soft hyphen is still a break opportunity.hyphensis a parameter onText/RichText,TextPainter,RenderParagraph,TextStyle.getParagraphStyleanddart:ui'sParagraphStyle, followingtextAlign/maxLines. It is not aTextStylefield. Customizing the hyphen string is deferred to #189617.Design doc: flutter.dev/go/soft-hyphens (tracking issue #185154).
Background
Flutter already breaks lines at U+00AD but never draws the hyphen, so it behaves like a zero-width space. This change doesn't add any new break opportunities or change where lines break, but the rendered hyphen makes its line wider, so it can change
Paragraph.longestLineand the size of text laid out withTextWidthBasis.longestLine.The Skia side landed in
98daf58d(Skia CL 1208837), behindParagraphStyle::setRenderSoftHyphens(bool), which defaults tofalsefor other Skia clients.Changes
Engine
txt::ParagraphStylegains arender_soft_hyphensflag (defaulttrue), whichParagraphBuilderSkia::TxtToSkiaforwards tosetRenderSoftHyphens. The Impeller interop toolkit buildstxt::ParagraphStyletoo, so it also gets the new default.paragraph_builder.ccdecodesParagraphStyle.hyphensfrom the FFI buffer (mask bit 13, slot 7).dart:uiadds the publicHyphensenum andParagraphStyle.hyphens. The webdart:uiaccepts and stores the value; web rendering is [web] Render soft hyphens (HyphensAPI) on CanvasKit, Skwasm, and WebParagraph #193506.Framework
Hyphensis re-exported fromdart:uiviapainting/basic_types.dart.hyphensis threaded throughTextStyle.getParagraphStyle→TextPainter→RenderParagraph→RichText→Text/Text.rich, including the selectable-text path.Breaking change
hyphenstoTextStyle.getParagraphStylebreaks classes that override it. In practice that's test doubles like the_TextStyleProxyintest/material/theme_test.dart, updated here; material_ui's copy was removed in [material_ui] Updatematerial_uitests to prevent them from failing when frameworkTextStylechanges packages#12728. The fix is to addHyphens? hyphensto the override. Migration guide: Add breaking change page for soft hyphen rendering website#13945.Hyphens.hiddenrestores the old look.Known limitations
maxWidthby the hyphen's advance.EditableText(and soTextField) andSelectableTextdon't take ahyphensparameter yet, so they always render the hyphen.Test plan
paragraph_builder_skia_tests.cc::RenderSoftHyphensEnabled:render_soft_hyphensmaps onto Skia in both statestesting/dart/text_test.dart:ParagraphStyle.hyphensencodes and round-trips intoStringtesting/dart/paragraph_test.dart:ParagraphStyle.hyphensreaches the text layout (a line that breaks at a soft hyphen is wider withmanualthan withhidden, and unset matchesmanual), which covers decoding the setting in the enginetest/widgets/rich_text_test.dart:RichTextexposeshyphensviadebugFillPropertiestest/widgets/text_test.dart:TextforwardshyphenstoRenderParagraph(incl. selectable path); default resolves toHyphens.manualtest/rendering/paragraph_test.dart:RenderParagraph.hyphenssetter triggers relayouttest/painting/text_painter_test.dart:TextPainter.hyphenssetter; the same width check throughTextPainter, and changinghyphensafter layout changes the widthText('inter\u00ADnational')in a narrow container showsinter-/national