-
Notifications
You must be signed in to change notification settings - Fork 24.5k
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
custom fonts support The Android Way #24595
Changes from 5 commits
f5d4aaf
f9586f5
8f2ea3c
ccaa2b3
2b02c1c
e7dddce
eb108ff
9bd2591
b2ba223
0f6df0a
08cabaf
fe3dd73
f9c1050
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
<?xml version="1.0" encoding="utf-8"?> | ||
<resources> | ||
<array name="fonts"> | ||
<item>@font/srisakdi</item> | ||
</array> | ||
</resources> |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,15 +7,23 @@ | |
|
||
package com.facebook.react.views.text; | ||
|
||
import javax.annotation.Nullable; | ||
|
||
import java.util.HashMap; | ||
import java.util.Map; | ||
|
||
import android.content.Context; | ||
import android.content.pm.ApplicationInfo; | ||
import android.content.pm.PackageManager; | ||
import android.content.res.AssetManager; | ||
import android.content.res.Resources; | ||
import android.content.res.TypedArray; | ||
import android.graphics.Typeface; | ||
import android.os.Bundle; | ||
import android.util.SparseArray; | ||
|
||
import androidx.annotation.Nullable; | ||
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 | ||
|
@@ -37,9 +45,12 @@ public class ReactFontManager { | |
private static ReactFontManager sReactFontManagerInstance; | ||
|
||
private Map<String, FontFamily> mFontCache; | ||
private Map<String, Typeface> mTypeCache; | ||
private boolean mTypeCacheLoaded = false; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think a better set of names is:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. or:
|
||
|
||
private ReactFontManager() { | ||
mFontCache = new HashMap<>(); | ||
mTypeCache = new HashMap<>(); | ||
} | ||
|
||
public static ReactFontManager getInstance() { | ||
|
@@ -49,8 +60,7 @@ public static ReactFontManager getInstance() { | |
return sReactFontManagerInstance; | ||
} | ||
|
||
public | ||
@Nullable Typeface getTypeface( | ||
private @Nullable Typeface getTypeface( | ||
String fontFamilyName, | ||
int style, | ||
AssetManager assetManager) { | ||
|
@@ -71,6 +81,49 @@ public static ReactFontManager getInstance() { | |
return typeface; | ||
} | ||
|
||
public @Nullable Typeface getTypeface( | ||
String fontFamilyName, | ||
int style, | ||
Context context) { | ||
|
||
if (!mTypeCacheLoaded) { | ||
try { | ||
ApplicationInfo app = context.getPackageManager().getApplicationInfo(context.getPackageName(), PackageManager.GET_META_DATA); | ||
Bundle bundle = app.metaData; | ||
int fontsId = bundle.getInt("rn_fonts", 0); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can this be namespaced like |
||
if (fontsId != 0) { | ||
Resources resources = context.getResources(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually, I'm not sure if accessing anything dynamically from Resources is fast, because it needs to crack them open from the APK. Can we make this not do any resource lookup by default and only perform this lookup if the hosting app configures it? You don't really need to change it to use AndroidManifest, using font resource is fine, but I'd like:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think @dulmandakh is trying to find a way where non-native developers don't have to touch java code to customize these things, that's why he switched to using AndroidManifest that's supposed to be a cheaper alternative to resource lookups. If an app like FB does not provide "rn_fonts" in AndroidManifest.xml, there will never be any look up (besides looking to AndroidManifest itself), isn't that sufficient? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. got it. I'll figure out another solution :D |
||
TypedArray fonts = resources.obtainTypedArray(fontsId); | ||
for (int i = 0; i < fonts.length(); i++) { | ||
int fontId = fonts.getResourceId(i, 0); | ||
if (fontId != 0) { | ||
Typeface font = ResourcesCompat.getFont(context, fontId); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How expensive is loading If it is also taking too long (for FB scale), although it's verbose you might do something like:
This way you can iterate the array in increments of 2, and get away with only caching There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also this should allow font file names to not strictly match font family names, ex:
Native devs will be more familiar underscored naming This will allow sharing cross platform styling code with iOS, where font family name is already retrieved from font file content, not from file names. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I tried to mimic iOS feature, where we only list font files, but also make typo-proof. Also I couldn't find anything about performance impact of getResourceEntryName, and hope it won't have impact on performance. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. iOS works a bit differently, it reads font family name from the file content, Android in your implementation reads from the file name. IMO this will force developers to write strange JS code like: Just looking at getResourceEntryName implementation in AOSP repo, it's implemented in C++ so you will at least jump between JNI bridge for each call. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yep, I found that Android doesn't allow to use font family name, but resource filename without extension. So difference is inevitable. With this change we can use various font weights like medium easily. Currently, you have to write something like which makes text style sharing difficult. {
fontFamily: Platform.OS == 'ios' ? 'Open Sans' : 'open_sans_medium'
fontWeight: Platform.OS == 'ios' ? '500' : 'normal'
} once PR lands you will write {
fontFamily: Platform.OS == 'ios' ? 'Open Sans' : 'open_sans'
fontWeight: '500'
} There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can currently name your file "Open Sans.ttf", "Open Sans_bold.ttf"... etc though, so the code can already be cross platform |
||
if (font != null) { | ||
String fontFamily = resources.getResourceEntryName(fontId); | ||
mTypeCache.put(fontFamily, font); | ||
} | ||
} | ||
} | ||
fonts.recycle(); | ||
} | ||
} catch (NullPointerException | PackageManager.NameNotFoundException | Resources.NotFoundException e) { | ||
// do nothing | ||
} | ||
mTypeCacheLoaded = true; | ||
} | ||
|
||
Typeface font = mTypeCache.get(fontFamilyName); | ||
|
||
if (font != null) { | ||
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 | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
remove extra newline