Skip to content

Commit 9c849cd

Browse files
committed
fix(android): honor textDecorationColor on Text strikethrough
1 parent 89090c5 commit 9c849cd

2 files changed

Lines changed: 66 additions & 5 deletions

File tree

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManager.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -318,7 +318,7 @@ internal object TextLayoutManager {
318318
ops.add(SetSpanOperation(start, end, ReactUnderlineSpan(textAttributes.textDecorationColor)))
319319
}
320320
if (textAttributes.isLineThroughTextDecorationSet) {
321-
ops.add(SetSpanOperation(start, end, ReactStrikethroughSpan()))
321+
ops.add(SetSpanOperation(start, end, ReactStrikethroughSpan(textAttributes.textDecorationColor)))
322322
}
323323
if (
324324
(textAttributes.textShadowOffsetDx != 0f ||
@@ -498,7 +498,7 @@ internal object TextLayoutManager {
498498
}
499499

500500
if (fragment.props.isLineThroughTextDecorationSet) {
501-
spannable.setSpan(ReactStrikethroughSpan(), start, end, spanFlags)
501+
spannable.setSpan(ReactStrikethroughSpan(fragment.props.textDecorationColor), start, end, spanFlags)
502502
}
503503

504504
if (

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/ReactStrikethroughSpan.kt

Lines changed: 64 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,68 @@
77

88
package com.facebook.react.views.text.internal.span
99

10-
import android.text.style.StrikethroughSpan
10+
import android.graphics.Canvas
11+
import android.graphics.Color
12+
import android.os.Build
13+
import android.text.Layout
14+
import kotlin.math.max
1115

12-
/** Wraps [StrikethroughSpan] as a [ReactSpan]. */
13-
internal class ReactStrikethroughSpan : StrikethroughSpan(), ReactSpan
16+
/**
17+
* Draws a strikethrough whose color may differ from the text color. Subclasses
18+
* [DrawCommandSpan] so [PreparedLayoutTextView] and [ReactTextView] invoke
19+
* [onDraw] after the layout renders its text. We do NOT extend
20+
* [android.text.style.StrikethroughSpan] here: the framework's `Layout.draw`
21+
* paints the strikethrough using `paint.color` with no field to override,
22+
* so the only way to get a distinct color is to draw it ourselves.
23+
*
24+
* When [color] is [Color.TRANSPARENT] (the default when no
25+
* `textDecorationColor` prop was passed), the strikethrough is drawn in the
26+
* text's foreground color, matching the platform's prior behavior.
27+
*/
28+
internal class ReactStrikethroughSpan(private val color: Int = Color.TRANSPARENT) :
29+
DrawCommandSpan() {
30+
31+
override fun onDraw(start: Int, end: Int, canvas: Canvas, layout: Layout) {
32+
val paint = layout.paint
33+
val savedColor = paint.color
34+
val savedStrokeWidth = paint.strokeWidth
35+
val savedStyle = paint.style
36+
val savedAntiAlias = paint.isAntiAlias
37+
val effectiveColor = if (color != Color.TRANSPARENT) color else savedColor
38+
val thickness =
39+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
40+
max(paint.underlineThickness, 1.5f)
41+
} else {
42+
max(paint.fontMetrics.descent * 0.1f, 1.5f)
43+
}
44+
45+
paint.color = effectiveColor
46+
paint.strokeWidth = thickness
47+
paint.style = android.graphics.Paint.Style.STROKE
48+
paint.isAntiAlias = true
49+
50+
// Position the strikethrough at the midpoint between the line's top
51+
// and baseline so it sits near the x-height midline like the platform
52+
// default. `fontMetrics.ascent` is negative and `descent` is positive,
53+
// so the sum / 2 gives a small negative offset from the baseline.
54+
val fm = paint.fontMetrics
55+
val offset = (fm.ascent + fm.descent) / 2f
56+
57+
val startLine = layout.getLineForOffset(start)
58+
val endLine = layout.getLineForOffset(end)
59+
for (line in startLine..endLine) {
60+
val baseline = layout.getLineBaseline(line).toFloat()
61+
val x1 =
62+
if (line == startLine) layout.getPrimaryHorizontal(start) else layout.getLineLeft(line)
63+
val x2 =
64+
if (line == endLine) layout.getPrimaryHorizontal(end) else layout.getLineRight(line)
65+
val y = baseline + offset
66+
canvas.drawLine(x1, y, x2, y, paint)
67+
}
68+
69+
paint.color = savedColor
70+
paint.strokeWidth = savedStrokeWidth
71+
paint.style = savedStyle
72+
paint.isAntiAlias = savedAntiAlias
73+
}
74+
}

0 commit comments

Comments
 (0)