From a3f0be4e796ac371d855515ea15b0f82e907e405 Mon Sep 17 00:00:00 2001 From: Autopilot Bot Date: Mon, 26 Oct 2026 07:48:45 -0700 Subject: [PATCH] Limit Android 15 bounds-based text width to RTL text (#59504) Summary: On Android 15+, every exactly sized `StaticLayout` enables `setUseBoundsForWidth` and `setShiftDrawingOffsetForStartOverhang` to keep RTL line-start ink from clipping. The exact width usually comes from an earlier `AT_MOST` measurement, which is advance-based. When a font's glyph bounds extend past their advances (seen with some OEM system fonts), a single-line LTR `Text` with `numberOfLines={1}` no longer fits the width it was measured at, and gets ellipsized well inside its own frame. Only enable the bounds-based width and start-overhang shift when the text contains RTL characters, which is the case the start-overhang handling was added for. LTR-only text goes back to advance-based layout in both passes, so the layout matches the measurement. Changelog: [Android][Fixed] - Fix single-line LTR `Text` being ellipsized inside its measured width on Android 15+ Reviewed By: christophpurrer Differential Revision: D126965612 --- .../react/views/text/TextLayoutManager.kt | 7 +++++- .../TextLayoutManagerStartOverhangTest.kt | 23 +++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManager.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManager.kt index 8f762006af5b..d906e9d78e6b 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManager.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManager.kt @@ -902,7 +902,12 @@ internal object TextLayoutManager { } } } - val enableStartOverhang = widthYogaMeasureMode == YogaMeasureMode.EXACTLY + // Bounds-based width is limited to text with RTL characters. An EXACTLY width usually comes + // from an earlier advance-based AT_MOST measurement, and LTR glyph bounds that are wider than + // their advances (some OEM fonts) would make a single line overflow that width and ellipsize. + val enableStartOverhang = + widthYogaMeasureMode == YogaMeasureMode.EXACTLY && + TextDirectionHeuristics.ANYRTL_LTR.isRtl(text, 0, text.length) val layout = buildLayout( text, diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/views/text/TextLayoutManagerStartOverhangTest.kt b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/views/text/TextLayoutManagerStartOverhangTest.kt index ecbdbf71a694..ee7001d3ba76 100644 --- a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/views/text/TextLayoutManagerStartOverhangTest.kt +++ b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/views/text/TextLayoutManagerStartOverhangTest.kt @@ -46,6 +46,29 @@ class TextLayoutManagerStartOverhangTest { assertThat(getBooleanLayoutProperty(layout, "getShiftDrawingOffsetForStartOverhang")).isFalse() } + @Test + @Config(sdk = [35]) + fun `EXACTLY mode keeps advance based width measurement for LTR text`() { + val layout = + createLayout( + YogaMeasureMode.EXACTLY, + text = SpannableString("Just listed"), + ellipsizeMode = TextUtils.TruncateAt.END, + maxNumberOfLines = 1, + ) + + assertThat(getBooleanLayoutProperty(layout, "getUseBoundsForWidth")).isFalse() + assertThat(getBooleanLayoutProperty(layout, "getShiftDrawingOffsetForStartOverhang")).isFalse() + } + + @Test + @Config(sdk = [35]) + fun `EXACTLY mode enables start overhang support for mixed LTR and RTL text`() { + val layout = createLayout(YogaMeasureMode.EXACTLY, text = SpannableString("Hello שלום")) + + assertThat(getBooleanLayoutProperty(layout, "getUseBoundsForWidth")).isTrue() + } + @Test @Config(sdk = [35]) fun `RTL right overhang is rounded up to reserve whole pixels`() {