Skip to content

Skip unused getDesiredWidth pass in Android text layout - #59064

Draft
tomekzaw wants to merge 1 commit into
react:mainfrom
tomekzaw:android-text-skip-unused-desired-width
Draft

tomekzaw wants to merge 1 commit into
react:mainfrom
tomekzaw:android-text-skip-unused-desired-width

Conversation

@tomekzaw

Copy link
Copy Markdown
Contributor

Summary:

For text that is not laid out as a BoringLayout, TextLayoutManager.createLayout (Kotlin) calls Layout.getDesiredWidth(text, paint) before building the StaticLayout. That call is a full measuring pass over the string, and its result is only needed to choose the layout width.

Current behavior. The pass runs even when its result cannot matter:

  • When Yoga's width mode is EXACTLY, the result is discarded.
  • When the text was "boring" but too wide for an AT_MOST width, the result is redundant. boring.width is already the unwrapped width of the same text, and it exceeded floor(width), so min(desiredWidth, floor(width)) is always floor(width).

For right-to-left and complex scripts isBoring bails out early, so getDesiredWidth is the pass that pays for shaping, and its result is then thrown away.

With this change. The pass is skipped in those two cases, and the resulting layout widths are identical.

Why. Text measurement runs on the JS thread during commit. For Arabic text this pass was 37% of createLayout.

Changelog:

[ANDROID] [CHANGED] - Skip an unnecessary measuring pass when laying out Text

Test Plan:

  • ./gradlew :packages:react-native:ReactAndroid:testDebugUnitTest --tests "com.facebook.react.views.text.*": 60 tests, 0 failures.
  • yarn format-check-kotlin and RNTester assembleDebug pass.
  • Screenshots were pixel-identical before and after for long Latin paragraphs and Arabic text.

Benchmarks. RNTester release build with a text benchmark screen that is not part of this PR.

Per-function times from Perfetto with temporary trace sections. Emulator (Pixel 10 Pro AVD, API 37), the same logic on an earlier main, medians of 4 runs, mount phase. measureContent covers all text measurement on the JS thread:

content getDesiredWidth time removed createLayout measureContent
300 Arabic strings 12.8 ms 34.8 → 24.9 ms (−28%) 39.8 → 30.8 ms (−23%)
100 social-feed posts 1.9 ms 6.0 → 4.6 ms (−24%) 11.6 → 10.2 ms (−13%)
300 Chinese strings 2.3 ms 12.9 → 11.1 ms (−14%) 18.6 → 17.2 ms (−7%)
100 rich paragraphs (nested spans) 2.0 ms 9.8 → 8.4 ms (−14%) 15.8 → 14.6 ms (−7%)
100 long Latin paragraphs 3.0 ms 15.0 → 14.5 ms (−3%) no change (StaticLayout then pays for shaping instead)
1000 one-line labels 0 (all BoringLayout) – –

Untraced JS-thread CPU, current main vs. this branch, same emulator, 6 runs: long paragraphs −15% on mount and −29% on a text update. In the other scenarios the difference is within emulator run-to-run noise at the whole-thread level.

🤖 Generated with Claude Code

TextLayoutManager.createLayout measured the whole string with
Layout.getDesiredWidth before building a StaticLayout even when the result
could not affect the layout width: for an EXACTLY width, and for boring text
that did not fit (min(desired, available) is then always available).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Oct 14, 2026
@facebook-github-tools facebook-github-tools Bot added p: Software Mansion Partner: Software Mansion Partner labels Oct 14, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. p: Software Mansion Partner: Software Mansion Partner

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant