Skip to content

Commit

Permalink
Fix medium font weights for TextInput on Android
Browse files Browse the repository at this point in the history
  • Loading branch information
janicduplessis committed Sep 14, 2019
1 parent dbf070c commit 8e227c5
Show file tree
Hide file tree
Showing 5 changed files with 138 additions and 126 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -71,37 +71,9 @@ public int getWeight() {

private static void apply(
Paint paint, int style, int weight, @Nullable String family, AssetManager assetManager) {
int oldStyle;
Typeface typeface = paint.getTypeface();
if (typeface == null) {
oldStyle = 0;
} else {
oldStyle = typeface.getStyle();
}

int want = 0;
if ((weight == Typeface.BOLD)
|| ((oldStyle & Typeface.BOLD) != 0 && weight == ReactTextShadowNode.UNSET)) {
want |= Typeface.BOLD;
}

if ((style == Typeface.ITALIC)
|| ((oldStyle & Typeface.ITALIC) != 0 && style == ReactTextShadowNode.UNSET)) {
want |= Typeface.ITALIC;
}

if (family != null) {
typeface = ReactFontManager.getInstance().getTypeface(family, want, weight, assetManager);
} else if (typeface != null) {
// TODO(t9055065): Fix custom fonts getting applied to text children with different style
typeface = Typeface.create(typeface, want);
}

if (typeface != null) {
paint.setTypeface(typeface);
} else {
paint.setTypeface(Typeface.defaultFromStyle(want));
}
Typeface typeface = ReactTypefaceUtils.applyStyles(
paint.getTypeface(), style, weight, family, assetManager);
paint.setTypeface(typeface);
paint.setSubpixelText(true);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -300,23 +300,6 @@ protected Spannable spannedFromShadowNode(
return sb;
}

/**
* Return -1 if the input string is not a valid numeric fontWeight (100, 200, ..., 900), otherwise
* return the weight.
*
* <p>This code is duplicated in ReactTextInputManager TODO: Factor into a common place they can
* both use
*/
private static int parseNumericFontWeight(String fontWeightString) {
// This should be much faster than using regex to verify input and Integer.parseInt
return fontWeightString.length() == 3
&& fontWeightString.endsWith("00")
&& fontWeightString.charAt(0) <= '9'
&& fontWeightString.charAt(0) >= '1'
? 100 * (fontWeightString.charAt(0) - '0')
: UNSET;
}

protected TextAttributes mTextAttributes;

protected boolean mIsColorSet = false;
Expand Down Expand Up @@ -490,37 +473,18 @@ public void setFontFamily(@Nullable String fontFamily) {
markUpdated();
}

/**
* /* This code is duplicated in ReactTextInputManager /* TODO: Factor into a common place they
* can both use
*/
@ReactProp(name = ViewProps.FONT_WEIGHT)
public void setFontWeight(@Nullable String fontWeightString) {
int fontWeightNumeric =
fontWeightString != null ? parseNumericFontWeight(fontWeightString) : UNSET;
int fontWeight = fontWeightNumeric != UNSET ? fontWeightNumeric : Typeface.NORMAL;

if (fontWeight == 700 || "bold".equals(fontWeightString)) fontWeight = Typeface.BOLD;
else if (fontWeight == 400 || "normal".equals(fontWeightString)) fontWeight = Typeface.NORMAL;

int fontWeight = ReactTypefaceUtils.parseFontWeight(fontWeightString);
if (fontWeight != mFontWeight) {
mFontWeight = fontWeight;
markUpdated();
}
}

/**
* /* This code is duplicated in ReactTextInputManager /* TODO: Factor into a common place they
* can both use
*/
@ReactProp(name = ViewProps.FONT_STYLE)
public void setFontStyle(@Nullable String fontStyleString) {
int fontStyle = UNSET;
if ("italic".equals(fontStyleString)) {
fontStyle = Typeface.ITALIC;
} else if ("normal".equals(fontStyleString)) {
fontStyle = Typeface.NORMAL;
}
int fontStyle = ReactTypefaceUtils.parseFontStyle(fontStyleString);
if (fontStyle != mFontStyle) {
mFontStyle = fontStyle;
markUpdated();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* <p>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.res.AssetManager;
import android.graphics.Paint;
import android.graphics.Typeface;

import androidx.annotation.Nullable;

public class ReactTypefaceUtils {
public static final int UNSET = -1;

public static int parseFontWeight(@Nullable String fontWeightString) {
int fontWeightNumeric =
fontWeightString != null ? parseNumericFontWeight(fontWeightString) : UNSET;
int fontWeight = fontWeightNumeric != UNSET ? fontWeightNumeric : Typeface.NORMAL;

if (fontWeight == 700 || "bold".equals(fontWeightString)) fontWeight = Typeface.BOLD;
else if (fontWeight == 400 || "normal".equals(fontWeightString)) fontWeight = Typeface.NORMAL;

return fontWeight;
}

public static int parseFontStyle(@Nullable String fontStyleString) {
int fontStyle = UNSET;
if ("italic".equals(fontStyleString)) {
fontStyle = Typeface.ITALIC;
} else if ("normal".equals(fontStyleString)) {
fontStyle = Typeface.NORMAL;
}

return fontStyle;
}

public static Typeface applyStyles(@Nullable Typeface typeface,
int style, int weight, @Nullable String family, AssetManager assetManager) {
int oldStyle;
if (typeface == null) {
oldStyle = 0;
} else {
oldStyle = typeface.getStyle();
}

int want = 0;
if ((weight == Typeface.BOLD)
|| ((oldStyle & Typeface.BOLD) != 0 && weight == ReactTextShadowNode.UNSET)) {
want |= Typeface.BOLD;
}

if ((style == Typeface.ITALIC)
|| ((oldStyle & Typeface.ITALIC) != 0 && style == ReactTextShadowNode.UNSET)) {
want |= Typeface.ITALIC;
}

if (family != null) {
typeface = ReactFontManager.getInstance().getTypeface(family, want, weight, assetManager);
} else if (typeface != null) {
// TODO(t9055065): Fix custom fonts getting applied to text children with different style
typeface = Typeface.create(typeface, want);
}

if (typeface != null) {
return typeface;
} else {
return Typeface.defaultFromStyle(want);
}
}

/**
* Return -1 if the input string is not a valid numeric fontWeight (100, 200, ..., 900), otherwise
* return the weight.
*/
private static int parseNumericFontWeight(String fontWeightString) {
// This should be much faster than using regex to verify input and Integer.parseInt
return fontWeightString.length() == 3
&& fontWeightString.endsWith("00")
&& fontWeightString.charAt(0) <= '9'
&& fontWeightString.charAt(0) >= '1'
? 100 * (fontWeightString.charAt(0) - '0')
: UNSET;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,11 @@
import com.facebook.infer.annotation.Assertions;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.uimanager.UIManagerModule;
import com.facebook.react.views.text.ReactFontManager;
import com.facebook.react.views.text.ReactSpan;
import com.facebook.react.views.text.ReactTextUpdate;
import com.facebook.react.views.text.ReactTypefaceUtils;
import com.facebook.react.views.text.TextAttributeProps;
import com.facebook.react.views.text.TextAttributes;
import com.facebook.react.views.text.TextInlineImageSpan;
import com.facebook.react.views.view.ReactViewBackgroundManager;
Expand Down Expand Up @@ -83,6 +86,10 @@ public class ReactEditText extends EditText {
private boolean mDetectScrollMovement = false;
private boolean mOnKeyPress = false;
private TextAttributes mTextAttributes;
private boolean mTypefaceDirty = false;
private @Nullable String mFontFamily = null;
private int mFontWeight = ReactTypefaceUtils.UNSET;
private int mFontStyle = ReactTypefaceUtils.UNSET;

private ReactViewBackgroundManager mReactBackgroundManager;

Expand Down Expand Up @@ -382,6 +389,39 @@ public void setInputType(int type) {
setKeyListener(mKeyListener);
}

public void setFontFamily(String fontFamily) {
mFontFamily = fontFamily;
mTypefaceDirty = true;
}

public void setFontWeight(String fontWeightString) {
int fontWeight = ReactTypefaceUtils.parseFontWeight(fontWeightString);
if (fontWeight != mFontWeight) {
mFontWeight = fontWeight;
mTypefaceDirty = true;
}
}

public void setFontStyle(String fontStyleString) {
int fontStyle = ReactTypefaceUtils.parseFontStyle(fontStyleString);
if (fontStyle != mFontStyle) {
mFontStyle = fontStyle;
mTypefaceDirty = true;
}
}

public void maybeUpdateTypeface() {
if (!mTypefaceDirty) {
return;
}

mTypefaceDirty = false;

Typeface newTypeface = ReactTypefaceUtils.applyStyles(
getTypeface(), mFontStyle, mFontWeight, mFontFamily, getContext().getAssets());
setTypeface(newTypeface);
}

// VisibleForTesting from {@link TextInputEventsTestCase}.
public void requestFocusFromJS() {
mShouldAllowFocus = true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -230,14 +230,7 @@ public void setFontSize(ReactEditText view, float fontSize) {

@ReactProp(name = ViewProps.FONT_FAMILY)
public void setFontFamily(ReactEditText view, String fontFamily) {
int style = Typeface.NORMAL;
if (view.getTypeface() != null) {
style = view.getTypeface().getStyle();
}
Typeface newTypeface =
ReactFontManager.getInstance()
.getTypeface(fontFamily, style, view.getContext().getAssets());
view.setTypeface(newTypeface);
view.setFontFamily(fontFamily);
}

@ReactProp(name = ViewProps.MAX_FONT_SIZE_MULTIPLIER, defaultFloat = Float.NaN)
Expand All @@ -250,45 +243,17 @@ public void setMaxFontSizeMultiplier(ReactEditText view, float maxFontSizeMultip
* Factor into a common place they can both use
*/
@ReactProp(name = ViewProps.FONT_WEIGHT)
public void setFontWeight(ReactEditText view, @Nullable String fontWeightString) {
int fontWeightNumeric =
fontWeightString != null ? parseNumericFontWeight(fontWeightString) : -1;
int fontWeight = UNSET;
if (fontWeightNumeric >= 500 || "bold".equals(fontWeightString)) {
fontWeight = Typeface.BOLD;
} else if ("normal".equals(fontWeightString)
|| (fontWeightNumeric != -1 && fontWeightNumeric < 500)) {
fontWeight = Typeface.NORMAL;
}
Typeface currentTypeface = view.getTypeface();
if (currentTypeface == null) {
currentTypeface = Typeface.DEFAULT;
}
if (fontWeight != currentTypeface.getStyle()) {
view.setTypeface(currentTypeface, fontWeight);
}
public void setFontWeight(ReactEditText view, @Nullable String fontWeight) {
view.setFontWeight(fontWeight);
}

/**
* /* This code was taken from the method setFontStyle of the class ReactTextShadowNode /* TODO:
* Factor into a common place they can both use
*/
@ReactProp(name = ViewProps.FONT_STYLE)
public void setFontStyle(ReactEditText view, @Nullable String fontStyleString) {
int fontStyle = UNSET;
if ("italic".equals(fontStyleString)) {
fontStyle = Typeface.ITALIC;
} else if ("normal".equals(fontStyleString)) {
fontStyle = Typeface.NORMAL;
}

Typeface currentTypeface = view.getTypeface();
if (currentTypeface == null) {
currentTypeface = Typeface.DEFAULT;
}
if (fontStyle != currentTypeface.getStyle()) {
view.setTypeface(currentTypeface, fontStyle);
}
public void setFontStyle(ReactEditText view, @Nullable String fontStyle) {
view.setFontStyle(fontStyle);
}

@ReactProp(name = "importantForAutofill")
Expand Down Expand Up @@ -800,6 +765,7 @@ public void setBorderColor(ReactEditText view, int index, Integer color) {
@Override
protected void onAfterUpdateTransaction(ReactEditText view) {
super.onAfterUpdateTransaction(view);
view.maybeUpdateTypeface();
view.commitStagedInputType();
}

Expand All @@ -813,23 +779,6 @@ private static void checkPasswordType(ReactEditText view) {
}
}

/**
* This code was taken from the method parseNumericFontWeight of the class ReactTextShadowNode
* TODO: Factor into a common place they can both use
*
* <p>Return -1 if the input string is not a valid numeric fontWeight (100, 200, ..., 900),
* otherwise return the weight.
*/
private static int parseNumericFontWeight(String fontWeightString) {
// This should be much faster than using regex to verify input and Integer.parseInt
return fontWeightString.length() == 3
&& fontWeightString.endsWith("00")
&& fontWeightString.charAt(0) <= '9'
&& fontWeightString.charAt(0) >= '1'
? 100 * (fontWeightString.charAt(0) - '0')
: -1;
}

private static void updateStagedInputTypeFlag(
ReactEditText view, int flagsToUnset, int flagsToSet) {
view.setStagedInputType((view.getStagedInputType() & ~flagsToUnset) | flagsToSet);
Expand Down

0 comments on commit 8e227c5

Please sign in to comment.