2 * Copyright (C) 2006 The Android Open Source Project
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package android.graphics;
19 import static android.content.res.FontResourcesParser.FamilyResourceEntry;
20 import static android.content.res.FontResourcesParser.FontFamilyFilesResourceEntry;
21 import static android.content.res.FontResourcesParser.FontFileResourceEntry;
22 import static android.content.res.FontResourcesParser.ProviderResourceEntry;
24 import android.annotation.IntDef;
25 import android.annotation.IntRange;
26 import android.annotation.NonNull;
27 import android.annotation.Nullable;
28 import android.content.res.AssetManager;
29 import android.graphics.fonts.FontVariationAxis;
30 import android.net.Uri;
31 import android.provider.FontRequest;
32 import android.provider.FontsContract;
33 import android.text.FontConfig;
34 import android.util.ArrayMap;
35 import android.util.Base64;
36 import android.util.Log;
37 import android.util.LongSparseArray;
38 import android.util.LruCache;
39 import android.util.SparseArray;
41 import com.android.internal.annotations.GuardedBy;
42 import com.android.internal.annotations.VisibleForTesting;
43 import com.android.internal.util.Preconditions;
45 import org.xmlpull.v1.XmlPullParserException;
48 import java.io.FileDescriptor;
49 import java.io.FileInputStream;
50 import java.io.FileNotFoundException;
51 import java.io.IOException;
52 import java.io.InputStream;
53 import java.lang.annotation.Retention;
54 import java.lang.annotation.RetentionPolicy;
55 import java.nio.ByteBuffer;
56 import java.nio.channels.FileChannel;
57 import java.util.ArrayList;
58 import java.util.Arrays;
59 import java.util.Collections;
60 import java.util.HashMap;
61 import java.util.List;
65 * The Typeface class specifies the typeface and intrinsic style of a font.
66 * This is used in the paint, along with optionally Paint settings like
67 * textSize, textSkewX, textScaleX to specify
68 * how text appears when drawn (and measured).
70 public class Typeface {
72 private static String TAG = "Typeface";
74 /** The default NORMAL typeface object */
75 public static final Typeface DEFAULT;
77 * The default BOLD typeface object. Note: this may be not actually be
78 * bold, depending on what fonts are installed. Call getStyle() to know
81 public static final Typeface DEFAULT_BOLD;
82 /** The NORMAL style of the default sans serif typeface. */
83 public static final Typeface SANS_SERIF;
84 /** The NORMAL style of the default serif typeface. */
85 public static final Typeface SERIF;
86 /** The NORMAL style of the default monospace typeface. */
87 public static final Typeface MONOSPACE;
89 static Typeface[] sDefaults;
92 * Cache for Typeface objects for style variant. Currently max size is 3.
94 @GuardedBy("sStyledCacheLock")
95 private static final LongSparseArray<SparseArray<Typeface>> sStyledTypefaceCache =
96 new LongSparseArray<>(3);
97 private static final Object sStyledCacheLock = new Object();
100 * Cache for Typeface objects for weight variant. Currently max size is 3.
102 @GuardedBy("sWeightCacheLock")
103 private static final LongSparseArray<SparseArray<Typeface>> sWeightTypefaceCache =
104 new LongSparseArray<>(3);
105 private static final Object sWeightCacheLock = new Object();
108 * Cache for Typeface objects dynamically loaded from assets. Currently max size is 16.
110 @GuardedBy("sDynamicCacheLock")
111 private static final LruCache<String, Typeface> sDynamicTypefaceCache = new LruCache<>(16);
112 private static final Object sDynamicCacheLock = new Object();
114 static Typeface sDefaultTypeface;
115 static final Map<String, Typeface> sSystemFontMap;
116 static final Map<String, FontFamily[]> sSystemFallbackMap;
121 public long native_instance;
124 @IntDef(value = {NORMAL, BOLD, ITALIC, BOLD_ITALIC})
125 @Retention(RetentionPolicy.SOURCE)
126 public @interface Style {}
129 public static final int NORMAL = 0;
130 public static final int BOLD = 1;
131 public static final int ITALIC = 2;
132 public static final int BOLD_ITALIC = 3;
133 /** @hide */ public static final int STYLE_MASK = 0x03;
135 private @Style int mStyle = 0;
138 * A maximum value for the weight value.
141 public static final int MAX_WEIGHT = 1000;
143 private @IntRange(from = 0, to = MAX_WEIGHT) int mWeight = 0;
145 // Value for weight and italic. Indicates the value is resolved by font metadata.
146 // Must be the same as the C++ constant in core/jni/android/graphics/FontFamily.cpp
148 public static final int RESOLVE_BY_FONT_TABLE = -1;
149 private static final String DEFAULT_FAMILY = "sans-serif";
151 // Style value for building typeface.
152 private static final int STYLE_NORMAL = 0;
153 private static final int STYLE_ITALIC = 1;
155 private int[] mSupportedAxes;
156 private static final int[] EMPTY_AXES = {};
158 private static void setDefault(Typeface t) {
159 sDefaultTypeface = t;
160 nativeSetDefault(t.native_instance);
163 // TODO: Make this public API. (b/64852739)
166 public int getWeight() {
170 /** Returns the typeface's intrinsic style attributes */
171 public @Style int getStyle() {
175 /** Returns true if getStyle() has the BOLD bit set. */
176 public final boolean isBold() {
177 return (mStyle & BOLD) != 0;
180 /** Returns true if getStyle() has the ITALIC bit set. */
181 public final boolean isItalic() {
182 return (mStyle & ITALIC) != 0;
187 * Used by Resources to load a font resource of type font file.
190 public static Typeface createFromResources(AssetManager mgr, String path, int cookie) {
191 synchronized (sDynamicCacheLock) {
192 final String key = Builder.createAssetUid(
193 mgr, path, 0 /* ttcIndex */, null /* axes */,
194 RESOLVE_BY_FONT_TABLE /* weight */, RESOLVE_BY_FONT_TABLE /* italic */,
196 Typeface typeface = sDynamicTypefaceCache.get(key);
197 if (typeface != null) return typeface;
199 FontFamily fontFamily = new FontFamily();
200 // TODO: introduce ttc index and variation settings to resource type font.
201 if (fontFamily.addFontFromAssetManager(mgr, path, cookie, false /* isAsset */,
202 0 /* ttcIndex */, RESOLVE_BY_FONT_TABLE /* weight */,
203 RESOLVE_BY_FONT_TABLE /* italic */, null /* axes */)) {
204 if (!fontFamily.freeze()) {
207 FontFamily[] families = {fontFamily};
208 typeface = createFromFamiliesWithDefault(families, DEFAULT_FAMILY,
209 RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE);
210 sDynamicTypefaceCache.put(key, typeface);
219 * Used by Resources to load a font resource of type xml.
222 public static Typeface createFromResources(
223 FamilyResourceEntry entry, AssetManager mgr, String path) {
224 if (entry instanceof ProviderResourceEntry) {
225 final ProviderResourceEntry providerEntry = (ProviderResourceEntry) entry;
227 List<List<String>> givenCerts = providerEntry.getCerts();
228 List<List<byte[]>> certs = new ArrayList<>();
229 if (givenCerts != null) {
230 for (int i = 0; i < givenCerts.size(); i++) {
231 List<String> certSet = givenCerts.get(i);
232 List<byte[]> byteArraySet = new ArrayList<>();
233 for (int j = 0; j < certSet.size(); j++) {
234 byteArraySet.add(Base64.decode(certSet.get(j), Base64.DEFAULT));
236 certs.add(byteArraySet);
239 // Downloaded font and it wasn't cached, request it again and return a
240 // default font instead (nothing we can do now).
241 FontRequest request = new FontRequest(providerEntry.getAuthority(),
242 providerEntry.getPackage(), providerEntry.getQuery(), certs);
243 Typeface typeface = FontsContract.getFontSync(request);
244 return typeface == null ? DEFAULT : typeface;
247 Typeface typeface = findFromCache(mgr, path);
248 if (typeface != null) return typeface;
250 // family is FontFamilyFilesResourceEntry
251 final FontFamilyFilesResourceEntry filesEntry = (FontFamilyFilesResourceEntry) entry;
253 FontFamily fontFamily = new FontFamily();
254 for (final FontFileResourceEntry fontFile : filesEntry.getEntries()) {
255 if (!fontFamily.addFontFromAssetManager(mgr, fontFile.getFileName(),
256 0 /* resourceCookie */, false /* isAsset */, fontFile.getTtcIndex(),
257 fontFile.getWeight(), fontFile.getItalic(),
258 FontVariationAxis.fromFontVariationSettings(fontFile.getVariationSettings()))) {
262 if (!fontFamily.freeze()) {
265 FontFamily[] familyChain = { fontFamily };
266 typeface = createFromFamiliesWithDefault(familyChain, DEFAULT_FAMILY,
267 RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE);
268 synchronized (sDynamicCacheLock) {
269 final String key = Builder.createAssetUid(mgr, path, 0 /* ttcIndex */,
270 null /* axes */, RESOLVE_BY_FONT_TABLE /* weight */,
271 RESOLVE_BY_FONT_TABLE /* italic */, DEFAULT_FAMILY);
272 sDynamicTypefaceCache.put(key, typeface);
278 * Used by resources for cached loading if the font is available.
281 public static Typeface findFromCache(AssetManager mgr, String path) {
282 synchronized (sDynamicCacheLock) {
283 final String key = Builder.createAssetUid(mgr, path, 0 /* ttcIndex */, null /* axes */,
284 RESOLVE_BY_FONT_TABLE /* weight */, RESOLVE_BY_FONT_TABLE /* italic */,
286 Typeface typeface = sDynamicTypefaceCache.get(key);
287 if (typeface != null) {
295 * A builder class for creating new Typeface instance.
299 * 1) Create Typeface from ttf file.
302 * Typeface.Builder buidler = new Typeface.Builder("your_font_file.ttf");
303 * Typeface typeface = builder.build();
307 * 2) Create Typeface from ttc file in assets directory.
310 * Typeface.Builder buidler = new Typeface.Builder(getAssets(), "your_font_file.ttc");
311 * builder.setTtcIndex(2); // Set index of font collection.
312 * Typeface typeface = builder.build();
316 * 3) Create Typeface with variation settings.
319 * Typeface.Builder buidler = new Typeface.Builder("your_font_file.ttf");
320 * builder.setFontVariationSettings("'wght' 700, 'slnt' 20, 'ital' 1");
321 * builder.setWeight(700); // Tell the system that this is a bold font.
322 * builder.setItalic(true); // Tell the system that this is an italic style font.
323 * Typeface typeface = builder.build();
328 public static final class Builder {
330 public static final int NORMAL_WEIGHT = 400;
332 public static final int BOLD_WEIGHT = 700;
334 private int mTtcIndex;
335 private FontVariationAxis[] mAxes;
337 private AssetManager mAssetManager;
338 private String mPath;
339 private FileDescriptor mFd;
341 private FontsContract.FontInfo[] mFonts;
342 private Map<Uri, ByteBuffer> mFontBuffers;
344 private String mFallbackFamilyName;
346 private int mWeight = RESOLVE_BY_FONT_TABLE;
347 private int mItalic = RESOLVE_BY_FONT_TABLE;
350 * Constructs a builder with a file path.
352 * @param path The file object refers to the font file.
354 public Builder(@NonNull File path) {
355 mPath = path.getAbsolutePath();
359 * Constructs a builder with a file descriptor.
361 * Caller is responsible for closing the passed file descriptor after {@link #build} is
364 * @param fd The file descriptor. The passed fd must be mmap-able.
366 public Builder(@NonNull FileDescriptor fd) {
371 * Constructs a builder with a file path.
373 * @param path The full path to the font file.
375 public Builder(@NonNull String path) {
380 * Constructs a builder from an asset manager and a file path in an asset directory.
382 * @param assetManager The application's asset manager
383 * @param path The file name of the font data in the asset directory
385 public Builder(@NonNull AssetManager assetManager, @NonNull String path) {
386 mAssetManager = Preconditions.checkNotNull(assetManager);
387 mPath = Preconditions.checkStringNotEmpty(path);
391 * Constracts a builder from an array of FontsContract.FontInfo.
393 * Since {@link FontsContract.FontInfo} holds information about TTC indices and
394 * variation settings, there is no need to call {@link #setTtcIndex} or
395 * {@link #setFontVariationSettings}. Similary, {@link FontsContract.FontInfo} holds
396 * weight and italic information, so {@link #setWeight} and {@link #setItalic} are used
397 * for style matching during font selection.
399 * @param fonts The array of {@link FontsContract.FontInfo}
400 * @param buffers The mapping from URI to buffers to be used during building.
403 public Builder(@NonNull FontsContract.FontInfo[] fonts,
404 @NonNull Map<Uri, ByteBuffer> buffers) {
406 mFontBuffers = buffers;
410 * Sets weight of the font.
412 * Tells the system the weight of the given font. If not provided, the system will resolve
413 * the weight value by reading font tables.
414 * @param weight a weight value.
416 public Builder setWeight(@IntRange(from = 1, to = 1000) int weight) {
422 * Sets italic information of the font.
424 * Tells the system the style of the given font. If not provided, the system will resolve
425 * the style by reading font tables.
426 * @param italic {@code true} if the font is italic. Otherwise {@code false}.
428 public Builder setItalic(boolean italic) {
429 mItalic = italic ? STYLE_ITALIC : STYLE_NORMAL;
434 * Sets an index of the font collection. See {@link android.R.attr#ttcIndex}.
436 * Can not be used for Typeface source. build() method will return null for invalid index.
437 * @param ttcIndex An index of the font collection. If the font source is not font
438 * collection, do not call this method or specify 0.
440 public Builder setTtcIndex(@IntRange(from = 0) int ttcIndex) {
441 if (mFonts != null) {
442 throw new IllegalArgumentException(
443 "TTC index can not be specified for FontResult source.");
445 mTtcIndex = ttcIndex;
450 * Sets a font variation settings.
452 * @param variationSettings See {@link android.widget.TextView#setFontVariationSettings}.
453 * @throws IllegalArgumentException If given string is not a valid font variation settings
456 public Builder setFontVariationSettings(@Nullable String variationSettings) {
457 if (mFonts != null) {
458 throw new IllegalArgumentException(
459 "Font variation settings can not be specified for FontResult source.");
462 throw new IllegalStateException("Font variation settings are already set.");
464 mAxes = FontVariationAxis.fromFontVariationSettings(variationSettings);
469 * Sets a font variation settings.
471 * @param axes An array of font variation axis tag-value pairs.
473 public Builder setFontVariationSettings(@Nullable FontVariationAxis[] axes) {
474 if (mFonts != null) {
475 throw new IllegalArgumentException(
476 "Font variation settings can not be specified for FontResult source.");
479 throw new IllegalStateException("Font variation settings are already set.");
486 * Sets a fallback family name.
488 * By specifying a fallback family name, a fallback Typeface will be returned if the
489 * {@link #build} method fails to create a Typeface from the provided font. The fallback
490 * family will be resolved with the provided weight and italic information specified by
491 * {@link #setWeight} and {@link #setItalic}.
493 * If {@link #setWeight} is not called, the fallback family keeps the default weight.
494 * Similary, if {@link #setItalic} is not called, the fallback family keeps the default
495 * italic information. For example, calling {@code builder.setFallback("sans-serif-light")}
496 * is equivalent to calling {@code builder.setFallback("sans-serif").setWeight(300)} in
497 * terms of fallback. The default weight and italic information are overridden by calling
498 * {@link #setWeight} and {@link #setItalic}. For example, if a Typeface is constructed
499 * using {@code builder.setFallback("sans-serif-light").setWeight(700)}, the fallback text
500 * will render as sans serif bold.
502 * @param familyName A family name to be used for fallback if the provided font can not be
503 * used. By passing {@code null}, build() returns {@code null}.
504 * If {@link #setFallback} is not called on the builder, {@code null}
507 public Builder setFallback(@Nullable String familyName) {
508 mFallbackFamilyName = familyName;
513 * Creates a unique id for a given AssetManager and asset path.
515 * @param mgr AssetManager instance
516 * @param path The path for the asset.
517 * @param ttcIndex The TTC index for the font.
518 * @param axes The font variation settings.
519 * @return Unique id for a given AssetManager and asset path.
521 private static String createAssetUid(final AssetManager mgr, String path, int ttcIndex,
522 @Nullable FontVariationAxis[] axes, int weight, int italic, String fallback) {
523 final SparseArray<String> pkgs = mgr.getAssignedPackageIdentifiers();
524 final StringBuilder builder = new StringBuilder();
525 final int size = pkgs.size();
526 for (int i = 0; i < size; i++) {
527 builder.append(pkgs.valueAt(i));
530 builder.append(path);
532 builder.append(Integer.toString(ttcIndex));
534 builder.append(Integer.toString(weight));
536 builder.append(Integer.toString(italic));
537 // Family name may contain hyphen. Use double hyphen for avoiding key conflicts before
538 // and after appending falblack name.
539 builder.append("--");
540 builder.append(fallback);
541 builder.append("--");
543 for (FontVariationAxis axis : axes) {
544 builder.append(axis.getTag());
546 builder.append(Float.toString(axis.getStyleValue()));
549 return builder.toString();
552 private Typeface resolveFallbackTypeface() {
553 if (mFallbackFamilyName == null) {
557 Typeface base = sSystemFontMap.get(mFallbackFamilyName);
559 base = sDefaultTypeface;
562 if (mWeight == RESOLVE_BY_FONT_TABLE && mItalic == RESOLVE_BY_FONT_TABLE) {
566 final int weight = (mWeight == RESOLVE_BY_FONT_TABLE) ? base.mWeight : mWeight;
567 final boolean italic =
568 (mItalic == RESOLVE_BY_FONT_TABLE) ? (base.mStyle & ITALIC) != 0 : mItalic == 1;
569 return createWeightStyle(base, weight, italic);
573 * Generates new Typeface from specified configuration.
575 * @return Newly created Typeface. May return null if some parameters are invalid.
577 public Typeface build() {
578 if (mFd != null) { // Builder is created with file descriptor.
579 try (FileInputStream fis = new FileInputStream(mFd)) {
580 FileChannel channel = fis.getChannel();
581 long size = channel.size();
582 ByteBuffer buffer = channel.map(FileChannel.MapMode.READ_ONLY, 0, size);
584 final FontFamily fontFamily = new FontFamily();
585 if (!fontFamily.addFontFromBuffer(buffer, mTtcIndex, mAxes, mWeight, mItalic)) {
586 fontFamily.abortCreation();
587 return resolveFallbackTypeface();
589 if (!fontFamily.freeze()) {
590 return resolveFallbackTypeface();
592 FontFamily[] families = { fontFamily };
593 return createFromFamiliesWithDefault(families, mFallbackFamilyName, mWeight,
595 } catch (IOException e) {
596 return resolveFallbackTypeface();
598 } else if (mAssetManager != null) { // Builder is created with asset manager.
599 final String key = createAssetUid(
600 mAssetManager, mPath, mTtcIndex, mAxes, mWeight, mItalic,
601 mFallbackFamilyName);
602 synchronized (sDynamicCacheLock) {
603 Typeface typeface = sDynamicTypefaceCache.get(key);
604 if (typeface != null) return typeface;
605 final FontFamily fontFamily = new FontFamily();
606 if (!fontFamily.addFontFromAssetManager(mAssetManager, mPath, mTtcIndex,
607 true /* isAsset */, mTtcIndex, mWeight, mItalic, mAxes)) {
608 fontFamily.abortCreation();
609 return resolveFallbackTypeface();
611 if (!fontFamily.freeze()) {
612 return resolveFallbackTypeface();
614 FontFamily[] families = { fontFamily };
615 typeface = createFromFamiliesWithDefault(families, mFallbackFamilyName,
617 sDynamicTypefaceCache.put(key, typeface);
620 } else if (mPath != null) { // Builder is created with file path.
621 final FontFamily fontFamily = new FontFamily();
622 if (!fontFamily.addFont(mPath, mTtcIndex, mAxes, mWeight, mItalic)) {
623 fontFamily.abortCreation();
624 return resolveFallbackTypeface();
626 if (!fontFamily.freeze()) {
627 return resolveFallbackTypeface();
629 FontFamily[] families = { fontFamily };
630 return createFromFamiliesWithDefault(families, mFallbackFamilyName, mWeight,
632 } else if (mFonts != null) {
633 final FontFamily fontFamily = new FontFamily();
634 boolean atLeastOneFont = false;
635 for (FontsContract.FontInfo font : mFonts) {
636 final ByteBuffer fontBuffer = mFontBuffers.get(font.getUri());
637 if (fontBuffer == null) {
640 final boolean success = fontFamily.addFontFromBuffer(fontBuffer,
641 font.getTtcIndex(), font.getAxes(), font.getWeight(),
642 font.isItalic() ? STYLE_ITALIC : STYLE_NORMAL);
644 fontFamily.abortCreation();
647 atLeastOneFont = true;
649 if (!atLeastOneFont) {
650 // No fonts are avaialble. No need to create new Typeface and returns fallback
652 fontFamily.abortCreation();
656 FontFamily[] families = { fontFamily };
657 return createFromFamiliesWithDefault(families, mFallbackFamilyName, mWeight,
661 // Must not reach here.
662 throw new IllegalArgumentException("No source was set.");
667 * Create a typeface object given a family name, and option style information.
668 * If null is passed for the name, then the "default" font will be chosen.
669 * The resulting typeface object can be queried (getStyle()) to discover what
670 * its "real" style characteristics are.
672 * @param familyName May be null. The name of the font family.
673 * @param style The style (normal, bold, italic) of the typeface.
674 * e.g. NORMAL, BOLD, ITALIC, BOLD_ITALIC
675 * @return The best matching typeface.
677 public static Typeface create(String familyName, @Style int style) {
678 return create(sSystemFontMap.get(familyName), style);
682 * Create a typeface object that best matches the specified existing
683 * typeface and the specified Style. Use this call if you want to pick a new
684 * style from the same family of an existing typeface object. If family is
685 * null, this selects from the default font's family.
688 * This method is not thread safe on API 27 or before.
689 * This method is thread safe on API 28 or after.
692 * @param family An existing {@link Typeface} object. In case of {@code null}, the default
693 * typeface is used instead.
694 * @param style The style (normal, bold, italic) of the typeface.
695 * e.g. NORMAL, BOLD, ITALIC, BOLD_ITALIC
696 * @return The best matching typeface.
698 public static Typeface create(Typeface family, @Style int style) {
699 if ((style & ~STYLE_MASK) != 0) {
702 if (family == null) {
703 family = sDefaultTypeface;
706 // Return early if we're asked for the same face/style
707 if (family.mStyle == style) {
711 final long ni = family.native_instance;
714 synchronized (sStyledCacheLock) {
715 SparseArray<Typeface> styles = sStyledTypefaceCache.get(ni);
717 if (styles == null) {
718 styles = new SparseArray<Typeface>(4);
719 sStyledTypefaceCache.put(ni, styles);
721 typeface = styles.get(style);
722 if (typeface != null) {
727 typeface = new Typeface(nativeCreateFromTypeface(ni, style));
728 styles.put(style, typeface);
734 * Creates a typeface object that best matches the specified existing typeface and the specified
735 * weight and italic style
738 * This method is thread safe.
741 * @param family An existing {@link Typeface} object. In case of {@code null}, the default
742 * typeface is used instead.
743 * @param weight The desired weight to be drawn.
744 * @param italic {@code true} if italic style is desired to be drawn. Otherwise, {@code false}
745 * @return A {@link Typeface} object for drawing specified weight and italic style. Never
746 * returns {@code null}
748 public static @NonNull Typeface create(@Nullable Typeface family,
749 @IntRange(from = 1, to = 1000) int weight, boolean italic) {
750 Preconditions.checkArgumentInRange(weight, 0, 1000, "weight");
751 if (family == null) {
752 family = sDefaultTypeface;
754 return createWeightStyle(family, weight, italic);
757 private static @NonNull Typeface createWeightStyle(@NonNull Typeface base,
758 @IntRange(from = 1, to = 1000) int weight, boolean italic) {
759 final int key = (weight << 1) | (italic ? 1 : 0);
762 synchronized(sWeightCacheLock) {
763 SparseArray<Typeface> innerCache = sWeightTypefaceCache.get(base.native_instance);
764 if (innerCache == null) {
765 innerCache = new SparseArray<>(4);
766 sWeightTypefaceCache.put(base.native_instance, innerCache);
768 typeface = innerCache.get(key);
769 if (typeface != null) {
774 typeface = new Typeface(
775 nativeCreateFromTypefaceWithExactStyle(
776 base.native_instance, weight, italic));
777 innerCache.put(key, typeface);
783 public static Typeface createFromTypefaceWithVariation(@Nullable Typeface family,
784 @NonNull List<FontVariationAxis> axes) {
785 final long ni = family == null ? 0 : family.native_instance;
786 return new Typeface(nativeCreateFromTypefaceWithVariation(ni, axes));
790 * Returns one of the default typeface objects, based on the specified style
792 * @return the default typeface that corresponds to the style
794 public static Typeface defaultFromStyle(@Style int style) {
795 return sDefaults[style];
799 * Create a new typeface from the specified font data.
801 * @param mgr The application's asset manager
802 * @param path The file name of the font data in the assets directory
803 * @return The new typeface.
805 public static Typeface createFromAsset(AssetManager mgr, String path) {
806 Preconditions.checkNotNull(path); // for backward compatibility
807 Preconditions.checkNotNull(mgr);
809 Typeface typeface = new Builder(mgr, path).build();
810 if (typeface != null) return typeface;
811 // check if the file exists, and throw an exception for backward compatibility
812 try (InputStream inputStream = mgr.open(path)) {
813 } catch (IOException e) {
814 throw new RuntimeException("Font asset not found " + path);
817 return Typeface.DEFAULT;
821 * Creates a unique id for a given font provider and query.
823 private static String createProviderUid(String authority, String query) {
824 final StringBuilder builder = new StringBuilder();
825 builder.append("provider:");
826 builder.append(authority);
828 builder.append(query);
829 return builder.toString();
833 * Create a new typeface from the specified font file.
835 * @param file The path to the font data.
836 * @return The new typeface.
838 public static Typeface createFromFile(@Nullable File file) {
839 // For the compatibility reasons, leaving possible NPE here.
840 // See android.graphics.cts.TypefaceTest#testCreateFromFileByFileReferenceNull
842 Typeface typeface = new Builder(file).build();
843 if (typeface != null) return typeface;
845 // check if the file exists, and throw an exception for backward compatibility
846 if (!file.exists()) {
847 throw new RuntimeException("Font asset not found " + file.getAbsolutePath());
850 return Typeface.DEFAULT;
854 * Create a new typeface from the specified font file.
856 * @param path The full path to the font data.
857 * @return The new typeface.
859 public static Typeface createFromFile(@Nullable String path) {
860 Preconditions.checkNotNull(path); // for backward compatibility
861 return createFromFile(new File(path));
865 * Create a new typeface from an array of font families.
867 * @param families array of font families
869 private static Typeface createFromFamilies(FontFamily[] families) {
870 long[] ptrArray = new long[families.length];
871 for (int i = 0; i < families.length; i++) {
872 ptrArray[i] = families[i].mNativePtr;
874 return new Typeface(nativeCreateFromArray(
875 ptrArray, RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE));
879 * Create a new typeface from an array of font families, including
880 * also the font families in the fallback list.
881 * @param fallbackName the family name. If given families don't support characters, the
882 * characters will be rendered with this family.
883 * @param weight the weight for this family. In that case, the table information in the first
884 * family's font is used. If the first family has multiple fonts, the closest to
885 * the regular weight and upright font is used.
886 * @param italic the italic information for this family. In that case, the table information in
887 * the first family's font is used. If the first family has multiple fonts, the
888 * closest to the regular weight and upright font is used.
889 * @param families array of font families
891 private static Typeface createFromFamiliesWithDefault(FontFamily[] families,
892 String fallbackName, int weight, int italic) {
893 FontFamily[] fallback = sSystemFallbackMap.get(fallbackName);
894 if (fallback == null) {
895 fallback = sSystemFallbackMap.get(DEFAULT_FAMILY);
897 long[] ptrArray = new long[families.length + fallback.length];
898 for (int i = 0; i < families.length; i++) {
899 ptrArray[i] = families[i].mNativePtr;
901 for (int i = 0; i < fallback.length; i++) {
902 ptrArray[i + families.length] = fallback[i].mNativePtr;
904 return new Typeface(nativeCreateFromArray(ptrArray, weight, italic));
907 // don't allow clients to call this directly
908 private Typeface(long ni) {
910 throw new RuntimeException("native typeface cannot be made");
913 native_instance = ni;
914 mStyle = nativeGetStyle(ni);
915 mWeight = nativeGetWeight(ni);
918 private static @Nullable ByteBuffer mmap(String fullPath) {
919 try (FileInputStream file = new FileInputStream(fullPath)) {
920 final FileChannel fileChannel = file.getChannel();
921 final long fontSize = fileChannel.size();
922 return fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, fontSize);
923 } catch (IOException e) {
924 Log.e(TAG, "Error mapping font file " + fullPath);
929 private static @Nullable FontFamily createFontFamily(
930 String familyName, List<FontConfig.Font> fonts, String[] languageTags, int variant,
931 Map<String, ByteBuffer> cache, String fontDir) {
932 final FontFamily family = new FontFamily(languageTags, variant);
933 for (int i = 0; i < fonts.size(); i++) {
934 final FontConfig.Font font = fonts.get(i);
935 final String fullPath = fontDir + font.getFontName();
936 ByteBuffer buffer = cache.get(fullPath);
937 if (buffer == null) {
938 if (cache.containsKey(fullPath)) {
939 continue; // Already failed to mmap. Skip it.
941 buffer = mmap(fullPath);
942 cache.put(fullPath, buffer);
943 if (buffer == null) {
947 if (!family.addFontFromBuffer(buffer, font.getTtcIndex(), font.getAxes(),
948 font.getWeight(), font.isItalic() ? STYLE_ITALIC : STYLE_NORMAL)) {
949 Log.e(TAG, "Error creating font " + fullPath + "#" + font.getTtcIndex());
952 if (!family.freeze()) {
953 Log.e(TAG, "Unable to load Family: " + familyName + " : "
954 + Arrays.toString(languageTags));
960 private static void pushFamilyToFallback(FontConfig.Family xmlFamily,
961 ArrayMap<String, ArrayList<FontFamily>> fallbackMap,
962 Map<String, ByteBuffer> cache,
965 final String[] languageTags = xmlFamily.getLanguages();
966 final int variant = xmlFamily.getVariant();
968 final ArrayList<FontConfig.Font> defaultFonts = new ArrayList<>();
969 final ArrayMap<String, ArrayList<FontConfig.Font>> specificFallbackFonts = new ArrayMap<>();
971 // Collect default fallback and specific fallback fonts.
972 for (final FontConfig.Font font : xmlFamily.getFonts()) {
973 final String fallbackName = font.getFallbackFor();
974 if (fallbackName == null) {
975 defaultFonts.add(font);
977 ArrayList<FontConfig.Font> fallback = specificFallbackFonts.get(fallbackName);
978 if (fallback == null) {
979 fallback = new ArrayList<>();
980 specificFallbackFonts.put(fallbackName, fallback);
986 final FontFamily defaultFamily = defaultFonts.isEmpty() ? null : createFontFamily(
987 xmlFamily.getName(), defaultFonts, languageTags, variant, cache, fontDir);
989 // Insert family into fallback map.
990 for (int i = 0; i < fallbackMap.size(); i++) {
991 final ArrayList<FontConfig.Font> fallback =
992 specificFallbackFonts.get(fallbackMap.keyAt(i));
993 if (fallback == null) {
994 if (defaultFamily != null) {
995 fallbackMap.valueAt(i).add(defaultFamily);
998 final FontFamily family = createFontFamily(
999 xmlFamily.getName(), fallback, languageTags, variant, cache, fontDir);
1000 if (family != null) {
1001 fallbackMap.valueAt(i).add(family);
1002 } else if (defaultFamily != null) {
1003 fallbackMap.valueAt(i).add(defaultFamily);
1005 // There is no valid for for default fallback. Ignore.
1012 * Build the system fallback from xml file.
1014 * @param xmlPath A full path string to the fonts.xml file.
1015 * @param fontDir A full path string to the system font directory. This must end with
1017 * @param fontMap An output system font map. Caller must pass empty map.
1018 * @param fallbackMap An output system fallback map. Caller must pass empty map.
1022 public static void buildSystemFallback(String xmlPath, String fontDir,
1023 ArrayMap<String, Typeface> fontMap, ArrayMap<String, FontFamily[]> fallbackMap) {
1025 final FileInputStream fontsIn = new FileInputStream(xmlPath);
1026 final FontConfig fontConfig = FontListParser.parse(fontsIn);
1028 final HashMap<String, ByteBuffer> bufferCache = new HashMap<String, ByteBuffer>();
1029 final FontConfig.Family[] xmlFamilies = fontConfig.getFamilies();
1031 final ArrayMap<String, ArrayList<FontFamily>> fallbackListMap = new ArrayMap<>();
1032 // First traverse families which have a 'name' attribute to create fallback map.
1033 for (final FontConfig.Family xmlFamily : xmlFamilies) {
1034 final String familyName = xmlFamily.getName();
1035 if (familyName == null) {
1038 final FontFamily family = createFontFamily(
1039 xmlFamily.getName(), Arrays.asList(xmlFamily.getFonts()),
1040 xmlFamily.getLanguages(), xmlFamily.getVariant(), bufferCache, fontDir);
1041 if (family == null) {
1044 final ArrayList<FontFamily> fallback = new ArrayList<>();
1045 fallback.add(family);
1046 fallbackListMap.put(familyName, fallback);
1049 // Then, add fallback fonts to the each fallback map.
1050 for (int i = 0; i < xmlFamilies.length; i++) {
1051 final FontConfig.Family xmlFamily = xmlFamilies[i];
1052 // The first family (usually the sans-serif family) is always placed immediately
1053 // after the primary family in the fallback.
1054 if (i == 0 || xmlFamily.getName() == null) {
1055 pushFamilyToFallback(xmlFamily, fallbackListMap, bufferCache, fontDir);
1059 // Build the font map and fallback map.
1060 for (int i = 0; i < fallbackListMap.size(); i++) {
1061 final String fallbackName = fallbackListMap.keyAt(i);
1062 final List<FontFamily> familyList = fallbackListMap.valueAt(i);
1063 final FontFamily[] families = familyList.toArray(new FontFamily[familyList.size()]);
1065 fallbackMap.put(fallbackName, families);
1066 final long[] ptrArray = new long[families.length];
1067 for (int j = 0; j < families.length; j++) {
1068 ptrArray[j] = families[j].mNativePtr;
1070 fontMap.put(fallbackName, new Typeface(nativeCreateFromArray(
1071 ptrArray, RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE)));
1074 // Insert alias to font maps.
1075 for (final FontConfig.Alias alias : fontConfig.getAliases()) {
1076 Typeface base = fontMap.get(alias.getToName());
1077 Typeface newFace = base;
1078 int weight = alias.getWeight();
1079 if (weight != 400) {
1080 newFace = new Typeface(nativeCreateWeightAlias(base.native_instance, weight));
1082 fontMap.put(alias.getName(), newFace);
1084 } catch (RuntimeException e) {
1085 Log.w(TAG, "Didn't create default family (most likely, non-Minikin build)", e);
1086 // TODO: normal in non-Minikin case, remove or make error when Minikin-only
1087 } catch (FileNotFoundException e) {
1088 Log.e(TAG, "Error opening " + xmlPath, e);
1089 } catch (IOException e) {
1090 Log.e(TAG, "Error reading " + xmlPath, e);
1091 } catch (XmlPullParserException e) {
1092 Log.e(TAG, "XML parse exception for " + xmlPath, e);
1097 final ArrayMap<String, Typeface> systemFontMap = new ArrayMap<>();
1098 final ArrayMap<String, FontFamily[]> systemFallbackMap = new ArrayMap<>();
1099 buildSystemFallback("/system/etc/fonts.xml", "/system/fonts/", systemFontMap,
1101 sSystemFontMap = Collections.unmodifiableMap(systemFontMap);
1102 sSystemFallbackMap = Collections.unmodifiableMap(systemFallbackMap);
1104 setDefault(sSystemFontMap.get(DEFAULT_FAMILY));
1106 // Set up defaults and typefaces exposed in public API
1107 DEFAULT = create((String) null, 0);
1108 DEFAULT_BOLD = create((String) null, Typeface.BOLD);
1109 SANS_SERIF = create("sans-serif", 0);
1110 SERIF = create("serif", 0);
1111 MONOSPACE = create("monospace", 0);
1113 sDefaults = new Typeface[] {
1116 create((String) null, Typeface.ITALIC),
1117 create((String) null, Typeface.BOLD_ITALIC),
1123 protected void finalize() throws Throwable {
1125 nativeUnref(native_instance);
1126 native_instance = 0; // Other finalizers can still call us.
1133 public boolean equals(Object o) {
1134 if (this == o) return true;
1135 if (o == null || getClass() != o.getClass()) return false;
1137 Typeface typeface = (Typeface) o;
1139 return mStyle == typeface.mStyle && native_instance == typeface.native_instance;
1143 public int hashCode() {
1145 * Modified method for hashCode with long native_instance derived from
1146 * http://developer.android.com/reference/java/lang/Object.html
1149 result = 31 * result + (int) (native_instance ^ (native_instance >>> 32));
1150 result = 31 * result + mStyle;
1155 public boolean isSupportedAxes(int axis) {
1156 if (mSupportedAxes == null) {
1157 synchronized (this) {
1158 if (mSupportedAxes == null) {
1159 mSupportedAxes = nativeGetSupportedAxes(native_instance);
1160 if (mSupportedAxes == null) {
1161 mSupportedAxes = EMPTY_AXES;
1166 return Arrays.binarySearch(mSupportedAxes, axis) >= 0;
1169 private static native long nativeCreateFromTypeface(long native_instance, int style);
1170 private static native long nativeCreateFromTypefaceWithExactStyle(
1171 long native_instance, int weight, boolean italic);
1172 // TODO: clean up: change List<FontVariationAxis> to FontVariationAxis[]
1173 private static native long nativeCreateFromTypefaceWithVariation(
1174 long native_instance, List<FontVariationAxis> axes);
1175 private static native long nativeCreateWeightAlias(long native_instance, int weight);
1176 private static native void nativeUnref(long native_instance);
1177 private static native int nativeGetStyle(long native_instance);
1178 private static native int nativeGetWeight(long native_instance);
1179 private static native long nativeCreateFromArray(long[] familyArray, int weight, int italic);
1180 private static native void nativeSetDefault(long native_instance);
1181 private static native int[] nativeGetSupportedAxes(long native_instance);