fix(android): honor textDecorationColor on Text decorations#56767
fix(android): honor textDecorationColor on Text decorations#56767quantizor wants to merge 4 commits into
Conversation
Android's `Layout.draw` paints the underline produced by `setUnderlineText(true)` using `paint.color`, ignoring `paint.underlineColor` on all API levels. This caused `textDecorationColor` to be silently dropped on Android. Refactor `ReactUnderlineSpan` to extend `DrawCommandSpan` and paint the underline itself in `onDraw`, falling back to the text color when no color was specified. Thread the color through `TextAttributeProps` (both MapBuffer and ReadableMap ingestion paths) and `TextLayoutManager`. Add `DrawCommandSpan` invocation to `ReactTextView.onDraw`, mirroring the existing `PreparedLayoutTextView` behavior so both text view classes honor custom-drawing spans. ## Changelog [ANDROID] [FIXED] - Text underlines honor `textDecorationColor` ## Test Plan Render a Text component with `textDecorationColor` set to a value distinct from the text color; the underline now renders in the specified color rather than the text color. Verified on Android API 36 emulator.
|
@fabriziocucci has imported this pull request. If you are a Meta employee, you can view this in D104669839. |
|
Hey @quantizor , there's a potential issue we might need to double-check before we land. The new Could you try this repro and see if the underline still tracks the text? If the magenta underline appears at the top of the gray box while "Hello" is centered/at the bottom, we'll need to add the gravity offset back in. Roughly: (getVerticalOffset is private in TextView, so we have to recompute it.) PreparedLayoutTextView doesn't have this problem because it owns its own draw and uses preparedLayout.verticalOffset. |
The DrawCommandSpan draw block in `ReactTextView.onDraw` translated by
`getCompoundPaddingLeft(), getExtendedPaddingTop()` only, which works
for the default `Gravity.TOP` text alignment. When `textAlignVertical`
is `center` or `bottom` and the text is shorter than the view, the
glyphs are positioned by `super.onDraw` with an additional gravity
offset that the span draws were missing — so colored
underline/strikethrough rendered at the top of the view while the text
itself was vertically centered or bottom-aligned.
Recompute the offset locally (mirroring TextView.getVerticalOffset(),
which is private upstream) and add it to the translate before invoking
the span draws.
## Changelog
[ANDROID] [FIXED] - Custom text decorations track `textAlignVertical`
## Test Plan
Render a `<Text style={{ height: 200, textAlignVertical: 'center', textDecorationLine: 'underline', textDecorationColor: '#ff00aa', fontSize: 24 }}>Hello</Text>` inside a fixed-height parent and verify the magenta underline sits flush under "Hello" in the vertical center. Repeat with `textAlignVertical: 'bottom'`. Both now track the text; previously the underline stayed at the top of the box. Verified on Android API 36 emulator.
|
Good catch, @fabriziocucci. Confirmed the drift on Android API 36 with a tall |
|
Warning JavaScript API change detected This PR commits an update to
This change was flagged as: |
|
False positive: the PR diff is five Android Kotlin/Java files only, no |
Upstream facebook#56705 renamed DrawCommandSpan and dropped its `ReactSpan` marker; re-add the interface on the underline/strikethrough subclasses so `SetSpanOperation` still accepts them.
Summary:
textDecorationColoris declared onTextStyleAndroidin the public types but has no visible effect on Android: the underline (and strikethrough) always paint in the text's foreground color. iOS honors color correctly viaNSUnderlineColorAttributeName. This PR closes the Android gap.Android's
Layout.drawpaints the underline produced bysetUnderlineText(true)usingpaint.color, ignoringpaint.underlineColoron every API level. The same applies to strikethrough.ReactUnderlineSpanandReactStrikethroughSpannow extendDrawCommandSpanand paint the decoration themselves inonDrawviaCanvas.drawLine, so the requested color actually reaches the paint. The color is threaded throughTextAttributeProps(both MapBuffer and ReadableMap ingestion paths) andTextLayoutManager, falling back to the text color when no color is specified.ReactTextView.onDrawinvokesDrawCommandSpan.onDrawaftersuper.onDraw, mirroring whatPreparedLayoutTextView.onDrawalready did. Without this, the new spans have no effect on the older view class, which is what some Text components on the new architecture still route through.Resolves the long-standing #4579 (filed 2015), which was closed but never actually fixed at the platform level.
Companion PRs (independent, also targeting
main):DrawCommandSpanrefactor; whichever lands first leaves the other with a trivial conflict to resolve.Changelog:
[ANDROID] [FIXED] - Text underlines and strikethroughs honor
textDecorationColorTest Plan:
Render a
<Text>component withtextDecorationLineset to"underline"or"line-through"andtextDecorationColorset to a value distinct from the foreground color. On stock 0.85.2 the decoration renders in the text color; with this patch the decoration renders in the specified color. Verified on Android API 36 emulator across single-line and wrapped multi-line cases.