diff --git a/Libraries/Text/RCTTextAttributes.m b/Libraries/Text/RCTTextAttributes.m index 01131cfb20e2fb..2bf3816b852c19 100644 --- a/Libraries/Text/RCTTextAttributes.m +++ b/Libraries/Text/RCTTextAttributes.m @@ -192,14 +192,52 @@ - (NSParagraphStyle *)effectiveParagraphStyle - (UIFont *)effectiveFont { - // 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]; + 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 diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/accessibilityinfo/AccessibilityInfoModule.java b/ReactAndroid/src/main/java/com/facebook/react/modules/accessibilityinfo/AccessibilityInfoModule.java index 02800eeda895b8..0ea17b3b52cda6 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/accessibilityinfo/AccessibilityInfoModule.java +++ b/ReactAndroid/src/main/java/com/facebook/react/modules/accessibilityinfo/AccessibilityInfoModule.java @@ -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"); } @Override diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactFontManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactFontManager.java index d81490fd82cb25..35d8c41fda8207 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactFontManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactFontManager.java @@ -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; /** @@ -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 = @@ -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 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 {