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.ProviderResourceEntry;
20 import static android.content.res.FontResourcesParser.FontFileResourceEntry;
21 import static android.content.res.FontResourcesParser.FontFamilyFilesResourceEntry;
22 import static android.content.res.FontResourcesParser.FamilyResourceEntry;
24 import static java.lang.annotation.RetentionPolicy.SOURCE;
26 import android.annotation.IntDef;
27 import android.annotation.IntRange;
28 import android.annotation.NonNull;
29 import android.annotation.Nullable;
30 import android.content.res.AssetManager;
31 import android.graphics.FontListParser;
32 import android.graphics.fonts.FontVariationAxis;
33 import android.net.Uri;
34 import android.os.Bundle;
35 import android.os.Handler;
36 import android.os.ParcelFileDescriptor;
37 import android.os.ResultReceiver;
38 import android.provider.FontRequest;
39 import android.provider.FontsContract;
40 import android.text.FontConfig;
41 import android.util.ArrayMap;
42 import android.util.Base64;
43 import android.util.Log;
44 import android.util.LongSparseArray;
45 import android.util.LruCache;
46 import android.util.SparseArray;
48 import com.android.internal.annotations.GuardedBy;
49 import com.android.internal.annotations.VisibleForTesting;
50 import com.android.internal.util.Preconditions;
52 import libcore.io.IoUtils;
54 import org.xmlpull.v1.XmlPullParserException;
57 import java.io.FileDescriptor;
58 import java.io.FileInputStream;
59 import java.io.FileNotFoundException;
60 import java.io.IOException;
61 import java.lang.annotation.Retention;
62 import java.lang.annotation.RetentionPolicy;
63 import java.nio.ByteBuffer;
64 import java.nio.channels.FileChannel;
65 import java.util.Arrays;
66 import java.util.ArrayList;
67 import java.util.Arrays;
68 import java.util.Collections;
69 import java.util.HashMap;
70 import java.util.List;
72 import java.util.concurrent.atomic.AtomicReference;
75 * The Typeface class specifies the typeface and intrinsic style of a font.
76 * This is used in the paint, along with optionally Paint settings like
77 * textSize, textSkewX, textScaleX to specify
78 * how text appears when drawn (and measured).
80 public class Typeface {
82 private static String TAG = "Typeface";
84 /** The default NORMAL typeface object */
85 public static final Typeface DEFAULT;
87 * The default BOLD typeface object. Note: this may be not actually be
88 * bold, depending on what fonts are installed. Call getStyle() to know
91 public static final Typeface DEFAULT_BOLD;
92 /** The NORMAL style of the default sans serif typeface. */
93 public static final Typeface SANS_SERIF;
94 /** The NORMAL style of the default serif typeface. */
95 public static final Typeface SERIF;
96 /** The NORMAL style of the default monospace typeface. */
97 public static final Typeface MONOSPACE;
99 static Typeface[] sDefaults;
100 private static final LongSparseArray<SparseArray<Typeface>> sTypefaceCache =
101 new LongSparseArray<>(3);
104 * Cache for Typeface objects dynamically loaded from assets. Currently max size is 16.
107 private static final LruCache<String, Typeface> sDynamicTypefaceCache = new LruCache<>(16);
109 static Typeface sDefaultTypeface;
110 static final Map<String, Typeface> sSystemFontMap;
111 static final Map<String, FontFamily[]> sSystemFallbackMap;
112 private static final Object sLock = new Object();
117 public long native_instance;
120 public static final int NORMAL = 0;
121 public static final int BOLD = 1;
122 public static final int ITALIC = 2;
123 public static final int BOLD_ITALIC = 3;
125 private int mStyle = 0;
126 private int mWeight = 0;
128 // Value for weight and italic. Indicates the value is resolved by font metadata.
129 // Must be the same as the C++ constant in core/jni/android/graphics/FontFamily.cpp
131 public static final int RESOLVE_BY_FONT_TABLE = -1;
132 private static final String DEFAULT_FAMILY = "sans-serif";
134 // Style value for building typeface.
135 private static final int STYLE_NORMAL = 0;
136 private static final int STYLE_ITALIC = 1;
138 private int[] mSupportedAxes;
139 private static final int[] EMPTY_AXES = {};
141 private static void setDefault(Typeface t) {
142 sDefaultTypeface = t;
143 nativeSetDefault(t.native_instance);
146 /** Returns the typeface's intrinsic style attributes */
147 public int getStyle() {
151 /** Returns true if getStyle() has the BOLD bit set. */
152 public final boolean isBold() {
153 return (mStyle & BOLD) != 0;
156 /** Returns true if getStyle() has the ITALIC bit set. */
157 public final boolean isItalic() {
158 return (mStyle & ITALIC) != 0;
163 * Used by Resources to load a font resource of type font file.
166 public static Typeface createFromResources(AssetManager mgr, String path, int cookie) {
167 synchronized (sDynamicTypefaceCache) {
168 final String key = Builder.createAssetUid(
169 mgr, path, 0 /* ttcIndex */, null /* axes */,
170 RESOLVE_BY_FONT_TABLE /* weight */, RESOLVE_BY_FONT_TABLE /* italic */,
172 Typeface typeface = sDynamicTypefaceCache.get(key);
173 if (typeface != null) return typeface;
175 FontFamily fontFamily = new FontFamily();
176 // TODO: introduce ttc index and variation settings to resource type font.
177 if (fontFamily.addFontFromAssetManager(mgr, path, cookie, false /* isAsset */,
178 0 /* ttcIndex */, RESOLVE_BY_FONT_TABLE /* weight */,
179 RESOLVE_BY_FONT_TABLE /* italic */, null /* axes */)) {
180 if (!fontFamily.freeze()) {
183 FontFamily[] families = {fontFamily};
184 typeface = createFromFamiliesWithDefault(families, DEFAULT_FAMILY,
185 RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE);
186 sDynamicTypefaceCache.put(key, typeface);
195 * Used by Resources to load a font resource of type xml.
198 public static Typeface createFromResources(
199 FamilyResourceEntry entry, AssetManager mgr, String path) {
200 if (entry instanceof ProviderResourceEntry) {
201 final ProviderResourceEntry providerEntry = (ProviderResourceEntry) entry;
203 List<List<String>> givenCerts = providerEntry.getCerts();
204 List<List<byte[]>> certs = new ArrayList<>();
205 if (givenCerts != null) {
206 for (int i = 0; i < givenCerts.size(); i++) {
207 List<String> certSet = givenCerts.get(i);
208 List<byte[]> byteArraySet = new ArrayList<>();
209 for (int j = 0; j < certSet.size(); j++) {
210 byteArraySet.add(Base64.decode(certSet.get(j), Base64.DEFAULT));
212 certs.add(byteArraySet);
215 // Downloaded font and it wasn't cached, request it again and return a
216 // default font instead (nothing we can do now).
217 FontRequest request = new FontRequest(providerEntry.getAuthority(),
218 providerEntry.getPackage(), providerEntry.getQuery(), certs);
219 Typeface typeface = FontsContract.getFontSync(request);
220 return typeface == null ? DEFAULT : typeface;
223 Typeface typeface = findFromCache(mgr, path);
224 if (typeface != null) return typeface;
226 // family is FontFamilyFilesResourceEntry
227 final FontFamilyFilesResourceEntry filesEntry = (FontFamilyFilesResourceEntry) entry;
229 FontFamily fontFamily = new FontFamily();
230 for (final FontFileResourceEntry fontFile : filesEntry.getEntries()) {
231 // TODO: Add ttc and variation font support. (b/37853920)
232 if (!fontFamily.addFontFromAssetManager(mgr, fontFile.getFileName(),
233 0 /* resourceCookie */, false /* isAsset */, 0 /* ttcIndex */,
234 fontFile.getWeight(), fontFile.getItalic(), null /* axes */)) {
238 if (!fontFamily.freeze()) {
241 FontFamily[] familyChain = { fontFamily };
242 typeface = createFromFamiliesWithDefault(familyChain, DEFAULT_FAMILY,
243 RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE);
244 synchronized (sDynamicTypefaceCache) {
245 final String key = Builder.createAssetUid(mgr, path, 0 /* ttcIndex */,
246 null /* axes */, RESOLVE_BY_FONT_TABLE /* weight */,
247 RESOLVE_BY_FONT_TABLE /* italic */, DEFAULT_FAMILY);
248 sDynamicTypefaceCache.put(key, typeface);
254 * Used by resources for cached loading if the font is available.
257 public static Typeface findFromCache(AssetManager mgr, String path) {
258 synchronized (sDynamicTypefaceCache) {
259 final String key = Builder.createAssetUid(mgr, path, 0 /* ttcIndex */, null /* axes */,
260 RESOLVE_BY_FONT_TABLE /* weight */, RESOLVE_BY_FONT_TABLE /* italic */,
262 Typeface typeface = sDynamicTypefaceCache.get(key);
263 if (typeface != null) {
271 * A builder class for creating new Typeface instance.
275 * 1) Create Typeface from ttf file.
278 * Typeface.Builder buidler = new Typeface.Builder("your_font_file.ttf");
279 * Typeface typeface = builder.build();
283 * 2) Create Typeface from ttc file in assets directory.
286 * Typeface.Builder buidler = new Typeface.Builder(getAssets(), "your_font_file.ttc");
287 * builder.setTtcIndex(2); // Set index of font collection.
288 * Typeface typeface = builder.build();
292 * 3) Create Typeface with variation settings.
295 * Typeface.Builder buidler = new Typeface.Builder("your_font_file.ttf");
296 * builder.setFontVariationSettings("'wght' 700, 'slnt' 20, 'ital' 1");
297 * builder.setWeight(700); // Tell the system that this is a bold font.
298 * builder.setItalic(true); // Tell the system that this is an italic style font.
299 * Typeface typeface = builder.build();
304 public static final class Builder {
306 public static final int NORMAL_WEIGHT = 400;
308 public static final int BOLD_WEIGHT = 700;
310 private int mTtcIndex;
311 private FontVariationAxis[] mAxes;
313 private AssetManager mAssetManager;
314 private String mPath;
315 private FileDescriptor mFd;
317 private FontsContract.FontInfo[] mFonts;
318 private Map<Uri, ByteBuffer> mFontBuffers;
320 private String mFallbackFamilyName;
322 private int mWeight = RESOLVE_BY_FONT_TABLE;
323 private int mItalic = RESOLVE_BY_FONT_TABLE;
326 * Constructs a builder with a file path.
328 * @param path The file object refers to the font file.
330 public Builder(@NonNull File path) {
331 mPath = path.getAbsolutePath();
335 * Constructs a builder with a file descriptor.
337 * Caller is responsible for closing the passed file descriptor after {@link #build} is
340 * @param fd The file descriptor. The passed fd must be mmap-able.
342 public Builder(@NonNull FileDescriptor fd) {
347 * Constructs a builder with a file path.
349 * @param path The full path to the font file.
351 public Builder(@NonNull String path) {
356 * Constructs a builder from an asset manager and a file path in an asset directory.
358 * @param assetManager The application's asset manager
359 * @param path The file name of the font data in the asset directory
361 public Builder(@NonNull AssetManager assetManager, @NonNull String path) {
362 mAssetManager = Preconditions.checkNotNull(assetManager);
363 mPath = Preconditions.checkStringNotEmpty(path);
367 * Constracts a builder from an array of FontsContract.FontInfo.
369 * Since {@link FontsContract.FontInfo} holds information about TTC indices and
370 * variation settings, there is no need to call {@link #setTtcIndex} or
371 * {@link #setFontVariationSettings}. Similary, {@link FontsContract.FontInfo} holds
372 * weight and italic information, so {@link #setWeight} and {@link #setItalic} are used
373 * for style matching during font selection.
375 * @param results The array of {@link FontsContract.FontInfo}
376 * @param buffers The mapping from URI to buffers to be used during building.
379 public Builder(@NonNull FontsContract.FontInfo[] fonts,
380 @NonNull Map<Uri, ByteBuffer> buffers) {
382 mFontBuffers = buffers;
386 * Sets weight of the font.
388 * Tells the system the weight of the given font. If not provided, the system will resolve
389 * the weight value by reading font tables.
390 * @param weight a weight value.
392 public Builder setWeight(@IntRange(from = 1, to = 1000) int weight) {
398 * Sets italic information of the font.
400 * Tells the system the style of the given font. If not provided, the system will resolve
401 * the style by reading font tables.
402 * @param italic {@code true} if the font is italic. Otherwise {@code false}.
404 public Builder setItalic(boolean italic) {
405 mItalic = italic ? STYLE_ITALIC : STYLE_NORMAL;
410 * Sets an index of the font collection.
412 * Can not be used for Typeface source. build() method will return null for invalid index.
413 * @param ttcIndex An index of the font collection. If the font source is not font
414 * collection, do not call this method or specify 0.
416 public Builder setTtcIndex(@IntRange(from = 0) int ttcIndex) {
417 if (mFonts != null) {
418 throw new IllegalArgumentException(
419 "TTC index can not be specified for FontResult source.");
421 mTtcIndex = ttcIndex;
426 * Sets a font variation settings.
428 * @param variationSettings See {@link android.widget.TextView#setFontVariationSettings}.
429 * @throws IllegalArgumentException If given string is not a valid font variation settings
432 public Builder setFontVariationSettings(@Nullable String variationSettings) {
433 if (mFonts != null) {
434 throw new IllegalArgumentException(
435 "Font variation settings can not be specified for FontResult source.");
438 throw new IllegalStateException("Font variation settings are already set.");
440 mAxes = FontVariationAxis.fromFontVariationSettings(variationSettings);
445 * Sets a font variation settings.
447 * @param axes An array of font variation axis tag-value pairs.
449 public Builder setFontVariationSettings(@Nullable FontVariationAxis[] axes) {
450 if (mFonts != null) {
451 throw new IllegalArgumentException(
452 "Font variation settings can not be specified for FontResult source.");
455 throw new IllegalStateException("Font variation settings are already set.");
462 * Sets a fallback family name.
464 * By specifying a fallback family name, a fallback Typeface will be returned if the
465 * {@link #build} method fails to create a Typeface from the provided font. The fallback
466 * family will be resolved with the provided weight and italic information specified by
467 * {@link #setWeight} and {@link #setItalic}.
469 * If {@link #setWeight} is not called, the fallback family keeps the default weight.
470 * Similary, if {@link #setItalic} is not called, the fallback family keeps the default
471 * italic information. For example, calling {@code builder.setFallback("sans-serif-light")}
472 * is equivalent to calling {@code builder.setFallback("sans-serif").setWeight(300)} in
473 * terms of fallback. The default weight and italic information are overridden by calling
474 * {@link #setWeight} and {@link #setItalic}. For example, if a Typeface is constructed
475 * using {@code builder.setFallback("sans-serif-light").setWeight(700)}, the fallback text
476 * will render as sans serif bold.
478 * @param familyName A family name to be used for fallback if the provided font can not be
479 * used. By passing {@code null}, build() returns {@code null}.
480 * If {@link #setFallback} is not called on the builder, {@code null}
483 public Builder setFallback(@Nullable String familyName) {
484 mFallbackFamilyName = familyName;
489 * Creates a unique id for a given AssetManager and asset path.
491 * @param mgr AssetManager instance
492 * @param path The path for the asset.
493 * @param ttcIndex The TTC index for the font.
494 * @param axes The font variation settings.
495 * @return Unique id for a given AssetManager and asset path.
497 private static String createAssetUid(final AssetManager mgr, String path, int ttcIndex,
498 @Nullable FontVariationAxis[] axes, int weight, int italic, String fallback) {
499 final SparseArray<String> pkgs = mgr.getAssignedPackageIdentifiers();
500 final StringBuilder builder = new StringBuilder();
501 final int size = pkgs.size();
502 for (int i = 0; i < size; i++) {
503 builder.append(pkgs.valueAt(i));
506 builder.append(path);
508 builder.append(Integer.toString(ttcIndex));
510 builder.append(Integer.toString(weight));
512 builder.append(Integer.toString(italic));
513 // Family name may contain hyphen. Use double hyphen for avoiding key conflicts before
514 // and after appending falblack name.
515 builder.append("--");
516 builder.append(fallback);
517 builder.append("--");
519 for (FontVariationAxis axis : axes) {
520 builder.append(axis.getTag());
522 builder.append(Float.toString(axis.getStyleValue()));
525 return builder.toString();
528 private static final Object sLock = new Object();
529 // TODO: Unify with Typeface.sTypefaceCache.
531 private static final LongSparseArray<SparseArray<Typeface>> sTypefaceCache =
532 new LongSparseArray<>(3);
534 private Typeface resolveFallbackTypeface() {
535 if (mFallbackFamilyName == null) {
539 Typeface base = sSystemFontMap.get(mFallbackFamilyName);
541 base = sDefaultTypeface;
544 if (mWeight == RESOLVE_BY_FONT_TABLE && mItalic == RESOLVE_BY_FONT_TABLE) {
548 final int weight = (mWeight == RESOLVE_BY_FONT_TABLE) ? base.mWeight : mWeight;
549 final boolean italic =
550 (mItalic == RESOLVE_BY_FONT_TABLE) ? (base.mStyle & ITALIC) != 0 : mItalic == 1;
551 final int key = weight << 1 | (italic ? 1 : 0);
554 synchronized(sLock) {
555 SparseArray<Typeface> innerCache = sTypefaceCache.get(base.native_instance);
556 if (innerCache != null) {
557 typeface = innerCache.get(key);
558 if (typeface != null) {
563 typeface = new Typeface(
564 nativeCreateFromTypefaceWithExactStyle(
565 base.native_instance, weight, italic));
567 if (innerCache == null) {
568 innerCache = new SparseArray<>(4); // [regular, bold] x [upright, italic]
569 sTypefaceCache.put(base.native_instance, innerCache);
571 innerCache.put(key, typeface);
577 * Generates new Typeface from specified configuration.
579 * @return Newly created Typeface. May return null if some parameters are invalid.
581 public Typeface build() {
582 if (mFd != null) { // Builder is created with file descriptor.
583 try (FileInputStream fis = new FileInputStream(mFd)) {
584 FileChannel channel = fis.getChannel();
585 long size = channel.size();
586 ByteBuffer buffer = channel.map(FileChannel.MapMode.READ_ONLY, 0, size);
588 final FontFamily fontFamily = new FontFamily();
589 if (!fontFamily.addFontFromBuffer(buffer, mTtcIndex, mAxes, mWeight, mItalic)) {
590 fontFamily.abortCreation();
591 return resolveFallbackTypeface();
593 if (!fontFamily.freeze()) {
594 return resolveFallbackTypeface();
596 FontFamily[] families = { fontFamily };
597 return createFromFamiliesWithDefault(families, mFallbackFamilyName, mWeight,
599 } catch (IOException e) {
600 return resolveFallbackTypeface();
602 } else if (mAssetManager != null) { // Builder is created with asset manager.
603 final String key = createAssetUid(
604 mAssetManager, mPath, mTtcIndex, mAxes, mWeight, mItalic,
605 mFallbackFamilyName);
606 synchronized (sLock) {
607 Typeface typeface = sDynamicTypefaceCache.get(key);
608 if (typeface != null) return typeface;
609 final FontFamily fontFamily = new FontFamily();
610 if (!fontFamily.addFontFromAssetManager(mAssetManager, mPath, mTtcIndex,
611 true /* isAsset */, mTtcIndex, mWeight, mItalic, mAxes)) {
612 fontFamily.abortCreation();
613 return resolveFallbackTypeface();
615 if (!fontFamily.freeze()) {
616 return resolveFallbackTypeface();
618 FontFamily[] families = { fontFamily };
619 typeface = createFromFamiliesWithDefault(families, mFallbackFamilyName,
621 sDynamicTypefaceCache.put(key, typeface);
624 } else if (mPath != null) { // Builder is created with file path.
625 final FontFamily fontFamily = new FontFamily();
626 if (!fontFamily.addFont(mPath, mTtcIndex, mAxes, mWeight, mItalic)) {
627 fontFamily.abortCreation();
628 return resolveFallbackTypeface();
630 if (!fontFamily.freeze()) {
631 return resolveFallbackTypeface();
633 FontFamily[] families = { fontFamily };
634 return createFromFamiliesWithDefault(families, mFallbackFamilyName, mWeight,
636 } else if (mFonts != null) {
637 final FontFamily fontFamily = new FontFamily();
638 boolean atLeastOneFont = false;
639 for (FontsContract.FontInfo font : mFonts) {
640 final ByteBuffer fontBuffer = mFontBuffers.get(font.getUri());
641 if (fontBuffer == null) {
644 final boolean success = fontFamily.addFontFromBuffer(fontBuffer,
645 font.getTtcIndex(), font.getAxes(), font.getWeight(),
646 font.isItalic() ? STYLE_ITALIC : STYLE_NORMAL);
648 fontFamily.abortCreation();
651 atLeastOneFont = true;
653 if (!atLeastOneFont) {
654 // No fonts are avaialble. No need to create new Typeface and returns fallback
656 fontFamily.abortCreation();
660 FontFamily[] families = { fontFamily };
661 return createFromFamiliesWithDefault(families, mFallbackFamilyName, mWeight,
665 // Must not reach here.
666 throw new IllegalArgumentException("No source was set.");
671 * Create a typeface object given a family name, and option style information.
672 * If null is passed for the name, then the "default" font will be chosen.
673 * The resulting typeface object can be queried (getStyle()) to discover what
674 * its "real" style characteristics are.
676 * @param familyName May be null. The name of the font family.
677 * @param style The style (normal, bold, italic) of the typeface.
678 * e.g. NORMAL, BOLD, ITALIC, BOLD_ITALIC
679 * @return The best matching typeface.
681 public static Typeface create(String familyName, int style) {
682 return create(sSystemFontMap.get(familyName), style);
686 * Create a typeface object that best matches the specified existing
687 * typeface and the specified Style. Use this call if you want to pick a new
688 * style from the same family of an existing typeface object. If family is
689 * null, this selects from the default font's family.
691 * @param family May be null. The name of the existing type face.
692 * @param style The style (normal, bold, italic) of the typeface.
693 * e.g. NORMAL, BOLD, ITALIC, BOLD_ITALIC
694 * @return The best matching typeface.
696 public static Typeface create(Typeface family, int style) {
697 if (style < 0 || style > 3) {
701 if (family != null) {
702 // Return early if we're asked for the same face/style
703 if (family.mStyle == style) {
707 ni = family.native_instance;
711 SparseArray<Typeface> styles = sTypefaceCache.get(ni);
713 if (styles != null) {
714 typeface = styles.get(style);
715 if (typeface != null) {
720 typeface = new Typeface(nativeCreateFromTypeface(ni, style));
721 if (styles == null) {
722 styles = new SparseArray<Typeface>(4);
723 sTypefaceCache.put(ni, styles);
725 styles.put(style, typeface);
731 public static Typeface createFromTypefaceWithVariation(@Nullable Typeface family,
732 @NonNull List<FontVariationAxis> axes) {
733 final long ni = family == null ? 0 : family.native_instance;
734 return new Typeface(nativeCreateFromTypefaceWithVariation(ni, axes));
738 * Returns one of the default typeface objects, based on the specified style
740 * @return the default typeface that corresponds to the style
742 public static Typeface defaultFromStyle(int style) {
743 return sDefaults[style];
747 * Create a new typeface from the specified font data.
749 * @param mgr The application's asset manager
750 * @param path The file name of the font data in the assets directory
751 * @return The new typeface.
753 public static Typeface createFromAsset(AssetManager mgr, String path) {
755 throw new NullPointerException(); // for backward compatibility
757 synchronized (sLock) {
758 Typeface typeface = new Builder(mgr, path).build();
759 if (typeface != null) return typeface;
761 final String key = Builder.createAssetUid(mgr, path, 0 /* ttcIndex */,
762 null /* axes */, RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE,
764 typeface = sDynamicTypefaceCache.get(key);
765 if (typeface != null) return typeface;
767 final FontFamily fontFamily = new FontFamily();
768 if (fontFamily.addFontFromAssetManager(mgr, path, 0, true /* isAsset */,
769 0 /* ttc index */, RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE,
771 // Due to backward compatibility, even if the font is not supported by our font
772 // stack, we need to place the empty font at the first place. The typeface with
773 // empty font behaves different from default typeface especially in fallback
775 fontFamily.allowUnsupportedFont();
777 final FontFamily[] families = { fontFamily };
778 typeface = createFromFamiliesWithDefault(families, DEFAULT_FAMILY,
779 RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE);
780 sDynamicTypefaceCache.put(key, typeface);
783 fontFamily.abortCreation();
786 throw new RuntimeException("Font asset not found " + path);
790 * Creates a unique id for a given font provider and query.
792 private static String createProviderUid(String authority, String query) {
793 final StringBuilder builder = new StringBuilder();
794 builder.append("provider:");
795 builder.append(authority);
797 builder.append(query);
798 return builder.toString();
802 * Create a new typeface from the specified font file.
804 * @param path The path to the font data.
805 * @return The new typeface.
807 public static Typeface createFromFile(@Nullable File path) {
808 // For the compatibility reasons, leaving possible NPE here.
809 // See android.graphics.cts.TypefaceTest#testCreateFromFileByFileReferenceNull
810 return createFromFile(path.getAbsolutePath());
814 * Create a new typeface from the specified font file.
816 * @param path The full path to the font data.
817 * @return The new typeface.
819 public static Typeface createFromFile(@Nullable String path) {
820 final FontFamily fontFamily = new FontFamily();
821 if (fontFamily.addFont(path, 0 /* ttcIndex */, null /* axes */,
822 RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE)) {
823 // Due to backward compatibility, even if the font is not supported by our font
824 // stack, we need to place the empty font at the first place. The typeface with
825 // empty font behaves different from default typeface especially in fallback font
827 fontFamily.allowUnsupportedFont();
829 FontFamily[] families = { fontFamily };
830 return createFromFamiliesWithDefault(families, DEFAULT_FAMILY,
831 RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE);
833 fontFamily.abortCreation();
835 throw new RuntimeException("Font not found " + path);
839 * Create a new typeface from an array of font families.
841 * @param families array of font families
843 private static Typeface createFromFamilies(FontFamily[] families) {
844 long[] ptrArray = new long[families.length];
845 for (int i = 0; i < families.length; i++) {
846 ptrArray[i] = families[i].mNativePtr;
848 return new Typeface(nativeCreateFromArray(
849 ptrArray, RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE));
853 * Create a new typeface from an array of font families, including
854 * also the font families in the fallback list.
855 * @param fallbackName the family name. If given families don't support characters, the
856 * characters will be rendered with this family.
857 * @param weight the weight for this family. {@link RESOLVE_BY_FONT_TABLE} can be used. In that
858 * case, the table information in the first family's font is used. If the first
859 * family has multiple fonts, the closest to the regular weight and upright font
861 * @param italic the italic information for this family. {@link RESOLVE_BY_FONT_TABLE} can be
862 * used. In that case, the table information in the first family's font is used.
863 * If the first family has multiple fonts, the closest to the regular weight and
864 * upright font is used.
865 * @param families array of font families
867 private static Typeface createFromFamiliesWithDefault(FontFamily[] families,
868 String fallbackName, int weight, int italic) {
869 FontFamily[] fallback = sSystemFallbackMap.get(fallbackName);
870 if (fallback == null) {
871 fallback = sSystemFallbackMap.get(DEFAULT_FAMILY);
873 long[] ptrArray = new long[families.length + fallback.length];
874 for (int i = 0; i < families.length; i++) {
875 ptrArray[i] = families[i].mNativePtr;
877 for (int i = 0; i < fallback.length; i++) {
878 ptrArray[i + families.length] = fallback[i].mNativePtr;
880 return new Typeface(nativeCreateFromArray(ptrArray, weight, italic));
883 // don't allow clients to call this directly
884 private Typeface(long ni) {
886 throw new RuntimeException("native typeface cannot be made");
889 native_instance = ni;
890 mStyle = nativeGetStyle(ni);
891 mWeight = nativeGetWeight(ni);
894 private static @Nullable ByteBuffer mmap(String fullPath) {
895 try (FileInputStream file = new FileInputStream(fullPath)) {
896 final FileChannel fileChannel = file.getChannel();
897 final long fontSize = fileChannel.size();
898 return fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, fontSize);
899 } catch (IOException e) {
900 Log.e(TAG, "Error mapping font file " + fullPath);
905 private static @Nullable FontFamily createFontFamily(
906 String familyName, List<FontConfig.Font> fonts, String languageTag, int variant,
907 Map<String, ByteBuffer> cache, String fontDir) {
908 final FontFamily family = new FontFamily(languageTag, variant);
909 for (int i = 0; i < fonts.size(); i++) {
910 final FontConfig.Font font = fonts.get(i);
911 final String fullPath = fontDir + font.getFontName();
912 ByteBuffer buffer = cache.get(fullPath);
913 if (buffer == null) {
914 if (cache.containsKey(fullPath)) {
915 continue; // Already failed to mmap. Skip it.
917 buffer = mmap(fullPath);
918 cache.put(fullPath, buffer);
919 if (buffer == null) {
923 if (!family.addFontFromBuffer(buffer, font.getTtcIndex(), font.getAxes(),
924 font.getWeight(), font.isItalic() ? STYLE_ITALIC : STYLE_NORMAL)) {
925 Log.e(TAG, "Error creating font " + fullPath + "#" + font.getTtcIndex());
928 if (!family.freeze()) {
929 Log.e(TAG, "Unable to load Family: " + familyName + " : " + languageTag);
935 private static void pushFamilyToFallback(FontConfig.Family xmlFamily,
936 ArrayMap<String, ArrayList<FontFamily>> fallbackMap,
937 Map<String, ByteBuffer> cache,
940 final String languageTag = xmlFamily.getLanguage();
941 final int variant = xmlFamily.getVariant();
943 final ArrayList<FontConfig.Font> defaultFonts = new ArrayList<>();
944 final ArrayMap<String, ArrayList<FontConfig.Font>> specificFallbackFonts = new ArrayMap<>();
946 // Collect default fallback and specific fallback fonts.
947 for (final FontConfig.Font font : xmlFamily.getFonts()) {
948 final String fallbackName = font.getFallbackFor();
949 if (fallbackName == null) {
950 defaultFonts.add(font);
952 ArrayList<FontConfig.Font> fallback = specificFallbackFonts.get(fallbackName);
953 if (fallback == null) {
954 fallback = new ArrayList<>();
955 specificFallbackFonts.put(fallbackName, fallback);
961 final FontFamily defaultFamily = defaultFonts.isEmpty() ? null : createFontFamily(
962 xmlFamily.getName(), defaultFonts, languageTag, variant, cache, fontDir);
964 // Insert family into fallback map.
965 for (int i = 0; i < fallbackMap.size(); i++) {
966 final ArrayList<FontConfig.Font> fallback =
967 specificFallbackFonts.get(fallbackMap.keyAt(i));
968 if (fallback == null) {
969 if (defaultFamily != null) {
970 fallbackMap.valueAt(i).add(defaultFamily);
973 final FontFamily family = createFontFamily(
974 xmlFamily.getName(), fallback, languageTag, variant, cache, fontDir);
975 if (family != null) {
976 fallbackMap.valueAt(i).add(family);
983 * Build the system fallback from xml file.
985 * @param xmlPath A full path string to the fonts.xml file.
986 * @param fontDir A full path string to the system font directory. This must end with
988 * @param fontMap An output system font map. Caller must pass empty map.
989 * @param fallbackMap An output system fallback map. Caller must pass empty map.
993 public static void buildSystemFallback(String xmlPath, String fontDir,
994 ArrayMap<String, Typeface> fontMap, ArrayMap<String, FontFamily[]> fallbackMap) {
996 final FileInputStream fontsIn = new FileInputStream(xmlPath);
997 final FontConfig fontConfig = FontListParser.parse(fontsIn);
999 final HashMap<String, ByteBuffer> bufferCache = new HashMap<String, ByteBuffer>();
1000 final FontConfig.Family[] xmlFamilies = fontConfig.getFamilies();
1002 final ArrayMap<String, ArrayList<FontFamily>> fallbackListMap = new ArrayMap<>();
1003 // First traverse families which have a 'name' attribute to create fallback map.
1004 for (final FontConfig.Family xmlFamily : xmlFamilies) {
1005 final String familyName = xmlFamily.getName();
1006 if (familyName == null) {
1009 final FontFamily family = createFontFamily(
1010 xmlFamily.getName(), Arrays.asList(xmlFamily.getFonts()),
1011 xmlFamily.getLanguage(), xmlFamily.getVariant(), bufferCache, fontDir);
1012 if (family == null) {
1015 final ArrayList<FontFamily> fallback = new ArrayList<>();
1016 fallback.add(family);
1017 fallbackListMap.put(familyName, fallback);
1020 // Then, add fallback fonts to the each fallback map.
1021 for (int i = 0; i < xmlFamilies.length; i++) {
1022 final FontConfig.Family xmlFamily = xmlFamilies[i];
1023 // The first family (usually the sans-serif family) is always placed immediately
1024 // after the primary family in the fallback.
1025 if (i == 0 || xmlFamily.getName() == null) {
1026 pushFamilyToFallback(xmlFamily, fallbackListMap, bufferCache, fontDir);
1030 // Build the font map and fallback map.
1031 for (int i = 0; i < fallbackListMap.size(); i++) {
1032 final String fallbackName = fallbackListMap.keyAt(i);
1033 final List<FontFamily> familyList = fallbackListMap.valueAt(i);
1034 final FontFamily[] families = familyList.toArray(new FontFamily[familyList.size()]);
1036 fallbackMap.put(fallbackName, families);
1037 final long[] ptrArray = new long[families.length];
1038 for (int j = 0; j < families.length; j++) {
1039 ptrArray[j] = families[j].mNativePtr;
1041 fontMap.put(fallbackName, new Typeface(nativeCreateFromArray(
1042 ptrArray, RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE)));
1045 // Insert alias to font maps.
1046 for (final FontConfig.Alias alias : fontConfig.getAliases()) {
1047 Typeface base = fontMap.get(alias.getToName());
1048 Typeface newFace = base;
1049 int weight = alias.getWeight();
1050 if (weight != 400) {
1051 newFace = new Typeface(nativeCreateWeightAlias(base.native_instance, weight));
1053 fontMap.put(alias.getName(), newFace);
1055 } catch (RuntimeException e) {
1056 Log.w(TAG, "Didn't create default family (most likely, non-Minikin build)", e);
1057 // TODO: normal in non-Minikin case, remove or make error when Minikin-only
1058 } catch (FileNotFoundException e) {
1059 Log.e(TAG, "Error opening " + xmlPath, e);
1060 } catch (IOException e) {
1061 Log.e(TAG, "Error reading " + xmlPath, e);
1062 } catch (XmlPullParserException e) {
1063 Log.e(TAG, "XML parse exception for " + xmlPath, e);
1068 final ArrayMap<String, Typeface> systemFontMap = new ArrayMap<>();
1069 final ArrayMap<String, FontFamily[]> systemFallbackMap = new ArrayMap<>();
1070 buildSystemFallback("/system/etc/fonts.xml", "/system/fonts/", systemFontMap,
1072 sSystemFontMap = Collections.unmodifiableMap(systemFontMap);
1073 sSystemFallbackMap = Collections.unmodifiableMap(systemFallbackMap);
1075 setDefault(sSystemFontMap.get(DEFAULT_FAMILY));
1077 // Set up defaults and typefaces exposed in public API
1078 DEFAULT = create((String) null, 0);
1079 DEFAULT_BOLD = create((String) null, Typeface.BOLD);
1080 SANS_SERIF = create("sans-serif", 0);
1081 SERIF = create("serif", 0);
1082 MONOSPACE = create("monospace", 0);
1084 sDefaults = new Typeface[] {
1087 create((String) null, Typeface.ITALIC),
1088 create((String) null, Typeface.BOLD_ITALIC),
1094 protected void finalize() throws Throwable {
1096 nativeUnref(native_instance);
1097 native_instance = 0; // Other finalizers can still call us.
1104 public boolean equals(Object o) {
1105 if (this == o) return true;
1106 if (o == null || getClass() != o.getClass()) return false;
1108 Typeface typeface = (Typeface) o;
1110 return mStyle == typeface.mStyle && native_instance == typeface.native_instance;
1114 public int hashCode() {
1116 * Modified method for hashCode with long native_instance derived from
1117 * http://developer.android.com/reference/java/lang/Object.html
1120 result = 31 * result + (int) (native_instance ^ (native_instance >>> 32));
1121 result = 31 * result + mStyle;
1126 public boolean isSupportedAxes(int axis) {
1127 if (mSupportedAxes == null) {
1128 synchronized (this) {
1129 if (mSupportedAxes == null) {
1130 mSupportedAxes = nativeGetSupportedAxes(native_instance);
1131 if (mSupportedAxes == null) {
1132 mSupportedAxes = EMPTY_AXES;
1137 return Arrays.binarySearch(mSupportedAxes, axis) >= 0;
1140 private static native long nativeCreateFromTypeface(long native_instance, int style);
1141 private static native long nativeCreateFromTypefaceWithExactStyle(
1142 long native_instance, int weight, boolean italic);
1143 // TODO: clean up: change List<FontVariationAxis> to FontVariationAxis[]
1144 private static native long nativeCreateFromTypefaceWithVariation(
1145 long native_instance, List<FontVariationAxis> axes);
1146 private static native long nativeCreateWeightAlias(long native_instance, int weight);
1147 private static native void nativeUnref(long native_instance);
1148 private static native int nativeGetStyle(long native_instance);
1149 private static native int nativeGetWeight(long native_instance);
1150 private static native long nativeCreateFromArray(long[] familyArray, int weight, int italic);
1151 private static native void nativeSetDefault(long native_instance);
1152 private static native int[] nativeGetSupportedAxes(long native_instance);