Skip to content

Commit 8e227c5

Browse files
Fix medium font weights for TextInput on Android
1 parent dbf070c commit 8e227c5

File tree

5 files changed

+138
-126
lines changed

5 files changed

+138
-126
lines changed

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

+3-31
Original file line numberDiff line numberDiff line change
@@ -71,37 +71,9 @@ public int getWeight() {
7171

7272
private static void apply(
7373
Paint paint, int style, int weight, @Nullable String family, AssetManager assetManager) {
74-
int oldStyle;
75-
Typeface typeface = paint.getTypeface();
76-
if (typeface == null) {
77-
oldStyle = 0;
78-
} else {
79-
oldStyle = typeface.getStyle();
80-
}
81-
82-
int want = 0;
83-
if ((weight == Typeface.BOLD)
84-
|| ((oldStyle & Typeface.BOLD) != 0 && weight == ReactTextShadowNode.UNSET)) {
85-
want |= Typeface.BOLD;
86-
}
87-
88-
if ((style == Typeface.ITALIC)
89-
|| ((oldStyle & Typeface.ITALIC) != 0 && style == ReactTextShadowNode.UNSET)) {
90-
want |= Typeface.ITALIC;
91-
}
92-
93-
if (family != null) {
94-
typeface = ReactFontManager.getInstance().getTypeface(family, want, weight, assetManager);
95-
} else if (typeface != null) {
96-
// TODO(t9055065): Fix custom fonts getting applied to text children with different style
97-
typeface = Typeface.create(typeface, want);
98-
}
99-
100-
if (typeface != null) {
101-
paint.setTypeface(typeface);
102-
} else {
103-
paint.setTypeface(Typeface.defaultFromStyle(want));
104-
}
74+
Typeface typeface = ReactTypefaceUtils.applyStyles(
75+
paint.getTypeface(), style, weight, family, assetManager);
76+
paint.setTypeface(typeface);
10577
paint.setSubpixelText(true);
10678
}
10779
}

ReactAndroid/src/main/java/com/facebook/react/views/text/ReactBaseTextShadowNode.java

+2-38
Original file line numberDiff line numberDiff line change
@@ -300,23 +300,6 @@ protected Spannable spannedFromShadowNode(
300300
return sb;
301301
}
302302

303-
/**
304-
* Return -1 if the input string is not a valid numeric fontWeight (100, 200, ..., 900), otherwise
305-
* return the weight.
306-
*
307-
* <p>This code is duplicated in ReactTextInputManager TODO: Factor into a common place they can
308-
* both use
309-
*/
310-
private static int parseNumericFontWeight(String fontWeightString) {
311-
// This should be much faster than using regex to verify input and Integer.parseInt
312-
return fontWeightString.length() == 3
313-
&& fontWeightString.endsWith("00")
314-
&& fontWeightString.charAt(0) <= '9'
315-
&& fontWeightString.charAt(0) >= '1'
316-
? 100 * (fontWeightString.charAt(0) - '0')
317-
: UNSET;
318-
}
319-
320303
protected TextAttributes mTextAttributes;
321304

322305
protected boolean mIsColorSet = false;
@@ -490,37 +473,18 @@ public void setFontFamily(@Nullable String fontFamily) {
490473
markUpdated();
491474
}
492475

493-
/**
494-
* /* This code is duplicated in ReactTextInputManager /* TODO: Factor into a common place they
495-
* can both use
496-
*/
497476
@ReactProp(name = ViewProps.FONT_WEIGHT)
498477
public void setFontWeight(@Nullable String fontWeightString) {
499-
int fontWeightNumeric =
500-
fontWeightString != null ? parseNumericFontWeight(fontWeightString) : UNSET;
501-
int fontWeight = fontWeightNumeric != UNSET ? fontWeightNumeric : Typeface.NORMAL;
502-
503-
if (fontWeight == 700 || "bold".equals(fontWeightString)) fontWeight = Typeface.BOLD;
504-
else if (fontWeight == 400 || "normal".equals(fontWeightString)) fontWeight = Typeface.NORMAL;
505-
478+
int fontWeight = ReactTypefaceUtils.parseFontWeight(fontWeightString);
506479
if (fontWeight != mFontWeight) {
507480
mFontWeight = fontWeight;
508481
markUpdated();
509482
}
510483
}
511484

512-
/**
513-
* /* This code is duplicated in ReactTextInputManager /* TODO: Factor into a common place they
514-
* can both use
515-
*/
516485
@ReactProp(name = ViewProps.FONT_STYLE)
517486
public void setFontStyle(@Nullable String fontStyleString) {
518-
int fontStyle = UNSET;
519-
if ("italic".equals(fontStyleString)) {
520-
fontStyle = Typeface.ITALIC;
521-
} else if ("normal".equals(fontStyleString)) {
522-
fontStyle = Typeface.NORMAL;
523-
}
487+
int fontStyle = ReactTypefaceUtils.parseFontStyle(fontStyleString);
524488
if (fontStyle != mFontStyle) {
525489
mFontStyle = fontStyle;
526490
markUpdated();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its affiliates.
3+
*
4+
* <p>This source code is licensed under the MIT license found in the LICENSE file in the root
5+
* directory of this source tree.
6+
*/
7+
package com.facebook.react.views.text;
8+
9+
import android.content.res.AssetManager;
10+
import android.graphics.Paint;
11+
import android.graphics.Typeface;
12+
13+
import androidx.annotation.Nullable;
14+
15+
public class ReactTypefaceUtils {
16+
public static final int UNSET = -1;
17+
18+
public static int parseFontWeight(@Nullable String fontWeightString) {
19+
int fontWeightNumeric =
20+
fontWeightString != null ? parseNumericFontWeight(fontWeightString) : UNSET;
21+
int fontWeight = fontWeightNumeric != UNSET ? fontWeightNumeric : Typeface.NORMAL;
22+
23+
if (fontWeight == 700 || "bold".equals(fontWeightString)) fontWeight = Typeface.BOLD;
24+
else if (fontWeight == 400 || "normal".equals(fontWeightString)) fontWeight = Typeface.NORMAL;
25+
26+
return fontWeight;
27+
}
28+
29+
public static int parseFontStyle(@Nullable String fontStyleString) {
30+
int fontStyle = UNSET;
31+
if ("italic".equals(fontStyleString)) {
32+
fontStyle = Typeface.ITALIC;
33+
} else if ("normal".equals(fontStyleString)) {
34+
fontStyle = Typeface.NORMAL;
35+
}
36+
37+
return fontStyle;
38+
}
39+
40+
public static Typeface applyStyles(@Nullable Typeface typeface,
41+
int style, int weight, @Nullable String family, AssetManager assetManager) {
42+
int oldStyle;
43+
if (typeface == null) {
44+
oldStyle = 0;
45+
} else {
46+
oldStyle = typeface.getStyle();
47+
}
48+
49+
int want = 0;
50+
if ((weight == Typeface.BOLD)
51+
|| ((oldStyle & Typeface.BOLD) != 0 && weight == ReactTextShadowNode.UNSET)) {
52+
want |= Typeface.BOLD;
53+
}
54+
55+
if ((style == Typeface.ITALIC)
56+
|| ((oldStyle & Typeface.ITALIC) != 0 && style == ReactTextShadowNode.UNSET)) {
57+
want |= Typeface.ITALIC;
58+
}
59+
60+
if (family != null) {
61+
typeface = ReactFontManager.getInstance().getTypeface(family, want, weight, assetManager);
62+
} else if (typeface != null) {
63+
// TODO(t9055065): Fix custom fonts getting applied to text children with different style
64+
typeface = Typeface.create(typeface, want);
65+
}
66+
67+
if (typeface != null) {
68+
return typeface;
69+
} else {
70+
return Typeface.defaultFromStyle(want);
71+
}
72+
}
73+
74+
/**
75+
* Return -1 if the input string is not a valid numeric fontWeight (100, 200, ..., 900), otherwise
76+
* return the weight.
77+
*/
78+
private static int parseNumericFontWeight(String fontWeightString) {
79+
// This should be much faster than using regex to verify input and Integer.parseInt
80+
return fontWeightString.length() == 3
81+
&& fontWeightString.endsWith("00")
82+
&& fontWeightString.charAt(0) <= '9'
83+
&& fontWeightString.charAt(0) >= '1'
84+
? 100 * (fontWeightString.charAt(0) - '0')
85+
: UNSET;
86+
}
87+
}

ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.java

+40
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,11 @@
3636
import com.facebook.infer.annotation.Assertions;
3737
import com.facebook.react.bridge.ReactContext;
3838
import com.facebook.react.uimanager.UIManagerModule;
39+
import com.facebook.react.views.text.ReactFontManager;
3940
import com.facebook.react.views.text.ReactSpan;
4041
import com.facebook.react.views.text.ReactTextUpdate;
42+
import com.facebook.react.views.text.ReactTypefaceUtils;
43+
import com.facebook.react.views.text.TextAttributeProps;
4144
import com.facebook.react.views.text.TextAttributes;
4245
import com.facebook.react.views.text.TextInlineImageSpan;
4346
import com.facebook.react.views.view.ReactViewBackgroundManager;
@@ -83,6 +86,10 @@ public class ReactEditText extends EditText {
8386
private boolean mDetectScrollMovement = false;
8487
private boolean mOnKeyPress = false;
8588
private TextAttributes mTextAttributes;
89+
private boolean mTypefaceDirty = false;
90+
private @Nullable String mFontFamily = null;
91+
private int mFontWeight = ReactTypefaceUtils.UNSET;
92+
private int mFontStyle = ReactTypefaceUtils.UNSET;
8693

8794
private ReactViewBackgroundManager mReactBackgroundManager;
8895

@@ -382,6 +389,39 @@ public void setInputType(int type) {
382389
setKeyListener(mKeyListener);
383390
}
384391

392+
public void setFontFamily(String fontFamily) {
393+
mFontFamily = fontFamily;
394+
mTypefaceDirty = true;
395+
}
396+
397+
public void setFontWeight(String fontWeightString) {
398+
int fontWeight = ReactTypefaceUtils.parseFontWeight(fontWeightString);
399+
if (fontWeight != mFontWeight) {
400+
mFontWeight = fontWeight;
401+
mTypefaceDirty = true;
402+
}
403+
}
404+
405+
public void setFontStyle(String fontStyleString) {
406+
int fontStyle = ReactTypefaceUtils.parseFontStyle(fontStyleString);
407+
if (fontStyle != mFontStyle) {
408+
mFontStyle = fontStyle;
409+
mTypefaceDirty = true;
410+
}
411+
}
412+
413+
public void maybeUpdateTypeface() {
414+
if (!mTypefaceDirty) {
415+
return;
416+
}
417+
418+
mTypefaceDirty = false;
419+
420+
Typeface newTypeface = ReactTypefaceUtils.applyStyles(
421+
getTypeface(), mFontStyle, mFontWeight, mFontFamily, getContext().getAssets());
422+
setTypeface(newTypeface);
423+
}
424+
385425
// VisibleForTesting from {@link TextInputEventsTestCase}.
386426
public void requestFocusFromJS() {
387427
mShouldAllowFocus = true;

ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.java

+6-57
Original file line numberDiff line numberDiff line change
@@ -230,14 +230,7 @@ public void setFontSize(ReactEditText view, float fontSize) {
230230

231231
@ReactProp(name = ViewProps.FONT_FAMILY)
232232
public void setFontFamily(ReactEditText view, String fontFamily) {
233-
int style = Typeface.NORMAL;
234-
if (view.getTypeface() != null) {
235-
style = view.getTypeface().getStyle();
236-
}
237-
Typeface newTypeface =
238-
ReactFontManager.getInstance()
239-
.getTypeface(fontFamily, style, view.getContext().getAssets());
240-
view.setTypeface(newTypeface);
233+
view.setFontFamily(fontFamily);
241234
}
242235

243236
@ReactProp(name = ViewProps.MAX_FONT_SIZE_MULTIPLIER, defaultFloat = Float.NaN)
@@ -250,45 +243,17 @@ public void setMaxFontSizeMultiplier(ReactEditText view, float maxFontSizeMultip
250243
* Factor into a common place they can both use
251244
*/
252245
@ReactProp(name = ViewProps.FONT_WEIGHT)
253-
public void setFontWeight(ReactEditText view, @Nullable String fontWeightString) {
254-
int fontWeightNumeric =
255-
fontWeightString != null ? parseNumericFontWeight(fontWeightString) : -1;
256-
int fontWeight = UNSET;
257-
if (fontWeightNumeric >= 500 || "bold".equals(fontWeightString)) {
258-
fontWeight = Typeface.BOLD;
259-
} else if ("normal".equals(fontWeightString)
260-
|| (fontWeightNumeric != -1 && fontWeightNumeric < 500)) {
261-
fontWeight = Typeface.NORMAL;
262-
}
263-
Typeface currentTypeface = view.getTypeface();
264-
if (currentTypeface == null) {
265-
currentTypeface = Typeface.DEFAULT;
266-
}
267-
if (fontWeight != currentTypeface.getStyle()) {
268-
view.setTypeface(currentTypeface, fontWeight);
269-
}
246+
public void setFontWeight(ReactEditText view, @Nullable String fontWeight) {
247+
view.setFontWeight(fontWeight);
270248
}
271249

272250
/**
273251
* /* This code was taken from the method setFontStyle of the class ReactTextShadowNode /* TODO:
274252
* Factor into a common place they can both use
275253
*/
276254
@ReactProp(name = ViewProps.FONT_STYLE)
277-
public void setFontStyle(ReactEditText view, @Nullable String fontStyleString) {
278-
int fontStyle = UNSET;
279-
if ("italic".equals(fontStyleString)) {
280-
fontStyle = Typeface.ITALIC;
281-
} else if ("normal".equals(fontStyleString)) {
282-
fontStyle = Typeface.NORMAL;
283-
}
284-
285-
Typeface currentTypeface = view.getTypeface();
286-
if (currentTypeface == null) {
287-
currentTypeface = Typeface.DEFAULT;
288-
}
289-
if (fontStyle != currentTypeface.getStyle()) {
290-
view.setTypeface(currentTypeface, fontStyle);
291-
}
255+
public void setFontStyle(ReactEditText view, @Nullable String fontStyle) {
256+
view.setFontStyle(fontStyle);
292257
}
293258

294259
@ReactProp(name = "importantForAutofill")
@@ -800,6 +765,7 @@ public void setBorderColor(ReactEditText view, int index, Integer color) {
800765
@Override
801766
protected void onAfterUpdateTransaction(ReactEditText view) {
802767
super.onAfterUpdateTransaction(view);
768+
view.maybeUpdateTypeface();
803769
view.commitStagedInputType();
804770
}
805771

@@ -813,23 +779,6 @@ private static void checkPasswordType(ReactEditText view) {
813779
}
814780
}
815781

816-
/**
817-
* This code was taken from the method parseNumericFontWeight of the class ReactTextShadowNode
818-
* TODO: Factor into a common place they can both use
819-
*
820-
* <p>Return -1 if the input string is not a valid numeric fontWeight (100, 200, ..., 900),
821-
* otherwise return the weight.
822-
*/
823-
private static int parseNumericFontWeight(String fontWeightString) {
824-
// This should be much faster than using regex to verify input and Integer.parseInt
825-
return fontWeightString.length() == 3
826-
&& fontWeightString.endsWith("00")
827-
&& fontWeightString.charAt(0) <= '9'
828-
&& fontWeightString.charAt(0) >= '1'
829-
? 100 * (fontWeightString.charAt(0) - '0')
830-
: -1;
831-
}
832-
833782
private static void updateStagedInputTypeFlag(
834783
ReactEditText view, int flagsToUnset, int flagsToSet) {
835784
view.setStagedInputType((view.getStagedInputType() & ~flagsToUnset) | flagsToSet);

0 commit comments

Comments
 (0)