Skip to content

Commit 73efe7d

Browse files
cortinicometa-codesync[bot]
authored andcommitted
Fix text decoration line thickness regression on Android
Summary: D104680895 replaced the native Android `UnderlineSpan`/`StrikethroughSpan` with custom `CanvasEffectSpan` canvas drawing to support `textDecorationStyle`. The custom drawing enforces a minimum stroke thickness of `1.5f * density` (1.5dp), which is noticeably thicker than what the native framework spans produce. This diff removes the 1.5dp minimum for SOLID style so the decoration thickness matches the native `Paint.getUnderlineThickness()` value, restoring the pre-D104680895 visual behavior. The minimum is kept for all other styles (DOUBLE, DOTTED, DASHED, WAVY) since they need the thickness for their visual patterns to render correctly (dash intervals, dot sizes, bezier wavelength). **Remaining work:** the underline position (`baseline + thickness + 1f` in `ReactUnderlineSpan.kt`) also depends on thickness, so with a thinner SOLID stroke the underline sits closer to the text than before D104680895. The pre-D104680895 behavior used the native framework `UnderlineSpan` which positions the underline via `Paint.getUnderlinePosition()`. Attempted using `baseline + paint.underlinePosition + thickness / 2f` and `+ thickness` on API 29+ but neither matched the native positioning exactly. This needs further investigation to match the original vertical gap between text and underline. Differential Revision: D107866867
1 parent 2546ce4 commit 73efe7d

1 file changed

Lines changed: 27 additions & 8 deletions

File tree

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

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,8 @@ private val dashedEffects =
6565
* allocations.
6666
*
6767
* WAVY wavelength and control-point distance follow Chromium/Blink's `decoration_line_painter.cc`.
68-
* DOUBLE uses a wider gap than Blink (`thickness + 2` vs `thickness + 1`) to keep both strokes
69-
* visually distinct with antialiasing on mobile displays.
68+
* DOUBLE uses a density-scaled gap (`thickness + 2dp`) to keep both strokes visually distinct with
69+
* antialiasing on mobile displays.
7070
*/
7171
private fun drawDecorationLine(
7272
canvas: Canvas,
@@ -75,13 +75,14 @@ private fun drawDecorationLine(
7575
x2: Float,
7676
y: Float,
7777
thickness: Float,
78+
density: Float,
7879
style: TextDecorationStyle,
7980
reusablePath: Path,
8081
) {
8182
when (style) {
8283
TextDecorationStyle.SOLID -> canvas.drawLine(x1, y, x2, y, paint)
8384
TextDecorationStyle.DOUBLE -> {
84-
val gap = thickness + 2f
85+
val gap = thickness + 2f * density
8586
canvas.drawLine(x1, y, x2, y, paint)
8687
canvas.drawLine(x1, y + gap, x2, y + gap, paint)
8788
}
@@ -140,12 +141,20 @@ internal fun drawSpannedDecoration(
140141
if (fgSpans != null && fgSpans.isNotEmpty()) fgSpans.last().foregroundColor
141142
else textPaint.color
142143
}
143-
val minThickness = 1.5f * textPaint.density
144144
val thickness =
145-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
146-
max(textPaint.underlineThickness, minThickness)
145+
if (style == TextDecorationStyle.SOLID || style == TextDecorationStyle.DOUBLE) {
146+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
147+
textPaint.underlineThickness
148+
} else {
149+
textPaint.fontMetrics.descent * 0.1f
150+
}
147151
} else {
148-
max(textPaint.fontMetrics.descent * 0.1f, minThickness)
152+
val minThickness = 1.5f * textPaint.density
153+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
154+
max(textPaint.underlineThickness, minThickness)
155+
} else {
156+
max(textPaint.fontMetrics.descent * 0.1f, minThickness)
157+
}
149158
}
150159

151160
decorationPaint.set(textPaint)
@@ -176,6 +185,16 @@ internal fun drawSpannedDecoration(
176185
val x1 = min(rawX1, rawX2)
177186
val x2 = max(rawX1, rawX2)
178187
val y = yOffsetForLine(decorationPaint, baseline, thickness)
179-
drawDecorationLine(canvas, decorationPaint, x1, x2, y, thickness, style, scratchPath)
188+
drawDecorationLine(
189+
canvas,
190+
decorationPaint,
191+
x1,
192+
x2,
193+
y,
194+
thickness,
195+
textPaint.density,
196+
style,
197+
scratchPath,
198+
)
180199
}
181200
}

0 commit comments

Comments
 (0)