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
This commit is contained in:
Thomas Nardone
2024-07-31 11:28:07 -07:00
committed by Facebook GitHub Bot
parent 34e3a6cc88
commit fbfb430b3b
3 changed files with 220 additions and 251 deletions
@@ -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 <init> ()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 <init> (I)V
public fun <init> (II)V
public synthetic fun <init> (IIILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun <init> (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 {
@@ -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.
*
* <p>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.
*
* <p>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:
*
* <ul>
* <li>ExampleFontFamily.ttf (or .otf)
* <li>ExampleFontFamily_bold.ttf (or .otf)
* <li>ExampleFontFamily_italic.ttf (or .otf)
* <li>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<String, AssetFontFamily> mFontCache;
private final Map<String, Typeface> 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<Typeface> 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);
}
}
}
@@ -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<String, AssetFontFamily> = mutableMapOf()
private val customTypefaceCache: MutableMap<String, Typeface> = 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<Typeface?> = SparseArray(4)
fun getTypefaceForStyle(style: Int): Typeface? = typefaceSparseArray[style]
fun setTypefaceForStyle(style: Int, typeface: Typeface?) {
typefaceSparseArray.put(style, typeface)
}
}
}