Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix medium font weights for TextInput on Android #26434

Closed
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -38,6 +38,7 @@
import com.facebook.react.uimanager.UIManagerModule;
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.TextAttributes;
import com.facebook.react.views.text.TextInlineImageSpan;
import com.facebook.react.views.view.ReactViewBackgroundManager;
Expand Down Expand Up @@ -83,6 +84,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 +387,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,65 +230,22 @@ 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)
public void setMaxFontSizeMultiplier(ReactEditText view, float maxFontSizeMultiplier) {
view.setMaxFontSizeMultiplier(maxFontSizeMultiplier);
}

/**
* /* This code was taken from the method setFontWeight of the class ReactTextShadowNode /* TODO:
* 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 +757,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 +771,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