Skip to content

Commit 7bcdb23

Browse files
cubuspl42facebook-github-bot
authored andcommitted
De-duplicate building Spannable on Android (#39630)
Summary: A first step in my work on react-native-community/discussions-and-proposals#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: #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
1 parent 6fbf042 commit 7bcdb23

28 files changed

+1051
-94
lines changed

packages/react-native/ReactAndroid/api/ReactAndroid.api

Lines changed: 46 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1686,6 +1686,7 @@ public final class com/facebook/react/common/MapBuilder$Builder {
16861686

16871687
public class com/facebook/react/common/ReactConstants {
16881688
public static final field TAG Ljava/lang/String;
1689+
public static final field UNSET I
16891690
public fun <init> ()V
16901691
}
16911692

@@ -1764,7 +1765,6 @@ public class com/facebook/react/common/assets/ReactFontManager {
17641765
public class com/facebook/react/common/assets/ReactFontManager$TypefaceStyle {
17651766
public static final field BOLD I
17661767
public static final field NORMAL I
1767-
public static final field UNSET I
17681768
public fun <init> (I)V
17691769
public fun <init> (II)V
17701770
public fun <init> (IZ)V
@@ -1906,6 +1906,7 @@ public class com/facebook/react/config/ReactFeatureFlags {
19061906
public static field enableMountHooks Z
19071907
public static field enableOnDemandReactChoreographer Z
19081908
public static field enableRemoveDeleteTreeInstruction Z
1909+
public static field enableSpannableBuildingUnification Z
19091910
public static field enableTextSpannableCache Z
19101911
public static field enableViewRecycling Z
19111912
public static field excludeYogaFromRawProps Z
@@ -6785,15 +6786,14 @@ public class com/facebook/react/views/text/ReactBackgroundColorSpan : android/te
67856786
public fun <init> (I)V
67866787
}
67876788

6788-
public abstract class com/facebook/react/views/text/ReactBaseTextShadowNode : com/facebook/react/uimanager/LayoutShadowNode {
6789+
public abstract class com/facebook/react/views/text/ReactBaseTextShadowNode : com/facebook/react/uimanager/LayoutShadowNode, com/facebook/react/views/text/BasicTextAttributeProvider {
67896790
public static final field DEFAULT_TEXT_SHADOW_COLOR I
67906791
public static final field PROP_SHADOW_COLOR Ljava/lang/String;
67916792
public static final field PROP_SHADOW_OFFSET Ljava/lang/String;
67926793
public static final field PROP_SHADOW_OFFSET_HEIGHT Ljava/lang/String;
67936794
public static final field PROP_SHADOW_OFFSET_WIDTH Ljava/lang/String;
67946795
public static final field PROP_SHADOW_RADIUS Ljava/lang/String;
67956796
public static final field PROP_TEXT_TRANSFORM Ljava/lang/String;
6796-
public static final field UNSET I
67976797
protected field mAccessibilityRole Lcom/facebook/react/uimanager/ReactAccessibilityDelegate$AccessibilityRole;
67986798
protected field mAdjustsFontSizeToFit Z
67996799
protected field mBackgroundColor I
@@ -6824,6 +6824,22 @@ public abstract class com/facebook/react/views/text/ReactBaseTextShadowNode : co
68246824
protected field mTextShadowRadius F
68256825
public fun <init> ()V
68266826
public fun <init> (Lcom/facebook/react/views/text/ReactTextViewManagerCallback;)V
6827+
public fun getAccessibilityRole ()Lcom/facebook/react/uimanager/ReactAccessibilityDelegate$AccessibilityRole;
6828+
public fun getBackgroundColor ()I
6829+
public fun getColor ()I
6830+
public fun getFontFamily ()Ljava/lang/String;
6831+
public fun getFontFeatureSettings ()Ljava/lang/String;
6832+
public fun getFontStyle ()I
6833+
public fun getFontWeight ()I
6834+
public fun getRole ()Lcom/facebook/react/uimanager/ReactAccessibilityDelegate$Role;
6835+
public fun getTextShadowColor ()I
6836+
public fun getTextShadowOffsetDx ()F
6837+
public fun getTextShadowOffsetDy ()F
6838+
public fun getTextShadowRadius ()F
6839+
public fun isBackgroundColorSet ()Z
6840+
public fun isColorSet ()Z
6841+
public fun isLineThroughTextDecorationSet ()Z
6842+
public fun isUnderlineTextDecorationSet ()Z
68276843
public fun setAccessibilityRole (Ljava/lang/String;)V
68286844
public fun setAdjustFontSizeToFit (Z)V
68296845
public fun setAllowFontScaling (Z)V
@@ -7084,12 +7100,19 @@ public class com/facebook/react/views/text/ReactVirtualTextViewManager$$PropsSet
70847100
public fun setProperty (Lcom/facebook/react/views/text/ReactVirtualTextViewManager;Landroid/view/View;Ljava/lang/String;Ljava/lang/Object;)V
70857101
}
70867102

7103+
public class com/facebook/react/views/text/SetSpanOperation {
7104+
protected field end I
7105+
protected field start I
7106+
protected field what Lcom/facebook/react/views/text/ReactSpan;
7107+
public fun execute (Landroid/text/SpannableStringBuilder;I)V
7108+
}
7109+
70877110
public class com/facebook/react/views/text/ShadowStyleSpan : android/text/style/CharacterStyle, com/facebook/react/views/text/ReactSpan {
70887111
public fun <init> (FFFI)V
70897112
public fun updateDrawState (Landroid/text/TextPaint;)V
70907113
}
70917114

7092-
public class com/facebook/react/views/text/TextAttributeProps {
7115+
public class com/facebook/react/views/text/TextAttributeProps : com/facebook/react/views/text/EffectiveTextAttributeProvider {
70937116
public static final field TA_KEY_ACCESSIBILITY_ROLE S
70947117
public static final field TA_KEY_ALIGNMENT S
70957118
public static final field TA_KEY_ALLOW_FONT_SCALING S
@@ -7117,7 +7140,6 @@ public class com/facebook/react/views/text/TextAttributeProps {
71177140
public static final field TA_KEY_TEXT_SHADOW_OFFSET_DY S
71187141
public static final field TA_KEY_TEXT_SHADOW_RADIUS S
71197142
public static final field TA_KEY_TEXT_TRANSFORM S
7120-
public static final field UNSET I
71217143
protected field mAccessibilityRole Lcom/facebook/react/uimanager/ReactAccessibilityDelegate$AccessibilityRole;
71227144
protected field mAllowFontScaling Z
71237145
protected field mBackgroundColor I
@@ -7149,13 +7171,32 @@ public class com/facebook/react/views/text/TextAttributeProps {
71497171
protected field mTextTransform Lcom/facebook/react/views/text/TextTransform;
71507172
public static fun fromMapBuffer (Lcom/facebook/react/common/mapbuffer/MapBuffer;)Lcom/facebook/react/views/text/TextAttributeProps;
71517173
public static fun fromReadableMap (Lcom/facebook/react/uimanager/ReactStylesDiffMap;)Lcom/facebook/react/views/text/TextAttributeProps;
7174+
public fun getAccessibilityRole ()Lcom/facebook/react/uimanager/ReactAccessibilityDelegate$AccessibilityRole;
7175+
public fun getBackgroundColor ()I
7176+
public fun getColor ()I
7177+
public fun getEffectiveFontSize ()I
7178+
public fun getEffectiveLetterSpacing ()F
71527179
public fun getEffectiveLineHeight ()F
7180+
public fun getFontFamily ()Ljava/lang/String;
7181+
public fun getFontFeatureSettings ()Ljava/lang/String;
7182+
public fun getFontStyle ()I
7183+
public fun getFontWeight ()I
71537184
public static fun getHyphenationFrequency (Ljava/lang/String;)I
71547185
public static fun getJustificationMode (Lcom/facebook/react/uimanager/ReactStylesDiffMap;I)I
71557186
public static fun getLayoutDirection (Ljava/lang/String;)I
71567187
public fun getLetterSpacing ()F
7188+
public fun getRole ()Lcom/facebook/react/uimanager/ReactAccessibilityDelegate$Role;
71577189
public static fun getTextAlignment (Lcom/facebook/react/uimanager/ReactStylesDiffMap;ZI)I
71587190
public static fun getTextBreakStrategy (Ljava/lang/String;)I
7191+
public fun getTextShadowColor ()I
7192+
public fun getTextShadowOffsetDx ()F
7193+
public fun getTextShadowOffsetDy ()F
7194+
public fun getTextShadowRadius ()F
7195+
public fun getTextTransform ()Lcom/facebook/react/views/text/TextTransform;
7196+
public fun isBackgroundColorSet ()Z
7197+
public fun isColorSet ()Z
7198+
public fun isLineThroughTextDecorationSet ()Z
7199+
public fun isUnderlineTextDecorationSet ()Z
71597200
}
71607201

71617202
public class com/facebook/react/views/text/TextAttributes {

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/common/ReactConstants.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,12 @@
1010
public class ReactConstants {
1111

1212
public static final String TAG = "ReactNative";
13+
14+
/**
15+
* Some types have built-in support for representing a "missing" or "unset" value, for example NaN
16+
* in the case of floating point numbers or null in the case of object references. Integers don't
17+
* have such a special value. When an integer represent an inherently non-negative value, we use a
18+
* special negative value to mark it as "unset".
19+
*/
20+
public static final int UNSET = -1;
1321
}

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/common/assets/ReactFontManager.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import androidx.annotation.Nullable;
1616
import androidx.core.content.res.ResourcesCompat;
1717
import com.facebook.infer.annotation.Nullsafe;
18+
import com.facebook.react.common.ReactConstants;
1819
import java.util.HashMap;
1920
import java.util.Map;
2021

@@ -167,8 +168,6 @@ public static class TypefaceStyle {
167168

168169
public static final int BOLD = 700;
169170
public static final int NORMAL = 400;
170-
public static final int UNSET = -1;
171-
172171
private static final int MIN_WEIGHT = 1;
173172
private static final int MAX_WEIGHT = 1000;
174173

@@ -177,11 +176,11 @@ public static class TypefaceStyle {
177176

178177
public TypefaceStyle(int weight, boolean italic) {
179178
mItalic = italic;
180-
mWeight = weight == UNSET ? NORMAL : weight;
179+
mWeight = weight == ReactConstants.UNSET ? NORMAL : weight;
181180
}
182181

183182
public TypefaceStyle(int style) {
184-
if (style == UNSET) {
183+
if (style == ReactConstants.UNSET) {
185184
style = Typeface.NORMAL;
186185
}
187186

@@ -194,12 +193,13 @@ public TypefaceStyle(int style) {
194193
* existing weight bit in `style` will be used.
195194
*/
196195
public TypefaceStyle(int style, int weight) {
197-
if (style == UNSET) {
196+
if (style == ReactConstants.UNSET) {
198197
style = Typeface.NORMAL;
199198
}
200199

201200
mItalic = (style & Typeface.ITALIC) != 0;
202-
mWeight = weight == UNSET ? (style & Typeface.BOLD) != 0 ? BOLD : NORMAL : weight;
201+
mWeight =
202+
weight == ReactConstants.UNSET ? (style & Typeface.BOLD) != 0 ? BOLD : NORMAL : weight;
203203
}
204204

205205
public int getNearestStyle() {

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/config/ReactFeatureFlags.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,4 +173,7 @@ public class ReactFeatureFlags {
173173
* when there is work to do.
174174
*/
175175
public static boolean enableOnDemandReactChoreographer = false;
176+
177+
/** Enables the new unified {@link android.text.Spannable} building logic. */
178+
public static boolean enableSpannableBuildingUnification = false;
176179
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
package com.facebook.react.views.text
9+
10+
import com.facebook.react.uimanager.ReactAccessibilityDelegate
11+
12+
/**
13+
* Interface for an entity providing basic text attributes of a text node/fragment. "Basic" means
14+
* that they can be provided trivially, without processing the parent element.
15+
*/
16+
internal interface BasicTextAttributeProvider {
17+
val role: ReactAccessibilityDelegate.Role?
18+
19+
val accessibilityRole: ReactAccessibilityDelegate.AccessibilityRole?
20+
21+
val isBackgroundColorSet: Boolean
22+
23+
val backgroundColor: Int
24+
25+
val isColorSet: Boolean
26+
27+
val color: Int
28+
29+
val fontStyle: Int
30+
31+
val fontWeight: Int
32+
33+
val fontFamily: String?
34+
35+
val fontFeatureSettings: String?
36+
37+
val isUnderlineTextDecorationSet: Boolean
38+
39+
val isLineThroughTextDecorationSet: Boolean
40+
41+
val textShadowOffsetDx: Float
42+
43+
val textShadowOffsetDy: Float
44+
45+
val textShadowRadius: Float
46+
47+
val textShadowColor: Int
48+
}

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/CustomStyleSpan.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import android.text.style.MetricAffectingSpan;
1515
import androidx.annotation.Nullable;
1616
import com.facebook.infer.annotation.Nullsafe;
17+
import com.facebook.react.common.ReactConstants;
1718
import com.facebook.react.common.assets.ReactFontManager;
1819

1920
@Nullsafe(Nullsafe.Mode.LOCAL)
@@ -61,13 +62,11 @@ public void updateMeasureState(TextPaint paint) {
6162
}
6263

6364
public int getStyle() {
64-
return mStyle == ReactFontManager.TypefaceStyle.UNSET ? Typeface.NORMAL : mStyle;
65+
return mStyle == ReactConstants.UNSET ? Typeface.NORMAL : mStyle;
6566
}
6667

6768
public int getWeight() {
68-
return mWeight == ReactFontManager.TypefaceStyle.UNSET
69-
? ReactFontManager.TypefaceStyle.NORMAL
70-
: mWeight;
69+
return mWeight == ReactConstants.UNSET ? ReactFontManager.TypefaceStyle.NORMAL : mWeight;
7170
}
7271

7372
public @Nullable String getFontFamily() {
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
package com.facebook.react.views.text
9+
10+
import com.facebook.react.common.ReactConstants.UNSET
11+
12+
/** Interface for an entity providing effective text attributes of a text node/fragment */
13+
internal interface EffectiveTextAttributeProvider : BasicTextAttributeProvider {
14+
val textTransform: TextTransform
15+
16+
val effectiveLetterSpacing: Float
17+
18+
/** @return The effective font size, or [UNSET] if not set */
19+
val effectiveFontSize: Int
20+
21+
val effectiveLineHeight: Float
22+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
package com.facebook.react.views.text
9+
10+
import com.facebook.react.common.ReactConstants
11+
12+
/**
13+
* Implementation of [EffectiveTextAttributeProvider] that provides effective text attributes based
14+
* on a [ReactBaseTextShadowNode] instance and its parent.
15+
*/
16+
internal class HierarchicTextAttributeProvider(
17+
private val textShadowNode: ReactBaseTextShadowNode,
18+
private val parentTextAttributes: TextAttributes?,
19+
private val textAttributes: TextAttributes
20+
) : EffectiveTextAttributeProvider, BasicTextAttributeProvider by textShadowNode {
21+
override val textTransform: TextTransform
22+
get() = textAttributes.textTransform
23+
24+
override val effectiveLetterSpacing: Float
25+
get() {
26+
val letterSpacing = textAttributes.effectiveLetterSpacing
27+
28+
val isParentLetterSpacingDifferent =
29+
parentTextAttributes == null ||
30+
parentTextAttributes.effectiveLetterSpacing != letterSpacing
31+
32+
return if (!letterSpacing.isNaN() && isParentLetterSpacingDifferent) {
33+
letterSpacing
34+
} else {
35+
Float.NaN
36+
}
37+
}
38+
39+
override val effectiveFontSize: Int
40+
get() {
41+
val fontSize = textAttributes.effectiveFontSize
42+
43+
return if (parentTextAttributes == null ||
44+
parentTextAttributes.effectiveFontSize != fontSize) {
45+
fontSize
46+
} else {
47+
ReactConstants.UNSET
48+
}
49+
}
50+
51+
override val effectiveLineHeight: Float
52+
get() {
53+
val lineHeight = textAttributes.effectiveLineHeight
54+
val isParentLineHeightDifferent =
55+
parentTextAttributes == null || parentTextAttributes.effectiveLineHeight != lineHeight
56+
57+
return if (!lineHeight.isNaN() && isParentLineHeightDifferent) {
58+
lineHeight
59+
} else {
60+
Float.NaN
61+
}
62+
}
63+
}

0 commit comments

Comments
 (0)