@@ -9,6 +9,7 @@ package com.facebook.react.views.text
99
1010import android.content.res.AssetManager
1111import android.graphics.Color
12+ import android.graphics.RectF
1213import android.graphics.Typeface
1314import android.os.Build
1415import android.text.BoringLayout
@@ -41,6 +42,7 @@ import com.facebook.react.uimanager.PixelUtil
4142import com.facebook.react.uimanager.PixelUtil.dpToPx
4243import com.facebook.react.uimanager.PixelUtil.pxToDp
4344import com.facebook.react.uimanager.ReactAccessibilityDelegate
45+ import com.facebook.react.util.AndroidVersion.VERSION_CODE_VANILLA_ICE_CREAM
4446import com.facebook.react.views.text.internal.span.CustomLetterSpacingSpan
4547import com.facebook.react.views.text.internal.span.CustomLineHeightSpan
4648import com.facebook.react.views.text.internal.span.CustomStyleSpan
@@ -114,6 +116,69 @@ internal object TextLayoutManager {
114116
115117 private val tagToSpannableCache = ConcurrentHashMap <Int , Spannable >()
116118
119+ // These wrappers mirror Android 15 APIs but use reflection because some internal targets still
120+ // compile against Android 14. They return null when the API is unavailable or cannot be invoked.
121+ private val setUseBoundsForWidthMethod: java.lang.reflect.Method ? by lazy {
122+ try {
123+ StaticLayout .Builder ::class
124+ .java
125+ .getMethod(" setUseBoundsForWidth" , Boolean ::class .javaPrimitiveType)
126+ } catch (_: ReflectiveOperationException ) {
127+ null
128+ }
129+ }
130+
131+ private fun setUseBoundsForWidth (
132+ builder : StaticLayout .Builder ,
133+ useBoundsForWidth : Boolean ,
134+ ): StaticLayout .Builder ? =
135+ try {
136+ setUseBoundsForWidthMethod?.invoke(builder, useBoundsForWidth) as ? StaticLayout .Builder
137+ } catch (_: ReflectiveOperationException ) {
138+ null
139+ }
140+
141+ private val setShiftDrawingOffsetForStartOverhangMethod: java.lang.reflect.Method ? by lazy {
142+ try {
143+ StaticLayout .Builder ::class
144+ .java
145+ .getMethod(
146+ " setShiftDrawingOffsetForStartOverhang" ,
147+ Boolean ::class .javaPrimitiveType,
148+ )
149+ } catch (_: ReflectiveOperationException ) {
150+ null
151+ }
152+ }
153+
154+ private fun setShiftDrawingOffsetForStartOverhang (
155+ builder : StaticLayout .Builder ,
156+ shiftDrawingOffsetForStartOverhang : Boolean ,
157+ ): StaticLayout .Builder ? =
158+ try {
159+ setShiftDrawingOffsetForStartOverhangMethod?.invoke(
160+ builder,
161+ shiftDrawingOffsetForStartOverhang,
162+ ) as ? StaticLayout .Builder
163+ } catch (_: ReflectiveOperationException ) {
164+ null
165+ }
166+
167+ private val computeDrawingBoundingBoxMethod: java.lang.reflect.Method ? by lazy {
168+ try {
169+ Layout ::class .java.getMethod(" computeDrawingBoundingBox" )
170+ } catch (_: ReflectiveOperationException ) {
171+ null
172+ }
173+ }
174+
175+ private fun computeDrawingBoundingBox (layout : Layout ): RectF ? =
176+ try {
177+ computeDrawingBoundingBoxMethod?.invoke(layout) as ? RectF
178+ } catch (_: ReflectiveOperationException ) {
179+ null
180+ }
181+
117182 fun setCachedSpannableForTag (reactTag : Int , sp : Spannable ) {
118183 tagToSpannableCache[reactTag] = sp
119184 }
@@ -875,7 +940,7 @@ internal object TextLayoutManager {
875940 @VisibleForTesting
876941 internal fun getRtlRightOverhang (layout : Layout ): Int {
877942 if (
878- Build .VERSION .SDK_INT < Build . VERSION_CODES . VANILLA_ICE_CREAM ||
943+ Build .VERSION .SDK_INT < VERSION_CODE_VANILLA_ICE_CREAM ||
879944 layout.lineCount == 0 ||
880945 (0 until layout.lineCount).any {
881946 layout.getParagraphDirection(it) != Layout .DIR_RIGHT_TO_LEFT
@@ -884,7 +949,7 @@ internal object TextLayoutManager {
884949 return 0
885950 }
886951
887- val drawingBounds = layout. computeDrawingBoundingBox()
952+ val drawingBounds = computeDrawingBoundingBox(layout) ? : return 0
888953
889954 return ceil(drawingBounds.right - layout.width).toInt().coerceAtLeast(0 )
890955 }
@@ -925,9 +990,9 @@ internal object TextLayoutManager {
925990 // Android shifts negative (left-side) start overhang itself. RTL start overhang is on the
926991 // right, so createLayout reserves that space in a second pass while preserving the EXACT Yoga
927992 // measurement returned to the caller.
928- if (Build .VERSION .SDK_INT >= Build . VERSION_CODES . VANILLA_ICE_CREAM ) {
929- builder. setUseBoundsForWidth(enableStartOverhang)
930- builder. setShiftDrawingOffsetForStartOverhang(enableStartOverhang)
993+ if (Build .VERSION .SDK_INT >= VERSION_CODE_VANILLA_ICE_CREAM ) {
994+ setUseBoundsForWidth(builder, enableStartOverhang)
995+ setShiftDrawingOffsetForStartOverhang(builder, enableStartOverhang)
931996 }
932997
933998 return builder.build()
0 commit comments