blob: a2dd9a8322b670267c5638918428d57a8a3c2070 [file] [log] [blame]
/*
* Copyright (C) 2006 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.graphics;
import static android.content.res.FontResourcesParser.FamilyResourceEntry;
import static android.content.res.FontResourcesParser.FontFamilyFilesResourceEntry;
import static android.content.res.FontResourcesParser.FontFileResourceEntry;
import static android.content.res.FontResourcesParser.ProviderResourceEntry;
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.res.AssetManager;
import android.graphics.fonts.Font;
import android.graphics.fonts.FontFamily;
import android.graphics.fonts.FontStyle;
import android.graphics.fonts.FontVariationAxis;
import android.graphics.fonts.SystemFonts;
import android.os.Build;
import android.os.ParcelFileDescriptor;
import android.provider.FontRequest;
import android.provider.FontsContract;
import android.text.FontConfig;
import android.util.Base64;
import android.util.LongSparseArray;
import android.util.LruCache;
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.Preconditions;
import dalvik.annotation.optimization.CriticalNative;
import libcore.util.NativeAllocationRegistry;
import java.io.File;
import java.io.FileDescriptor;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* The Typeface class specifies the typeface and intrinsic style of a font.
* This is used in the paint, along with optionally Paint settings like
* textSize, textSkewX, textScaleX to specify
* how text appears when drawn (and measured).
*/
public class Typeface {
private static String TAG = "Typeface";
private static final NativeAllocationRegistry sRegistry =
NativeAllocationRegistry.createMalloced(
Typeface.class.getClassLoader(), nativeGetReleaseFunc());
/** The default NORMAL typeface object */
public static final Typeface DEFAULT;
/**
* The default BOLD typeface object. Note: this may be not actually be
* bold, depending on what fonts are installed. Call getStyle() to know
* for sure.
*/
public static final Typeface DEFAULT_BOLD;
/** The NORMAL style of the default sans serif typeface. */
public static final Typeface SANS_SERIF;
/** The NORMAL style of the default serif typeface. */
public static final Typeface SERIF;
/** The NORMAL style of the default monospace typeface. */
public static final Typeface MONOSPACE;
/**
* The default {@link Typeface}s for different text styles.
* Call {@link #defaultFromStyle(int)} to get the default typeface for the given text style.
* It shouldn't be changed for app wide typeface settings. Please use theme and font XML for
* the same purpose.
*/
@UnsupportedAppUsage(trackingBug = 123769446)
static Typeface[] sDefaults;
/**
* Cache for Typeface objects for style variant. Currently max size is 3.
*/
@GuardedBy("sStyledCacheLock")
private static final LongSparseArray<SparseArray<Typeface>> sStyledTypefaceCache =
new LongSparseArray<>(3);
private static final Object sStyledCacheLock = new Object();
/**
* Cache for Typeface objects for weight variant. Currently max size is 3.
*/
@GuardedBy("sWeightCacheLock")
private static final LongSparseArray<SparseArray<Typeface>> sWeightTypefaceCache =
new LongSparseArray<>(3);
private static final Object sWeightCacheLock = new Object();
/**
* Cache for Typeface objects dynamically loaded from assets. Currently max size is 16.
*/
@GuardedBy("sDynamicCacheLock")
private static final LruCache<String, Typeface> sDynamicTypefaceCache = new LruCache<>(16);
private static final Object sDynamicCacheLock = new Object();
static Typeface sDefaultTypeface;
// Following two fields are not used but left for hiddenapi private list
/**
* sSystemFontMap is read only and unmodifiable.
* Use public API {@link #create(String, int)} to get the typeface for given familyName.
*/
@UnsupportedAppUsage(trackingBug = 123769347)
static final Map<String, Typeface> sSystemFontMap;
// We cannot support sSystemFallbackMap since we will migrate to public FontFamily API.
/**
* @deprecated Use {@link android.graphics.fonts.FontFamily} instead.
*/
@UnsupportedAppUsage(trackingBug = 123768928)
@Deprecated
static final Map<String, android.graphics.FontFamily[]> sSystemFallbackMap =
Collections.emptyMap();
/**
* @hide
*/
@UnsupportedAppUsage
public long native_instance;
/** @hide */
@IntDef(value = {NORMAL, BOLD, ITALIC, BOLD_ITALIC})
@Retention(RetentionPolicy.SOURCE)
public @interface Style {}
// Style
public static final int NORMAL = 0;
public static final int BOLD = 1;
public static final int ITALIC = 2;
public static final int BOLD_ITALIC = 3;
/** @hide */ public static final int STYLE_MASK = 0x03;
@UnsupportedAppUsage
private @Style int mStyle = 0;
private @IntRange(from = 0, to = FontStyle.FONT_WEIGHT_MAX) int mWeight = 0;
// Value for weight and italic. Indicates the value is resolved by font metadata.
// Must be the same as the C++ constant in core/jni/android/graphics/FontFamily.cpp
/** @hide */
public static final int RESOLVE_BY_FONT_TABLE = -1;
private static final String DEFAULT_FAMILY = "sans-serif";
// Style value for building typeface.
private static final int STYLE_NORMAL = 0;
private static final int STYLE_ITALIC = 1;
private int[] mSupportedAxes;
private static final int[] EMPTY_AXES = {};
/**
* Please use font in xml and also your application global theme to change the default Typeface.
* android:textViewStyle and its attribute android:textAppearance can be used in order to change
* typeface and other text related properties.
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
private static void setDefault(Typeface t) {
sDefaultTypeface = t;
nativeSetDefault(t.native_instance);
}
/** Returns the typeface's weight value */
public @IntRange(from = 0, to = 1000) int getWeight() {
return mWeight;
}
/** Returns the typeface's intrinsic style attributes */
public @Style int getStyle() {
return mStyle;
}
/** Returns true if getStyle() has the BOLD bit set. */
public final boolean isBold() {
return (mStyle & BOLD) != 0;
}
/** Returns true if getStyle() has the ITALIC bit set. */
public final boolean isItalic() {
return (mStyle & ITALIC) != 0;
}
/**
* @hide
* Used by Resources to load a font resource of type xml.
*/
@Nullable
public static Typeface createFromResources(
FamilyResourceEntry entry, AssetManager mgr, String path) {
if (entry instanceof ProviderResourceEntry) {
final ProviderResourceEntry providerEntry = (ProviderResourceEntry) entry;
// Downloadable font
List<List<String>> givenCerts = providerEntry.getCerts();
List<List<byte[]>> certs = new ArrayList<>();
if (givenCerts != null) {
for (int i = 0; i < givenCerts.size(); i++) {
List<String> certSet = givenCerts.get(i);
List<byte[]> byteArraySet = new ArrayList<>();
for (int j = 0; j < certSet.size(); j++) {
byteArraySet.add(Base64.decode(certSet.get(j), Base64.DEFAULT));
}
certs.add(byteArraySet);
}
}
// Downloaded font and it wasn't cached, request it again and return a
// default font instead (nothing we can do now).
FontRequest request = new FontRequest(providerEntry.getAuthority(),
providerEntry.getPackage(), providerEntry.getQuery(), certs);
Typeface typeface = FontsContract.getFontSync(request);
return typeface == null ? DEFAULT : typeface;
}
Typeface typeface = findFromCache(mgr, path);
if (typeface != null) return typeface;
// family is FontFamilyFilesResourceEntry
final FontFamilyFilesResourceEntry filesEntry = (FontFamilyFilesResourceEntry) entry;
try {
FontFamily.Builder familyBuilder = null;
for (final FontFileResourceEntry fontFile : filesEntry.getEntries()) {
final Font.Builder fontBuilder = new Font.Builder(mgr, fontFile.getFileName(),
false /* isAsset */, 0 /* cookie */)
.setTtcIndex(fontFile.getTtcIndex())
.setFontVariationSettings(fontFile.getVariationSettings());
if (fontFile.getWeight() != Typeface.RESOLVE_BY_FONT_TABLE) {
fontBuilder.setWeight(fontFile.getWeight());
}
if (fontFile.getItalic() != Typeface.RESOLVE_BY_FONT_TABLE) {
fontBuilder.setSlant(fontFile.getItalic() == FontFileResourceEntry.ITALIC
? FontStyle.FONT_SLANT_ITALIC : FontStyle.FONT_SLANT_UPRIGHT);
}
if (familyBuilder == null) {
familyBuilder = new FontFamily.Builder(fontBuilder.build());
} else {
familyBuilder.addFont(fontBuilder.build());
}
}
if (familyBuilder == null) {
return Typeface.DEFAULT;
}
final FontFamily family = familyBuilder.build();
final FontStyle normal = new FontStyle(FontStyle.FONT_WEIGHT_NORMAL,
FontStyle.FONT_SLANT_UPRIGHT);
Font bestFont = family.getFont(0);
int bestScore = normal.getMatchScore(bestFont.getStyle());
for (int i = 1; i < family.getSize(); ++i) {
final Font candidate = family.getFont(i);
final int score = normal.getMatchScore(candidate.getStyle());
if (score < bestScore) {
bestFont = candidate;
bestScore = score;
}
}
typeface = new Typeface.CustomFallbackBuilder(family)
.setStyle(bestFont.getStyle())
.build();
} catch (IllegalArgumentException e) {
// To be a compatible behavior with API28 or before, catch IllegalArgumentExcetpion
// thrown by native code and returns null.
return null;
} catch (IOException e) {
typeface = Typeface.DEFAULT;
}
synchronized (sDynamicCacheLock) {
final String key = Builder.createAssetUid(mgr, path, 0 /* ttcIndex */,
null /* axes */, RESOLVE_BY_FONT_TABLE /* weight */,
RESOLVE_BY_FONT_TABLE /* italic */, DEFAULT_FAMILY);
sDynamicTypefaceCache.put(key, typeface);
}
return typeface;
}
/**
* Used by resources for cached loading if the font is available.
* @hide
*/
public static Typeface findFromCache(AssetManager mgr, String path) {
synchronized (sDynamicCacheLock) {
final String key = Builder.createAssetUid(mgr, path, 0 /* ttcIndex */, null /* axes */,
RESOLVE_BY_FONT_TABLE /* weight */, RESOLVE_BY_FONT_TABLE /* italic */,
DEFAULT_FAMILY);
Typeface typeface = sDynamicTypefaceCache.get(key);
if (typeface != null) {
return typeface;
}
}
return null;
}
/**
* A builder class for creating new Typeface instance.
*
* <p>
* Examples,
* 1) Create Typeface from ttf file.
* <pre>
* <code>
* Typeface.Builder buidler = new Typeface.Builder("your_font_file.ttf");
* Typeface typeface = builder.build();
* </code>
* </pre>
*
* 2) Create Typeface from ttc file in assets directory.
* <pre>
* <code>
* Typeface.Builder buidler = new Typeface.Builder(getAssets(), "your_font_file.ttc");
* builder.setTtcIndex(2); // Set index of font collection.
* Typeface typeface = builder.build();
* </code>
* </pre>
*
* 3) Create Typeface with variation settings.
* <pre>
* <code>
* Typeface.Builder buidler = new Typeface.Builder("your_font_file.ttf");
* builder.setFontVariationSettings("'wght' 700, 'slnt' 20, 'ital' 1");
* builder.setWeight(700); // Tell the system that this is a bold font.
* builder.setItalic(true); // Tell the system that this is an italic style font.
* Typeface typeface = builder.build();
* </code>
* </pre>
* </p>
*/
public static final class Builder {
/** @hide */
public static final int NORMAL_WEIGHT = 400;
/** @hide */
public static final int BOLD_WEIGHT = 700;
// Kept for generating asset cache key.
private final AssetManager mAssetManager;
private final String mPath;
private final @Nullable Font.Builder mFontBuilder;
private String mFallbackFamilyName;
private int mWeight = RESOLVE_BY_FONT_TABLE;
private int mItalic = RESOLVE_BY_FONT_TABLE;
/**
* Constructs a builder with a file path.
*
* @param path The file object refers to the font file.
*/
public Builder(@NonNull File path) {
mFontBuilder = new Font.Builder(path);
mAssetManager = null;
mPath = null;
}
/**
* Constructs a builder with a file descriptor.
*
* Caller is responsible for closing the passed file descriptor after {@link #build} is
* called.
*
* @param fd The file descriptor. The passed fd must be mmap-able.
*/
public Builder(@NonNull FileDescriptor fd) {
Font.Builder builder;
try {
builder = new Font.Builder(ParcelFileDescriptor.dup(fd));
} catch (IOException e) {
// We cannot tell the error to developer at this moment since we cannot change the
// public API signature. Instead, silently fallbacks to system fallback in the build
// method as the same as other error cases.
builder = null;
}
mFontBuilder = builder;
mAssetManager = null;
mPath = null;
}
/**
* Constructs a builder with a file path.
*
* @param path The full path to the font file.
*/
public Builder(@NonNull String path) {
mFontBuilder = new Font.Builder(new File(path));
mAssetManager = null;
mPath = null;
}
/**
* Constructs a builder from an asset manager and a file path in an asset directory.
*
* @param assetManager The application's asset manager
* @param path The file name of the font data in the asset directory
*/
public Builder(@NonNull AssetManager assetManager, @NonNull String path) {
this(assetManager, path, true /* is asset */, 0 /* cookie */);
}
/**
* Constructs a builder from an asset manager and a file path in an asset directory.
*
* @param assetManager The application's asset manager
* @param path The file name of the font data in the asset directory
* @param cookie a cookie for the asset
* @hide
*/
public Builder(@NonNull AssetManager assetManager, @NonNull String path, boolean isAsset,
int cookie) {
mFontBuilder = new Font.Builder(assetManager, path, isAsset, cookie);
mAssetManager = assetManager;
mPath = path;
}
/**
* Sets weight of the font.
*
* Tells the system the weight of the given font. If not provided, the system will resolve
* the weight value by reading font tables.
* @param weight a weight value.
*/
public Builder setWeight(@IntRange(from = 1, to = 1000) int weight) {
mWeight = weight;
mFontBuilder.setWeight(weight);
return this;
}
/**
* Sets italic information of the font.
*
* Tells the system the style of the given font. If not provided, the system will resolve
* the style by reading font tables.
* @param italic {@code true} if the font is italic. Otherwise {@code false}.
*/
public Builder setItalic(boolean italic) {
mItalic = italic ? FontStyle.FONT_SLANT_ITALIC : FontStyle.FONT_SLANT_UPRIGHT;
mFontBuilder.setSlant(mItalic);
return this;
}
/**
* Sets an index of the font collection. See {@link android.R.attr#ttcIndex}.
*
* Can not be used for Typeface source. build() method will return null for invalid index.
* @param ttcIndex An index of the font collection. If the font source is not font
* collection, do not call this method or specify 0.
*/
public Builder setTtcIndex(@IntRange(from = 0) int ttcIndex) {
mFontBuilder.setTtcIndex(ttcIndex);
return this;
}
/**
* Sets a font variation settings.
*
* @param variationSettings See {@link android.widget.TextView#setFontVariationSettings}.
* @throws IllegalArgumentException If given string is not a valid font variation settings
* format.
*/
public Builder setFontVariationSettings(@Nullable String variationSettings) {
mFontBuilder.setFontVariationSettings(variationSettings);
return this;
}
/**
* Sets a font variation settings.
*
* @param axes An array of font variation axis tag-value pairs.
*/
public Builder setFontVariationSettings(@Nullable FontVariationAxis[] axes) {
mFontBuilder.setFontVariationSettings(axes);
return this;
}
/**
* Sets a fallback family name.
*
* By specifying a fallback family name, a fallback Typeface will be returned if the
* {@link #build} method fails to create a Typeface from the provided font. The fallback
* family will be resolved with the provided weight and italic information specified by
* {@link #setWeight} and {@link #setItalic}.
*
* If {@link #setWeight} is not called, the fallback family keeps the default weight.
* Similary, if {@link #setItalic} is not called, the fallback family keeps the default
* italic information. For example, calling {@code builder.setFallback("sans-serif-light")}
* is equivalent to calling {@code builder.setFallback("sans-serif").setWeight(300)} in
* terms of fallback. The default weight and italic information are overridden by calling
* {@link #setWeight} and {@link #setItalic}. For example, if a Typeface is constructed
* using {@code builder.setFallback("sans-serif-light").setWeight(700)}, the fallback text
* will render as sans serif bold.
*
* @param familyName A family name to be used for fallback if the provided font can not be
* used. By passing {@code null}, build() returns {@code null}.
* If {@link #setFallback} is not called on the builder, {@code null}
* is assumed.
*/
public Builder setFallback(@Nullable String familyName) {
mFallbackFamilyName = familyName;
return this;
}
/**
* Creates a unique id for a given AssetManager and asset path.
*
* @param mgr AssetManager instance
* @param path The path for the asset.
* @param ttcIndex The TTC index for the font.
* @param axes The font variation settings.
* @return Unique id for a given AssetManager and asset path.
*/
private static String createAssetUid(final AssetManager mgr, String path, int ttcIndex,
@Nullable FontVariationAxis[] axes, int weight, int italic, String fallback) {
final SparseArray<String> pkgs = mgr.getAssignedPackageIdentifiers();
final StringBuilder builder = new StringBuilder();
final int size = pkgs.size();
for (int i = 0; i < size; i++) {
builder.append(pkgs.valueAt(i));
builder.append("-");
}
builder.append(path);
builder.append("-");
builder.append(Integer.toString(ttcIndex));
builder.append("-");
builder.append(Integer.toString(weight));
builder.append("-");
builder.append(Integer.toString(italic));
// Family name may contain hyphen. Use double hyphen for avoiding key conflicts before
// and after appending falblack name.
builder.append("--");
builder.append(fallback);
builder.append("--");
if (axes != null) {
for (FontVariationAxis axis : axes) {
builder.append(axis.getTag());
builder.append("-");
builder.append(Float.toString(axis.getStyleValue()));
}
}
return builder.toString();
}
private Typeface resolveFallbackTypeface() {
if (mFallbackFamilyName == null) {
return null;
}
final Typeface base = getSystemDefaultTypeface(mFallbackFamilyName);
if (mWeight == RESOLVE_BY_FONT_TABLE && mItalic == RESOLVE_BY_FONT_TABLE) {
return base;
}
final int weight = (mWeight == RESOLVE_BY_FONT_TABLE) ? base.mWeight : mWeight;
final boolean italic =
(mItalic == RESOLVE_BY_FONT_TABLE) ? (base.mStyle & ITALIC) != 0 : mItalic == 1;
return createWeightStyle(base, weight, italic);
}
/**
* Generates new Typeface from specified configuration.
*
* @return Newly created Typeface. May return null if some parameters are invalid.
*/
public Typeface build() {
if (mFontBuilder == null) {
return resolveFallbackTypeface();
}
try {
final Font font = mFontBuilder.build();
final String key = mAssetManager == null ? null : createAssetUid(
mAssetManager, mPath, font.getTtcIndex(), font.getAxes(),
mWeight, mItalic,
mFallbackFamilyName == null ? DEFAULT_FAMILY : mFallbackFamilyName);
if (key != null) {
// Dynamic cache lookup is only for assets.
synchronized (sDynamicCacheLock) {
final Typeface typeface = sDynamicTypefaceCache.get(key);
if (typeface != null) {
return typeface;
}
}
}
final FontFamily family = new FontFamily.Builder(font).build();
final int weight = mWeight == RESOLVE_BY_FONT_TABLE
? font.getStyle().getWeight() : mWeight;
final int slant = mItalic == RESOLVE_BY_FONT_TABLE
? font.getStyle().getSlant() : mItalic;
final CustomFallbackBuilder builder = new CustomFallbackBuilder(family)
.setStyle(new FontStyle(weight, slant));
if (mFallbackFamilyName != null) {
builder.setSystemFallback(mFallbackFamilyName);
}
final Typeface typeface = builder.build();
if (key != null) {
synchronized (sDynamicCacheLock) {
sDynamicTypefaceCache.put(key, typeface);
}
}
return typeface;
} catch (IOException | IllegalArgumentException e) {
return resolveFallbackTypeface();
}
}
}
/**
* A builder class for creating new Typeface instance.
*
* There are two font fallback mechanisms, custom font fallback and system font fallback.
* The custom font fallback is a simple ordered list. The text renderer tries to see if it can
* render a character with the first font and if that font does not support the character, try
* next one and so on. It will keep trying until end of the custom fallback chain. The maximum
* length of the custom fallback chain is 64.
* The system font fallback is a system pre-defined fallback chain. The system fallback is
* processed only when no matching font is found in the custom font fallback.
*
* <p>
* Examples,
* 1) Create Typeface from single ttf file.
* <pre>
* <code>
* Font font = new Font.Builder("your_font_file.ttf").build();
* FontFamily family = new FontFamily.Builder(font).build();
* Typeface typeface = new Typeface.CustomFallbackBuilder(family).build();
* </code>
* </pre>
*
* 2) Create Typeface from multiple font files and select bold style by default.
* <pre>
* <code>
* Font regularFont = new Font.Builder("regular.ttf").build();
* Font boldFont = new Font.Builder("bold.ttf").build();
* FontFamily family = new FontFamily.Builder(regularFont)
* .addFont(boldFont).build();
* Typeface typeface = new Typeface.CustomFallbackBuilder(family)
* .setWeight(Font.FONT_WEIGHT_BOLD) // Set bold style as the default style.
* // If the font family doesn't have bold style font,
* // system will select the closest font.
* .build();
* </code>
* </pre>
*
* 3) Create Typeface from single ttf file and if that font does not have glyph for the
* characters, use "serif" font family instead.
* <pre>
* <code>
* Font font = new Font.Builder("your_font_file.ttf").build();
* FontFamily family = new FontFamily.Builder(font).build();
* Typeface typeface = new Typeface.CustomFallbackBuilder(family)
* .setSystemFallback("serif") // Set serif font family as the fallback.
* .build();
* </code>
* </pre>
* 4) Create Typeface from single ttf file and set another ttf file for the fallback.
* <pre>
* <code>
* Font font = new Font.Builder("English.ttf").build();
* FontFamily family = new FontFamily.Builder(font).build();
*
* Font fallbackFont = new Font.Builder("Arabic.ttf").build();
* FontFamily fallbackFamily = new FontFamily.Builder(fallbackFont).build();
* Typeface typeface = new Typeface.CustomFallbackBuilder(family)
* .addCustomFallback(fallbackFamily) // Specify fallback family.
* .setSystemFallback("serif") // Set serif font family as the fallback.
* .build();
* </code>
* </pre>
* </p>
*/
public static final class CustomFallbackBuilder {
private static final int MAX_CUSTOM_FALLBACK = 64;
private final ArrayList<FontFamily> mFamilies = new ArrayList<>();
private String mFallbackName = null;
private @Nullable FontStyle mStyle;
/**
* Returns the maximum capacity of custom fallback families.
*
* This includes the the first font family passed to the constructor.
* It is guaranteed that the value will be greater than or equal to 64.
*
* @return the maximum number of font families for the custom fallback
*/
public static @IntRange(from = 64) int getMaxCustomFallbackCount() {
return MAX_CUSTOM_FALLBACK;
}
/**
* Constructs a builder with a font family.
*
* @param family a family object
*/
public CustomFallbackBuilder(@NonNull FontFamily family) {
Preconditions.checkNotNull(family);
mFamilies.add(family);
}
/**
* Sets a system fallback by name.
*
* You can specify generic font familiy names or OEM specific family names. If the system
* don't have a specified fallback, the default fallback is used instead.
* For more information about generic font families, see <a
* href="https://www.w3.org/TR/css-fonts-4/#generic-font-families">CSS specification</a>
*
* For more information about fallback, see class description.
*
* @param familyName a family name to be used for fallback if the provided fonts can not be
* used
*/
public @NonNull CustomFallbackBuilder setSystemFallback(@NonNull String familyName) {
Preconditions.checkNotNull(familyName);
mFallbackName = familyName;
return this;
}
/**
* Sets a font style of the Typeface.
*
* If the font family doesn't have a font of given style, system will select the closest
* font from font family. For example, if a font family has fonts of 300 weight and 700
* weight then setWeight(400) is called, system will select the font of 300 weight.
*
* @param style a font style
*/
public @NonNull CustomFallbackBuilder setStyle(@NonNull FontStyle style) {
mStyle = style;
return this;
}
/**
* Append a font family to the end of the custom font fallback.
*
* You can set up to 64 custom fallback families including the first font family you passed
* to the constructor.
* For more information about fallback, see class description.
*
* @param family a fallback family
* @throws IllegalArgumentException if you give more than 64 custom fallback families
*/
public @NonNull CustomFallbackBuilder addCustomFallback(@NonNull FontFamily family) {
Preconditions.checkNotNull(family);
Preconditions.checkArgument(mFamilies.size() < getMaxCustomFallbackCount(),
"Custom fallback limit exceeded(" + getMaxCustomFallbackCount() + ")");
mFamilies.add(family);
return this;
}
/**
* Create the Typeface based on the configured values.
*
* @return the Typeface object
*/
public @NonNull Typeface build() {
final int userFallbackSize = mFamilies.size();
final FontFamily[] fallback = SystemFonts.getSystemFallback(mFallbackName);
final long[] ptrArray = new long[fallback.length + userFallbackSize];
for (int i = 0; i < userFallbackSize; ++i) {
ptrArray[i] = mFamilies.get(i).getNativePtr();
}
for (int i = 0; i < fallback.length; ++i) {
ptrArray[i + userFallbackSize] = fallback[i].getNativePtr();
}
final int weight = mStyle == null ? 400 : mStyle.getWeight();
final int italic =
(mStyle == null || mStyle.getSlant() == FontStyle.FONT_SLANT_UPRIGHT) ? 0 : 1;
return new Typeface(nativeCreateFromArray(ptrArray, weight, italic));
}
}
/**
* Create a typeface object given a family name, and option style information.
* If null is passed for the name, then the "default" font will be chosen.
* The resulting typeface object can be queried (getStyle()) to discover what
* its "real" style characteristics are.
*
* @param familyName May be null. The name of the font family.
* @param style The style (normal, bold, italic) of the typeface.
* e.g. NORMAL, BOLD, ITALIC, BOLD_ITALIC
* @return The best matching typeface.
*/
public static Typeface create(String familyName, @Style int style) {
return create(getSystemDefaultTypeface(familyName), style);
}
/**
* Create a typeface object that best matches the specified existing
* typeface and the specified Style. Use this call if you want to pick a new
* style from the same family of an existing typeface object. If family is
* null, this selects from the default font's family.
*
* <p>
* This method is not thread safe on API 27 or before.
* This method is thread safe on API 28 or after.
* </p>
*
* @param family An existing {@link Typeface} object. In case of {@code null}, the default
* typeface is used instead.
* @param style The style (normal, bold, italic) of the typeface.
* e.g. NORMAL, BOLD, ITALIC, BOLD_ITALIC
* @return The best matching typeface.
*/
public static Typeface create(Typeface family, @Style int style) {
if ((style & ~STYLE_MASK) != 0) {
style = NORMAL;
}
if (family == null) {
family = sDefaultTypeface;
}
// Return early if we're asked for the same face/style
if (family.mStyle == style) {
return family;
}
final long ni = family.native_instance;
Typeface typeface;
synchronized (sStyledCacheLock) {
SparseArray<Typeface> styles = sStyledTypefaceCache.get(ni);
if (styles == null) {
styles = new SparseArray<Typeface>(4);
sStyledTypefaceCache.put(ni, styles);
} else {
typeface = styles.get(style);
if (typeface != null) {
return typeface;
}
}
typeface = new Typeface(nativeCreateFromTypeface(ni, style));
styles.put(style, typeface);
}
return typeface;
}
/**
* Creates a typeface object that best matches the specified existing typeface and the specified
* weight and italic style
* <p>Below are numerical values and corresponding common weight names.</p>
* <table>
* <thead>
* <tr><th>Value</th><th>Common weight name</th></tr>
* </thead>
* <tbody>
* <tr><td>100</td><td>Thin</td></tr>
* <tr><td>200</td><td>Extra Light</td></tr>
* <tr><td>300</td><td>Light</td></tr>
* <tr><td>400</td><td>Normal</td></tr>
* <tr><td>500</td><td>Medium</td></tr>
* <tr><td>600</td><td>Semi Bold</td></tr>
* <tr><td>700</td><td>Bold</td></tr>
* <tr><td>800</td><td>Extra Bold</td></tr>
* <tr><td>900</td><td>Black</td></tr>
* </tbody>
* </table>
*
* <p>
* This method is thread safe.
* </p>
*
* @param family An existing {@link Typeface} object. In case of {@code null}, the default
* typeface is used instead.
* @param weight The desired weight to be drawn.
* @param italic {@code true} if italic style is desired to be drawn. Otherwise, {@code false}
* @return A {@link Typeface} object for drawing specified weight and italic style. Never
* returns {@code null}
*
* @see #getWeight()
* @see #isItalic()
*/
public static @NonNull Typeface create(@Nullable Typeface family,
@IntRange(from = 1, to = 1000) int weight, boolean italic) {
Preconditions.checkArgumentInRange(weight, 0, 1000, "weight");
if (family == null) {
family = sDefaultTypeface;
}
return createWeightStyle(family, weight, italic);
}
private static @NonNull Typeface createWeightStyle(@NonNull Typeface base,
@IntRange(from = 1, to = 1000) int weight, boolean italic) {
final int key = (weight << 1) | (italic ? 1 : 0);
Typeface typeface;
synchronized(sWeightCacheLock) {
SparseArray<Typeface> innerCache = sWeightTypefaceCache.get(base.native_instance);
if (innerCache == null) {
innerCache = new SparseArray<>(4);
sWeightTypefaceCache.put(base.native_instance, innerCache);
} else {
typeface = innerCache.get(key);
if (typeface != null) {
return typeface;
}
}
typeface = new Typeface(
nativeCreateFromTypefaceWithExactStyle(base.native_instance, weight, italic));
innerCache.put(key, typeface);
}
return typeface;
}
/** @hide */
public static Typeface createFromTypefaceWithVariation(@Nullable Typeface family,
@NonNull List<FontVariationAxis> axes) {
final Typeface base = family == null ? Typeface.DEFAULT : family;
return new Typeface(nativeCreateFromTypefaceWithVariation(base.native_instance, axes));
}
/**
* Returns one of the default typeface objects, based on the specified style
*
* @return the default typeface that corresponds to the style
*/
public static Typeface defaultFromStyle(@Style int style) {
return sDefaults[style];
}
/**
* Create a new typeface from the specified font data.
*
* @param mgr The application's asset manager
* @param path The file name of the font data in the assets directory
* @return The new typeface.
*/
public static Typeface createFromAsset(AssetManager mgr, String path) {
Preconditions.checkNotNull(path); // for backward compatibility
Preconditions.checkNotNull(mgr);
Typeface typeface = new Builder(mgr, path).build();
if (typeface != null) return typeface;
// check if the file exists, and throw an exception for backward compatibility
try (InputStream inputStream = mgr.open(path)) {
} catch (IOException e) {
throw new RuntimeException("Font asset not found " + path);
}
return Typeface.DEFAULT;
}
/**
* Creates a unique id for a given font provider and query.
*/
private static String createProviderUid(String authority, String query) {
final StringBuilder builder = new StringBuilder();
builder.append("provider:");
builder.append(authority);
builder.append("-");
builder.append(query);
return builder.toString();
}
/**
* Create a new typeface from the specified font file.
*
* @param file The path to the font data.
* @return The new typeface.
*/
public static Typeface createFromFile(@Nullable File file) {
// For the compatibility reasons, leaving possible NPE here.
// See android.graphics.cts.TypefaceTest#testCreateFromFileByFileReferenceNull
Typeface typeface = new Builder(file).build();
if (typeface != null) return typeface;
// check if the file exists, and throw an exception for backward compatibility
if (!file.exists()) {
throw new RuntimeException("Font asset not found " + file.getAbsolutePath());
}
return Typeface.DEFAULT;
}
/**
* Create a new typeface from the specified font file.
*
* @param path The full path to the font data.
* @return The new typeface.
*/
public static Typeface createFromFile(@Nullable String path) {
Preconditions.checkNotNull(path); // for backward compatibility
return createFromFile(new File(path));
}
/**
* Create a new typeface from an array of font families.
*
* @param families array of font families
* @deprecated
*/
@Deprecated
@UnsupportedAppUsage(trackingBug = 123768928)
private static Typeface createFromFamilies(android.graphics.FontFamily[] families) {
long[] ptrArray = new long[families.length];
for (int i = 0; i < families.length; i++) {
ptrArray[i] = families[i].mNativePtr;
}
return new Typeface(nativeCreateFromArray(
ptrArray, RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE));
}
/**
* Create a new typeface from an array of android.graphics.fonts.FontFamily.
*
* @param families array of font families
*/
private static Typeface createFromFamilies(@Nullable FontFamily[] families) {
final long[] ptrArray = new long[families.length];
for (int i = 0; i < families.length; ++i) {
ptrArray[i] = families[i].getNativePtr();
}
return new Typeface(nativeCreateFromArray(ptrArray,
RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE));
}
/**
* This method is used by supportlib-v27.
*
* @deprecated Use {@link android.graphics.fonts.FontFamily} instead.
*/
@UnsupportedAppUsage(trackingBug = 123768395)
@Deprecated
private static Typeface createFromFamiliesWithDefault(
android.graphics.FontFamily[] families, int weight, int italic) {
return createFromFamiliesWithDefault(families, DEFAULT_FAMILY, weight, italic);
}
/**
* Create a new typeface from an array of font families, including
* also the font families in the fallback list.
* @param fallbackName the family name. If given families don't support characters, the
* characters will be rendered with this family.
* @param weight the weight for this family. In that case, the table information in the first
* family's font is used. If the first family has multiple fonts, the closest to
* the regular weight and upright font is used.
* @param italic the italic information for this family. In that case, the table information in
* the first family's font is used. If the first family has multiple fonts, the
* closest to the regular weight and upright font is used.
* @param families array of font families
*
* @deprecated Use {@link android.graphics.fonts.FontFamily} instead.
*/
@UnsupportedAppUsage(trackingBug = 123768928)
@Deprecated
private static Typeface createFromFamiliesWithDefault(android.graphics.FontFamily[] families,
String fallbackName, int weight, int italic) {
android.graphics.fonts.FontFamily[] fallback = SystemFonts.getSystemFallback(fallbackName);
long[] ptrArray = new long[families.length + fallback.length];
for (int i = 0; i < families.length; i++) {
ptrArray[i] = families[i].mNativePtr;
}
for (int i = 0; i < fallback.length; i++) {
ptrArray[i + families.length] = fallback[i].getNativePtr();
}
return new Typeface(nativeCreateFromArray(ptrArray, weight, italic));
}
// don't allow clients to call this directly
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
private Typeface(long ni) {
if (ni == 0) {
throw new RuntimeException("native typeface cannot be made");
}
native_instance = ni;
sRegistry.registerNativeAllocation(this, native_instance);
mStyle = nativeGetStyle(ni);
mWeight = nativeGetWeight(ni);
}
private static Typeface getSystemDefaultTypeface(@NonNull String familyName) {
Typeface tf = sSystemFontMap.get(familyName);
return tf == null ? Typeface.DEFAULT : tf;
}
/** @hide */
@VisibleForTesting
public static void initSystemDefaultTypefaces(Map<String, Typeface> systemFontMap,
Map<String, FontFamily[]> fallbacks,
FontConfig.Alias[] aliases) {
for (Map.Entry<String, FontFamily[]> entry : fallbacks.entrySet()) {
systemFontMap.put(entry.getKey(), createFromFamilies(entry.getValue()));
}
for (FontConfig.Alias alias : aliases) {
if (systemFontMap.containsKey(alias.getName())) {
continue; // If alias and named family are conflict, use named family.
}
final Typeface base = systemFontMap.get(alias.getToName());
if (base == null) {
// The missing target is a valid thing, some configuration don't have font files,
// e.g. wear devices. Just skip this alias.
continue;
}
final int weight = alias.getWeight();
final Typeface newFace = weight == 400 ? base :
new Typeface(nativeCreateWeightAlias(base.native_instance, weight));
systemFontMap.put(alias.getName(), newFace);
}
}
private static void registerGenericFamilyNative(@NonNull String familyName,
@Nullable Typeface typeface) {
if (typeface != null) {
nativeRegisterGenericFamily(familyName, typeface.native_instance);
}
}
static {
final HashMap<String, Typeface> systemFontMap = new HashMap<>();
initSystemDefaultTypefaces(systemFontMap, SystemFonts.getRawSystemFallbackMap(),
SystemFonts.getAliases());
sSystemFontMap = Collections.unmodifiableMap(systemFontMap);
// We can't assume DEFAULT_FAMILY available on Roboletric.
if (sSystemFontMap.containsKey(DEFAULT_FAMILY)) {
setDefault(sSystemFontMap.get(DEFAULT_FAMILY));
}
// Set up defaults and typefaces exposed in public API
DEFAULT = create((String) null, 0);
DEFAULT_BOLD = create((String) null, Typeface.BOLD);
SANS_SERIF = create("sans-serif", 0);
SERIF = create("serif", 0);
MONOSPACE = create("monospace", 0);
sDefaults = new Typeface[] {
DEFAULT,
DEFAULT_BOLD,
create((String) null, Typeface.ITALIC),
create((String) null, Typeface.BOLD_ITALIC),
};
// A list of generic families to be registered in native.
// https://www.w3.org/TR/css-fonts-4/#generic-font-families
String[] genericFamilies = {
"serif", "sans-serif", "cursive", "fantasy", "monospace", "system-ui"
};
for (String genericFamily : genericFamilies) {
registerGenericFamilyNative(genericFamily, systemFontMap.get(genericFamily));
}
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Typeface typeface = (Typeface) o;
return mStyle == typeface.mStyle && native_instance == typeface.native_instance;
}
@Override
public int hashCode() {
/*
* Modified method for hashCode with long native_instance derived from
* http://developer.android.com/reference/java/lang/Object.html
*/
int result = 17;
result = 31 * result + (int) (native_instance ^ (native_instance >>> 32));
result = 31 * result + mStyle;
return result;
}
/** @hide */
public boolean isSupportedAxes(int axis) {
if (mSupportedAxes == null) {
synchronized (this) {
if (mSupportedAxes == null) {
mSupportedAxes = nativeGetSupportedAxes(native_instance);
if (mSupportedAxes == null) {
mSupportedAxes = EMPTY_AXES;
}
}
}
}
return Arrays.binarySearch(mSupportedAxes, axis) >= 0;
}
private static native long nativeCreateFromTypeface(long native_instance, int style);
private static native long nativeCreateFromTypefaceWithExactStyle(
long native_instance, int weight, boolean italic);
// TODO: clean up: change List<FontVariationAxis> to FontVariationAxis[]
private static native long nativeCreateFromTypefaceWithVariation(
long native_instance, List<FontVariationAxis> axes);
@UnsupportedAppUsage
private static native long nativeCreateWeightAlias(long native_instance, int weight);
@UnsupportedAppUsage
private static native long nativeCreateFromArray(long[] familyArray, int weight, int italic);
private static native int[] nativeGetSupportedAxes(long native_instance);
@CriticalNative
private static native void nativeSetDefault(long nativePtr);
@CriticalNative
private static native int nativeGetStyle(long nativePtr);
@CriticalNative
private static native int nativeGetWeight(long nativePtr);
@CriticalNative
private static native long nativeGetReleaseFunc();
private static native void nativeRegisterGenericFamily(String str, long nativePtr);
}