From 4f13b639ae062f01ff21f2262eff5554056bad20 Mon Sep 17 00:00:00 2001 From: Nick Gerleman Date: Wed, 11 Feb 2026 12:38:36 -0800 Subject: [PATCH] Add DrawCommandSpan Usable with PreparedLayoutTextView Summary: This allows character styling to be implemented, which can draw to the canvas, before or after main text draw, with full access to layout information. This allows very efficiently drawing some effects not possible on TextView. Changelog: [Android][Added] -Add Unstable DrawCommandSpan Usable with PreparedLayoutTextView Reviewed By: Abbondanzo Differential Revision: D92796048 --- .../views/text/PreparedLayoutTextView.kt | 27 +++++++++++++++++++ .../text/internal/span/DrawCommandSpan.kt | 27 +++++++++++++++++++ 2 files changed, 54 insertions(+) create mode 100644 packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/DrawCommandSpan.kt diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/PreparedLayoutTextView.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/PreparedLayoutTextView.kt index 38570086f38e1f..afd3cefc09e0f7 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/PreparedLayoutTextView.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/PreparedLayoutTextView.kt @@ -27,6 +27,7 @@ import com.facebook.proguard.annotations.DoNotStrip import com.facebook.react.uimanager.BackgroundStyleApplicator import com.facebook.react.uimanager.ReactCompoundView import com.facebook.react.uimanager.style.Overflow +import com.facebook.react.views.text.internal.span.DrawCommandSpan import com.facebook.react.views.text.internal.span.ReactFragmentIndexSpan import kotlin.collections.ArrayList import kotlin.math.roundToInt @@ -119,11 +120,37 @@ internal class PreparedLayoutTextView(context: Context) : ViewGroup(context), Re selectionColor ?: DefaultStyleValuesUtil.getDefaultTextColorHighlight(context) } + val spanned = text as? Spanned + val drawCommandSpans = + spanned?.getSpans(0, spanned.length, DrawCommandSpan::class.java) ?: emptyArray() + + if (spanned != null) { + for (span in drawCommandSpans) { + span.onPreDraw( + spanned.getSpanStart(span), + spanned.getSpanEnd(span), + canvas, + layout, + ) + } + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { Api34Utils.draw(layout, canvas, selection?.path, selectionPaint) } else { layout.draw(canvas, selection?.path, selectionPaint, 0) } + + if (spanned != null) { + for (span in drawCommandSpans) { + span.onDraw( + spanned.getSpanStart(span), + spanned.getSpanEnd(span), + canvas, + layout, + ) + } + } } } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/DrawCommandSpan.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/DrawCommandSpan.kt new file mode 100644 index 00000000000000..20d0f7be33f2b9 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/DrawCommandSpan.kt @@ -0,0 +1,27 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.facebook.react.views.text.internal.span + +import android.graphics.Canvas +import android.text.Layout +import android.text.style.UpdateAppearance + +/** + * May be overriden to implement charater styles which are applied by [PreparedLayoutTextView] + * during the drawing of text, against the underlying Android canvas + */ +public abstract class DrawCommandSpan : UpdateAppearance, ReactSpan { + /** + * Called before the text is drawn. This happens after the Paragraph component has drawn its + * background, but may be called before text spans with their own background color are drawn. + */ + public open fun onPreDraw(start: Int, end: Int, canvas: Canvas, layout: Layout): Unit = Unit + + /** Called after the text is drawn, including some effects like text shadows */ + public open fun onDraw(start: Int, end: Int, canvas: Canvas, layout: Layout): Unit = Unit +}