diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactBaseTextShadowNode.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactBaseTextShadowNode.java index bb1a82bdd50..1da3e2f7b9f 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactBaseTextShadowNode.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactBaseTextShadowNode.java @@ -382,7 +382,7 @@ public abstract class ReactBaseTextShadowNode extends LayoutShadowNode // or images. for (int priorityIndex = 0; priorityIndex < ops.size(); priorityIndex++) { final SetSpanOperation op = ops.get(ops.size() - priorityIndex - 1); - final ReactSpan what = op.getWhat(); + final ReactSpan what = op.what; boolean isInlineImage = what instanceof TextInlineImageSpan; if (isInlineImage || what instanceof TextInlineViewPlaceholderSpan) { diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/CustomLetterSpacingSpan.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/CustomLetterSpacingSpan.java deleted file mode 100644 index c62df01777b..00000000000 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/CustomLetterSpacingSpan.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * 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.text.TextPaint; -import android.text.style.MetricAffectingSpan; -import com.facebook.infer.annotation.Nullsafe; - -/** - * A {@link MetricAffectingSpan} that allows to set the letter spacing on the selected text span. - * - *

The letter spacing is specified in pixels, which are converted to ems at paint time; this span - * must therefore be applied after any spans affecting font size. - */ -@Nullsafe(Nullsafe.Mode.LOCAL) -public class CustomLetterSpacingSpan extends MetricAffectingSpan implements ReactSpan { - - private final float mLetterSpacing; - - public CustomLetterSpacingSpan(float letterSpacing) { - mLetterSpacing = letterSpacing; - } - - @Override - public void updateDrawState(TextPaint paint) { - apply(paint); - } - - @Override - public void updateMeasureState(TextPaint paint) { - apply(paint); - } - - public float getSpacing() { - return mLetterSpacing; - } - - private void apply(TextPaint paint) { - if (!Float.isNaN(mLetterSpacing)) { - paint.setLetterSpacing(mLetterSpacing); - } - } -} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/CustomLetterSpacingSpan.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/CustomLetterSpacingSpan.kt new file mode 100644 index 00000000000..4e81aea3e38 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/CustomLetterSpacingSpan.kt @@ -0,0 +1,33 @@ +/* + * 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.text.TextPaint +import android.text.style.MetricAffectingSpan + +/** + * A [MetricAffectingSpan] that allows to set the letter spacing on the selected text span. + * + * The letter spacing is specified in pixels, which are converted to ems at paint time; this span + * must therefore be applied after any spans affecting font size. + */ +public class CustomLetterSpacingSpan(public val spacing: Float) : MetricAffectingSpan(), ReactSpan { + public override fun updateDrawState(paint: TextPaint) { + apply(paint) + } + + public override fun updateMeasureState(paint: TextPaint) { + apply(paint) + } + + private fun apply(paint: TextPaint) { + if (!spacing.isNaN()) { + paint.letterSpacing = spacing + } + } +} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/CustomLineHeightSpan.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/CustomLineHeightSpan.java deleted file mode 100644 index f9b63f0f461..00000000000 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/CustomLineHeightSpan.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * 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.Paint; -import android.text.style.LineHeightSpan; -import com.facebook.infer.annotation.Nullsafe; - -/** - * We use a custom {@link LineHeightSpan}, because `lineSpacingExtra` is broken. Details here: - * https://github.com/facebook/react-native/issues/7546 - */ -@Nullsafe(Nullsafe.Mode.LOCAL) -public class CustomLineHeightSpan implements LineHeightSpan, ReactSpan { - private final int mHeight; - - public CustomLineHeightSpan(float height) { - mHeight = (int) Math.ceil(height); - } - - public int getLineHeight() { - return mHeight; - } - - @Override - public void chooseHeight( - CharSequence text, int start, int end, int spanstartv, int v, Paint.FontMetricsInt fm) { - // This is more complicated that I wanted it to be. You can find a good explanation of what the - // FontMetrics mean here: http://stackoverflow.com/questions/27631736. - // The general solution is that if there's not enough height to show the full line height, we - // will prioritize in this order: descent, ascent, bottom, top - - if (fm.descent > mHeight) { - // Show as much descent as possible - fm.bottom = fm.descent = Math.min(mHeight, fm.descent); - fm.top = fm.ascent = 0; - } else if (-fm.ascent + fm.descent > mHeight) { - // Show all descent, and as much ascent as possible - fm.bottom = fm.descent; - fm.top = fm.ascent = -mHeight + fm.descent; - } else if (-fm.ascent + fm.bottom > mHeight) { - // Show all ascent, descent, as much bottom as possible - fm.top = fm.ascent; - fm.bottom = fm.ascent + mHeight; - } else if (-fm.top + fm.bottom > mHeight) { - // Show all ascent, descent, bottom, as much top as possible - fm.top = fm.bottom - mHeight; - } else { - // Show proportionally additional ascent / top & descent / bottom - final int additional = mHeight - (-fm.top + fm.bottom); - - // Round up for the negative values and down for the positive values (arbitrary choice) - // So that bottom - top equals additional even if it's an odd number. - final int top = (int) (fm.top - Math.ceil(additional / 2.0f)); - final int bottom = (int) (fm.bottom + Math.floor(additional / 2.0f)); - - fm.top = top; - fm.ascent = top; - fm.descent = bottom; - fm.bottom = bottom; - } - } -} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/CustomLineHeightSpan.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/CustomLineHeightSpan.kt new file mode 100644 index 00000000000..830d987439c --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/CustomLineHeightSpan.kt @@ -0,0 +1,69 @@ +/* + * 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.Paint.FontMetricsInt +import android.text.style.LineHeightSpan +import kotlin.math.ceil +import kotlin.math.floor +import kotlin.math.min + +/** + * We use a custom [LineHeightSpan], because `lineSpacingExtra` is broken. Details here: + * https://github.com/facebook/react-native/issues/7546 + */ +public class CustomLineHeightSpan(height: Float) : LineHeightSpan, ReactSpan { + public val lineHeight: Int = ceil(height.toDouble()).toInt() + + public override fun chooseHeight( + text: CharSequence?, + start: Int, + end: Int, + spanstartv: Int, + v: Int, + fm: FontMetricsInt + ) { + // This is more complicated that I wanted it to be. You can find a good explanation of what the + // FontMetrics mean here: http://stackoverflow.com/questions/27631736. + // The general solution is that if there's not enough height to show the full line height, we + // will prioritize in this order: descent, ascent, bottom, top + + if (fm.descent > lineHeight) { + // Show as much descent as possible + fm.descent = min(lineHeight.toDouble(), fm.descent.toDouble()).toInt() + fm.bottom = fm.descent + fm.ascent = 0 + fm.top = fm.ascent + } else if (-fm.ascent + fm.descent > lineHeight) { + // Show all descent, and as much ascent as possible + fm.bottom = fm.descent + fm.ascent = -lineHeight + fm.descent + fm.top = fm.ascent + } else if (-fm.ascent + fm.bottom > lineHeight) { + // Show all ascent, descent, as much bottom as possible + fm.top = fm.ascent + fm.bottom = fm.ascent + lineHeight + } else if (-fm.top + fm.bottom > lineHeight) { + // Show all ascent, descent, bottom, as much top as possible + fm.top = fm.bottom - lineHeight + } else { + // Show proportionally additional ascent / top & descent / bottom + val additional = lineHeight - (-fm.top + fm.bottom) + + // Round up for the negative values and down for the positive values (arbitrary choice) + // So that bottom - top equals additional even if it's an odd number. + val top = (fm.top - ceil(additional / 2.0f)).toInt() + val bottom = (fm.bottom + floor(additional / 2.0f)).toInt() + + fm.top = top + fm.ascent = top + fm.descent = bottom + fm.bottom = bottom + } + } +} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/CustomStyleSpan.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/CustomStyleSpan.java deleted file mode 100644 index 7fa67a06b62..00000000000 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/CustomStyleSpan.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * 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.content.res.AssetManager; -import android.graphics.Paint; -import android.graphics.Typeface; -import android.text.TextPaint; -import android.text.style.MetricAffectingSpan; -import androidx.annotation.Nullable; -import com.facebook.infer.annotation.Nullsafe; -import com.facebook.react.common.ReactConstants; -import com.facebook.react.common.assets.ReactFontManager; -import com.facebook.react.views.text.ReactTypefaceUtils; - -/** - * A {@link MetricAffectingSpan} that allows to change the style of the displayed font. - * CustomStyleSpan will try to load the fontFamily with the right style and weight from the assets. - * The custom fonts will have to be located in the res/assets folder of the application. The - * supported custom fonts extensions are .ttf and .otf. For each font family the bold, italic and - * bold_italic variants are supported. Given a "family" font family the files in the assets/fonts - * folder need to be family.ttf(.otf) family_bold.ttf(.otf) family_italic.ttf(.otf) and - * family_bold_italic.ttf(.otf). If the right font is not found in the assets folder CustomStyleSpan - * will fallback on the most appropriate default typeface depending on the style. Fonts are - * retrieved and cached using the {@link ReactFontManager} - */ -@Nullsafe(Nullsafe.Mode.LOCAL) -public class CustomStyleSpan extends MetricAffectingSpan implements ReactSpan { - private final AssetManager mAssetManager; - - private final int mStyle; - private final int mWeight; - private final @Nullable String mFeatureSettings; - private final @Nullable String mFontFamily; - - public CustomStyleSpan( - int fontStyle, - int fontWeight, - @Nullable String fontFeatureSettings, - @Nullable String fontFamily, - AssetManager assetManager) { - mStyle = fontStyle; - mWeight = fontWeight; - mFeatureSettings = fontFeatureSettings; - mFontFamily = fontFamily; - mAssetManager = assetManager; - } - - @Override - public void updateDrawState(TextPaint ds) { - apply(ds, mStyle, mWeight, mFeatureSettings, mFontFamily, mAssetManager); - } - - @Override - public void updateMeasureState(TextPaint paint) { - apply(paint, mStyle, mWeight, mFeatureSettings, mFontFamily, mAssetManager); - } - - public int getStyle() { - return mStyle == ReactConstants.UNSET ? Typeface.NORMAL : mStyle; - } - - public int getWeight() { - return mWeight == ReactConstants.UNSET ? ReactFontManager.TypefaceStyle.NORMAL : mWeight; - } - - public @Nullable String getFontFamily() { - return mFontFamily; - } - - public @Nullable String getFontFeatureSettings() { - return mFeatureSettings; - } - - private static void apply( - Paint paint, - int style, - int weight, - @Nullable String fontFeatureSettings, - @Nullable String family, - AssetManager assetManager) { - Typeface typeface = - ReactTypefaceUtils.applyStyles(paint.getTypeface(), style, weight, family, assetManager); - paint.setFontFeatureSettings(fontFeatureSettings); - paint.setTypeface(typeface); - paint.setSubpixelText(true); - } -} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/CustomStyleSpan.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/CustomStyleSpan.kt new file mode 100644 index 00000000000..81bb303e6a3 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/CustomStyleSpan.kt @@ -0,0 +1,79 @@ +/* + * 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.content.res.AssetManager +import android.graphics.Paint +import android.graphics.Typeface +import android.text.TextPaint +import android.text.style.MetricAffectingSpan +import com.facebook.react.common.ReactConstants +import com.facebook.react.common.assets.ReactFontManager +import com.facebook.react.views.text.ReactTypefaceUtils + +/** + * A [MetricAffectingSpan] that allows to change the style of the displayed font. CustomStyleSpan + * will try to load the fontFamily with the right style and weight from the assets. The custom fonts + * will have to be located in the res/assets folder of the application. The supported custom fonts + * extensions are .ttf and .otf. For each font family the bold, italic and bold_italic variants are + * supported. Given a "family" font family the files in the assets/fonts folder need to be + * family.ttf(.otf) family_bold.ttf(.otf) family_italic.ttf(.otf) and family_bold_italic.ttf(.otf). + * If the right font is not found in the assets folder CustomStyleSpan will fallback on the most + * appropriate default typeface depending on the style. Fonts are retrieved and cached using the + * [ReactFontManager] + */ +public class CustomStyleSpan( + private val privateStyle: Int, + private val privateWeight: Int, + public val fontFeatureSettings: String?, + public val fontFamily: String?, + private val assetManager: AssetManager +) : MetricAffectingSpan(), ReactSpan { + public override fun updateDrawState(ds: TextPaint) { + apply(ds, privateStyle, privateWeight, fontFeatureSettings, fontFamily, assetManager) + } + + public override fun updateMeasureState(paint: TextPaint) { + apply(paint, privateStyle, privateWeight, fontFeatureSettings, fontFamily, assetManager) + } + + public val style: Int + get() = + if (privateStyle == ReactConstants.UNSET) { + Typeface.NORMAL + } else { + privateStyle + } + + public val weight: Int + get() = + if (privateWeight == ReactConstants.UNSET) { + ReactFontManager.TypefaceStyle.NORMAL + } else { + privateWeight + } + + public companion object { + private fun apply( + paint: Paint, + style: Int, + weight: Int, + fontFeatureSettingsParam: String?, + family: String?, + assetManager: AssetManager + ) { + val typeface = + ReactTypefaceUtils.applyStyles(paint.typeface, style, weight, family, assetManager) + paint.apply { + fontFeatureSettings = fontFeatureSettingsParam + setTypeface(typeface) + isSubpixelText = true + } + } + } +} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/ReactAbsoluteSizeSpan.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/ReactAbsoluteSizeSpan.java deleted file mode 100644 index b39e89dda92..00000000000 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/ReactAbsoluteSizeSpan.java +++ /dev/null @@ -1,21 +0,0 @@ -/* - * 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.text.style.AbsoluteSizeSpan; -import com.facebook.infer.annotation.Nullsafe; - -/* - * Wraps {@link AbsoluteSizeSpan} as a {@link ReactSpan}. - */ -@Nullsafe(Nullsafe.Mode.LOCAL) -public class ReactAbsoluteSizeSpan extends AbsoluteSizeSpan implements ReactSpan { - public ReactAbsoluteSizeSpan(int size) { - super(size); - } -} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/ReactAbsoluteSizeSpan.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/ReactAbsoluteSizeSpan.kt new file mode 100644 index 00000000000..575513e23a8 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/ReactAbsoluteSizeSpan.kt @@ -0,0 +1,13 @@ +/* + * 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.text.style.AbsoluteSizeSpan + +/** Wraps [AbsoluteSizeSpan] as a [ReactSpan]. */ +public class ReactAbsoluteSizeSpan(size: Int) : AbsoluteSizeSpan(size), ReactSpan diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/ReactBackgroundColorSpan.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/ReactBackgroundColorSpan.java deleted file mode 100644 index 9eb94d3df76..00000000000 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/ReactBackgroundColorSpan.java +++ /dev/null @@ -1,21 +0,0 @@ -/* - * 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.text.style.BackgroundColorSpan; -import com.facebook.infer.annotation.Nullsafe; - -/* - * Wraps {@link BackgroundColorSpan} as a {@link ReactSpan}. - */ -@Nullsafe(Nullsafe.Mode.LOCAL) -public class ReactBackgroundColorSpan extends BackgroundColorSpan implements ReactSpan { - public ReactBackgroundColorSpan(int color) { - super(color); - } -} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/ReactBackgroundColorSpan.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/ReactBackgroundColorSpan.kt new file mode 100644 index 00000000000..3814bd1acf9 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/ReactBackgroundColorSpan.kt @@ -0,0 +1,13 @@ +/* + * 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.text.style.BackgroundColorSpan + +/** Wraps [BackgroundColorSpan] as a [ReactSpan]. */ +public class ReactBackgroundColorSpan(color: Int) : BackgroundColorSpan(color), ReactSpan diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/ReactClickableSpan.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/ReactClickableSpan.java deleted file mode 100644 index 17bb4dd4308..00000000000 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/ReactClickableSpan.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * 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.text.TextPaint; -import android.text.style.ClickableSpan; -import android.view.View; -import androidx.annotation.NonNull; -import com.facebook.infer.annotation.Nullsafe; -import com.facebook.react.bridge.ReactContext; -import com.facebook.react.uimanager.UIManagerHelper; -import com.facebook.react.uimanager.events.EventDispatcher; -import com.facebook.react.views.text.ReactTextView; -import com.facebook.react.views.text.TextLayoutManager; -import com.facebook.react.views.view.ViewGroupClickEvent; - -/** - * This class is used in {@link TextLayoutManager} to linkify and style a span of text with - * accessibilityRole="link". This is needed to make nested Text components accessible. - * - *

For example, if your React component looks like this: - * - *

{@code
- * 
- *   Some text with
- *   a link
- *   in the middle.
- * 
- * }
- * - * then only one {@link ReactTextView} will be created, for the parent. The child Text component - * does not exist as a native view, and therefore has no accessibility properties. Instead, we have - * to use spans on the parent's {@link ReactTextView} to properly style the child, and to make it - * accessible (TalkBack announces that the text has links available, and the links are exposed in - * the context menu). - */ -@Nullsafe(Nullsafe.Mode.LOCAL) -public class ReactClickableSpan extends ClickableSpan implements ReactSpan { - - private final int mReactTag; - - public ReactClickableSpan(int reactTag) { - mReactTag = reactTag; - } - - @Override - public void onClick(@NonNull View view) { - ReactContext context = (ReactContext) view.getContext(); - EventDispatcher eventDispatcher = - UIManagerHelper.getEventDispatcherForReactTag(context, mReactTag); - if (eventDispatcher != null) { - eventDispatcher.dispatchEvent( - new ViewGroupClickEvent(UIManagerHelper.getSurfaceId(context), mReactTag)); - } - } - - @Override - public void updateDrawState(@NonNull TextPaint ds) { - // no-op to make sure we don't change the link color or add an underline by default, as the - // superclass does. - } - - public int getReactTag() { - return mReactTag; - } -} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/ReactClickableSpan.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/ReactClickableSpan.kt new file mode 100644 index 00000000000..21211a586a7 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/ReactClickableSpan.kt @@ -0,0 +1,49 @@ +/* + * 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.text.TextPaint +import android.text.style.ClickableSpan +import android.view.View +import com.facebook.react.bridge.ReactContext +import com.facebook.react.uimanager.UIManagerHelper +import com.facebook.react.views.text.ReactTextView +import com.facebook.react.views.view.ViewGroupClickEvent + +/** + * This class is used in [TextLayoutManager] to linkify and style a span of text with + * accessibilityRole="link". This is needed to make nested Text components accessible. + * + * For example, if your React component looks like this: + * ```js + * + * Some text with + * a link + * in the middle. + * + * ``` + * + * then only one [ReactTextView] will be created, for the parent. The child Text component does not + * exist as a native view, and therefore has no accessibility properties. Instead, we have to use + * spans on the parent's [ReactTextView] to properly style the child, and to make it accessible + * (TalkBack announces that the text has links available, and the links are exposed in the context + * menu). + */ +public class ReactClickableSpan(public val reactTag: Int) : ClickableSpan(), ReactSpan { + public override fun onClick(view: View) { + val context = view.context as ReactContext + val eventDispatcher = UIManagerHelper.getEventDispatcherForReactTag(context, reactTag) + eventDispatcher?.dispatchEvent( + ViewGroupClickEvent(UIManagerHelper.getSurfaceId(context), reactTag)) + } + + public override fun updateDrawState(ds: TextPaint) { + // no-op to make sure we don't change the link color or add an underline by default, as the + // superclass does. + } +} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/ReactForegroundColorSpan.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/ReactForegroundColorSpan.java deleted file mode 100644 index b348f1af3e6..00000000000 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/ReactForegroundColorSpan.java +++ /dev/null @@ -1,21 +0,0 @@ -/* - * 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.text.style.ForegroundColorSpan; -import com.facebook.infer.annotation.Nullsafe; - -/* - * Wraps {@link ForegroundColorSpan} as a {@link ReactSpan}. - */ -@Nullsafe(Nullsafe.Mode.LOCAL) -public class ReactForegroundColorSpan extends ForegroundColorSpan implements ReactSpan { - public ReactForegroundColorSpan(int color) { - super(color); - } -} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/ReactForegroundColorSpan.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/ReactForegroundColorSpan.kt new file mode 100644 index 00000000000..66e7aadc099 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/ReactForegroundColorSpan.kt @@ -0,0 +1,13 @@ +/* + * 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.text.style.ForegroundColorSpan + +/** Wraps [ForegroundColorSpan] as a [ReactSpan]. */ +public class ReactForegroundColorSpan(color: Int) : ForegroundColorSpan(color), ReactSpan diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/ReactSpan.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/ReactSpan.java deleted file mode 100644 index 4d0bdc212d0..00000000000 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/ReactSpan.java +++ /dev/null @@ -1,17 +0,0 @@ -/* - * 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 com.facebook.infer.annotation.Nullsafe; - -/* - * Enables us to distinguish between spans that were added by React Native and spans that were - * added by something else. All spans that React Native adds should implement this interface. - */ -@Nullsafe(Nullsafe.Mode.LOCAL) -public interface ReactSpan {} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/ReactSpan.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/ReactSpan.kt new file mode 100644 index 00000000000..264aaf95486 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/ReactSpan.kt @@ -0,0 +1,14 @@ +/* + * 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 + +/** + * Enables us to distinguish between spans that were added by React Native and spans that were added + * by something else. All spans that React Native adds should implement this interface. + */ +public interface ReactSpan diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/ReactStrikethroughSpan.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/ReactStrikethroughSpan.java deleted file mode 100644 index 1263f679dd7..00000000000 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/ReactStrikethroughSpan.java +++ /dev/null @@ -1,17 +0,0 @@ -/* - * 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.text.style.StrikethroughSpan; -import com.facebook.infer.annotation.Nullsafe; - -/* - * Wraps {@link StrikethroughSpan} as a {@link ReactSpan}. - */ -@Nullsafe(Nullsafe.Mode.LOCAL) -public class ReactStrikethroughSpan extends StrikethroughSpan implements ReactSpan {} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/ReactStrikethroughSpan.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/ReactStrikethroughSpan.kt new file mode 100644 index 00000000000..c1b49c3407f --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/ReactStrikethroughSpan.kt @@ -0,0 +1,13 @@ +/* + * 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.text.style.StrikethroughSpan + +/** Wraps [StrikethroughSpan] as a [ReactSpan]. */ +public class ReactStrikethroughSpan : StrikethroughSpan(), ReactSpan diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/ReactTagSpan.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/ReactTagSpan.java deleted file mode 100644 index 965035f98b2..00000000000 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/ReactTagSpan.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * 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 com.facebook.infer.annotation.Nullsafe; - -/** - * Instances of this class are used to place reactTag information of nested text react nodes into - * spannable text rendered by single {@link TextView} - */ -@Nullsafe(Nullsafe.Mode.LOCAL) -public class ReactTagSpan implements ReactSpan { - - private final int mReactTag; - - public ReactTagSpan(int reactTag) { - mReactTag = reactTag; - } - - public int getReactTag() { - return mReactTag; - } -} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/ReactTagSpan.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/ReactTagSpan.kt new file mode 100644 index 00000000000..1e1c8a17877 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/ReactTagSpan.kt @@ -0,0 +1,14 @@ +/* + * 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 + +/** + * Instances of this class are used to place reactTag information of nested text react nodes into + * spannable text rendered by single [TextView] + */ +public class ReactTagSpan(public val reactTag: Int) : ReactSpan diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/ReactUnderlineSpan.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/ReactUnderlineSpan.java deleted file mode 100644 index 4b40239aeed..00000000000 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/ReactUnderlineSpan.java +++ /dev/null @@ -1,17 +0,0 @@ -/* - * 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.text.style.UnderlineSpan; -import com.facebook.infer.annotation.Nullsafe; - -/* - * Wraps {@link UnderlineSpan} as a {@link ReactSpan}. - */ -@Nullsafe(Nullsafe.Mode.LOCAL) -public class ReactUnderlineSpan extends UnderlineSpan implements ReactSpan {} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/ReactUnderlineSpan.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/ReactUnderlineSpan.kt new file mode 100644 index 00000000000..a1daa1fea65 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/ReactUnderlineSpan.kt @@ -0,0 +1,13 @@ +/* + * 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.text.style.UnderlineSpan + +/** Wraps [UnderlineSpan] as a [ReactSpan]. */ +public class ReactUnderlineSpan : UnderlineSpan(), ReactSpan diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/SetSpanOperation.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/SetSpanOperation.java deleted file mode 100644 index 03a84fa11a3..00000000000 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/SetSpanOperation.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * 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.text.Spannable; -import android.text.SpannableStringBuilder; -import android.text.Spanned; -import com.facebook.common.logging.FLog; -import com.facebook.infer.annotation.Nullsafe; - -@Nullsafe(Nullsafe.Mode.LOCAL) -public class SetSpanOperation { - private static final String TAG = "SetSpanOperation"; - static final int SPAN_MAX_PRIORITY = Spanned.SPAN_PRIORITY >> Spanned.SPAN_PRIORITY_SHIFT; - - protected int start, end; - - private final ReactSpan what; - - public SetSpanOperation(int start, int end, ReactSpan what) { - this.start = start; - this.end = end; - this.what = what; - } - - /** - * @param sb builder - * @param priorityIndex index of this operation in the topological sorting which puts operations - * with higher priority before operations with lower priority. - */ - public void execute(SpannableStringBuilder sb, int priorityIndex) { - assert priorityIndex >= 0; - - // All spans will automatically extend to the right of the text, but not the left - except - // for spans that start at the beginning of the text. - int spanFlags = Spannable.SPAN_EXCLUSIVE_INCLUSIVE; - if (start == 0) { - spanFlags = Spannable.SPAN_INCLUSIVE_INCLUSIVE; - } - - // Calculate priority, assigning the highest values to operations with the highest priority - final int priority = SPAN_MAX_PRIORITY - priorityIndex; - - if (priority < 0) { - FLog.w(TAG, "Text tree size exceeded the limit, styling may become unpredictable"); - } - - // If the computed priority doesn't fit in the flags, clamp it. The effect might not be correct - // in 100% of cases, but doing nothing (as we did in the past) leads to totally random results. - final int effectivePriority = Math.max(priority, 0); - - spanFlags &= ~Spannable.SPAN_PRIORITY; - spanFlags |= (effectivePriority << Spannable.SPAN_PRIORITY_SHIFT) & Spannable.SPAN_PRIORITY; - - sb.setSpan(what, start, end, spanFlags); - } - - public ReactSpan getWhat() { - return what; - } -} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/SetSpanOperation.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/SetSpanOperation.kt new file mode 100644 index 00000000000..3b48749f2b5 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/SetSpanOperation.kt @@ -0,0 +1,58 @@ +/* + * 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.text.Spannable +import android.text.SpannableStringBuilder +import android.text.Spanned +import com.facebook.common.logging.FLog +import kotlin.math.max + +public class SetSpanOperation( + private val start: Int, + private val end: Int, + @JvmField public val what: ReactSpan +) { + /** + * @param builder Spannable string builder + * @param priorityIndex index of this operation in the topological sorting which puts operations + * with higher priority before operations with lower priority. + */ + public fun execute(builder: SpannableStringBuilder, priorityIndex: Int) { + check(priorityIndex >= 0) + // All spans will automatically extend to the right of the text, but not the left - except + // for spans that start at the beginning of the text. + var spanFlags = Spannable.SPAN_EXCLUSIVE_INCLUSIVE + if (start == 0) { + spanFlags = Spannable.SPAN_INCLUSIVE_INCLUSIVE + } + + // Calculate priority, assigning the highest values to operations with the highest priority + val priority = SPAN_MAX_PRIORITY - priorityIndex + + if (priority < 0) { + FLog.w(TAG, "Text tree size exceeded the limit, styling may become unpredictable") + } + + // If the computed priority doesn't fit in the flags, clamp it. The effect might not be correct + // in 100% of cases, but doing nothing (as we did in the past) leads to totally random results. + val effectivePriority = max(priority, 0) + + spanFlags = spanFlags and Spannable.SPAN_PRIORITY.inv() + spanFlags = + spanFlags or + ((effectivePriority shl Spannable.SPAN_PRIORITY_SHIFT) and Spannable.SPAN_PRIORITY) + + builder.setSpan(what, start, end, spanFlags) + } + + public companion object { + private const val TAG = "SetSpanOperation" + public const val SPAN_MAX_PRIORITY: Int = Spanned.SPAN_PRIORITY shr Spanned.SPAN_PRIORITY_SHIFT + } +} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/ShadowStyleSpan.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/ShadowStyleSpan.java deleted file mode 100644 index 2e0eb235308..00000000000 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/ShadowStyleSpan.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * 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.text.TextPaint; -import android.text.style.CharacterStyle; -import com.facebook.infer.annotation.Nullsafe; - -@Nullsafe(Nullsafe.Mode.LOCAL) -public class ShadowStyleSpan extends CharacterStyle implements ReactSpan { - private final float mDx, mDy, mRadius; - private final int mColor; - - public ShadowStyleSpan(float dx, float dy, float radius, int color) { - mDx = dx; - mDy = dy; - mRadius = radius; - mColor = color; - } - - @Override - public void updateDrawState(TextPaint textPaint) { - textPaint.setShadowLayer(mRadius, mDx, mDy, mColor); - } -} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/ShadowStyleSpan.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/ShadowStyleSpan.kt new file mode 100644 index 00000000000..392dd518f93 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/ShadowStyleSpan.kt @@ -0,0 +1,22 @@ +/* + * 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.text.TextPaint +import android.text.style.CharacterStyle + +public class ShadowStyleSpan( + private val dx: Float, + private val dy: Float, + private val radius: Float, + private val color: Int +) : CharacterStyle(), ReactSpan { + public override fun updateDrawState(textPaint: TextPaint) { + textPaint.setShadowLayer(radius, dx, dy, color) + } +} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/TextInlineImageSpan.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/TextInlineImageSpan.java deleted file mode 100644 index 8bdc0e9e020..00000000000 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/TextInlineImageSpan.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * 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.drawable.Drawable; -import android.text.Spannable; -import android.text.style.ReplacementSpan; -import android.view.View; -import android.widget.TextView; -import androidx.annotation.Nullable; -import com.facebook.infer.annotation.Nullsafe; - -/** Base class for inline image spans. */ -@Nullsafe(Nullsafe.Mode.LOCAL) -public abstract class TextInlineImageSpan extends ReplacementSpan implements ReactSpan { - - /** - * For TextInlineImageSpan we need to update the Span to know that the window is attached and the - * TextView that we will set as the callback on the Drawable. - * - * @param spannable The spannable that may contain TextInlineImageSpans - * @param view The view which will be set as the callback for the Drawable - */ - public static void possiblyUpdateInlineImageSpans(Spannable spannable, TextView view) { - TextInlineImageSpan[] spans = - spannable.getSpans(0, spannable.length(), TextInlineImageSpan.class); - for (TextInlineImageSpan span : spans) { - span.onAttachedToWindow(); - span.setTextView(view); - } - } - - /** Get the drawable that is span represents. */ - public abstract @Nullable Drawable getDrawable(); - - /** Called by the text view from {@link View#onDetachedFromWindow()}, */ - public abstract void onDetachedFromWindow(); - - /** Called by the text view from {@link View#onStartTemporaryDetach()}. */ - public abstract void onStartTemporaryDetach(); - - /** Called by the text view from {@link View#onAttachedToWindow()}. */ - public abstract void onAttachedToWindow(); - - /** Called by the text view from {@link View#onFinishTemporaryDetach()}. */ - public abstract void onFinishTemporaryDetach(); - - /** Set the textview that will contain this span. */ - public abstract void setTextView(TextView textView); - - /** Get the width of the span. */ - public abstract int getWidth(); - - /** Get the height of the span. */ - public abstract int getHeight(); -} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/TextInlineImageSpan.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/TextInlineImageSpan.kt new file mode 100644 index 00000000000..63772089a2b --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/TextInlineImageSpan.kt @@ -0,0 +1,58 @@ +/* + * 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.drawable.Drawable +import android.text.Spannable +import android.text.style.ReplacementSpan +import android.view.View +import android.widget.TextView + +/** Base class for inline image spans. */ +public abstract class TextInlineImageSpan : ReplacementSpan(), ReactSpan { + /** Get the drawable that is span represents. */ + public abstract val drawable: Drawable? + + /** Called by the text view from [View.onDetachedFromWindow], */ + public abstract fun onDetachedFromWindow() + + /** Called by the text view from [View.onStartTemporaryDetach]. */ + public abstract fun onStartTemporaryDetach() + + /** Called by the text view from [View.onAttachedToWindow]. */ + public abstract fun onAttachedToWindow() + + /** Called by the text view from [View.onFinishTemporaryDetach]. */ + public abstract fun onFinishTemporaryDetach() + + /** Set the textview that will contain this span. */ + public abstract fun setTextView(textView: TextView?) + + /** Get the width of the span. */ + public abstract val width: Int + + /** Get the height of the span. */ + public abstract val height: Int + + public companion object { + /** + * For TextInlineImageSpan we need to update the Span to know that the window is attached and + * the TextView that we will set as the callback on the Drawable. + * + * @param spannable The spannable that may contain TextInlineImageSpans + * @param view The view which will be set as the callback for the Drawable + */ + @JvmStatic + public fun possiblyUpdateInlineImageSpans(spannable: Spannable, view: TextView?) { + spannable.getSpans(0, spannable.length, TextInlineImageSpan::class.java).forEach { s -> + s.onAttachedToWindow() + s.setTextView(view) + } + } + } +} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/TextInlineViewPlaceholderSpan.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/TextInlineViewPlaceholderSpan.java deleted file mode 100644 index 5ecfc969854..00000000000 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/TextInlineViewPlaceholderSpan.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * 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.graphics.Paint; -import android.text.style.ReplacementSpan; -import com.facebook.infer.annotation.Nullsafe; - -/** - * TextInlineViewPlaceholderSpan is a span for inlined views that are inside . It computes - * its size based on the input size. It contains no draw logic, just positioning logic. - */ -@Nullsafe(Nullsafe.Mode.LOCAL) -public class TextInlineViewPlaceholderSpan extends ReplacementSpan implements ReactSpan { - private int mReactTag; - private int mWidth; - private int mHeight; - - public TextInlineViewPlaceholderSpan(int reactTag, int width, int height) { - mReactTag = reactTag; - mWidth = width; - mHeight = height; - } - - public int getReactTag() { - return mReactTag; - } - - public int getWidth() { - return mWidth; - } - - public int getHeight() { - return mHeight; - } - - @Override - public int getSize(Paint paint, CharSequence text, int start, int end, Paint.FontMetricsInt fm) { - // NOTE: This getSize code is copied from DynamicDrawableSpan and modified to not use a Drawable - - if (fm != null) { - fm.ascent = -mHeight; - fm.descent = 0; - - fm.top = fm.ascent; - fm.bottom = 0; - } - - return mWidth; - } - - @Override - public void draw( - Canvas canvas, - CharSequence text, - int start, - int end, - float x, - int top, - int y, - int bottom, - Paint paint) {} -} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/TextInlineViewPlaceholderSpan.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/TextInlineViewPlaceholderSpan.kt new file mode 100644 index 00000000000..e1a960be0b5 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/TextInlineViewPlaceholderSpan.kt @@ -0,0 +1,52 @@ +/* + * 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.graphics.Paint +import android.graphics.Paint.FontMetricsInt +import android.text.style.ReplacementSpan + +/** + * TextInlineViewPlaceholderSpan is a span for inlined views that are inside . It + * computes its size based on the input size. It contains no draw logic, just positioning logic. + */ +public class TextInlineViewPlaceholderSpan( + public val reactTag: Int, + public val width: Int, + public val height: Int +) : ReplacementSpan(), ReactSpan { + public override fun getSize( + paint: Paint, + text: CharSequence?, + start: Int, + end: Int, + fm: FontMetricsInt? + ): Int { + // NOTE: This getSize code is copied from DynamicDrawableSpan and modified to not use a Drawable + if (fm != null) { + fm.ascent = -height + fm.descent = 0 + fm.top = fm.ascent + fm.bottom = 0 + } + return width + } + + public override fun draw( + canvas: Canvas, + text: CharSequence?, + start: Int, + end: Int, + x: Float, + top: Int, + y: Int, + bottom: Int, + paint: Paint + ): Unit = Unit +}