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

Alter font loading code to support fallback fonts in fontFamily #31

Merged
merged 8 commits into from
Nov 15, 2022
Merged
54 changes: 46 additions & 8 deletions Libraries/Text/RCTTextAttributes.m
Original file line number Diff line number Diff line change
Expand Up @@ -192,14 +192,52 @@ - (NSParagraphStyle *)effectiveParagraphStyle

- (UIFont *)effectiveFont
{
// FIXME: RCTFont has thread-safety issues and must be rewritten.
Copy link
Author

Choose a reason for hiding this comment

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

confidence inspiring

return [RCTFont updateFont:nil
withFamily:_fontFamily
size:@(isnan(_fontSize) ? 0 : _fontSize)
weight:_fontWeight
style:_fontStyle
variant:_fontVariant
scaleMultiplier:self.effectiveFontSizeMultiplier];
NSArray *rawFontFamilies = [_fontFamily componentsSeparatedByString:@","];

// If _fontFamily is nil or has a single fontFamily, then use the original RN logic.
if (rawFontFamilies.count <= 1) {
// FIXME: RCTFont has thread-safety issues and must be rewritten.
return [RCTFont updateFont:nil
withFamily:_fontFamily
size:@(isnan(_fontSize) ? 0 : _fontSize)
weight:_fontWeight
style:_fontStyle
variant:_fontVariant
scaleMultiplier:self.effectiveFontSizeMultiplier];
}

NSMutableArray *fonts = [NSMutableArray new];
for (NSString *rawFontFamily in rawFontFamilies) {
NSString *fontFamily = [rawFontFamily stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
if (fontFamily.length == 0) {
continue;
}

UIFont *font = [RCTFont updateFont:nil
withFamily:fontFamily
size:@(isnan(_fontSize) ? 0 : _fontSize)
weight:_fontWeight
style:_fontStyle
variant:_fontVariant
scaleMultiplier:self.effectiveFontSizeMultiplier];

if (font) {
[fonts addObject:font];
}
}

UIFont *primaryFont = fonts[0];

NSMutableArray *fontDescriptors = [NSMutableArray new];
for (NSUInteger i = 1; i < fonts.count; i++) {
UIFont *font = fonts[i];
[fontDescriptors addObject:font.fontDescriptor];
}

UIFontDescriptor *fontDescriptor = [primaryFont.fontDescriptor fontDescriptorByAddingAttributes:
@{UIFontDescriptorCascadeListAttribute: fontDescriptors}];

return [UIFont fontWithDescriptor:fontDescriptor size:primaryFont.pointSize];
}

- (CGFloat)effectiveFontSizeMultiplier
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,9 +108,10 @@ public String getName() {

@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private boolean getIsReduceMotionEnabledValue() {
float defaultAnimationScale = Float.parseFloat(Settings.Global.TRANSITION_ANIMATION_SCALE);
float animationScale = Settings.Global.getFloat(mContentResolver, defaultAnimationScale);
return animationScale == 0f;
String value =
Settings.Global.getString(mContentResolver, Settings.Global.TRANSITION_ANIMATION_SCALE);

return value != null && value.equals("0.0");
}
Comment on lines 110 to 115
Copy link
Author

Choose a reason for hiding this comment

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

These changes are unrelated to the PR but they're necessary to get RN to compile locally


@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,19 @@
import android.content.Context;
import android.content.res.AssetManager;
import android.graphics.Typeface;
import android.graphics.fonts.Font;
import android.graphics.fonts.FontFamily;
import android.os.Build;
import android.util.SparseArray;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.core.content.res.ResourcesCompat;
import com.facebook.infer.annotation.Nullsafe;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
Expand Down Expand Up @@ -132,6 +140,28 @@ public void setTypeface(String fontFamilyName, int style, Typeface typeface) {

private static Typeface createAssetTypeface(
String fontFamilyName, int style, AssetManager assetManager) {
// This logic attempts to safely check if the frontend code is attempting to use
// fallback fonts, and if it is, to use the fallback typeface creation logic.
String[] fontFamilyNames = fontFamilyName != null ? fontFamilyName.split(",") : null;
if (fontFamilyNames != null) {
for (int i = 0; i < fontFamilyNames.length; i++) {
fontFamilyNames[i] = fontFamilyNames[i].trim();
}
}

// If there are multiple font family names:
// For newer versions of Android, construct a Typeface with fallbacks
// For older versions of Android, ignore all the fallbacks and just use the first font family
if (fontFamilyNames != null && fontFamilyNames.length > 1) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
return createAssetTypefaceWithFallbacks(fontFamilyNames, style, assetManager);
} else {
fontFamilyName = fontFamilyNames[0];
}
}

// Lastly, after all those checks above, this is the original RN logic for
// getting the typeface.
String extension = EXTENSIONS[style];
for (String fileExtension : FILE_EXTENSIONS) {
String fileName =
Expand All @@ -151,6 +181,44 @@ private static Typeface createAssetTypeface(
return Typeface.create(fontFamilyName, style);
}

@RequiresApi(api = Build.VERSION_CODES.Q)
private static Typeface createAssetTypefaceWithFallbacks(
String[] fontFamilyNames, int style, AssetManager assetManager) {
List<FontFamily> fontFamilies = new ArrayList<>();

// Iterate over the list of fontFamilyNames, constructing new FontFamily objects
// for use in the CustomFallbackBuilder below.
for (String fontFamilyName : fontFamilyNames) {
String extension = EXTENSIONS[style];
for (String fileExtension : FILE_EXTENSIONS) {
String fileName =
new StringBuilder()
.append(FONTS_ASSET_PATH)
.append(fontFamilyName)
.append(extension)
.append(fileExtension)
.toString();
try {
Font font = new Font.Builder(assetManager, fileName).build();
FontFamily family = new FontFamily.Builder(font).build();
fontFamilies.add(family);
} catch (RuntimeException e) {
// If the typeface asset does not exist, try another extension.
continue;
} catch (IOException e) {
// If the font asset does not exist, try another extension.
continue;
}
}
}

Typeface.CustomFallbackBuilder fallbackBuilder = new Typeface.CustomFallbackBuilder(fontFamilies.get(0));
for (int i = 1; i < fontFamilies.size(); i++) {
fallbackBuilder.addCustomFallback(fontFamilies.get(i));
}
return fallbackBuilder.build();
}

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

Expand Down