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

[pull] master from facebook:master #852

Merged
merged 1 commit into from
Jul 13, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
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 @@ -12,9 +12,10 @@
import android.graphics.Typeface;
import android.text.TextPaint;
import android.text.style.MetricAffectingSpan;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.facebook.infer.annotation.Nullsafe;

@Nullsafe(Nullsafe.Mode.LOCAL)
public class CustomStyleSpan extends MetricAffectingSpan implements ReactSpan {

/**
Expand All @@ -40,7 +41,7 @@ public CustomStyleSpan(
int fontWeight,
@Nullable String fontFeatureSettings,
@Nullable String fontFamily,
@NonNull AssetManager assetManager) {
AssetManager assetManager) {
mStyle = fontStyle;
mWeight = fontWeight;
mFeatureSettings = fontFeatureSettings;
Expand All @@ -54,21 +55,18 @@ public void updateDrawState(TextPaint ds) {
}

@Override
public void updateMeasureState(@NonNull TextPaint paint) {
public void updateMeasureState(TextPaint paint) {
apply(paint, mStyle, mWeight, mFeatureSettings, mFontFamily, mAssetManager);
}

/** Returns {@link Typeface#NORMAL} or {@link Typeface#ITALIC}. */
public int getStyle() {
return (mStyle == ReactTextShadowNode.UNSET ? 0 : mStyle);
return mStyle == ReactBaseTextShadowNode.UNSET ? Typeface.NORMAL : mStyle;
}

/** Returns {@link Typeface#NORMAL} or {@link Typeface#BOLD}. */
public int getWeight() {
return (mWeight == ReactTextShadowNode.UNSET ? 0 : mWeight);
return mWeight == ReactBaseTextShadowNode.UNSET ? TypefaceStyle.NORMAL : mWeight;
}

/** Returns the font family set for this StyleSpan. */
public @Nullable String getFontFamily() {
return mFontFamily;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,31 +10,39 @@
import android.content.Context;
import android.content.res.AssetManager;
import android.graphics.Typeface;
import android.os.Build;
import android.util.SparseArray;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.res.ResourcesCompat;
import com.facebook.infer.annotation.Nullsafe;
import java.util.HashMap;
import java.util.Map;

/**
* Class responsible to load and cache Typeface objects. It will first try to load typefaces inside
* the assets/fonts folder and if it doesn't find the right Typeface in that folder will fall back
* on the best matching system Typeface The supported custom fonts extensions are .ttf and .otf. For
* each font family the bold, italic and bold_italic variants are supported. Given a "family" font
* family the files in the assets/fonts folder need to be family.ttf(.otf) family_bold.ttf(.otf)
* family_italic.ttf(.otf) and family_bold_italic.ttf(.otf)
* Responsible for loading and caching Typeface objects.
*
* <p>This will first try to load a typeface from the assets/fonts folder. If one is not found in
* that folder, this will fallback to the best matching system typeface.
*
* <p>Custom fonts support the extensions `.ttf` and `.otf` and the variants `bold`, `italic`, and
* `bold_italic`. For example, given a font named "ExampleFontFamily", the following are supported:
*
* <ul>
* <li>ExampleFontFamily.ttf (or .otf)
* <li>ExampleFontFamily_bold.ttf (or .otf)
* <li>ExampleFontFamily_italic.ttf (or .otf)
* <li>ExampleFontFamily_bold_italic.ttf (or .otf)
*/
@Nullsafe(Nullsafe.Mode.LOCAL)
public class ReactFontManager {

// NOTE: Indices in `EXTENSIONS` correspond to the `TypeFace` style constants.
private static final String[] EXTENSIONS = {"", "_bold", "_italic", "_bold_italic"};
private static final String[] FILE_EXTENSIONS = {".ttf", ".otf"};
private static final String FONTS_ASSET_PATH = "fonts/";

private static ReactFontManager sReactFontManagerInstance;

private final Map<String, FontFamily> mFontCache;
private final Map<String, AssetFontFamily> mFontCache;
private final Map<String, Typeface> mCustomTypefaceCache;

private ReactFontManager() {
Expand All @@ -49,36 +57,43 @@ public static ReactFontManager getInstance() {
return sReactFontManagerInstance;
}

public @Nullable Typeface getTypeface(
String fontFamilyName, int style, AssetManager assetManager) {
return getTypeface(fontFamilyName, style, 0, assetManager);
public Typeface getTypeface(String fontFamilyName, int style, AssetManager assetManager) {
return getTypeface(fontFamilyName, new TypefaceStyle(style), assetManager);
}

public @Nullable Typeface getTypeface(
public Typeface getTypeface(
String fontFamilyName, int weight, boolean italic, AssetManager assetManager) {
return getTypeface(fontFamilyName, new TypefaceStyle(weight, italic), assetManager);
}

public Typeface getTypeface(
String fontFamilyName, int style, int weight, AssetManager assetManager) {
return getTypeface(fontFamilyName, new TypefaceStyle(style, weight), assetManager);
}

public Typeface getTypeface(
String fontFamilyName, TypefaceStyle typefaceStyle, AssetManager assetManager) {
if (mCustomTypefaceCache.containsKey(fontFamilyName)) {
Typeface typeface = mCustomTypefaceCache.get(fontFamilyName);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && weight >= 100 && weight <= 1000) {
return Typeface.create(typeface, weight, (style & Typeface.ITALIC) != 0);
}
return Typeface.create(typeface, style);
// Apply `typefaceStyle` because custom fonts configure variants using `app:fontStyle` and
// `app:fontWeight` in their resource XML configuration file.
return typefaceStyle.apply(mCustomTypefaceCache.get(fontFamilyName));
}

FontFamily fontFamily = mFontCache.get(fontFamilyName);
if (fontFamily == null) {
fontFamily = new FontFamily();
mFontCache.put(fontFamilyName, fontFamily);
AssetFontFamily assetFontFamily = mFontCache.get(fontFamilyName);
if (assetFontFamily == null) {
assetFontFamily = new AssetFontFamily();
mFontCache.put(fontFamilyName, assetFontFamily);
}

Typeface typeface = fontFamily.getTypeface(style);
if (typeface == null) {
typeface = createTypeface(fontFamilyName, style, assetManager);
if (typeface != null) {
fontFamily.setTypeface(style, typeface);
}
}
int style = typefaceStyle.getNearestStyle();

return typeface;
Typeface assetTypeface = assetFontFamily.getTypefaceForStyle(style);
if (assetTypeface == null) {
assetTypeface = createAssetTypeface(fontFamilyName, style, assetManager);
assetFontFamily.setTypefaceForStyle(style, assetTypeface);
}
// Do not apply `typefaceStyle` because asset font files already incorporate the style.
return assetTypeface;
}

/*
Expand All @@ -88,7 +103,7 @@ public static ReactFontManager getInstance() {
*
* ReactFontManager.getInstance().addCustomFont(this, "Srisakdi", R.font.srisakdi);
*/
public void addCustomFont(@NonNull Context context, @NonNull String fontFamily, int fontId) {
public void addCustomFont(Context context, String fontFamily, int fontId) {
Typeface font = ResourcesCompat.getFont(context, fontId);
if (font != null) {
mCustomTypefaceCache.put(fontFamily, font);
Expand All @@ -106,16 +121,16 @@ public void addCustomFont(@NonNull Context context, @NonNull String fontFamily,
*/
public void setTypeface(String fontFamilyName, int style, Typeface typeface) {
if (typeface != null) {
FontFamily fontFamily = mFontCache.get(fontFamilyName);
if (fontFamily == null) {
fontFamily = new FontFamily();
mFontCache.put(fontFamilyName, fontFamily);
AssetFontFamily assetFontFamily = mFontCache.get(fontFamilyName);
if (assetFontFamily == null) {
assetFontFamily = new AssetFontFamily();
mFontCache.put(fontFamilyName, assetFontFamily);
}
fontFamily.setTypeface(style, typeface);
assetFontFamily.setTypefaceForStyle(style, typeface);
}
}

private static @Nullable Typeface createTypeface(
private static Typeface createAssetTypeface(
String fontFamilyName, int style, AssetManager assetManager) {
String extension = EXTENSIONS[style];
for (String fileExtension : FILE_EXTENSIONS) {
Expand All @@ -129,27 +144,27 @@ public void setTypeface(String fontFamilyName, int style, Typeface typeface) {
try {
return Typeface.createFromAsset(assetManager, fileName);
} catch (RuntimeException e) {
// unfortunately Typeface.createFromAsset throws an exception instead of returning null
// if the typeface doesn't exist
// If the typeface asset does not exist, try another extension.
continue;
}
}

return Typeface.create(fontFamilyName, style);
}

private static class FontFamily {
/** Responsible for caching typefaces for each custom font family. */
private static class AssetFontFamily {

private SparseArray<Typeface> mTypefaceSparseArray;

private FontFamily() {
private AssetFontFamily() {
mTypefaceSparseArray = new SparseArray<>(4);
}

public Typeface getTypeface(int style) {
public @Nullable Typeface getTypefaceForStyle(int style) {
return mTypefaceSparseArray.get(style);
}

public void setTypeface(int style, Typeface typeface) {
public void setTypefaceForStyle(int style, Typeface typeface) {
mTypefaceSparseArray.put(style, typeface);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,38 +9,54 @@

import android.content.res.AssetManager;
import android.graphics.Typeface;
import android.os.Build;
import android.text.TextUtils;
import androidx.annotation.Nullable;
import com.facebook.common.logging.FLog;
import com.facebook.infer.annotation.Nullsafe;
import com.facebook.react.bridge.ReadableArray;
import java.util.ArrayList;
import java.util.List;

@Nullsafe(Nullsafe.Mode.LOCAL)
public class ReactTypefaceUtils {
private static final String TAG = "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 ("bold".equals(fontWeightString)) fontWeight = Typeface.BOLD;
else if ("normal".equals(fontWeightString)) fontWeight = Typeface.NORMAL;

return fontWeight;
if (fontWeightString != null) {
switch (fontWeightString) {
case "100":
return 100;
case "200":
return 200;
case "300":
return 300;
case "normal":
case "400":
return 400;
case "500":
return 500;
case "600":
return 600;
case "bold":
case "700":
return 700;
case "800":
return 800;
case "900":
return 900;
}
}
return ReactBaseTextShadowNode.UNSET;
}

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;
if (fontStyleString != null) {
if ("italic".equals(fontStyleString)) {
return Typeface.ITALIC;
}
if ("normal".equals(fontStyleString)) {
return Typeface.NORMAL;
}
}

return fontStyle;
return ReactBaseTextShadowNode.UNSET;
}

public static @Nullable String parseFontVariant(@Nullable ReadableArray fontVariantArray) {
Expand Down Expand Up @@ -80,67 +96,14 @@ public static Typeface applyStyles(
@Nullable Typeface typeface,
int style,
int weight,
@Nullable String family,
@Nullable String fontFamilyName,
AssetManager assetManager) {
int oldStyle;
if (typeface == null) {
oldStyle = Typeface.NORMAL;
TypefaceStyle typefaceStyle = new TypefaceStyle(style, weight);
if (fontFamilyName == null) {
return typefaceStyle.apply(typeface == null ? Typeface.DEFAULT : typeface);
} else {
oldStyle = typeface.getStyle();
}

int newStyle = oldStyle;
boolean italic = false;
if (weight == UNSET) weight = Typeface.NORMAL;
if (style == Typeface.ITALIC) italic = true;
boolean UNDER_SDK_28 = Build.VERSION.SDK_INT < Build.VERSION_CODES.P;
boolean applyNumericValues = !(weight < (Typeface.BOLD_ITALIC + 1) || family != null);
boolean numericBold = UNDER_SDK_28 && weight > 699 && applyNumericValues;
boolean numericNormal = UNDER_SDK_28 && weight < 700 && applyNumericValues;
if (weight == Typeface.BOLD) {
newStyle = (newStyle == Typeface.ITALIC) ? Typeface.BOLD_ITALIC : Typeface.BOLD;
typeface = Typeface.create(typeface, newStyle);
return ReactFontManager.getInstance()
.getTypeface(fontFamilyName, typefaceStyle, assetManager);
}
if (weight == Typeface.NORMAL) {
typeface = Typeface.create(typeface, Typeface.NORMAL);
newStyle = Typeface.NORMAL;
}
if (style == Typeface.ITALIC) {
newStyle = (newStyle == Typeface.BOLD) ? Typeface.BOLD_ITALIC : Typeface.ITALIC;
typeface = Typeface.create(typeface, newStyle);
}
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.O_MR1 && weight > Typeface.BOLD_ITALIC) {
typeface = Typeface.create(typeface, weight, italic);
}
if (family != null && UNDER_SDK_28 && weight > Typeface.BOLD_ITALIC) {
FLog.d(
TAG,
"Support for numeric font weight numeric values with custom fonts under Android API 28 Pie is not yet supported in ReactNative.");
}
if (family != null) {
typeface = ReactFontManager.getInstance().getTypeface(family, newStyle, weight, assetManager);
}
if (numericBold || numericNormal) {
newStyle = numericBold ? Typeface.BOLD : Typeface.NORMAL;
typeface = Typeface.create(typeface, newStyle);
FLog.d(
TAG,
"Support for numeric font weight numeric values available only from Android API 28 Pie. Android device lower then API 28 will use normal or bold.");
}
return typeface;
}

/**
* 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;
}
}
Loading