From fc7ca37354a7ac3fce40b878e4560aee58bf1f2d Mon Sep 17 00:00:00 2001 From: Dark Knight Date: Mon, 28 Sep 2026 18:49:46 -0700 Subject: [PATCH] Revert D122079801 Summary: Reverts https://github.com/react/react-native/pull/58719 Changelog: [Internal] Reviewed By: Abbondanzo Differential Revision: D122273888 --- .../react/views/text/TextLayoutManager.kt | 75 +++++++++++++++++-- 1 file changed, 70 insertions(+), 5 deletions(-) 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 6cf683d666a8..53a67377158f 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 @@ -9,6 +9,7 @@ package com.facebook.react.views.text import android.content.res.AssetManager import android.graphics.Color +import android.graphics.RectF import android.graphics.Typeface import android.os.Build import android.text.BoringLayout @@ -41,6 +42,7 @@ import com.facebook.react.uimanager.PixelUtil import com.facebook.react.uimanager.PixelUtil.dpToPx import com.facebook.react.uimanager.PixelUtil.pxToDp import com.facebook.react.uimanager.ReactAccessibilityDelegate +import com.facebook.react.util.AndroidVersion.VERSION_CODE_VANILLA_ICE_CREAM import com.facebook.react.views.text.internal.span.CustomLetterSpacingSpan import com.facebook.react.views.text.internal.span.CustomLineHeightSpan import com.facebook.react.views.text.internal.span.CustomStyleSpan @@ -114,6 +116,69 @@ internal object TextLayoutManager { private val tagToSpannableCache = ConcurrentHashMap() + // These wrappers mirror Android 15 APIs but use reflection because some internal targets still + // compile against Android 14. They return null when the API is unavailable or cannot be invoked. + private val setUseBoundsForWidthMethod: java.lang.reflect.Method? by lazy { + try { + StaticLayout.Builder::class + .java + .getMethod("setUseBoundsForWidth", Boolean::class.javaPrimitiveType) + } catch (_: ReflectiveOperationException) { + null + } + } + + private fun setUseBoundsForWidth( + builder: StaticLayout.Builder, + useBoundsForWidth: Boolean, + ): StaticLayout.Builder? = + try { + setUseBoundsForWidthMethod?.invoke(builder, useBoundsForWidth) as? StaticLayout.Builder + } catch (_: ReflectiveOperationException) { + null + } + + private val setShiftDrawingOffsetForStartOverhangMethod: java.lang.reflect.Method? by lazy { + try { + StaticLayout.Builder::class + .java + .getMethod( + "setShiftDrawingOffsetForStartOverhang", + Boolean::class.javaPrimitiveType, + ) + } catch (_: ReflectiveOperationException) { + null + } + } + + private fun setShiftDrawingOffsetForStartOverhang( + builder: StaticLayout.Builder, + shiftDrawingOffsetForStartOverhang: Boolean, + ): StaticLayout.Builder? = + try { + setShiftDrawingOffsetForStartOverhangMethod?.invoke( + builder, + shiftDrawingOffsetForStartOverhang, + ) as? StaticLayout.Builder + } catch (_: ReflectiveOperationException) { + null + } + + private val computeDrawingBoundingBoxMethod: java.lang.reflect.Method? by lazy { + try { + Layout::class.java.getMethod("computeDrawingBoundingBox") + } catch (_: ReflectiveOperationException) { + null + } + } + + private fun computeDrawingBoundingBox(layout: Layout): RectF? = + try { + computeDrawingBoundingBoxMethod?.invoke(layout) as? RectF + } catch (_: ReflectiveOperationException) { + null + } + fun setCachedSpannableForTag(reactTag: Int, sp: Spannable) { tagToSpannableCache[reactTag] = sp } @@ -875,7 +940,7 @@ internal object TextLayoutManager { @VisibleForTesting internal fun getRtlRightOverhang(layout: Layout): Int { if ( - Build.VERSION.SDK_INT < Build.VERSION_CODES.VANILLA_ICE_CREAM || + Build.VERSION.SDK_INT < VERSION_CODE_VANILLA_ICE_CREAM || layout.lineCount == 0 || (0 until layout.lineCount).any { layout.getParagraphDirection(it) != Layout.DIR_RIGHT_TO_LEFT @@ -884,7 +949,7 @@ internal object TextLayoutManager { return 0 } - val drawingBounds = layout.computeDrawingBoundingBox() + val drawingBounds = computeDrawingBoundingBox(layout) ?: return 0 return ceil(drawingBounds.right - layout.width).toInt().coerceAtLeast(0) } @@ -925,9 +990,9 @@ internal object TextLayoutManager { // Android shifts negative (left-side) start overhang itself. RTL start overhang is on the // right, so createLayout reserves that space in a second pass while preserving the EXACT Yoga // measurement returned to the caller. - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) { - builder.setUseBoundsForWidth(enableStartOverhang) - builder.setShiftDrawingOffsetForStartOverhang(enableStartOverhang) + if (Build.VERSION.SDK_INT >= VERSION_CODE_VANILLA_ICE_CREAM) { + setUseBoundsForWidth(builder, enableStartOverhang) + setShiftDrawingOffsetForStartOverhang(builder, enableStartOverhang) } return builder.build()