feat(android): honor textDecorationStyle on Text decorations#56768
Open
quantizor wants to merge 2 commits into
Open
feat(android): honor textDecorationStyle on Text decorations#56768quantizor wants to merge 2 commits into
quantizor wants to merge 2 commits into
Conversation
`textDecorationStyle` is declared on `TextStyleAndroid` in the public types but `TA_KEY_TEXT_DECORATION_STYLE` was a no-op handler: every value silently rendered as a solid line. This PR wires the prop through the existing C++ → Kotlin pipeline and implements `solid`, `double`, `dotted`, `dashed`, and `wavy` for both underlines and strikethroughs. Background: Android's `Layout.draw` paints the underline produced by `setUnderlineText(true)` using `paint.color`, ignoring `paint.underlineColor` on every API level, and offers no native way to draw a dotted / dashed / wavy decoration. The same applies to strikethrough. `ReactUnderlineSpan` and `ReactStrikethroughSpan` now extend `DrawCommandSpan` and paint the decoration themselves in `onDraw` via `Canvas.drawLine` / `Canvas.drawPath`, dispatching by style. This also makes `textDecorationColor` reach the paint as a side effect, closing a separate long-standing gap (see facebook#4579 from 2015). `TextDecorationStyle::Wavy` is added to the Fabric C++ primitives / conversions so the JS value flows through instead of being rejected with an `Unsupported value` log; the same enum is shared with iOS. The wavy curve uses Chromium/Blink's formula from `decoration_line_painter.cc` (`wavelength = 1 + 2 * round(2 * thickness + 0.5)`, `controlPointDistance = 0.5 + round(3 * thickness + 0.5)`, one cubic Bezier per wavelength with both control points at the midpoint, one above and one below the y-axis). The minimum stroke thickness is density-aware (1.5 dp) so decorations read consistently across display densities. The drawing loop iterates `while x < x2` so the final cycle continues through the last character (including trailing punctuation that would otherwise be visually uncovered when the run width is not an integer multiple of the wavelength). `ReactTextView.onDraw` invokes `DrawCommandSpan.onDraw` after `super.onDraw`, mirroring what `PreparedLayoutTextView.onDraw` already 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. ## Changelog: [GENERAL] [ADDED] - `textDecorationStyle: 'wavy'` for `<Text>` (see corresponding iOS PR for the iOS counterpart) [ANDROID] [ADDED] - Text decorations honor `textDecorationStyle` (`solid`, `double`, `dotted`, `dashed`, `wavy`) ## Test Plan: Rendered `<Text>` components with `textDecorationLine` set to `"underline"` or `"line-through"` and `textDecorationStyle` cycling through `solid` / `double` / `dotted` / `dashed` / `wavy`. On stock 0.85.2 every value renders as a solid line and `wavy` logs an `Unsupported value` warning; with this patch each style renders with the requested stroke geometry. Verified single-line and wrapped multi-line cases on an Android API 36 emulator: each visual line within a wrapped block receives its own correctly-styled decoration that starts and ends at the line's content boundaries. ```tsx <Text style={{ color: 'black', textDecorationLine: 'underline', textDecorationStyle: 'wavy', }}> Hello </Text> ```
This was referenced May 11, 2026
|
@CalixTang has imported this pull request. If you are a Meta employee, you can view this in D104680895. |
|
Warning JavaScript API change detected This PR commits an update to
This change was flagged as: |
Three CI breakages were introduced by changes that landed on `main` between the original PR push and now: 1. PR facebook#56705 renamed `DrawCommandSpan` to `CanvasEffectSpan` and dropped the `ReactSpan` / `UpdateAppearance` interfaces from the base class. `ReactUnderlineSpan` and `ReactStrikethroughSpan` were extending the old name; rename them and re-declare `ReactSpan` so they remain valid `SetSpanOperation` arguments. `ReactTextView.onDraw` updated to import the new name. 2. Adding `Wavy` to `facebook::react::TextDecorationStyle` (this PR) left `RCTNSUnderlineStyleFromTextDecorationStyle` non-exhaustive, tripping `-Werror,-Wreturn-type` on iOS builds. Add a `Wavy` case that falls back to a solid underline (the actual wavy rendering ships in companion PR facebook#56769). 3. `validate_cxx_api_snapshots` flagged the missing `Wavy` entry in all six snapshots under `scripts/cxx-api/api-snapshots/`. Regenerate. No behavior change beyond what was already in the feature commit; this is purely "rebase the implementation onto current `main`."
3b58384 to
676ed17
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary:
textDecorationStyleis declared onTextStyleAndroidin the public types butTA_KEY_TEXT_DECORATION_STYLEwas a no-op handler: every value silently rendered as a solid line, andwavywas additionally rejected at the Fabric C++ enum boundary with anUnsupported valuelog. This PR wires the prop through the existing C++ → Kotlin pipeline and implementssolid,double,dotted,dashed, andwavyfor both underlines and strikethroughs.Android's
Layout.drawpaints the underline produced bysetUnderlineText(true)usingpaint.coloronly and offers no native way to draw a dotted / dashed / wavy decoration.ReactUnderlineSpanandReactStrikethroughSpannow extendDrawCommandSpanand paint the decoration themselves inonDrawviaCanvas.drawLine/Canvas.drawPath, dispatching by style. As a side effect this also makestextDecorationColorreach the paint, closing the separate long-standing gap filed as #4579 in 2015 — the companion color-focused PR #56767 isolates that fix for reviewers who only want the color change.TextDecorationStyle::Wavyis added to the Fabric C++ primitives / conversions so thewavyJS value flows through; the same enum is shared with iOS (see companion iOS PR #56769).The wavy curve uses Chromium/Blink's formula from
decoration_line_painter.cc(wavelength = 1 + 2 * round(2 * thickness + 0.5),controlPointDistance = 0.5 + round(3 * thickness + 0.5), one cubic Bezier per wavelength with both control points at the midpoint, one above and one below the y-axis). The minimum stroke thickness is density-aware (1.5 dp) so decorations read consistently across display densities. The drawing loop iterateswhile x < x2so the final cycle continues through the last character (including trailing punctuation that would otherwise be visually uncovered when the run width is not an integer multiple of the wavelength).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.Companion PRs (independent, also targeting
main):TextDecorationStyle::Wavyenum addition; whichever lands first leaves the other with a trivial conflict to resolve.Changelog:
[GENERAL] [ADDED] -
textDecorationStyle: 'wavy'for<Text>(see corresponding iOS PR for the iOS counterpart)[ANDROID] [ADDED] - Text decorations honor
textDecorationStyle(solid,double,dotted,dashed,wavy)Test Plan:
Rendered
<Text>components withtextDecorationLineset to"underline"or"line-through"andtextDecorationStylecycling throughsolid/double/dotted/dashed/wavy. On stock 0.85.2 every value renders as a solid line andwavylogs anUnsupported valuewarning; with this patch each style renders with the requested stroke geometry. Verified single-line and wrapped multi-line cases on an Android API 36 emulator: each visual line within a wrapped block receives its own correctly-styled decoration that starts and ends at the line's content boundaries.