From fbfb430b3b32c906f4f642b28ff1c2bf96ce5ada Mon Sep 17 00:00:00 2001 From: Thomas Nardone Date: Wed, 31 Jul 2024 11:28:07 -0700 Subject: [PATCH] Convert common/assets/ReactFontManager.java (#45668) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/45668 Changelog: [Internal] [Changed] - Convert com.facebook.react.common.assets.ReactFontManager to Kotlin Reviewed By: cortinico Differential Revision: D60188666 fbshipit-source-id: 17ca6f162545e9185e1e0a24d8ebacf6cb49a064 --- .../ReactAndroid/api/ReactAndroid.api | 35 ++- .../react/common/assets/ReactFontManager.java | 239 ------------------ .../react/common/assets/ReactFontManager.kt | 197 +++++++++++++++ 3 files changed, 220 insertions(+), 251 deletions(-) delete mode 100644 packages/react-native/ReactAndroid/src/main/java/com/facebook/react/common/assets/ReactFontManager.java create mode 100644 packages/react-native/ReactAndroid/src/main/java/com/facebook/react/common/assets/ReactFontManager.kt diff --git a/packages/react-native/ReactAndroid/api/ReactAndroid.api b/packages/react-native/ReactAndroid/api/ReactAndroid.api index c96350d7fce..5b522c5a8c2 100644 --- a/packages/react-native/ReactAndroid/api/ReactAndroid.api +++ b/packages/react-native/ReactAndroid/api/ReactAndroid.api @@ -1884,25 +1884,36 @@ public abstract interface annotation class com/facebook/react/common/annotations public abstract interface annotation class com/facebook/react/common/annotations/VisibleForTesting : java/lang/annotation/Annotation { } -public class com/facebook/react/common/assets/ReactFontManager { - public fun addCustomFont (Landroid/content/Context;Ljava/lang/String;I)V - public fun addCustomFont (Ljava/lang/String;Landroid/graphics/Typeface;)V - public static fun getInstance ()Lcom/facebook/react/common/assets/ReactFontManager; - public fun getTypeface (Ljava/lang/String;IILandroid/content/res/AssetManager;)Landroid/graphics/Typeface; - public fun getTypeface (Ljava/lang/String;ILandroid/content/res/AssetManager;)Landroid/graphics/Typeface; - public fun getTypeface (Ljava/lang/String;IZLandroid/content/res/AssetManager;)Landroid/graphics/Typeface; - public fun getTypeface (Ljava/lang/String;Lcom/facebook/react/common/assets/ReactFontManager$TypefaceStyle;Landroid/content/res/AssetManager;)Landroid/graphics/Typeface; - public fun setTypeface (Ljava/lang/String;ILandroid/graphics/Typeface;)V +public final class com/facebook/react/common/assets/ReactFontManager { + public static final field Companion Lcom/facebook/react/common/assets/ReactFontManager$Companion; + public fun ()V + public final fun addCustomFont (Landroid/content/Context;Ljava/lang/String;I)V + public final fun addCustomFont (Ljava/lang/String;Landroid/graphics/Typeface;)V + public static final fun getInstance ()Lcom/facebook/react/common/assets/ReactFontManager; + public final fun getTypeface (Ljava/lang/String;IILandroid/content/res/AssetManager;)Landroid/graphics/Typeface; + public final fun getTypeface (Ljava/lang/String;ILandroid/content/res/AssetManager;)Landroid/graphics/Typeface; + public final fun getTypeface (Ljava/lang/String;IZLandroid/content/res/AssetManager;)Landroid/graphics/Typeface; + public final fun getTypeface (Ljava/lang/String;Lcom/facebook/react/common/assets/ReactFontManager$TypefaceStyle;Landroid/content/res/AssetManager;)Landroid/graphics/Typeface; + public final fun setTypeface (Ljava/lang/String;ILandroid/graphics/Typeface;)V } -public class com/facebook/react/common/assets/ReactFontManager$TypefaceStyle { +public final class com/facebook/react/common/assets/ReactFontManager$Companion { + public final fun getInstance ()Lcom/facebook/react/common/assets/ReactFontManager; +} + +public final class com/facebook/react/common/assets/ReactFontManager$TypefaceStyle { public static final field BOLD I + public static final field Companion Lcom/facebook/react/common/assets/ReactFontManager$TypefaceStyle$Companion; public static final field NORMAL I public fun (I)V public fun (II)V + public synthetic fun (IIILkotlin/jvm/internal/DefaultConstructorMarker;)V public fun (IZ)V - public fun apply (Landroid/graphics/Typeface;)Landroid/graphics/Typeface; - public fun getNearestStyle ()I + public final fun apply (Landroid/graphics/Typeface;)Landroid/graphics/Typeface; + public final fun getNearestStyle ()I +} + +public final class com/facebook/react/common/assets/ReactFontManager$TypefaceStyle$Companion { } public class com/facebook/react/common/build/ReactBuildConfig { diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/common/assets/ReactFontManager.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/common/assets/ReactFontManager.java deleted file mode 100644 index 62efef79e73..00000000000 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/common/assets/ReactFontManager.java +++ /dev/null @@ -1,239 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -package com.facebook.react.common.assets; - -import android.content.Context; -import android.content.res.AssetManager; -import android.graphics.Typeface; -import android.os.Build; -import android.util.SparseArray; -import androidx.annotation.Nullable; -import androidx.core.content.res.ResourcesCompat; -import com.facebook.infer.annotation.Nullsafe; -import com.facebook.react.common.ReactConstants; -import java.util.HashMap; -import java.util.Map; - -/** - * Responsible for loading and caching Typeface objects. - * - *

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. - * - *

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: - * - *

    - *
  • ExampleFontFamily.ttf (or .otf) - *
  • ExampleFontFamily_bold.ttf (or .otf) - *
  • ExampleFontFamily_italic.ttf (or .otf) - *
  • 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 mFontCache; - private final Map mCustomTypefaceCache; - - private ReactFontManager() { - mFontCache = new HashMap<>(); - mCustomTypefaceCache = new HashMap<>(); - } - - public static ReactFontManager getInstance() { - if (sReactFontManagerInstance == null) { - sReactFontManagerInstance = new ReactFontManager(); - } - return sReactFontManagerInstance; - } - - public Typeface getTypeface(String fontFamilyName, int style, AssetManager assetManager) { - return getTypeface(fontFamilyName, new TypefaceStyle(style), assetManager); - } - - 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)) { - // 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)); - } - - AssetFontFamily assetFontFamily = mFontCache.get(fontFamilyName); - if (assetFontFamily == null) { - assetFontFamily = new AssetFontFamily(); - mFontCache.put(fontFamilyName, assetFontFamily); - } - - int style = typefaceStyle.getNearestStyle(); - - 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; - } - - /* - * This method allows you to load custom fonts from res/font folder as provided font family name. - * Fonts may be one of .ttf, .otf or XML (https://developer.android.com/guide/topics/ui/look-and-feel/fonts-in-xml). - * To support multiple font styles or weights, you must provide a font in XML format. - * - * ReactFontManager.getInstance().addCustomFont(this, "Srisakdi", R.font.srisakdi); - */ - public void addCustomFont(Context context, String fontFamily, int fontId) { - Typeface font = ResourcesCompat.getFont(context, fontId); - if (font != null) { - mCustomTypefaceCache.put(fontFamily, font); - } - } - - /** - * Equivalent method to {@see addCustomFont(Context, String, int)} which accepts a Typeface - * object. - */ - public void addCustomFont(String fontFamily, @Nullable Typeface font) { - if (font != null) { - mCustomTypefaceCache.put(fontFamily, font); - } - } - - /** - * Add additional font family, or replace the exist one in the font memory cache. - * - * @param style - * @see {@link Typeface#DEFAULT} - * @see {@link Typeface#BOLD} - * @see {@link Typeface#ITALIC} - * @see {@link Typeface#BOLD_ITALIC} - */ - public void setTypeface(String fontFamilyName, int style, Typeface typeface) { - if (typeface != null) { - AssetFontFamily assetFontFamily = mFontCache.get(fontFamilyName); - if (assetFontFamily == null) { - assetFontFamily = new AssetFontFamily(); - mFontCache.put(fontFamilyName, assetFontFamily); - } - assetFontFamily.setTypefaceForStyle(style, typeface); - } - } - - private static Typeface createAssetTypeface( - String fontFamilyName, int style, AssetManager assetManager) { - 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 { - return Typeface.createFromAsset(assetManager, fileName); - } catch (RuntimeException e) { - // If the typeface asset does not exist, try another extension. - continue; - } - } - return Typeface.create(fontFamilyName, style); - } - - /** Responsible for normalizing style and numeric weight for backward compatibility. */ - public static class TypefaceStyle { - - public static final int BOLD = 700; - public static final int NORMAL = 400; - private static final int MIN_WEIGHT = 1; - private static final int MAX_WEIGHT = 1000; - - private final boolean mItalic; - private final int mWeight; - - public TypefaceStyle(int weight, boolean italic) { - mItalic = italic; - mWeight = weight == ReactConstants.UNSET ? NORMAL : weight; - } - - public TypefaceStyle(int style) { - if (style == ReactConstants.UNSET) { - style = Typeface.NORMAL; - } - - mItalic = (style & Typeface.ITALIC) != 0; - mWeight = (style & Typeface.BOLD) != 0 ? BOLD : NORMAL; - } - - /** - * If `weight` is supplied, it will be combined with the italic bit from `style`. Otherwise, any - * existing weight bit in `style` will be used. - */ - public TypefaceStyle(int style, int weight) { - if (style == ReactConstants.UNSET) { - style = Typeface.NORMAL; - } - - mItalic = (style & Typeface.ITALIC) != 0; - mWeight = - weight == ReactConstants.UNSET ? (style & Typeface.BOLD) != 0 ? BOLD : NORMAL : weight; - } - - public int getNearestStyle() { - if (mWeight < BOLD) { - return mItalic ? Typeface.ITALIC : Typeface.NORMAL; - } else { - return mItalic ? Typeface.BOLD_ITALIC : Typeface.BOLD; - } - } - - public Typeface apply(Typeface typeface) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) { - return Typeface.create(typeface, getNearestStyle()); - } else { - return Typeface.create(typeface, mWeight, mItalic); - } - } - } - - /** Responsible for caching typefaces for each custom font family. */ - private static class AssetFontFamily { - - private SparseArray mTypefaceSparseArray; - - private AssetFontFamily() { - mTypefaceSparseArray = new SparseArray<>(4); - } - - public @Nullable Typeface getTypefaceForStyle(int style) { - return mTypefaceSparseArray.get(style); - } - - public void setTypefaceForStyle(int style, Typeface typeface) { - mTypefaceSparseArray.put(style, typeface); - } - } -} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/common/assets/ReactFontManager.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/common/assets/ReactFontManager.kt new file mode 100644 index 00000000000..8850773c4e9 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/common/assets/ReactFontManager.kt @@ -0,0 +1,197 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.facebook.react.common.assets + +import android.content.Context +import android.content.res.AssetManager +import android.graphics.Typeface +import android.os.Build +import android.util.SparseArray +import androidx.core.content.res.ResourcesCompat +import com.facebook.react.common.ReactConstants + +/** + * Responsible for loading and caching Typeface objects. + * + * 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. + * + * 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: + * * ExampleFontFamily.ttf (or .otf) + * * ExampleFontFamily_bold.ttf (or .otf) + * * ExampleFontFamily_italic.ttf (or .otf) + * * ExampleFontFamily_bold_italic.ttf (or .otf) + */ +public class ReactFontManager { + + private val fontCache: MutableMap = mutableMapOf() + private val customTypefaceCache: MutableMap = mutableMapOf() + + public fun getTypeface( + fontFamilyName: String, + style: Int, + assetManager: AssetManager?, + ): Typeface = getTypeface(fontFamilyName, TypefaceStyle(style), assetManager) + + public fun getTypeface( + fontFamilyName: String, + weight: Int, + italic: Boolean, + assetManager: AssetManager?, + ): Typeface = getTypeface(fontFamilyName, TypefaceStyle(weight, italic), assetManager) + + public fun getTypeface( + fontFamilyName: String, + style: Int, + weight: Int, + assetManager: AssetManager?, + ): Typeface = getTypeface(fontFamilyName, TypefaceStyle(style, weight), assetManager) + + public fun getTypeface( + fontFamilyName: String, + typefaceStyle: TypefaceStyle, + assetManager: AssetManager?, + ): Typeface { + if (customTypefaceCache.containsKey(fontFamilyName)) { + // Apply `typefaceStyle` because custom fonts configure variants using `app:fontStyle` and + // `app:fontWeight` in their resource XML configuration file. + return typefaceStyle.apply(customTypefaceCache[fontFamilyName]) + } + + val assetFontFamily = fontCache.getOrPut(fontFamilyName) { AssetFontFamily() } + val style = typefaceStyle.nearestStyle + return assetFontFamily.getTypefaceForStyle(style) + ?: createAssetTypeface(fontFamilyName, style, assetManager).also { + assetFontFamily.setTypefaceForStyle(style, it) + } + } + + /* + * This method allows you to load custom fonts from res/font folder as provided font family name. + * Fonts may be one of .ttf, .otf or XML (https://developer.android.com/guide/topics/ui/look-and-feel/fonts-in-xml). + * To support multiple font styles or weights, you must provide a font in XML format. + * + * ReactFontManager.getInstance().addCustomFont(this, "Srisakdi", R.font.srisakdi); + */ + public fun addCustomFont(context: Context, fontFamily: String, fontId: Int): Unit { + addCustomFont(fontFamily, ResourcesCompat.getFont(context, fontId)) + } + + /** + * Equivalent method to {@see addCustomFont(Context, String, int)} which accepts a Typeface + * object. + */ + public fun addCustomFont(fontFamily: String, font: Typeface?): Unit { + if (font != null) { + customTypefaceCache[fontFamily] = font + } + } + + /** + * Add additional font family, or replace the exist one in the font memory cache. + * + * @see [Typeface.DEFAULT] + * @see [Typeface.BOLD] + * @see [Typeface.ITALIC] + * @see [Typeface.BOLD_ITALIC] + */ + public fun setTypeface(fontFamilyName: String, style: Int, typeface: Typeface?): Unit { + if (typeface != null) { + fontCache.getOrPut(fontFamilyName) { AssetFontFamily() }.setTypefaceForStyle(style, typeface) + } + } + + /** Responsible for normalizing style and numeric weight for backward compatibility. */ + public class TypefaceStyle { + private val italic: Boolean + private val weight: Int + + public constructor(weight: Int, italic: Boolean) { + this.italic = italic + this.weight = if (weight == ReactConstants.UNSET) NORMAL else weight + } + + /** + * If `weight` is supplied, it will be combined with the italic bit from `style`. Otherwise, any + * existing weight bit in `style` will be used. + */ + @JvmOverloads + public constructor(style: Int, weight: Int = ReactConstants.UNSET) { + val fixedStyle = if (style == ReactConstants.UNSET) Typeface.NORMAL else style + italic = (fixedStyle and Typeface.ITALIC) != 0 + this.weight = + if (weight == ReactConstants.UNSET) + (if ((fixedStyle and Typeface.BOLD) != 0) BOLD else NORMAL) + else weight + } + + public val nearestStyle: Int + get() = + if (weight < BOLD) { + if (italic) Typeface.ITALIC else Typeface.NORMAL + } else { + if (italic) Typeface.BOLD_ITALIC else Typeface.BOLD + } + + public fun apply(typeface: Typeface?): Typeface = + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) { + Typeface.create(typeface, nearestStyle) + } else { + Typeface.create(typeface, weight, italic) + } + + public companion object { + public const val BOLD: Int = 700 + public const val NORMAL: Int = 400 + } + } + + public companion object { + + // NOTE: Indices in `EXTENSIONS` correspond to the `TypeFace` style constants. + private val EXTENSIONS = arrayOf("", "_bold", "_italic", "_bold_italic") + private val FILE_EXTENSIONS = arrayOf(".ttf", ".otf") + private const val FONTS_ASSET_PATH = "fonts/" + + private val _instance = ReactFontManager() + + @JvmStatic public fun getInstance(): ReactFontManager = _instance + + private fun createAssetTypeface( + fontFamilyName: String, + style: Int, + assetManager: AssetManager?, + ): Typeface { + if (assetManager != null) { + val extension = EXTENSIONS[style] + for (fileExtension in FILE_EXTENSIONS) { + val fileName = "$FONTS_ASSET_PATH$fontFamilyName$extension$fileExtension" + try { + return Typeface.createFromAsset(assetManager, fileName) + } catch (e: RuntimeException) { + // If the typeface asset does not exist, try another extension. + continue + } + } + } + return Typeface.create(fontFamilyName, style) + } + } + + /** Responsible for caching typefaces for each custom font family. */ + private class AssetFontFamily { + private val typefaceSparseArray: SparseArray = SparseArray(4) + + fun getTypefaceForStyle(style: Int): Typeface? = typefaceSparseArray[style] + + fun setTypefaceForStyle(style: Int, typeface: Typeface?) { + typefaceSparseArray.put(style, typeface) + } + } +}