From a20fbe6115f03544cab65a3349c117b59c875c19 Mon Sep 17 00:00:00 2001 From: Nicola Corti Date: Fri, 5 Jun 2026 05:38:45 -0700 Subject: [PATCH] Fix text decoration color fallback to use per-span foreground color Summary: D104680895 introduced custom decoration drawing that falls back to `layout.paint.color` when `textDecorationColor` is not set. This is the base paint color (typically black), not the actual foreground color of the text span. For text with `ForegroundColorSpan` (e.g., link text with teal color, white text on dark backgrounds), the decoration was drawn in the wrong color. Fix: when `textDecorationColor` is TRANSPARENT (default), look up the `ForegroundColorSpan` at the decoration start position and use its color instead. Changelog: [Android][Fixed] - Fix text decoration color not matching foreground color when `textDecorationColor` is not set Differential Revision: D107645191 --- .../react/views/text/TextDecorationStyle.kt | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextDecorationStyle.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextDecorationStyle.kt index eef94858088..acd4200eb55 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextDecorationStyle.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextDecorationStyle.kt @@ -14,6 +14,8 @@ import android.graphics.Paint import android.graphics.Path import android.os.Build import android.text.Layout +import android.text.Spanned +import android.text.style.ForegroundColorSpan import kotlin.math.max import kotlin.math.min import kotlin.math.roundToInt @@ -126,7 +128,18 @@ internal fun drawSpannedDecoration( yOffsetForLine: (paint: Paint, baseline: Float, thickness: Float) -> Float, ) { val textPaint = layout.paint - val effectiveColor = if (color != Color.TRANSPARENT) color else textPaint.color + val effectiveColor = + if (color != Color.TRANSPARENT) { + color + } else { + // Look up the actual foreground color at the span position. layout.paint + // is the base paint whose color may differ from per-span foreground colors + // applied via ForegroundColorSpan (e.g., link text, colored text). + val spanned = layout.text as? Spanned + val fgSpans = spanned?.getSpans(start, start + 1, ForegroundColorSpan::class.java) + if (fgSpans != null && fgSpans.isNotEmpty()) fgSpans.last().foregroundColor + else textPaint.color + } val minThickness = 1.5f * textPaint.density val thickness = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {