Repository navigation
Draw ellipsized text without shaping its hidden text on Android - #59644
Open
hryhoriiK97 wants to merge 2 commits into
Open
hryhoriiK97 wants to merge 2 commits into
hryhoriiK97 wants to merge 2 commits into
Conversation
The last line of an ellipsized layout keeps all the text after the ellipsis, as zero-width characters, and Android shapes all of it every time the layout is drawn. For a long text cut to a few lines, that is most of the work of drawing it, on the main thread. Behind the new enableEllipsizedTextTrimmingAndroid flag, when a prepared layout is ellipsized at the end, wrap it in EllipsisTrimmedLayout, a delegating Layout like MutableSpannableLayout whose text stops right after the ellipsis character and whose line metrics come from the original layout, so nothing is laid out again. The original layout is kept when the text is justified or hyphenated, has right-to-left characters, is centered or end-aligned with letter spacing in its hidden part, or when its hidden part has links, replacement spans (such as inline views), text effects or touchable spans. Selectable text, which lays the text out again in a TextView, still gets the whole text. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Keep the whole text when it has any right-to-left or directional formatting character. TextDirectionHeuristics.ANYRTL_LTR skips bidi isolates, but their runs are still reordered, so a hidden run could come before visible ones and stop the trimmed line from drawing them. - Keep the whole text when it has letter spacing, whatever the alignment: before Android 15 the hidden characters are spaced too. - Don't delegate getLineMax, getLineWidth, getLineLeft and getLineRight: StaticLayout computes them by shaping its whole last line, hidden text included, so this layout computes them from its own, shorter text. - Check justification and hyphenation in createIfNeeded, look for all the hidden span types in one pass, and move the flag check to TextLayoutManager.layoutForDrawing and the selectable-text lookup to EllipsisTrimmedLayout.untrimmedText, so both can be tested. - Test isolates, letter spacing, justification and hyphenation, stateful spans through MutableSpannableLayout, the feature flag and untrimmedText. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@andrewdacenko has imported this pull request. If you are a Meta employee, you can view this in D128621322. |
This branch has not been deployed
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.
Summary:
On Android, the last line of an ellipsized layout keeps all the text after the ellipsis.
StaticLayoutputs the rest of the text on that line andTextLinereplaces the hidden part with zero-width characters when drawing, so every time the layout is drawn, all of the hidden text is shaped again on the main thread, most of it missing minikin's layout cache. For a long text cut to a few lines, such as a message, comment or article preview withnumberOfLines, that is most of the work of drawing it.On a moto g57 power, a screen of 60 one-line texts with long hidden text spent 54% of its main thread constructing
minikin::LayoutPieces underPreparedLayoutTextView.onDraw, and cutting the same strings to 80 characters in JS made the main thread 40% cheaper.With this change, behind a new flag,
enableEllipsizedTextTrimmingAndroid(off by default), when a prepared layout is ellipsized at the end,TextLayoutManager.createPreparedLayoutwraps it in a newEllipsisTrimmedLayout: a delegatingLayout, likeMutableSpannableLayout, whose text stops right after the ellipsis character and whose line metrics all come from the original layout. Nothing is laid out again, and the visible lines are the same. Measurement gets the same line metrics, andonTextLayoutlines are unchanged.The original layout is kept when the hidden text matters for more than drawing:
Line widths (
getLineMax,getLineWidth,getLineLeft,getLineRight) are computed from the trimmed text rather than delegated, sinceStaticLayoutcomputes them by shaping the whole last line, hidden text included. Selectable text, which lays the text out again in aTextView, still gets the whole text (EllipsisTrimmedLayout.untrimmedText).PreparedLayout.sourceText, which accessibility and the view hierarchy read, is still the full text.An alternative would be to keep
PreparedLayout.layoutas the original and add a separate layout used only for drawing and hit-testing, which would make some of these exclusions unnecessary but changesPreparedLayout; this PR keeps the existing shape.Changelog:
[ANDROID] [ADDED] -
enableEllipsizedTextTrimmingAndroidfeature flag: draw ellipsized text without shaping its hidden textTest Plan:
EllipsisTrimmedLayoutTest, run on API 28, 30, 34 and 35:MutableSpannableLayout;untrimmedTextreturns the whole text../gradlew :packages:react-native:ReactAndroid:testDebugUnitTest --tests 'com.facebook.react.views.text.*'passes (177 tests).yarn format-check-kotlinreports no changes.Pixel_9_ProAVD), with one RNTester release APK in which the flag is replaced by a system property, so both arms are the same binary:numberOfLines(one- and two-line ellipsized text, a large font, theonTextLayoutexamples with tail and head ellipsis): the screen is pixel-identical with the trimming off and on, including theonTextLayoutlines it prints./procover 20 s, means of three alternating rounds:These rounds ran before the review changes above, which don't apply to these screens (no RTL text or letter spacing). With the final version, the
numberOfLinesscreen is again pixel-identical, and two more alternating rounds gave −54% and −81% (restyled), −60% and −52% (new text), −7% and −16% (feed) on the main thread, with the control within noise.<Text onPress>link on it fired with both.dumpsys gfxinfo)The emulator renders too few frames to measure jank, so the janky-frame numbers are from the device only.
🤖 Generated with Claude Code