Skip to content
Closed
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
5 changes: 5 additions & 0 deletions RNTester/android/app/src/main/res/font/srisakdi.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<font-family xmlns:app="http://schemas.android.com/apk/res-auto">
<font app:fontStyle="normal" app:fontWeight="400" app:font="@font/srisakdi_regular"/>
<font app:fontStyle="normal" app:fontWeight="700" app:font="@font/srisakdi_bold" />
</font-family>
Binary file not shown.
Binary file not shown.
8 changes: 8 additions & 0 deletions RNTester/js/TextExample.android.js
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,14 @@ class TextExample extends React.Component<{}> {
<Text style={{fontFamily: 'notoserif', fontStyle: 'italic'}}>
NotoSerif Italic (Missing Font file)
</Text>
<Text style={{fontFamily: 'srisakdi'}}>Srisakdi Regular</Text>
<Text
style={{
fontFamily: 'srisakdi',
fontWeight: 'bold',
}}>
Srisakdi Bold
</Text>
</View>
</View>
</RNTesterBlock>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@

package com.facebook.react.views.text;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;

import android.content.Context;
import android.content.res.AssetManager;
import android.graphics.Paint;
import android.graphics.Typeface;
Expand All @@ -29,31 +31,30 @@ public class CustomStyleSpan extends MetricAffectingSpan implements ReactSpan {
* Fonts are retrieved and cached using the {@link ReactFontManager}
*/

private final AssetManager mAssetManager;

private final int mStyle;
private final int mWeight;
private final @Nullable String mFontFamily;
private final Context mContext;

public CustomStyleSpan(
int fontStyle,
int fontWeight,
@Nullable String fontFamily,
AssetManager assetManager) {
@Nonnull Context context) {
mStyle = fontStyle;
mWeight = fontWeight;
mFontFamily = fontFamily;
mAssetManager = assetManager;
mContext = context;
}

@Override
public void updateDrawState(TextPaint ds) {
apply(ds, mStyle, mWeight, mFontFamily, mAssetManager);
apply(ds, mStyle, mWeight, mFontFamily, mContext);
}

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

/**
Expand Down Expand Up @@ -82,7 +83,7 @@ private static void apply(
int style,
int weight,
@Nullable String family,
AssetManager assetManager) {
Context context) {
int oldStyle;
Typeface typeface = paint.getTypeface();
if (typeface == null) {
Expand All @@ -103,7 +104,7 @@ private static void apply(
}

if (family != null) {
typeface = ReactFontManager.getInstance().getTypeface(family, want, assetManager);
typeface = ReactFontManager.getInstance().getTypeface(family, want, context);
} else if (typeface != null) {
// TODO(t9055065): Fix custom fonts getting applied to text children with different style
typeface = Typeface.create(typeface, want);
Expand All @@ -116,5 +117,4 @@ private static void apply(
}
paint.setSubpixelText(true);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ private static void buildSpannedFromShadowNode(
textShadowNode.mFontStyle,
textShadowNode.mFontWeight,
textShadowNode.mFontFamily,
textShadowNode.getThemedContext().getAssets())));
textShadowNode.getThemedContext())));
}
if (textShadowNode.mIsUnderlineTextDecorationSet) {
ops.add(new SetSpanOperation(start, end, new ReactUnderlineSpan()));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,14 @@
import java.util.HashMap;
import java.util.Map;

import android.content.Context;
import android.content.res.AssetManager;
import android.graphics.Typeface;
import android.util.SparseArray;

import androidx.core.content.res.ResourcesCompat;


/**
* 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
Expand All @@ -37,9 +41,11 @@ public class ReactFontManager {
private static ReactFontManager sReactFontManagerInstance;

private Map<String, FontFamily> mFontCache;
private Map<String, Typeface> mTypeCache;

private ReactFontManager() {
mFontCache = new HashMap<>();
mTypeCache = new HashMap<>();
}

public static ReactFontManager getInstance() {
Expand All @@ -49,8 +55,7 @@ public static ReactFontManager getInstance() {
return sReactFontManagerInstance;
}

public
@Nullable Typeface getTypeface(
private @Nullable Typeface getTypeface(
String fontFamilyName,
int style,
AssetManager assetManager) {
Expand All @@ -71,6 +76,33 @@ public static ReactFontManager getInstance() {
return typeface;
}

public @Nullable Typeface getTypeface(
String fontFamilyName,
int style,
Context context) {
Typeface font = mTypeCache.get(fontFamilyName);

if (font != null) {
return Typeface.create(
font,
style
);
}

int fontId = context.getResources().getIdentifier(fontFamilyName, "font", context.getPackageName());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So getIdentifier() is problematic in a big app that has many resources. This could contribute to non-trivial msec, just trying to crack open the resources, then looking it up from the APK. Rather than calling this for all scenarios, we'd instead build a "provider" pattern where:

  • each app decides if it wants to provide a custom fonts
  • if no provider is given, avoid asking getIdentifier() completely
  • if given, ask the provider for a resourceId (int), then use it

Now the implementation of the provider needs to be a switch statement for the best performance. Example:

int getCustomFontProvider(name) {
  switch(name) {
    case "mine":
       return R.font.mine;
    ...

Referencing R.font.* directly is fast and efficient, because it points exactly to an int resource Id. The only problem is, that each app needs to implement this code themselves, because each app has different set of custom fonts.

What do you think?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if (fontId != 0) {
font = ResourcesCompat.getFont(context, fontId);
if (font != null) {
mTypeCache.put(fontFamilyName, font);
return Typeface.create(
font,
style
);
}
}
return getTypeface(fontFamilyName, style, context.getAssets());
}

/**
* Add additional font family, or replace the exist one in the font memory cache.
* @param style
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ private static void buildSpannableFromFragment(
textAttributes.mFontStyle,
textAttributes.mFontWeight,
textAttributes.mFontFamily,
context.getAssets())));
context)));
}
if (textAttributes.mIsUnderlineTextDecorationSet) {
ops.add(new SetSpanOperation(start, end, new ReactUnderlineSpan()));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ public void setFontFamily(ReactEditText view, String fontFamily) {
Typeface newTypeface = ReactFontManager.getInstance().getTypeface(
fontFamily,
style,
view.getContext().getAssets());
view.getContext());
view.setTypeface(newTypeface);
}

Expand Down