77
88package com .facebook .react .views .text ;
99
10+ import android .graphics .Color ;
1011import android .graphics .Typeface ;
1112import android .os .Build ;
1213import android .text .Layout ;
2425import com .facebook .react .internal .views .text .BasicTextAttributeProvider ;
2526import com .facebook .react .internal .views .text .HierarchicTextAttributeProvider ;
2627import com .facebook .react .internal .views .text .TextLayoutUtils ;
28+ import com .facebook .react .config .ReactFeatureFlags ;
2729import com .facebook .react .uimanager .IllegalViewOperationException ;
2830import com .facebook .react .uimanager .LayoutShadowNode ;
2931import com .facebook .react .uimanager .NativeViewHierarchyOptimizer ;
@@ -74,6 +76,189 @@ public abstract class ReactBaseTextShadowNode extends LayoutShadowNode implement
7476 protected @ Nullable ReactTextViewManagerCallback mReactTextViewManagerCallback ;
7577
7678 private static void buildSpannedFromShadowNode (
79+ ReactBaseTextShadowNode textShadowNode ,
80+ SpannableStringBuilder sb ,
81+ List <SetSpanOperation > ops ,
82+ @ Nullable TextAttributes parentTextAttributes ,
83+ boolean supportsInlineViews ,
84+ @ Nullable Map <Integer , ReactShadowNode > inlineViews ,
85+ int start ) {
86+ if (ReactFeatureFlags .enableSpannableBuildingUnification ) {
87+ buildSpannedFromShadowNodeUnified (
88+ textShadowNode ,
89+ sb ,
90+ ops ,
91+ parentTextAttributes ,
92+ supportsInlineViews ,
93+ inlineViews ,
94+ start
95+ );
96+ } else {
97+ buildSpannedFromShadowNodeDuplicated (
98+ textShadowNode ,
99+ sb ,
100+ ops ,
101+ parentTextAttributes ,
102+ supportsInlineViews ,
103+ inlineViews ,
104+ start
105+ );
106+ }
107+ }
108+
109+ private static void buildSpannedFromShadowNodeDuplicated (
110+ ReactBaseTextShadowNode textShadowNode ,
111+ SpannableStringBuilder sb ,
112+ List <SetSpanOperation > ops ,
113+ @ Nullable TextAttributes parentTextAttributes ,
114+ boolean supportsInlineViews ,
115+ @ Nullable Map <Integer , ReactShadowNode > inlineViews ,
116+ int start ) {
117+
118+ TextAttributes textAttributes ;
119+ if (parentTextAttributes != null ) {
120+ textAttributes = parentTextAttributes .applyChild (textShadowNode .mTextAttributes );
121+ } else {
122+ textAttributes = textShadowNode .mTextAttributes ;
123+ }
124+
125+ for (int i = 0 , length = textShadowNode .getChildCount (); i < length ; i ++) {
126+ ReactShadowNode child = textShadowNode .getChildAt (i );
127+
128+ if (child instanceof ReactRawTextShadowNode ) {
129+ sb .append (
130+ TextTransform .apply (
131+ ((ReactRawTextShadowNode ) child ).getText (), textAttributes .getTextTransform ()));
132+ } else if (child instanceof ReactBaseTextShadowNode ) {
133+ buildSpannedFromShadowNodeDuplicated (
134+ (ReactBaseTextShadowNode ) child ,
135+ sb ,
136+ ops ,
137+ textAttributes ,
138+ supportsInlineViews ,
139+ inlineViews ,
140+ sb .length ());
141+ } else if (child instanceof ReactTextInlineImageShadowNode ) {
142+ // We make the image take up 1 character in the span and put a corresponding character into
143+ // the text so that the image doesn't run over any following text.
144+ sb .append (INLINE_VIEW_PLACEHOLDER );
145+ ops .add (
146+ new SetSpanOperation (
147+ sb .length () - INLINE_VIEW_PLACEHOLDER .length (),
148+ sb .length (),
149+ ((ReactTextInlineImageShadowNode ) child ).buildInlineImageSpan ()));
150+ } else if (supportsInlineViews ) {
151+ int reactTag = child .getReactTag ();
152+ YogaValue widthValue = child .getStyleWidth ();
153+ YogaValue heightValue = child .getStyleHeight ();
154+
155+ float width ;
156+ float height ;
157+ if (widthValue .unit != YogaUnit .POINT || heightValue .unit != YogaUnit .POINT ) {
158+ // If the measurement of the child isn't calculated, we calculate the layout for the
159+ // view using Yoga
160+ child .calculateLayout ();
161+ width = child .getLayoutWidth ();
162+ height = child .getLayoutHeight ();
163+ } else {
164+ width = widthValue .value ;
165+ height = heightValue .value ;
166+ }
167+
168+ // We make the inline view take up 1 character in the span and put a corresponding character
169+ // into
170+ // the text so that the inline view doesn't run over any following text.
171+ sb .append (INLINE_VIEW_PLACEHOLDER );
172+ ops .add (
173+ new SetSpanOperation (
174+ sb .length () - INLINE_VIEW_PLACEHOLDER .length (),
175+ sb .length (),
176+ new TextInlineViewPlaceholderSpan (reactTag , (int ) width , (int ) height )));
177+ inlineViews .put (reactTag , child );
178+ } else {
179+ throw new IllegalViewOperationException (
180+ "Unexpected view type nested under a <Text> or <TextInput> node: " + child .getClass ());
181+ }
182+ child .markUpdateSeen ();
183+ }
184+ int end = sb .length ();
185+ if (end >= start ) {
186+ if (textShadowNode .mIsColorSet ) {
187+ ops .add (
188+ new SetSpanOperation (start , end , new ReactForegroundColorSpan (textShadowNode .mColor )));
189+ }
190+ if (textShadowNode .mIsBackgroundColorSet ) {
191+ ops .add (
192+ new SetSpanOperation (
193+ start , end , new ReactBackgroundColorSpan (textShadowNode .mBackgroundColor )));
194+ }
195+ boolean roleIsLink =
196+ textShadowNode .mRole != null
197+ ? textShadowNode .mRole == Role .LINK
198+ : textShadowNode .mAccessibilityRole == AccessibilityRole .LINK ;
199+ if (roleIsLink ) {
200+ ops .add (
201+ new SetSpanOperation (start , end , new ReactClickableSpan (textShadowNode .getReactTag ())));
202+ }
203+ float effectiveLetterSpacing = textAttributes .getEffectiveLetterSpacing ();
204+ if (!Float .isNaN (effectiveLetterSpacing )
205+ && (parentTextAttributes == null
206+ || parentTextAttributes .getEffectiveLetterSpacing () != effectiveLetterSpacing )) {
207+ ops .add (
208+ new SetSpanOperation (start , end , new CustomLetterSpacingSpan (effectiveLetterSpacing )));
209+ }
210+ int effectiveFontSize = textAttributes .getEffectiveFontSize ();
211+ if ( // `getEffectiveFontSize` always returns a value so don't need to check for anything like
212+ // `Float.NaN`.
213+ parentTextAttributes == null
214+ || parentTextAttributes .getEffectiveFontSize () != effectiveFontSize ) {
215+ ops .add (new SetSpanOperation (start , end , new ReactAbsoluteSizeSpan (effectiveFontSize )));
216+ }
217+ if (textShadowNode .mFontStyle != UNSET
218+ || textShadowNode .mFontWeight != UNSET
219+ || textShadowNode .mFontFamily != null ) {
220+ ops .add (
221+ new SetSpanOperation (
222+ start ,
223+ end ,
224+ new CustomStyleSpan (
225+ textShadowNode .mFontStyle ,
226+ textShadowNode .mFontWeight ,
227+ textShadowNode .mFontFeatureSettings ,
228+ textShadowNode .mFontFamily ,
229+ textShadowNode .getThemedContext ().getAssets ())));
230+ }
231+ if (textShadowNode .mIsUnderlineTextDecorationSet ) {
232+ ops .add (new SetSpanOperation (start , end , new ReactUnderlineSpan ()));
233+ }
234+ if (textShadowNode .mIsLineThroughTextDecorationSet ) {
235+ ops .add (new SetSpanOperation (start , end , new ReactStrikethroughSpan ()));
236+ }
237+ if ((textShadowNode .mTextShadowOffsetDx != 0
238+ || textShadowNode .mTextShadowOffsetDy != 0
239+ || textShadowNode .mTextShadowRadius != 0 )
240+ && Color .alpha (textShadowNode .mTextShadowColor ) != 0 ) {
241+ ops .add (
242+ new SetSpanOperation (
243+ start ,
244+ end ,
245+ new ShadowStyleSpan (
246+ textShadowNode .mTextShadowOffsetDx ,
247+ textShadowNode .mTextShadowOffsetDy ,
248+ textShadowNode .mTextShadowRadius ,
249+ textShadowNode .mTextShadowColor )));
250+ }
251+ float effectiveLineHeight = textAttributes .getEffectiveLineHeight ();
252+ if (!Float .isNaN (effectiveLineHeight )
253+ && (parentTextAttributes == null
254+ || parentTextAttributes .getEffectiveLineHeight () != effectiveLineHeight )) {
255+ ops .add (new SetSpanOperation (start , end , new CustomLineHeightSpan (effectiveLineHeight )));
256+ }
257+ ops .add (new SetSpanOperation (start , end , new ReactTagSpan (textShadowNode .getReactTag ())));
258+ }
259+ }
260+
261+ private static void buildSpannedFromShadowNodeUnified (
77262 ReactBaseTextShadowNode textShadowNode ,
78263 SpannableStringBuilder sb ,
79264 List <SetSpanOperation > ops ,
@@ -97,7 +282,7 @@ private static void buildSpannedFromShadowNode(
97282 if (child instanceof ReactRawTextShadowNode ) {
98283 sTextLayoutUtils .addText (sb , ((ReactRawTextShadowNode ) child ).getText (), textAttributeProvider );
99284 } else if (child instanceof ReactBaseTextShadowNode ) {
100- buildSpannedFromShadowNode (
285+ buildSpannedFromShadowNodeUnified (
101286 (ReactBaseTextShadowNode ) child ,
102287 sb ,
103288 ops ,
0 commit comments