mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
De-duplicate building Spannable on Android (#39630)
Summary: A first step in my work on https://github.com/react-native-community/discussions-and-proposals/issues/695 De-duplicate the code for creating `Spannable` on Android. I'm planning to add quite serious new features to this module. This would be really hard with the current level of code duplication. ## Changelog: [INTERNAL] [CHANGED] - De-duplicate building `Spannable` on Android Pull Request resolved: https://github.com/facebook/react-native/pull/39630 Test Plan: I tried to ensure that the refactored code is relatively easy to prove to be equivalent to the original duplicated one, but there's always a risk of a human mistake in this process. So far, I have been testing this by ensuring that nothing broke in the `Text` example section in RNTester. Reviewed By: mdvacca Differential Revision: D51016244 Pulled By: NickGerleman fbshipit-source-id: e9f873c01b2af0685c7b0943aebea170c997d22e
This commit is contained in:
committed by
Facebook GitHub Bot
parent
6fbf042d93
commit
7bcdb23cf0
@@ -1686,6 +1686,7 @@ public final class com/facebook/react/common/MapBuilder$Builder {
|
||||
|
||||
public class com/facebook/react/common/ReactConstants {
|
||||
public static final field TAG Ljava/lang/String;
|
||||
public static final field UNSET I
|
||||
public fun <init> ()V
|
||||
}
|
||||
|
||||
@@ -1764,7 +1765,6 @@ public class com/facebook/react/common/assets/ReactFontManager {
|
||||
public class com/facebook/react/common/assets/ReactFontManager$TypefaceStyle {
|
||||
public static final field BOLD I
|
||||
public static final field NORMAL I
|
||||
public static final field UNSET I
|
||||
public fun <init> (I)V
|
||||
public fun <init> (II)V
|
||||
public fun <init> (IZ)V
|
||||
@@ -1906,6 +1906,7 @@ public class com/facebook/react/config/ReactFeatureFlags {
|
||||
public static field enableMountHooks Z
|
||||
public static field enableOnDemandReactChoreographer Z
|
||||
public static field enableRemoveDeleteTreeInstruction Z
|
||||
public static field enableSpannableBuildingUnification Z
|
||||
public static field enableTextSpannableCache Z
|
||||
public static field enableViewRecycling Z
|
||||
public static field excludeYogaFromRawProps Z
|
||||
@@ -6785,7 +6786,7 @@ public class com/facebook/react/views/text/ReactBackgroundColorSpan : android/te
|
||||
public fun <init> (I)V
|
||||
}
|
||||
|
||||
public abstract class com/facebook/react/views/text/ReactBaseTextShadowNode : com/facebook/react/uimanager/LayoutShadowNode {
|
||||
public abstract class com/facebook/react/views/text/ReactBaseTextShadowNode : com/facebook/react/uimanager/LayoutShadowNode, com/facebook/react/views/text/BasicTextAttributeProvider {
|
||||
public static final field DEFAULT_TEXT_SHADOW_COLOR I
|
||||
public static final field PROP_SHADOW_COLOR Ljava/lang/String;
|
||||
public static final field PROP_SHADOW_OFFSET Ljava/lang/String;
|
||||
@@ -6793,7 +6794,6 @@ public abstract class com/facebook/react/views/text/ReactBaseTextShadowNode : co
|
||||
public static final field PROP_SHADOW_OFFSET_WIDTH Ljava/lang/String;
|
||||
public static final field PROP_SHADOW_RADIUS Ljava/lang/String;
|
||||
public static final field PROP_TEXT_TRANSFORM Ljava/lang/String;
|
||||
public static final field UNSET I
|
||||
protected field mAccessibilityRole Lcom/facebook/react/uimanager/ReactAccessibilityDelegate$AccessibilityRole;
|
||||
protected field mAdjustsFontSizeToFit Z
|
||||
protected field mBackgroundColor I
|
||||
@@ -6824,6 +6824,22 @@ public abstract class com/facebook/react/views/text/ReactBaseTextShadowNode : co
|
||||
protected field mTextShadowRadius F
|
||||
public fun <init> ()V
|
||||
public fun <init> (Lcom/facebook/react/views/text/ReactTextViewManagerCallback;)V
|
||||
public fun getAccessibilityRole ()Lcom/facebook/react/uimanager/ReactAccessibilityDelegate$AccessibilityRole;
|
||||
public fun getBackgroundColor ()I
|
||||
public fun getColor ()I
|
||||
public fun getFontFamily ()Ljava/lang/String;
|
||||
public fun getFontFeatureSettings ()Ljava/lang/String;
|
||||
public fun getFontStyle ()I
|
||||
public fun getFontWeight ()I
|
||||
public fun getRole ()Lcom/facebook/react/uimanager/ReactAccessibilityDelegate$Role;
|
||||
public fun getTextShadowColor ()I
|
||||
public fun getTextShadowOffsetDx ()F
|
||||
public fun getTextShadowOffsetDy ()F
|
||||
public fun getTextShadowRadius ()F
|
||||
public fun isBackgroundColorSet ()Z
|
||||
public fun isColorSet ()Z
|
||||
public fun isLineThroughTextDecorationSet ()Z
|
||||
public fun isUnderlineTextDecorationSet ()Z
|
||||
public fun setAccessibilityRole (Ljava/lang/String;)V
|
||||
public fun setAdjustFontSizeToFit (Z)V
|
||||
public fun setAllowFontScaling (Z)V
|
||||
@@ -7084,12 +7100,19 @@ public class com/facebook/react/views/text/ReactVirtualTextViewManager$$PropsSet
|
||||
public fun setProperty (Lcom/facebook/react/views/text/ReactVirtualTextViewManager;Landroid/view/View;Ljava/lang/String;Ljava/lang/Object;)V
|
||||
}
|
||||
|
||||
public class com/facebook/react/views/text/SetSpanOperation {
|
||||
protected field end I
|
||||
protected field start I
|
||||
protected field what Lcom/facebook/react/views/text/ReactSpan;
|
||||
public fun execute (Landroid/text/SpannableStringBuilder;I)V
|
||||
}
|
||||
|
||||
public class com/facebook/react/views/text/ShadowStyleSpan : android/text/style/CharacterStyle, com/facebook/react/views/text/ReactSpan {
|
||||
public fun <init> (FFFI)V
|
||||
public fun updateDrawState (Landroid/text/TextPaint;)V
|
||||
}
|
||||
|
||||
public class com/facebook/react/views/text/TextAttributeProps {
|
||||
public class com/facebook/react/views/text/TextAttributeProps : com/facebook/react/views/text/EffectiveTextAttributeProvider {
|
||||
public static final field TA_KEY_ACCESSIBILITY_ROLE S
|
||||
public static final field TA_KEY_ALIGNMENT S
|
||||
public static final field TA_KEY_ALLOW_FONT_SCALING S
|
||||
@@ -7117,7 +7140,6 @@ public class com/facebook/react/views/text/TextAttributeProps {
|
||||
public static final field TA_KEY_TEXT_SHADOW_OFFSET_DY S
|
||||
public static final field TA_KEY_TEXT_SHADOW_RADIUS S
|
||||
public static final field TA_KEY_TEXT_TRANSFORM S
|
||||
public static final field UNSET I
|
||||
protected field mAccessibilityRole Lcom/facebook/react/uimanager/ReactAccessibilityDelegate$AccessibilityRole;
|
||||
protected field mAllowFontScaling Z
|
||||
protected field mBackgroundColor I
|
||||
@@ -7149,13 +7171,32 @@ public class com/facebook/react/views/text/TextAttributeProps {
|
||||
protected field mTextTransform Lcom/facebook/react/views/text/TextTransform;
|
||||
public static fun fromMapBuffer (Lcom/facebook/react/common/mapbuffer/MapBuffer;)Lcom/facebook/react/views/text/TextAttributeProps;
|
||||
public static fun fromReadableMap (Lcom/facebook/react/uimanager/ReactStylesDiffMap;)Lcom/facebook/react/views/text/TextAttributeProps;
|
||||
public fun getAccessibilityRole ()Lcom/facebook/react/uimanager/ReactAccessibilityDelegate$AccessibilityRole;
|
||||
public fun getBackgroundColor ()I
|
||||
public fun getColor ()I
|
||||
public fun getEffectiveFontSize ()I
|
||||
public fun getEffectiveLetterSpacing ()F
|
||||
public fun getEffectiveLineHeight ()F
|
||||
public fun getFontFamily ()Ljava/lang/String;
|
||||
public fun getFontFeatureSettings ()Ljava/lang/String;
|
||||
public fun getFontStyle ()I
|
||||
public fun getFontWeight ()I
|
||||
public static fun getHyphenationFrequency (Ljava/lang/String;)I
|
||||
public static fun getJustificationMode (Lcom/facebook/react/uimanager/ReactStylesDiffMap;I)I
|
||||
public static fun getLayoutDirection (Ljava/lang/String;)I
|
||||
public fun getLetterSpacing ()F
|
||||
public fun getRole ()Lcom/facebook/react/uimanager/ReactAccessibilityDelegate$Role;
|
||||
public static fun getTextAlignment (Lcom/facebook/react/uimanager/ReactStylesDiffMap;ZI)I
|
||||
public static fun getTextBreakStrategy (Ljava/lang/String;)I
|
||||
public fun getTextShadowColor ()I
|
||||
public fun getTextShadowOffsetDx ()F
|
||||
public fun getTextShadowOffsetDy ()F
|
||||
public fun getTextShadowRadius ()F
|
||||
public fun getTextTransform ()Lcom/facebook/react/views/text/TextTransform;
|
||||
public fun isBackgroundColorSet ()Z
|
||||
public fun isColorSet ()Z
|
||||
public fun isLineThroughTextDecorationSet ()Z
|
||||
public fun isUnderlineTextDecorationSet ()Z
|
||||
}
|
||||
|
||||
public class com/facebook/react/views/text/TextAttributes {
|
||||
|
||||
+8
@@ -10,4 +10,12 @@ package com.facebook.react.common;
|
||||
public class ReactConstants {
|
||||
|
||||
public static final String TAG = "ReactNative";
|
||||
|
||||
/**
|
||||
* Some types have built-in support for representing a "missing" or "unset" value, for example NaN
|
||||
* in the case of floating point numbers or null in the case of object references. Integers don't
|
||||
* have such a special value. When an integer represent an inherently non-negative value, we use a
|
||||
* special negative value to mark it as "unset".
|
||||
*/
|
||||
public static final int UNSET = -1;
|
||||
}
|
||||
|
||||
+6
-6
@@ -15,6 +15,7 @@ import android.util.SparseArray;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.content.res.ResourcesCompat;
|
||||
import com.facebook.infer.annotation.Nullsafe;
|
||||
import com.facebook.react.common.ReactConstants;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -167,8 +168,6 @@ public class ReactFontManager {
|
||||
|
||||
public static final int BOLD = 700;
|
||||
public static final int NORMAL = 400;
|
||||
public static final int UNSET = -1;
|
||||
|
||||
private static final int MIN_WEIGHT = 1;
|
||||
private static final int MAX_WEIGHT = 1000;
|
||||
|
||||
@@ -177,11 +176,11 @@ public class ReactFontManager {
|
||||
|
||||
public TypefaceStyle(int weight, boolean italic) {
|
||||
mItalic = italic;
|
||||
mWeight = weight == UNSET ? NORMAL : weight;
|
||||
mWeight = weight == ReactConstants.UNSET ? NORMAL : weight;
|
||||
}
|
||||
|
||||
public TypefaceStyle(int style) {
|
||||
if (style == UNSET) {
|
||||
if (style == ReactConstants.UNSET) {
|
||||
style = Typeface.NORMAL;
|
||||
}
|
||||
|
||||
@@ -194,12 +193,13 @@ public class ReactFontManager {
|
||||
* existing weight bit in `style` will be used.
|
||||
*/
|
||||
public TypefaceStyle(int style, int weight) {
|
||||
if (style == UNSET) {
|
||||
if (style == ReactConstants.UNSET) {
|
||||
style = Typeface.NORMAL;
|
||||
}
|
||||
|
||||
mItalic = (style & Typeface.ITALIC) != 0;
|
||||
mWeight = weight == UNSET ? (style & Typeface.BOLD) != 0 ? BOLD : NORMAL : weight;
|
||||
mWeight =
|
||||
weight == ReactConstants.UNSET ? (style & Typeface.BOLD) != 0 ? BOLD : NORMAL : weight;
|
||||
}
|
||||
|
||||
public int getNearestStyle() {
|
||||
|
||||
+3
@@ -173,4 +173,7 @@ public class ReactFeatureFlags {
|
||||
* when there is work to do.
|
||||
*/
|
||||
public static boolean enableOnDemandReactChoreographer = false;
|
||||
|
||||
/** Enables the new unified {@link android.text.Spannable} building logic. */
|
||||
public static boolean enableSpannableBuildingUnification = false;
|
||||
}
|
||||
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
import com.facebook.react.uimanager.ReactAccessibilityDelegate
|
||||
|
||||
/**
|
||||
* Interface for an entity providing basic text attributes of a text node/fragment. "Basic" means
|
||||
* that they can be provided trivially, without processing the parent element.
|
||||
*/
|
||||
internal interface BasicTextAttributeProvider {
|
||||
val role: ReactAccessibilityDelegate.Role?
|
||||
|
||||
val accessibilityRole: ReactAccessibilityDelegate.AccessibilityRole?
|
||||
|
||||
val isBackgroundColorSet: Boolean
|
||||
|
||||
val backgroundColor: Int
|
||||
|
||||
val isColorSet: Boolean
|
||||
|
||||
val color: Int
|
||||
|
||||
val fontStyle: Int
|
||||
|
||||
val fontWeight: Int
|
||||
|
||||
val fontFamily: String?
|
||||
|
||||
val fontFeatureSettings: String?
|
||||
|
||||
val isUnderlineTextDecorationSet: Boolean
|
||||
|
||||
val isLineThroughTextDecorationSet: Boolean
|
||||
|
||||
val textShadowOffsetDx: Float
|
||||
|
||||
val textShadowOffsetDy: Float
|
||||
|
||||
val textShadowRadius: Float
|
||||
|
||||
val textShadowColor: Int
|
||||
}
|
||||
+3
-4
@@ -14,6 +14,7 @@ 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;
|
||||
|
||||
@Nullsafe(Nullsafe.Mode.LOCAL)
|
||||
@@ -61,13 +62,11 @@ public class CustomStyleSpan extends MetricAffectingSpan implements ReactSpan {
|
||||
}
|
||||
|
||||
public int getStyle() {
|
||||
return mStyle == ReactFontManager.TypefaceStyle.UNSET ? Typeface.NORMAL : mStyle;
|
||||
return mStyle == ReactConstants.UNSET ? Typeface.NORMAL : mStyle;
|
||||
}
|
||||
|
||||
public int getWeight() {
|
||||
return mWeight == ReactFontManager.TypefaceStyle.UNSET
|
||||
? ReactFontManager.TypefaceStyle.NORMAL
|
||||
: mWeight;
|
||||
return mWeight == ReactConstants.UNSET ? ReactFontManager.TypefaceStyle.NORMAL : mWeight;
|
||||
}
|
||||
|
||||
public @Nullable String getFontFamily() {
|
||||
|
||||
+22
@@ -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
|
||||
|
||||
import com.facebook.react.common.ReactConstants.UNSET
|
||||
|
||||
/** Interface for an entity providing effective text attributes of a text node/fragment */
|
||||
internal interface EffectiveTextAttributeProvider : BasicTextAttributeProvider {
|
||||
val textTransform: TextTransform
|
||||
|
||||
val effectiveLetterSpacing: Float
|
||||
|
||||
/** @return The effective font size, or [UNSET] if not set */
|
||||
val effectiveFontSize: Int
|
||||
|
||||
val effectiveLineHeight: Float
|
||||
}
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
import com.facebook.react.common.ReactConstants
|
||||
|
||||
/**
|
||||
* Implementation of [EffectiveTextAttributeProvider] that provides effective text attributes based
|
||||
* on a [ReactBaseTextShadowNode] instance and its parent.
|
||||
*/
|
||||
internal class HierarchicTextAttributeProvider(
|
||||
private val textShadowNode: ReactBaseTextShadowNode,
|
||||
private val parentTextAttributes: TextAttributes?,
|
||||
private val textAttributes: TextAttributes
|
||||
) : EffectiveTextAttributeProvider, BasicTextAttributeProvider by textShadowNode {
|
||||
override val textTransform: TextTransform
|
||||
get() = textAttributes.textTransform
|
||||
|
||||
override val effectiveLetterSpacing: Float
|
||||
get() {
|
||||
val letterSpacing = textAttributes.effectiveLetterSpacing
|
||||
|
||||
val isParentLetterSpacingDifferent =
|
||||
parentTextAttributes == null ||
|
||||
parentTextAttributes.effectiveLetterSpacing != letterSpacing
|
||||
|
||||
return if (!letterSpacing.isNaN() && isParentLetterSpacingDifferent) {
|
||||
letterSpacing
|
||||
} else {
|
||||
Float.NaN
|
||||
}
|
||||
}
|
||||
|
||||
override val effectiveFontSize: Int
|
||||
get() {
|
||||
val fontSize = textAttributes.effectiveFontSize
|
||||
|
||||
return if (parentTextAttributes == null ||
|
||||
parentTextAttributes.effectiveFontSize != fontSize) {
|
||||
fontSize
|
||||
} else {
|
||||
ReactConstants.UNSET
|
||||
}
|
||||
}
|
||||
|
||||
override val effectiveLineHeight: Float
|
||||
get() {
|
||||
val lineHeight = textAttributes.effectiveLineHeight
|
||||
val isParentLineHeightDifferent =
|
||||
parentTextAttributes == null || parentTextAttributes.effectiveLineHeight != lineHeight
|
||||
|
||||
return if (!lineHeight.isNaN() && isParentLineHeightDifferent) {
|
||||
lineHeight
|
||||
} else {
|
||||
Float.NaN
|
||||
}
|
||||
}
|
||||
}
|
||||
+201
-11
@@ -21,7 +21,7 @@ import com.facebook.infer.annotation.Assertions;
|
||||
import com.facebook.react.bridge.ReadableArray;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
import com.facebook.react.common.ReactConstants;
|
||||
import com.facebook.react.common.assets.ReactFontManager;
|
||||
import com.facebook.react.config.ReactFeatureFlags;
|
||||
import com.facebook.react.uimanager.IllegalViewOperationException;
|
||||
import com.facebook.react.uimanager.LayoutShadowNode;
|
||||
import com.facebook.react.uimanager.NativeViewHierarchyOptimizer;
|
||||
@@ -49,13 +49,13 @@ import java.util.Map;
|
||||
* <p>This also node calculates {@link Spannable} object based on subnodes of the same type, which
|
||||
* can be used in concrete classes to feed native views and compute layout.
|
||||
*/
|
||||
public abstract class ReactBaseTextShadowNode extends LayoutShadowNode {
|
||||
public abstract class ReactBaseTextShadowNode extends LayoutShadowNode
|
||||
implements BasicTextAttributeProvider {
|
||||
|
||||
// Use a direction weak character so the placeholder doesn't change the direction of the previous
|
||||
// character.
|
||||
// https://en.wikipedia.org/wiki/Bi-directional_text#weak_characters
|
||||
private static final String INLINE_VIEW_PLACEHOLDER = "0";
|
||||
public static final int UNSET = ReactFontManager.TypefaceStyle.UNSET;
|
||||
|
||||
public static final String PROP_SHADOW_OFFSET = "textShadowOffset";
|
||||
public static final String PROP_SHADOW_OFFSET_WIDTH = "width";
|
||||
@@ -77,6 +77,23 @@ public abstract class ReactBaseTextShadowNode extends LayoutShadowNode {
|
||||
boolean supportsInlineViews,
|
||||
@Nullable Map<Integer, ReactShadowNode> inlineViews,
|
||||
int start) {
|
||||
if (ReactFeatureFlags.enableSpannableBuildingUnification) {
|
||||
buildSpannedFromShadowNodeUnified(
|
||||
textShadowNode, sb, ops, parentTextAttributes, supportsInlineViews, inlineViews, start);
|
||||
} else {
|
||||
buildSpannedFromShadowNodeDuplicated(
|
||||
textShadowNode, sb, ops, parentTextAttributes, supportsInlineViews, inlineViews, start);
|
||||
}
|
||||
}
|
||||
|
||||
private static void buildSpannedFromShadowNodeDuplicated(
|
||||
ReactBaseTextShadowNode textShadowNode,
|
||||
SpannableStringBuilder sb,
|
||||
List<SetSpanOperation> ops,
|
||||
@Nullable TextAttributes parentTextAttributes,
|
||||
boolean supportsInlineViews,
|
||||
@Nullable Map<Integer, ReactShadowNode> inlineViews,
|
||||
int start) {
|
||||
|
||||
TextAttributes textAttributes;
|
||||
if (parentTextAttributes != null) {
|
||||
@@ -93,7 +110,7 @@ public abstract class ReactBaseTextShadowNode extends LayoutShadowNode {
|
||||
TextTransform.apply(
|
||||
((ReactRawTextShadowNode) child).getText(), textAttributes.getTextTransform()));
|
||||
} else if (child instanceof ReactBaseTextShadowNode) {
|
||||
buildSpannedFromShadowNode(
|
||||
buildSpannedFromShadowNodeDuplicated(
|
||||
(ReactBaseTextShadowNode) child,
|
||||
sb,
|
||||
ops,
|
||||
@@ -177,8 +194,8 @@ public abstract class ReactBaseTextShadowNode extends LayoutShadowNode {
|
||||
|| parentTextAttributes.getEffectiveFontSize() != effectiveFontSize) {
|
||||
ops.add(new SetSpanOperation(start, end, new ReactAbsoluteSizeSpan(effectiveFontSize)));
|
||||
}
|
||||
if (textShadowNode.mFontStyle != UNSET
|
||||
|| textShadowNode.mFontWeight != UNSET
|
||||
if (textShadowNode.mFontStyle != ReactConstants.UNSET
|
||||
|| textShadowNode.mFontWeight != ReactConstants.UNSET
|
||||
|| textShadowNode.mFontFamily != null) {
|
||||
ops.add(
|
||||
new SetSpanOperation(
|
||||
@@ -221,6 +238,99 @@ public abstract class ReactBaseTextShadowNode extends LayoutShadowNode {
|
||||
}
|
||||
}
|
||||
|
||||
private static void buildSpannedFromShadowNodeUnified(
|
||||
ReactBaseTextShadowNode textShadowNode,
|
||||
SpannableStringBuilder sb,
|
||||
List<SetSpanOperation> ops,
|
||||
@Nullable TextAttributes parentTextAttributes,
|
||||
boolean supportsInlineViews,
|
||||
@Nullable Map<Integer, ReactShadowNode> inlineViews,
|
||||
int start) {
|
||||
|
||||
TextAttributes textAttributes;
|
||||
if (parentTextAttributes != null) {
|
||||
textAttributes = parentTextAttributes.applyChild(textShadowNode.mTextAttributes);
|
||||
} else {
|
||||
textAttributes = textShadowNode.mTextAttributes;
|
||||
}
|
||||
|
||||
final HierarchicTextAttributeProvider textAttributeProvider =
|
||||
new HierarchicTextAttributeProvider(textShadowNode, parentTextAttributes, textAttributes);
|
||||
|
||||
for (int i = 0, length = textShadowNode.getChildCount(); i < length; i++) {
|
||||
ReactShadowNode child = textShadowNode.getChildAt(i);
|
||||
|
||||
if (child instanceof ReactRawTextShadowNode) {
|
||||
TextLayoutUtils.addText(
|
||||
sb, ((ReactRawTextShadowNode) child).getText(), textAttributeProvider);
|
||||
} else if (child instanceof ReactBaseTextShadowNode) {
|
||||
buildSpannedFromShadowNodeUnified(
|
||||
(ReactBaseTextShadowNode) child,
|
||||
sb,
|
||||
ops,
|
||||
textAttributes,
|
||||
supportsInlineViews,
|
||||
inlineViews,
|
||||
sb.length());
|
||||
} else if (child instanceof ReactTextInlineImageShadowNode) {
|
||||
addInlineImageSpan(ops, sb, (ReactTextInlineImageShadowNode) child);
|
||||
} else if (supportsInlineViews) {
|
||||
addInlineViewPlaceholderSpan(ops, sb, child);
|
||||
|
||||
inlineViews.put(child.getReactTag(), child);
|
||||
} else {
|
||||
throw new IllegalViewOperationException(
|
||||
"Unexpected view type nested under a <Text> or <TextInput> node: " + child.getClass());
|
||||
}
|
||||
child.markUpdateSeen();
|
||||
}
|
||||
int end = sb.length();
|
||||
if (end >= start) {
|
||||
final int reactTag = textShadowNode.getReactTag();
|
||||
|
||||
TextLayoutUtils.addApplicableTextAttributeSpans(
|
||||
ops, textAttributeProvider, reactTag, textShadowNode.getThemedContext(), start, end);
|
||||
}
|
||||
}
|
||||
|
||||
private static void addInlineImageSpan(
|
||||
List<SetSpanOperation> ops, SpannableStringBuilder sb, ReactTextInlineImageShadowNode child) {
|
||||
// We make the image take up 1 character in the span and put a corresponding character into
|
||||
// the text so that the image doesn't run over any following text.
|
||||
sb.append(INLINE_VIEW_PLACEHOLDER);
|
||||
ops.add(
|
||||
new SetSpanOperation(
|
||||
sb.length() - INLINE_VIEW_PLACEHOLDER.length(),
|
||||
sb.length(),
|
||||
child.buildInlineImageSpan()));
|
||||
}
|
||||
|
||||
private static void addInlineViewPlaceholderSpan(
|
||||
List<SetSpanOperation> ops, SpannableStringBuilder sb, ReactShadowNode child) {
|
||||
YogaValue widthValue = child.getStyleWidth();
|
||||
YogaValue heightValue = child.getStyleHeight();
|
||||
|
||||
float width;
|
||||
float height;
|
||||
if (widthValue.unit != YogaUnit.POINT || heightValue.unit != YogaUnit.POINT) {
|
||||
// If the measurement of the child isn't calculated, we calculate the layout for the
|
||||
// view using Yoga
|
||||
child.calculateLayout();
|
||||
width = child.getLayoutWidth();
|
||||
height = child.getLayoutHeight();
|
||||
} else {
|
||||
width = widthValue.value;
|
||||
height = heightValue.value;
|
||||
}
|
||||
|
||||
// We make the inline view take up 1 character in the span and put a corresponding character
|
||||
// into the text so that
|
||||
// the inline view doesn't run over any following text.
|
||||
sb.append(INLINE_VIEW_PLACEHOLDER);
|
||||
|
||||
TextLayoutUtils.addInlineViewPlaceholderSpan(ops, sb, child.getReactTag(), width, height);
|
||||
}
|
||||
|
||||
// `nativeViewHierarchyOptimizer` can be `null` as long as `supportsInlineViews` is `false`.
|
||||
protected Spannable spannedFromShadowNode(
|
||||
ReactBaseTextShadowNode textShadowNode,
|
||||
@@ -307,7 +417,7 @@ public abstract class ReactBaseTextShadowNode extends LayoutShadowNode {
|
||||
protected @Nullable AccessibilityRole mAccessibilityRole = null;
|
||||
protected @Nullable Role mRole = null;
|
||||
|
||||
protected int mNumberOfLines = UNSET;
|
||||
protected int mNumberOfLines = ReactConstants.UNSET;
|
||||
protected int mTextAlign = Gravity.NO_GRAVITY;
|
||||
protected int mTextBreakStrategy = Layout.BREAK_STRATEGY_HIGH_QUALITY;
|
||||
protected int mHyphenationFrequency = Layout.HYPHENATION_FREQUENCY_NONE;
|
||||
@@ -329,9 +439,9 @@ public abstract class ReactBaseTextShadowNode extends LayoutShadowNode {
|
||||
* mFontStyle can be {@link Typeface#NORMAL} or {@link Typeface#ITALIC}. mFontWeight can be {@link
|
||||
* Typeface#NORMAL} or {@link Typeface#BOLD}.
|
||||
*/
|
||||
protected int mFontStyle = UNSET;
|
||||
protected int mFontStyle = ReactConstants.UNSET;
|
||||
|
||||
protected int mFontWeight = UNSET;
|
||||
protected int mFontWeight = ReactConstants.UNSET;
|
||||
/**
|
||||
* NB: If a font family is used that does not have a style in a certain Android version (ie.
|
||||
* monospace bold pre Android 5.0), that style (ie. bold) will not be inherited by nested Text
|
||||
@@ -384,9 +494,9 @@ public abstract class ReactBaseTextShadowNode extends LayoutShadowNode {
|
||||
return textAlign;
|
||||
}
|
||||
|
||||
@ReactProp(name = ViewProps.NUMBER_OF_LINES, defaultInt = UNSET)
|
||||
@ReactProp(name = ViewProps.NUMBER_OF_LINES, defaultInt = ReactConstants.UNSET)
|
||||
public void setNumberOfLines(int numberOfLines) {
|
||||
mNumberOfLines = numberOfLines == 0 ? UNSET : numberOfLines;
|
||||
mNumberOfLines = numberOfLines == 0 ? ReactConstants.UNSET : numberOfLines;
|
||||
markUpdated();
|
||||
}
|
||||
|
||||
@@ -452,6 +562,11 @@ public abstract class ReactBaseTextShadowNode extends LayoutShadowNode {
|
||||
markUpdated();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getColor() {
|
||||
return mColor;
|
||||
}
|
||||
|
||||
@ReactProp(name = ViewProps.COLOR, customType = "Color")
|
||||
public void setColor(@Nullable Integer color) {
|
||||
mIsColorSet = (color != null);
|
||||
@@ -461,6 +576,16 @@ public abstract class ReactBaseTextShadowNode extends LayoutShadowNode {
|
||||
markUpdated();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isColorSet() {
|
||||
return mIsColorSet;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBackgroundColor() {
|
||||
return mBackgroundColor;
|
||||
}
|
||||
|
||||
@ReactProp(name = ViewProps.BACKGROUND_COLOR, customType = "Color")
|
||||
public void setBackgroundColor(@Nullable Integer color) {
|
||||
// Background color needs to be handled here for virtual nodes so it can be incorporated into
|
||||
@@ -476,6 +601,16 @@ public abstract class ReactBaseTextShadowNode extends LayoutShadowNode {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBackgroundColorSet() {
|
||||
return mIsBackgroundColorSet;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable AccessibilityRole getAccessibilityRole() {
|
||||
return mAccessibilityRole;
|
||||
}
|
||||
|
||||
@ReactProp(name = ViewProps.ACCESSIBILITY_ROLE)
|
||||
public void setAccessibilityRole(@Nullable String accessibilityRole) {
|
||||
if (isVirtual()) {
|
||||
@@ -484,6 +619,11 @@ public abstract class ReactBaseTextShadowNode extends LayoutShadowNode {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Role getRole() {
|
||||
return mRole;
|
||||
}
|
||||
|
||||
@ReactProp(name = ViewProps.ROLE)
|
||||
public void setRole(@Nullable String role) {
|
||||
if (isVirtual()) {
|
||||
@@ -492,12 +632,22 @@ public abstract class ReactBaseTextShadowNode extends LayoutShadowNode {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFontFamily() {
|
||||
return mFontFamily;
|
||||
}
|
||||
|
||||
@ReactProp(name = ViewProps.FONT_FAMILY)
|
||||
public void setFontFamily(@Nullable String fontFamily) {
|
||||
mFontFamily = fontFamily;
|
||||
markUpdated();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getFontWeight() {
|
||||
return mFontWeight;
|
||||
}
|
||||
|
||||
@ReactProp(name = ViewProps.FONT_WEIGHT)
|
||||
public void setFontWeight(@Nullable String fontWeightString) {
|
||||
int fontWeight = ReactTypefaceUtils.parseFontWeight(fontWeightString);
|
||||
@@ -517,6 +667,16 @@ public abstract class ReactBaseTextShadowNode extends LayoutShadowNode {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFontFeatureSettings() {
|
||||
return mFontFeatureSettings;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getFontStyle() {
|
||||
return mFontStyle;
|
||||
}
|
||||
|
||||
@ReactProp(name = ViewProps.FONT_STYLE)
|
||||
public void setFontStyle(@Nullable String fontStyleString) {
|
||||
int fontStyle = ReactTypefaceUtils.parseFontStyle(fontStyleString);
|
||||
@@ -547,6 +707,16 @@ public abstract class ReactBaseTextShadowNode extends LayoutShadowNode {
|
||||
markUpdated();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUnderlineTextDecorationSet() {
|
||||
return mIsUnderlineTextDecorationSet;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLineThroughTextDecorationSet() {
|
||||
return mIsLineThroughTextDecorationSet;
|
||||
}
|
||||
|
||||
@ReactProp(name = ViewProps.TEXT_BREAK_STRATEGY)
|
||||
public void setTextBreakStrategy(@Nullable String textBreakStrategy) {
|
||||
if (textBreakStrategy == null || "highQuality".equals(textBreakStrategy)) {
|
||||
@@ -584,6 +754,21 @@ public abstract class ReactBaseTextShadowNode extends LayoutShadowNode {
|
||||
markUpdated();
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getTextShadowOffsetDx() {
|
||||
return mTextShadowOffsetDx;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getTextShadowOffsetDy() {
|
||||
return mTextShadowOffsetDy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getTextShadowRadius() {
|
||||
return mTextShadowRadius;
|
||||
}
|
||||
|
||||
@ReactProp(name = PROP_SHADOW_RADIUS, defaultInt = 1)
|
||||
public void setTextShadowRadius(float textShadowRadius) {
|
||||
if (textShadowRadius != mTextShadowRadius) {
|
||||
@@ -592,6 +777,11 @@ public abstract class ReactBaseTextShadowNode extends LayoutShadowNode {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTextShadowColor() {
|
||||
return mTextShadowColor;
|
||||
}
|
||||
|
||||
@ReactProp(name = PROP_SHADOW_COLOR, defaultInt = DEFAULT_TEXT_SHADOW_COLOR, customType = "Color")
|
||||
public void setTextShadowColor(int textShadowColor) {
|
||||
if (textShadowColor != mTextShadowColor) {
|
||||
|
||||
+4
-3
@@ -23,6 +23,7 @@ import com.facebook.react.bridge.ReactNoCrashSoftException;
|
||||
import com.facebook.react.bridge.ReactSoftExceptionLogger;
|
||||
import com.facebook.react.bridge.WritableArray;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.facebook.react.common.ReactConstants;
|
||||
import com.facebook.react.uimanager.NativeViewHierarchyOptimizer;
|
||||
import com.facebook.react.uimanager.PixelUtil;
|
||||
import com.facebook.react.uimanager.ReactShadowNode;
|
||||
@@ -80,7 +81,7 @@ public class ReactTextShadowNode extends ReactBaseTextShadowNode {
|
||||
int minimumFontSize =
|
||||
(int) Math.max(mMinimumFontScale * initialFontSize, PixelUtil.toPixelFromDIP(4));
|
||||
while (currentFontSize > minimumFontSize
|
||||
&& (mNumberOfLines != UNSET && layout.getLineCount() > mNumberOfLines
|
||||
&& (mNumberOfLines != ReactConstants.UNSET && layout.getLineCount() > mNumberOfLines
|
||||
|| heightMode != YogaMeasureMode.UNDEFINED && layout.getHeight() > height)) {
|
||||
// TODO: We could probably use a smarter algorithm here. This will require 0(n)
|
||||
// measurements
|
||||
@@ -122,7 +123,7 @@ public class ReactTextShadowNode extends ReactBaseTextShadowNode {
|
||||
}
|
||||
|
||||
final int lineCount =
|
||||
mNumberOfLines == UNSET
|
||||
mNumberOfLines == ReactConstants.UNSET
|
||||
? layout.getLineCount()
|
||||
: Math.min(mNumberOfLines, layout.getLineCount());
|
||||
|
||||
@@ -327,7 +328,7 @@ public class ReactTextShadowNode extends ReactBaseTextShadowNode {
|
||||
ReactTextUpdate reactTextUpdate =
|
||||
new ReactTextUpdate(
|
||||
mPreparedSpannableText,
|
||||
UNSET,
|
||||
ReactConstants.UNSET,
|
||||
mContainsImages,
|
||||
getPadding(Spacing.START),
|
||||
getPadding(Spacing.TOP),
|
||||
|
||||
+5
-6
@@ -7,10 +7,9 @@
|
||||
|
||||
package com.facebook.react.views.text;
|
||||
|
||||
import static com.facebook.react.views.text.TextAttributeProps.UNSET;
|
||||
|
||||
import android.text.Layout;
|
||||
import android.text.Spannable;
|
||||
import com.facebook.react.common.ReactConstants;
|
||||
|
||||
/**
|
||||
* Class that contains the data needed for a text update. Used by both <Text/> and <TextInput/>
|
||||
@@ -67,10 +66,10 @@ public class ReactTextUpdate {
|
||||
text,
|
||||
jsEventCounter,
|
||||
containsImages,
|
||||
UNSET,
|
||||
UNSET,
|
||||
UNSET,
|
||||
UNSET,
|
||||
ReactConstants.UNSET,
|
||||
ReactConstants.UNSET,
|
||||
ReactConstants.UNSET,
|
||||
ReactConstants.UNSET,
|
||||
textAlign,
|
||||
textBreakStrategy,
|
||||
justificationMode);
|
||||
|
||||
+4
-6
@@ -7,8 +7,6 @@
|
||||
|
||||
package com.facebook.react.views.text;
|
||||
|
||||
import static com.facebook.react.views.text.TextAttributeProps.UNSET;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Build;
|
||||
@@ -376,10 +374,10 @@ public class ReactTextView extends AppCompatTextView implements ReactCompoundVie
|
||||
// In Fabric padding is set by the update of Layout Metrics and not as part of the "setText"
|
||||
// operation
|
||||
// TODO T56559197: remove this condition when we migrate 100% to Fabric
|
||||
if (paddingLeft != UNSET
|
||||
&& paddingTop != UNSET
|
||||
&& paddingRight != UNSET
|
||||
&& paddingBottom != UNSET) {
|
||||
if (paddingLeft != ReactConstants.UNSET
|
||||
&& paddingTop != ReactConstants.UNSET
|
||||
&& paddingRight != ReactConstants.UNSET
|
||||
&& paddingBottom != ReactConstants.UNSET) {
|
||||
|
||||
setPadding(
|
||||
(int) Math.floor(paddingLeft),
|
||||
|
||||
+3
-2
@@ -13,6 +13,7 @@ import android.text.TextUtils;
|
||||
import androidx.annotation.Nullable;
|
||||
import com.facebook.infer.annotation.Nullsafe;
|
||||
import com.facebook.react.bridge.ReadableArray;
|
||||
import com.facebook.react.common.ReactConstants;
|
||||
import com.facebook.react.common.assets.ReactFontManager;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -45,7 +46,7 @@ public class ReactTypefaceUtils {
|
||||
return 900;
|
||||
}
|
||||
}
|
||||
return ReactFontManager.TypefaceStyle.UNSET;
|
||||
return ReactConstants.UNSET;
|
||||
}
|
||||
|
||||
public static int parseFontStyle(@Nullable String fontStyleString) {
|
||||
@@ -57,7 +58,7 @@ public class ReactTypefaceUtils {
|
||||
return Typeface.NORMAL;
|
||||
}
|
||||
}
|
||||
return ReactFontManager.TypefaceStyle.UNSET;
|
||||
return ReactConstants.UNSET;
|
||||
}
|
||||
|
||||
public static @Nullable String parseFontVariant(@Nullable ReadableArray fontVariantArray) {
|
||||
|
||||
+1
-1
@@ -12,7 +12,7 @@ import android.text.SpannableStringBuilder;
|
||||
import android.text.Spanned;
|
||||
import com.facebook.common.logging.FLog;
|
||||
|
||||
class SetSpanOperation {
|
||||
public class SetSpanOperation {
|
||||
private static final String TAG = "SetSpanOperation";
|
||||
static final int SPAN_MAX_PRIORITY = Spanned.SPAN_PRIORITY >> Spanned.SPAN_PRIORITY_SHIFT;
|
||||
|
||||
|
||||
+117
-20
@@ -12,6 +12,7 @@ import android.text.Layout;
|
||||
import android.text.TextUtils;
|
||||
import android.util.LayoutDirection;
|
||||
import android.view.Gravity;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import com.facebook.common.logging.FLog;
|
||||
import com.facebook.react.bridge.ReadableArray;
|
||||
@@ -30,7 +31,7 @@ import java.util.List;
|
||||
// TODO: T63643819 refactor naming of TextAttributeProps to make explicit that this represents
|
||||
// TextAttributes and not TextProps. As part of this refactor extract methods that don't belong to
|
||||
// TextAttributeProps (e.g. TextAlign)
|
||||
public class TextAttributeProps {
|
||||
public class TextAttributeProps implements EffectiveTextAttributeProvider {
|
||||
|
||||
// constants for Text Attributes serialization
|
||||
public static final short TA_KEY_FOREGROUND_COLOR = 0;
|
||||
@@ -61,8 +62,6 @@ public class TextAttributeProps {
|
||||
public static final short TA_KEY_ROLE = 26;
|
||||
public static final short TA_KEY_TEXT_TRANSFORM = 27;
|
||||
|
||||
public static final int UNSET = -1;
|
||||
|
||||
private static final String PROP_SHADOW_OFFSET = "textShadowOffset";
|
||||
private static final String PROP_SHADOW_OFFSET_WIDTH = "width";
|
||||
private static final String PROP_SHADOW_OFFSET_HEIGHT = "height";
|
||||
@@ -84,17 +83,17 @@ public class TextAttributeProps {
|
||||
protected boolean mIsBackgroundColorSet = false;
|
||||
protected int mBackgroundColor;
|
||||
|
||||
protected int mNumberOfLines = UNSET;
|
||||
protected int mFontSize = UNSET;
|
||||
protected float mFontSizeInput = UNSET;
|
||||
protected float mLineHeightInput = UNSET;
|
||||
protected int mNumberOfLines = ReactConstants.UNSET;
|
||||
protected int mFontSize = ReactConstants.UNSET;
|
||||
protected float mFontSizeInput = ReactConstants.UNSET;
|
||||
protected float mLineHeightInput = ReactConstants.UNSET;
|
||||
protected float mLetterSpacingInput = Float.NaN;
|
||||
protected int mTextAlign = Gravity.NO_GRAVITY;
|
||||
|
||||
// `UNSET` is -1 and is the same as `LayoutDirection.UNDEFINED` but the symbol isn't available.
|
||||
protected int mLayoutDirection = UNSET;
|
||||
// `ReactConstants.UNSET` is -1, same as `LayoutDirection.UNDEFINED` (which is a hidden symbol)
|
||||
protected int mLayoutDirection = ReactConstants.UNSET;
|
||||
|
||||
protected TextTransform mTextTransform = TextTransform.NONE;
|
||||
@NonNull protected TextTransform mTextTransform = TextTransform.NONE;
|
||||
|
||||
protected float mTextShadowOffsetDx = 0;
|
||||
protected float mTextShadowOffsetDy = 0;
|
||||
@@ -108,8 +107,8 @@ public class TextAttributeProps {
|
||||
protected @Nullable AccessibilityRole mAccessibilityRole = null;
|
||||
protected @Nullable Role mRole = null;
|
||||
|
||||
protected int mFontStyle = UNSET;
|
||||
protected int mFontWeight = UNSET;
|
||||
protected int mFontStyle = ReactConstants.UNSET;
|
||||
protected int mFontWeight = ReactConstants.UNSET;
|
||||
/**
|
||||
* NB: If a font family is used that does not have a style in a certain Android version (ie.
|
||||
* monospace bold pre Android 5.0), that style (ie. bold) will not be inherited by nested Text
|
||||
@@ -233,11 +232,11 @@ public class TextAttributeProps {
|
||||
|
||||
public static TextAttributeProps fromReadableMap(ReactStylesDiffMap props) {
|
||||
TextAttributeProps result = new TextAttributeProps();
|
||||
result.setNumberOfLines(getIntProp(props, ViewProps.NUMBER_OF_LINES, UNSET));
|
||||
result.setLineHeight(getFloatProp(props, ViewProps.LINE_HEIGHT, UNSET));
|
||||
result.setNumberOfLines(getIntProp(props, ViewProps.NUMBER_OF_LINES, ReactConstants.UNSET));
|
||||
result.setLineHeight(getFloatProp(props, ViewProps.LINE_HEIGHT, ReactConstants.UNSET));
|
||||
result.setLetterSpacing(getFloatProp(props, ViewProps.LETTER_SPACING, Float.NaN));
|
||||
result.setAllowFontScaling(getBooleanProp(props, ViewProps.ALLOW_FONT_SCALING, true));
|
||||
result.setFontSize(getFloatProp(props, ViewProps.FONT_SIZE, UNSET));
|
||||
result.setFontSize(getFloatProp(props, ViewProps.FONT_SIZE, ReactConstants.UNSET));
|
||||
result.setColor(props.hasKey(ViewProps.COLOR) ? props.getInt(ViewProps.COLOR, 0) : null);
|
||||
result.setColor(
|
||||
props.hasKey(ViewProps.FOREGROUND_COLOR)
|
||||
@@ -343,6 +342,7 @@ public class TextAttributeProps {
|
||||
|
||||
// Returns a line height which takes into account the requested line height
|
||||
// and the height of the inline images.
|
||||
@Override
|
||||
public float getEffectiveLineHeight() {
|
||||
boolean useInlineViewHeight =
|
||||
!Float.isNaN(mLineHeight)
|
||||
@@ -352,12 +352,12 @@ public class TextAttributeProps {
|
||||
}
|
||||
|
||||
private void setNumberOfLines(int numberOfLines) {
|
||||
mNumberOfLines = numberOfLines == 0 ? UNSET : numberOfLines;
|
||||
mNumberOfLines = numberOfLines == 0 ? ReactConstants.UNSET : numberOfLines;
|
||||
}
|
||||
|
||||
private void setLineHeight(float lineHeight) {
|
||||
mLineHeightInput = lineHeight;
|
||||
if (lineHeight == UNSET) {
|
||||
if (lineHeight == ReactConstants.UNSET) {
|
||||
mLineHeight = Float.NaN;
|
||||
} else {
|
||||
mLineHeight =
|
||||
@@ -371,6 +371,12 @@ public class TextAttributeProps {
|
||||
mLetterSpacingInput = letterSpacing;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public TextTransform getTextTransform() {
|
||||
return mTextTransform;
|
||||
}
|
||||
|
||||
public float getLetterSpacing() {
|
||||
float letterSpacingPixels =
|
||||
mAllowFontScaling
|
||||
@@ -386,6 +392,16 @@ public class TextAttributeProps {
|
||||
return letterSpacingPixels / mFontSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getEffectiveLetterSpacing() {
|
||||
return getLetterSpacing();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getEffectiveFontSize() {
|
||||
return mFontSize;
|
||||
}
|
||||
|
||||
private void setAllowFontScaling(boolean allowFontScaling) {
|
||||
if (allowFontScaling != mAllowFontScaling) {
|
||||
mAllowFontScaling = allowFontScaling;
|
||||
@@ -397,7 +413,7 @@ public class TextAttributeProps {
|
||||
|
||||
private void setFontSize(float fontSize) {
|
||||
mFontSizeInput = fontSize;
|
||||
if (fontSize != UNSET) {
|
||||
if (fontSize != ReactConstants.UNSET) {
|
||||
fontSize =
|
||||
mAllowFontScaling
|
||||
? (float) Math.ceil(PixelUtil.toPixelFromSP(fontSize))
|
||||
@@ -406,6 +422,11 @@ public class TextAttributeProps {
|
||||
mFontSize = (int) fontSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getColor() {
|
||||
return mColor;
|
||||
}
|
||||
|
||||
private void setColor(@Nullable Integer color) {
|
||||
mIsColorSet = (color != null);
|
||||
if (mIsColorSet) {
|
||||
@@ -413,6 +434,16 @@ public class TextAttributeProps {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isColorSet() {
|
||||
return mIsColorSet;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBackgroundColor() {
|
||||
return mBackgroundColor;
|
||||
}
|
||||
|
||||
private void setBackgroundColor(Integer color) {
|
||||
// TODO: Don't apply background color to anchor TextView since it will be applied on the View
|
||||
// directly
|
||||
@@ -424,6 +455,21 @@ public class TextAttributeProps {
|
||||
// }
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBackgroundColorSet() {
|
||||
return mIsBackgroundColorSet;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getFontStyle() {
|
||||
return mFontStyle;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFontFamily() {
|
||||
return mFontFamily;
|
||||
}
|
||||
|
||||
private void setFontFamily(@Nullable String fontFamily) {
|
||||
mFontFamily = fontFamily;
|
||||
}
|
||||
@@ -526,6 +572,16 @@ public class TextAttributeProps {
|
||||
mFontFeatureSettings = TextUtils.join(", ", features);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFontFeatureSettings() {
|
||||
return mFontFeatureSettings;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getFontWeight() {
|
||||
return mFontWeight;
|
||||
}
|
||||
|
||||
private void setFontWeight(@Nullable String fontWeightString) {
|
||||
mFontWeight = ReactTypefaceUtils.parseFontWeight(fontWeightString);
|
||||
}
|
||||
@@ -552,6 +608,16 @@ public class TextAttributeProps {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUnderlineTextDecorationSet() {
|
||||
return mIsUnderlineTextDecorationSet;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLineThroughTextDecorationSet() {
|
||||
return mIsLineThroughTextDecorationSet;
|
||||
}
|
||||
|
||||
private void setTextShadowOffset(ReadableMap offsetMap) {
|
||||
mTextShadowOffsetDx = 0;
|
||||
mTextShadowOffsetDy = 0;
|
||||
@@ -570,10 +636,20 @@ public class TextAttributeProps {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getTextShadowOffsetDx() {
|
||||
return mTextShadowOffsetDx;
|
||||
}
|
||||
|
||||
private void setTextShadowOffsetDx(float dx) {
|
||||
mTextShadowOffsetDx = PixelUtil.toPixelFromDIP(dx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getTextShadowOffsetDy() {
|
||||
return mTextShadowOffsetDy;
|
||||
}
|
||||
|
||||
private void setTextShadowOffsetDy(float dy) {
|
||||
mTextShadowOffsetDy = PixelUtil.toPixelFromDIP(dy);
|
||||
}
|
||||
@@ -581,14 +657,14 @@ public class TextAttributeProps {
|
||||
public static int getLayoutDirection(@Nullable String layoutDirection) {
|
||||
int androidLayoutDirection;
|
||||
if (layoutDirection == null || "undefined".equals(layoutDirection)) {
|
||||
androidLayoutDirection = UNSET;
|
||||
androidLayoutDirection = ReactConstants.UNSET;
|
||||
} else if ("rtl".equals(layoutDirection)) {
|
||||
androidLayoutDirection = LayoutDirection.RTL;
|
||||
} else if ("ltr".equals(layoutDirection)) {
|
||||
androidLayoutDirection = LayoutDirection.LTR;
|
||||
} else {
|
||||
FLog.w(ReactConstants.TAG, "Invalid layoutDirection: " + layoutDirection);
|
||||
androidLayoutDirection = UNSET;
|
||||
androidLayoutDirection = ReactConstants.UNSET;
|
||||
}
|
||||
return androidLayoutDirection;
|
||||
}
|
||||
@@ -597,12 +673,22 @@ public class TextAttributeProps {
|
||||
mLayoutDirection = getLayoutDirection(layoutDirection);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getTextShadowRadius() {
|
||||
return mTextShadowRadius;
|
||||
}
|
||||
|
||||
private void setTextShadowRadius(float textShadowRadius) {
|
||||
if (textShadowRadius != mTextShadowRadius) {
|
||||
mTextShadowRadius = textShadowRadius;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTextShadowColor() {
|
||||
return mTextShadowColor;
|
||||
}
|
||||
|
||||
private void setTextShadowColor(int textShadowColor) {
|
||||
if (textShadowColor != mTextShadowColor) {
|
||||
mTextShadowColor = textShadowColor;
|
||||
@@ -624,6 +710,11 @@ public class TextAttributeProps {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public AccessibilityRole getAccessibilityRole() {
|
||||
return mAccessibilityRole;
|
||||
}
|
||||
|
||||
private void setAccessibilityRole(@Nullable String accessibilityRole) {
|
||||
if (accessibilityRole == null) {
|
||||
mAccessibilityRole = null;
|
||||
@@ -632,6 +723,12 @@ public class TextAttributeProps {
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Role getRole() {
|
||||
return mRole;
|
||||
}
|
||||
|
||||
private void setRole(@Nullable String role) {
|
||||
if (role == null) {
|
||||
mRole = null;
|
||||
|
||||
+4
-2
@@ -7,6 +7,7 @@
|
||||
|
||||
package com.facebook.react.views.text;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import com.facebook.common.logging.FLog;
|
||||
import com.facebook.react.common.ReactConstants;
|
||||
import com.facebook.react.uimanager.PixelUtil;
|
||||
@@ -29,7 +30,8 @@ public class TextAttributes {
|
||||
private float mLetterSpacing = Float.NaN;
|
||||
private float mMaxFontSizeMultiplier = Float.NaN;
|
||||
private float mHeightOfTallestInlineViewOrImage = Float.NaN;
|
||||
private TextTransform mTextTransform = TextTransform.UNSET;
|
||||
|
||||
@NonNull private TextTransform mTextTransform = TextTransform.UNSET;
|
||||
|
||||
public TextAttributes() {}
|
||||
|
||||
@@ -118,7 +120,7 @@ public class TextAttributes {
|
||||
return mTextTransform;
|
||||
}
|
||||
|
||||
public void setTextTransform(TextTransform textTransform) {
|
||||
public void setTextTransform(@NonNull TextTransform textTransform) {
|
||||
mTextTransform = textTransform;
|
||||
}
|
||||
|
||||
|
||||
+32
-8
@@ -7,8 +7,6 @@
|
||||
|
||||
package com.facebook.react.views.text;
|
||||
|
||||
import static com.facebook.react.views.text.TextAttributeProps.UNSET;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Color;
|
||||
import android.os.Build;
|
||||
@@ -31,12 +29,15 @@ import com.facebook.react.bridge.ReadableArray;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
import com.facebook.react.bridge.ReadableNativeMap;
|
||||
import com.facebook.react.bridge.WritableArray;
|
||||
import com.facebook.react.common.ReactConstants;
|
||||
import com.facebook.react.common.build.ReactBuildConfig;
|
||||
import com.facebook.react.config.ReactFeatureFlags;
|
||||
import com.facebook.react.uimanager.PixelUtil;
|
||||
import com.facebook.react.uimanager.ReactAccessibilityDelegate.AccessibilityRole;
|
||||
import com.facebook.react.uimanager.ReactAccessibilityDelegate.Role;
|
||||
import com.facebook.react.uimanager.ReactStylesDiffMap;
|
||||
import com.facebook.react.uimanager.ViewProps;
|
||||
import com.facebook.react.views.text.fragments.BridgeTextFragmentList;
|
||||
import com.facebook.yoga.YogaConstants;
|
||||
import com.facebook.yoga.YogaMeasureMode;
|
||||
import com.facebook.yoga.YogaMeasureOutput;
|
||||
@@ -99,7 +100,19 @@ public class TextLayoutManager {
|
||||
sTagToSpannableCache.remove(reactTag);
|
||||
}
|
||||
|
||||
private static void buildSpannableFromFragment(
|
||||
private static void buildSpannableFromFragments(
|
||||
Context context,
|
||||
ReadableArray fragments,
|
||||
SpannableStringBuilder sb,
|
||||
List<SetSpanOperation> ops) {
|
||||
if (ReactFeatureFlags.enableSpannableBuildingUnification) {
|
||||
buildSpannableFromFragmentsUnified(context, fragments, sb, ops);
|
||||
} else {
|
||||
buildSpannableFromFragmentsDuplicated(context, fragments, sb, ops);
|
||||
}
|
||||
}
|
||||
|
||||
private static void buildSpannableFromFragmentsDuplicated(
|
||||
Context context,
|
||||
ReadableArray fragments,
|
||||
SpannableStringBuilder sb,
|
||||
@@ -152,8 +165,8 @@ public class TextLayoutManager {
|
||||
}
|
||||
ops.add(
|
||||
new SetSpanOperation(start, end, new ReactAbsoluteSizeSpan(textAttributes.mFontSize)));
|
||||
if (textAttributes.mFontStyle != UNSET
|
||||
|| textAttributes.mFontWeight != UNSET
|
||||
if (textAttributes.mFontStyle != ReactConstants.UNSET
|
||||
|| textAttributes.mFontWeight != ReactConstants.UNSET
|
||||
|| textAttributes.mFontFamily != null) {
|
||||
ops.add(
|
||||
new SetSpanOperation(
|
||||
@@ -197,6 +210,17 @@ public class TextLayoutManager {
|
||||
}
|
||||
}
|
||||
|
||||
private static void buildSpannableFromFragmentsUnified(
|
||||
Context context,
|
||||
ReadableArray fragments,
|
||||
SpannableStringBuilder sb,
|
||||
List<SetSpanOperation> ops) {
|
||||
|
||||
final BridgeTextFragmentList textFragmentList = new BridgeTextFragmentList(fragments);
|
||||
|
||||
TextLayoutUtils.buildSpannableFromTextFragmentList(context, textFragmentList, sb, ops);
|
||||
}
|
||||
|
||||
// public because both ReactTextViewManager and ReactTextInputManager need to use this
|
||||
public static Spannable getOrCreateSpannableForText(
|
||||
Context context,
|
||||
@@ -219,7 +243,7 @@ public class TextLayoutManager {
|
||||
// a new spannable will be wiped out
|
||||
List<SetSpanOperation> ops = new ArrayList<>();
|
||||
|
||||
buildSpannableFromFragment(context, attributedString.getArray("fragments"), sb, ops);
|
||||
buildSpannableFromFragments(context, attributedString.getArray("fragments"), sb, ops);
|
||||
|
||||
// TODO T31905686: add support for inline Images
|
||||
// While setting the Spans on the final text, we also check whether any of them are images.
|
||||
@@ -368,10 +392,10 @@ public class TextLayoutManager {
|
||||
int maximumNumberOfLines =
|
||||
paragraphAttributes.hasKey(MAXIMUM_NUMBER_OF_LINES_KEY)
|
||||
? paragraphAttributes.getInt(MAXIMUM_NUMBER_OF_LINES_KEY)
|
||||
: UNSET;
|
||||
: ReactConstants.UNSET;
|
||||
|
||||
int calculatedLineCount =
|
||||
maximumNumberOfLines == UNSET || maximumNumberOfLines == 0
|
||||
maximumNumberOfLines == ReactConstants.UNSET || maximumNumberOfLines == 0
|
||||
? layout.getLineCount()
|
||||
: Math.min(maximumNumberOfLines, layout.getLineCount());
|
||||
|
||||
|
||||
+26
-7
@@ -8,7 +8,6 @@
|
||||
package com.facebook.react.views.text;
|
||||
|
||||
import static com.facebook.react.config.ReactFeatureFlags.enableTextSpannableCache;
|
||||
import static com.facebook.react.views.text.TextAttributeProps.UNSET;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Color;
|
||||
@@ -29,12 +28,15 @@ import com.facebook.common.logging.FLog;
|
||||
import com.facebook.react.bridge.ReactNoCrashSoftException;
|
||||
import com.facebook.react.bridge.ReactSoftExceptionLogger;
|
||||
import com.facebook.react.bridge.WritableArray;
|
||||
import com.facebook.react.common.ReactConstants;
|
||||
import com.facebook.react.common.build.ReactBuildConfig;
|
||||
import com.facebook.react.common.mapbuffer.MapBuffer;
|
||||
import com.facebook.react.common.mapbuffer.ReadableMapBuffer;
|
||||
import com.facebook.react.config.ReactFeatureFlags;
|
||||
import com.facebook.react.uimanager.PixelUtil;
|
||||
import com.facebook.react.uimanager.ReactAccessibilityDelegate.AccessibilityRole;
|
||||
import com.facebook.react.uimanager.ReactAccessibilityDelegate.Role;
|
||||
import com.facebook.react.views.text.fragments.MapBufferTextFragmentList;
|
||||
import com.facebook.yoga.YogaConstants;
|
||||
import com.facebook.yoga.YogaMeasureMode;
|
||||
import com.facebook.yoga.YogaMeasureOutput;
|
||||
@@ -123,7 +125,16 @@ public class TextLayoutManagerMapBuffer {
|
||||
== LayoutDirection.RTL;
|
||||
}
|
||||
|
||||
private static void buildSpannableFromFragment(
|
||||
private static void buildSpannableFromFragments(
|
||||
Context context, MapBuffer fragments, SpannableStringBuilder sb, List<SetSpanOperation> ops) {
|
||||
if (ReactFeatureFlags.enableSpannableBuildingUnification) {
|
||||
buildSpannableFromFragmentsUnified(context, fragments, sb, ops);
|
||||
} else {
|
||||
buildSpannableFromFragmentsDuplicated(context, fragments, sb, ops);
|
||||
}
|
||||
}
|
||||
|
||||
private static void buildSpannableFromFragmentsDuplicated(
|
||||
Context context, MapBuffer fragments, SpannableStringBuilder sb, List<SetSpanOperation> ops) {
|
||||
|
||||
for (int i = 0, length = fragments.getCount(); i < length; i++) {
|
||||
@@ -172,8 +183,8 @@ public class TextLayoutManagerMapBuffer {
|
||||
}
|
||||
ops.add(
|
||||
new SetSpanOperation(start, end, new ReactAbsoluteSizeSpan(textAttributes.mFontSize)));
|
||||
if (textAttributes.mFontStyle != UNSET
|
||||
|| textAttributes.mFontWeight != UNSET
|
||||
if (textAttributes.mFontStyle != ReactConstants.UNSET
|
||||
|| textAttributes.mFontWeight != ReactConstants.UNSET
|
||||
|| textAttributes.mFontFamily != null) {
|
||||
ops.add(
|
||||
new SetSpanOperation(
|
||||
@@ -217,6 +228,14 @@ public class TextLayoutManagerMapBuffer {
|
||||
}
|
||||
}
|
||||
|
||||
private static void buildSpannableFromFragmentsUnified(
|
||||
Context context, MapBuffer fragments, SpannableStringBuilder sb, List<SetSpanOperation> ops) {
|
||||
|
||||
final MapBufferTextFragmentList textFragmentList = new MapBufferTextFragmentList(fragments);
|
||||
|
||||
TextLayoutUtils.buildSpannableFromTextFragmentList(context, textFragmentList, sb, ops);
|
||||
}
|
||||
|
||||
// public because both ReactTextViewManager and ReactTextInputManager need to use this
|
||||
public static Spannable getOrCreateSpannableForText(
|
||||
Context context,
|
||||
@@ -260,7 +279,7 @@ public class TextLayoutManagerMapBuffer {
|
||||
// a new spannable will be wiped out
|
||||
List<SetSpanOperation> ops = new ArrayList<>();
|
||||
|
||||
buildSpannableFromFragment(context, attributedString.getMapBuffer(AS_KEY_FRAGMENTS), sb, ops);
|
||||
buildSpannableFromFragments(context, attributedString.getMapBuffer(AS_KEY_FRAGMENTS), sb, ops);
|
||||
|
||||
// TODO T31905686: add support for inline Images
|
||||
// While setting the Spans on the final text, we also check whether any of them are images.
|
||||
@@ -390,10 +409,10 @@ public class TextLayoutManagerMapBuffer {
|
||||
int maximumNumberOfLines =
|
||||
paragraphAttributes.contains(PA_KEY_MAX_NUMBER_OF_LINES)
|
||||
? paragraphAttributes.getInt(PA_KEY_MAX_NUMBER_OF_LINES)
|
||||
: UNSET;
|
||||
: ReactConstants.UNSET;
|
||||
|
||||
int calculatedLineCount =
|
||||
maximumNumberOfLines == UNSET || maximumNumberOfLines == 0
|
||||
maximumNumberOfLines == ReactConstants.UNSET || maximumNumberOfLines == 0
|
||||
? layout.getLineCount()
|
||||
: Math.min(maximumNumberOfLines, layout.getLineCount());
|
||||
|
||||
|
||||
+280
@@ -0,0 +1,280 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Color
|
||||
import android.text.*
|
||||
import android.view.View
|
||||
import com.facebook.react.common.ReactConstants
|
||||
import com.facebook.react.uimanager.PixelUtil
|
||||
import com.facebook.react.uimanager.ReactAccessibilityDelegate
|
||||
import com.facebook.react.views.text.fragments.TextFragmentList
|
||||
|
||||
/** Utility methods for building [Spannable]s */
|
||||
internal object TextLayoutUtils {
|
||||
private const val INLINE_VIEW_PLACEHOLDER = "0"
|
||||
|
||||
@JvmStatic
|
||||
fun buildSpannableFromTextFragmentList(
|
||||
context: Context,
|
||||
textFragmentList: TextFragmentList,
|
||||
sb: SpannableStringBuilder,
|
||||
ops: MutableList<SetSpanOperation>,
|
||||
) {
|
||||
|
||||
for (i in 0 until textFragmentList.count) {
|
||||
val fragment = textFragmentList.getFragment(i)
|
||||
val start = sb.length
|
||||
|
||||
// ReactRawText
|
||||
val textAttributes = fragment.textAttributeProps
|
||||
|
||||
addText(sb, fragment.string, textAttributes)
|
||||
|
||||
val end = sb.length
|
||||
val reactTag = if (fragment.hasReactTag()) fragment.reactTag else View.NO_ID
|
||||
if (fragment.hasIsAttachment() && fragment.isAttachment) {
|
||||
val width = PixelUtil.toPixelFromSP(fragment.width)
|
||||
val height = PixelUtil.toPixelFromSP(fragment.height)
|
||||
|
||||
addInlineViewPlaceholderSpan(ops, sb, reactTag, width, height)
|
||||
} else if (end >= start) {
|
||||
addApplicableTextAttributeSpans(ops, textAttributes, reactTag, context, start, end)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun addText(
|
||||
sb: SpannableStringBuilder,
|
||||
text: String?,
|
||||
textAttributeProvider: EffectiveTextAttributeProvider
|
||||
) {
|
||||
sb.append(TextTransform.apply(text, textAttributeProvider.textTransform))
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun addInlineViewPlaceholderSpan(
|
||||
ops: MutableList<SetSpanOperation>,
|
||||
sb: SpannableStringBuilder,
|
||||
reactTag: Int,
|
||||
width: Float,
|
||||
height: Float
|
||||
) {
|
||||
ops.add(
|
||||
SetSpanOperation(
|
||||
sb.length - INLINE_VIEW_PLACEHOLDER.length,
|
||||
sb.length,
|
||||
TextInlineViewPlaceholderSpan(reactTag, width.toInt(), height.toInt())))
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun addApplicableTextAttributeSpans(
|
||||
ops: MutableList<SetSpanOperation>,
|
||||
textAttributeProvider: EffectiveTextAttributeProvider,
|
||||
reactTag: Int,
|
||||
context: Context,
|
||||
start: Int,
|
||||
end: Int
|
||||
) {
|
||||
addColorSpanIfApplicable(ops, textAttributeProvider, start, end)
|
||||
|
||||
addBackgroundColorSpanIfApplicable(ops, textAttributeProvider, start, end)
|
||||
|
||||
addLinkSpanIfApplicable(ops, textAttributeProvider, reactTag, start, end)
|
||||
|
||||
addLetterSpacingSpanIfApplicable(ops, textAttributeProvider, start, end)
|
||||
|
||||
addFontSizeSpanIfApplicable(ops, textAttributeProvider, start, end)
|
||||
|
||||
addCustomStyleSpanIfApplicable(ops, textAttributeProvider, context, start, end)
|
||||
|
||||
addUnderlineSpanIfApplicable(ops, textAttributeProvider, start, end)
|
||||
|
||||
addStrikethroughSpanIfApplicable(ops, textAttributeProvider, start, end)
|
||||
|
||||
addShadowStyleSpanIfApplicable(ops, textAttributeProvider, start, end)
|
||||
|
||||
addLineHeightSpanIfApplicable(ops, textAttributeProvider, start, end)
|
||||
|
||||
addReactTagSpan(ops, start, end, reactTag)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
private fun addLinkSpanIfApplicable(
|
||||
ops: MutableList<SetSpanOperation>,
|
||||
textAttributeProvider: EffectiveTextAttributeProvider,
|
||||
reactTag: Int,
|
||||
start: Int,
|
||||
end: Int
|
||||
) {
|
||||
val roleIsLink =
|
||||
textAttributeProvider.role?.let { it == ReactAccessibilityDelegate.Role.LINK }
|
||||
?: (textAttributeProvider.accessibilityRole ==
|
||||
ReactAccessibilityDelegate.AccessibilityRole.LINK)
|
||||
if (roleIsLink) {
|
||||
ops.add(SetSpanOperation(start, end, ReactClickableSpan(reactTag)))
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
private fun addColorSpanIfApplicable(
|
||||
ops: MutableList<SetSpanOperation>,
|
||||
textAttributeProvider: EffectiveTextAttributeProvider,
|
||||
start: Int,
|
||||
end: Int
|
||||
) {
|
||||
if (textAttributeProvider.isColorSet) {
|
||||
ops.add(SetSpanOperation(start, end, ReactForegroundColorSpan(textAttributeProvider.color)))
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
private fun addBackgroundColorSpanIfApplicable(
|
||||
ops: MutableList<SetSpanOperation>,
|
||||
textAttributeProvider: EffectiveTextAttributeProvider,
|
||||
start: Int,
|
||||
end: Int
|
||||
) {
|
||||
if (textAttributeProvider.isBackgroundColorSet) {
|
||||
ops.add(
|
||||
SetSpanOperation(
|
||||
start, end, ReactBackgroundColorSpan(textAttributeProvider.backgroundColor)))
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
private fun addLetterSpacingSpanIfApplicable(
|
||||
ops: MutableList<SetSpanOperation>,
|
||||
textAttributeProvider: EffectiveTextAttributeProvider,
|
||||
start: Int,
|
||||
end: Int
|
||||
) {
|
||||
val effectiveLetterSpacing = textAttributeProvider.effectiveLetterSpacing
|
||||
|
||||
if (!effectiveLetterSpacing.isNaN()) {
|
||||
ops.add(SetSpanOperation(start, end, CustomLetterSpacingSpan(effectiveLetterSpacing)))
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
private fun addFontSizeSpanIfApplicable(
|
||||
ops: MutableList<SetSpanOperation>,
|
||||
textAttributeProvider: EffectiveTextAttributeProvider,
|
||||
start: Int,
|
||||
end: Int
|
||||
) {
|
||||
val effectiveFontSize = textAttributeProvider.effectiveFontSize
|
||||
|
||||
if (effectiveFontSize != ReactConstants.UNSET) {
|
||||
ops.add(SetSpanOperation(start, end, ReactAbsoluteSizeSpan(effectiveFontSize)))
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
private fun addCustomStyleSpanIfApplicable(
|
||||
ops: MutableList<SetSpanOperation>,
|
||||
textAttributeProvider: EffectiveTextAttributeProvider,
|
||||
context: Context,
|
||||
start: Int,
|
||||
end: Int
|
||||
) {
|
||||
val fontStyle = textAttributeProvider.fontStyle
|
||||
val fontWeight = textAttributeProvider.fontWeight
|
||||
val fontFamily = textAttributeProvider.fontFamily
|
||||
|
||||
if (fontStyle != ReactConstants.UNSET ||
|
||||
fontWeight != ReactConstants.UNSET ||
|
||||
fontFamily != null) {
|
||||
ops.add(
|
||||
SetSpanOperation(
|
||||
start,
|
||||
end,
|
||||
CustomStyleSpan(
|
||||
fontStyle,
|
||||
fontWeight,
|
||||
textAttributeProvider.fontFeatureSettings,
|
||||
fontFamily,
|
||||
context.assets)))
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
private fun addUnderlineSpanIfApplicable(
|
||||
ops: MutableList<SetSpanOperation>,
|
||||
textAttributeProvider: EffectiveTextAttributeProvider,
|
||||
start: Int,
|
||||
end: Int
|
||||
) {
|
||||
if (textAttributeProvider.isUnderlineTextDecorationSet) {
|
||||
ops.add(SetSpanOperation(start, end, ReactUnderlineSpan()))
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
private fun addStrikethroughSpanIfApplicable(
|
||||
ops: MutableList<SetSpanOperation>,
|
||||
textAttributeProvider: EffectiveTextAttributeProvider,
|
||||
start: Int,
|
||||
end: Int
|
||||
) {
|
||||
if (textAttributeProvider.isLineThroughTextDecorationSet) {
|
||||
ops.add(SetSpanOperation(start, end, ReactStrikethroughSpan()))
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
private fun addShadowStyleSpanIfApplicable(
|
||||
ops: MutableList<SetSpanOperation>,
|
||||
textAttributeProvider: EffectiveTextAttributeProvider,
|
||||
start: Int,
|
||||
end: Int
|
||||
) {
|
||||
val hasTextShadowOffset =
|
||||
textAttributeProvider.textShadowOffsetDx != 0f ||
|
||||
textAttributeProvider.textShadowOffsetDy != 0f
|
||||
val hasTextShadowRadius = textAttributeProvider.textShadowRadius != 0f
|
||||
val hasTextShadowColorAlpha = Color.alpha(textAttributeProvider.textShadowColor) != 0
|
||||
|
||||
if ((hasTextShadowOffset || hasTextShadowRadius) && hasTextShadowColorAlpha) {
|
||||
ops.add(
|
||||
SetSpanOperation(
|
||||
start,
|
||||
end,
|
||||
ShadowStyleSpan(
|
||||
textAttributeProvider.textShadowOffsetDx,
|
||||
textAttributeProvider.textShadowOffsetDy,
|
||||
textAttributeProvider.textShadowRadius,
|
||||
textAttributeProvider.textShadowColor)))
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
private fun addLineHeightSpanIfApplicable(
|
||||
ops: MutableList<SetSpanOperation>,
|
||||
textAttributeProvider: EffectiveTextAttributeProvider,
|
||||
start: Int,
|
||||
end: Int
|
||||
) {
|
||||
val effectiveLineHeight = textAttributeProvider.effectiveLineHeight
|
||||
if (!effectiveLineHeight.isNaN()) {
|
||||
ops.add(SetSpanOperation(start, end, CustomLineHeightSpan(effectiveLineHeight)))
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
private fun addReactTagSpan(
|
||||
ops: MutableList<SetSpanOperation>,
|
||||
start: Int,
|
||||
end: Int,
|
||||
reactTag: Int
|
||||
) {
|
||||
ops.add(SetSpanOperation(start, end, ReactTagSpan(reactTag)))
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -7,6 +7,7 @@
|
||||
|
||||
package com.facebook.react.views.text;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import java.text.BreakIterator;
|
||||
|
||||
/** Types of text transforms for CustomTextTransformSpan */
|
||||
@@ -17,7 +18,7 @@ public enum TextTransform {
|
||||
CAPITALIZE,
|
||||
UNSET;
|
||||
|
||||
public static String apply(String text, TextTransform textTransform) {
|
||||
public static String apply(@Nullable String text, TextTransform textTransform) {
|
||||
if (text == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* 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.fragments
|
||||
|
||||
import com.facebook.react.bridge.ReadableMap
|
||||
import com.facebook.react.uimanager.ReactStylesDiffMap
|
||||
import com.facebook.react.uimanager.ViewProps
|
||||
import com.facebook.react.views.text.TextAttributeProps
|
||||
|
||||
/** A [TextFragment] implementation backed by a a [ReadableMap] */
|
||||
internal class BridgeTextFragment(private val fragment: ReadableMap) : TextFragment {
|
||||
override val textAttributeProps: TextAttributeProps
|
||||
get() =
|
||||
TextAttributeProps.fromReadableMap(ReactStylesDiffMap(fragment.getMap("textAttributes")))
|
||||
|
||||
override val string: String?
|
||||
get() = fragment.getString("string")
|
||||
|
||||
override fun hasReactTag(): Boolean = fragment.hasKey("reactTag")
|
||||
|
||||
override val reactTag: Int
|
||||
get() = fragment.getInt("reactTag")
|
||||
|
||||
override fun hasIsAttachment(): Boolean = fragment.hasKey(ViewProps.IS_ATTACHMENT)
|
||||
|
||||
override val isAttachment: Boolean
|
||||
get() = fragment.getBoolean(ViewProps.IS_ATTACHMENT)
|
||||
|
||||
override val width: Double
|
||||
get() = fragment.getDouble(ViewProps.WIDTH)
|
||||
|
||||
override val height: Double
|
||||
get() = fragment.getDouble(ViewProps.HEIGHT)
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* 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.fragments
|
||||
|
||||
import com.facebook.react.bridge.ReadableArray
|
||||
|
||||
/** A list of [TextFragment]s backed by a [ReadableArray] */
|
||||
internal class BridgeTextFragmentList(private val fragments: ReadableArray) : TextFragmentList {
|
||||
override fun getFragment(index: Int): TextFragment = BridgeTextFragment(fragments.getMap(index))
|
||||
|
||||
override val count: Int
|
||||
get() = fragments.size()
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* 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.fragments
|
||||
|
||||
import com.facebook.react.common.mapbuffer.MapBuffer
|
||||
import com.facebook.react.views.text.TextAttributeProps
|
||||
import com.facebook.react.views.text.TextLayoutManagerMapBuffer.FR_KEY_HEIGHT
|
||||
import com.facebook.react.views.text.TextLayoutManagerMapBuffer.FR_KEY_IS_ATTACHMENT
|
||||
import com.facebook.react.views.text.TextLayoutManagerMapBuffer.FR_KEY_REACT_TAG
|
||||
import com.facebook.react.views.text.TextLayoutManagerMapBuffer.FR_KEY_STRING
|
||||
import com.facebook.react.views.text.TextLayoutManagerMapBuffer.FR_KEY_TEXT_ATTRIBUTES
|
||||
import com.facebook.react.views.text.TextLayoutManagerMapBuffer.FR_KEY_WIDTH
|
||||
|
||||
/** A [TextFragment] implementation backed by a [MapBuffer] */
|
||||
internal class MapBufferTextFragment(private val fragment: MapBuffer) : TextFragment {
|
||||
override val textAttributeProps: TextAttributeProps
|
||||
get() = TextAttributeProps.fromMapBuffer(fragment.getMapBuffer(FR_KEY_TEXT_ATTRIBUTES.toInt()))
|
||||
|
||||
override val string: String
|
||||
get() = fragment.getString(FR_KEY_STRING.toInt())
|
||||
|
||||
override fun hasReactTag(): Boolean = fragment.contains(FR_KEY_REACT_TAG.toInt())
|
||||
|
||||
override val reactTag: Int
|
||||
get() = fragment.getInt(FR_KEY_REACT_TAG.toInt())
|
||||
|
||||
override fun hasIsAttachment(): Boolean = fragment.contains(FR_KEY_IS_ATTACHMENT.toInt())
|
||||
|
||||
override val isAttachment: Boolean
|
||||
get() = fragment.getBoolean(FR_KEY_IS_ATTACHMENT.toInt())
|
||||
|
||||
override val width: Double
|
||||
get() = fragment.getDouble(FR_KEY_WIDTH.toInt())
|
||||
|
||||
override val height: Double
|
||||
get() = fragment.getDouble(FR_KEY_HEIGHT.toInt())
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* 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.fragments
|
||||
|
||||
import com.facebook.react.common.mapbuffer.MapBuffer
|
||||
|
||||
/** A list of [TextFragment]s backed by a [MapBuffer] */
|
||||
internal class MapBufferTextFragmentList(private val fragments: MapBuffer) : TextFragmentList {
|
||||
override fun getFragment(index: Int): TextFragment =
|
||||
MapBufferTextFragment(fragments.getMapBuffer(index))
|
||||
|
||||
override val count: Int
|
||||
get() = fragments.count
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* 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.fragments
|
||||
|
||||
import com.facebook.react.views.text.TextAttributeProps
|
||||
|
||||
/** Interface for a text fragment */
|
||||
internal interface TextFragment {
|
||||
val textAttributeProps: TextAttributeProps
|
||||
|
||||
val string: String?
|
||||
|
||||
fun hasReactTag(): Boolean
|
||||
|
||||
val reactTag: Int
|
||||
|
||||
fun hasIsAttachment(): Boolean
|
||||
|
||||
val isAttachment: Boolean
|
||||
|
||||
val width: Double
|
||||
|
||||
val height: Double
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
/*
|
||||
* 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.fragments
|
||||
|
||||
/** Interface for a list of [TextFragment]s */
|
||||
internal interface TextFragmentList {
|
||||
fun getFragment(index: Int): TextFragment
|
||||
|
||||
val count: Int
|
||||
}
|
||||
+8
-9
@@ -46,6 +46,7 @@ import com.facebook.common.logging.FLog;
|
||||
import com.facebook.infer.annotation.Assertions;
|
||||
import com.facebook.react.bridge.ReactContext;
|
||||
import com.facebook.react.bridge.ReactSoftExceptionLogger;
|
||||
import com.facebook.react.common.ReactConstants;
|
||||
import com.facebook.react.common.build.ReactBuildConfig;
|
||||
import com.facebook.react.uimanager.ReactAccessibilityDelegate;
|
||||
import com.facebook.react.uimanager.StateWrapper;
|
||||
@@ -96,8 +97,6 @@ public class ReactEditText extends AppCompatEditText {
|
||||
/** A count of events sent to JS or C++. */
|
||||
protected int mNativeEventCount;
|
||||
|
||||
private static final int UNSET = -1;
|
||||
|
||||
private @Nullable ArrayList<TextWatcher> mListeners;
|
||||
private @Nullable TextWatcherDelegator mTextWatcherDelegator;
|
||||
private int mStagedInputType;
|
||||
@@ -114,8 +113,8 @@ public class ReactEditText extends AppCompatEditText {
|
||||
private TextAttributes mTextAttributes;
|
||||
private boolean mTypefaceDirty = false;
|
||||
private @Nullable String mFontFamily = null;
|
||||
private int mFontWeight = UNSET;
|
||||
private int mFontStyle = UNSET;
|
||||
private int mFontWeight = ReactConstants.UNSET;
|
||||
private int mFontStyle = ReactConstants.UNSET;
|
||||
private boolean mAutoFocus = false;
|
||||
private boolean mDidAttachToWindow = false;
|
||||
private @Nullable String mPlaceholder = null;
|
||||
@@ -379,7 +378,7 @@ public class ReactEditText extends AppCompatEditText {
|
||||
return;
|
||||
}
|
||||
|
||||
if (start != UNSET && end != UNSET) {
|
||||
if (start != ReactConstants.UNSET && end != ReactConstants.UNSET) {
|
||||
// clamp selection values for safety
|
||||
start = clampToTextLength(start);
|
||||
end = clampToTextLength(end);
|
||||
@@ -588,8 +587,8 @@ public class ReactEditText extends AppCompatEditText {
|
||||
|
||||
// Match behavior of CustomStyleSpan and enable SUBPIXEL_TEXT_FLAG when setting anything
|
||||
// nonstandard
|
||||
if (mFontStyle != UNSET
|
||||
|| mFontWeight != UNSET
|
||||
if (mFontStyle != ReactConstants.UNSET
|
||||
|| mFontWeight != ReactConstants.UNSET
|
||||
|| mFontFamily != null
|
||||
|| getFontFeatureSettings() != null) {
|
||||
setPaintFlags(getPaintFlags() | Paint.SUBPIXEL_TEXT_FLAG);
|
||||
@@ -819,8 +818,8 @@ public class ReactEditText extends AppCompatEditText {
|
||||
new CustomLetterSpacingSpan(effectiveLetterSpacing), 0, workingText.length(), spanFlags);
|
||||
}
|
||||
|
||||
if (mFontStyle != UNSET
|
||||
|| mFontWeight != UNSET
|
||||
if (mFontStyle != ReactConstants.UNSET
|
||||
|| mFontWeight != ReactConstants.UNSET
|
||||
|| mFontFamily != null
|
||||
|| getFontFeatureSettings() != null) {
|
||||
workingText.setSpan(
|
||||
|
||||
+3
-3
@@ -36,7 +36,7 @@ import com.facebook.yoga.YogaNode;
|
||||
public class ReactTextInputShadowNode extends ReactBaseTextShadowNode
|
||||
implements YogaMeasureFunction {
|
||||
|
||||
private int mMostRecentEventCount = UNSET;
|
||||
private int mMostRecentEventCount = ReactConstants.UNSET;
|
||||
private @Nullable EditText mInternalEditText;
|
||||
private @Nullable ReactTextInputLocalData mLocalData;
|
||||
|
||||
@@ -108,7 +108,7 @@ public class ReactTextInputShadowNode extends ReactBaseTextShadowNode
|
||||
} else {
|
||||
editText.setTextSize(TypedValue.COMPLEX_UNIT_PX, mTextAttributes.getEffectiveFontSize());
|
||||
|
||||
if (mNumberOfLines != UNSET) {
|
||||
if (mNumberOfLines != ReactConstants.UNSET) {
|
||||
editText.setLines(mNumberOfLines);
|
||||
}
|
||||
|
||||
@@ -191,7 +191,7 @@ public class ReactTextInputShadowNode extends ReactBaseTextShadowNode
|
||||
public void onCollectExtraUpdates(UIViewOperationQueue uiViewOperationQueue) {
|
||||
super.onCollectExtraUpdates(uiViewOperationQueue);
|
||||
|
||||
if (mMostRecentEventCount != UNSET) {
|
||||
if (mMostRecentEventCount != ReactConstants.UNSET) {
|
||||
ReactTextUpdate reactTextUpdate =
|
||||
new ReactTextUpdate(
|
||||
spannedFromShadowNode(
|
||||
|
||||
Reference in New Issue
Block a user