Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -114,6 +116,69 @@ internal object TextLayoutManager {

private val tagToSpannableCache = ConcurrentHashMap<Int, Spannable>()

// 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
}
Expand Down Expand Up @@ -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
Expand All @@ -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)
}
Expand Down Expand Up @@ -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()
Expand Down
Loading