OSDN Git Service

New attribute textFontWeight for selecting weight in the font family
[android-x86/frameworks-base.git] / graphics / java / android / graphics / Typeface.java
1 /*
2  * Copyright (C) 2006 The Android Open Source Project
3  *
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
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16
17 package android.graphics;
18
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;
23
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;
40
41 import com.android.internal.annotations.GuardedBy;
42 import com.android.internal.annotations.VisibleForTesting;
43 import com.android.internal.util.Preconditions;
44
45 import org.xmlpull.v1.XmlPullParserException;
46
47 import java.io.File;
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;
62 import java.util.Map;
63
64 /**
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).
69  */
70 public class Typeface {
71
72     private static String TAG = "Typeface";
73
74     /** The default NORMAL typeface object */
75     public static final Typeface DEFAULT;
76     /**
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
79      * for sure.
80      */
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;
88
89     static Typeface[] sDefaults;
90
91     /**
92      * Cache for Typeface objects for style variant. Currently max size is 3.
93      */
94     @GuardedBy("sStyledCacheLock")
95     private static final LongSparseArray<SparseArray<Typeface>> sStyledTypefaceCache =
96             new LongSparseArray<>(3);
97     private static final Object sStyledCacheLock = new Object();
98
99     /**
100      * Cache for Typeface objects for weight variant. Currently max size is 3.
101      */
102     @GuardedBy("sWeightCacheLock")
103     private static final LongSparseArray<SparseArray<Typeface>> sWeightTypefaceCache =
104             new LongSparseArray<>(3);
105     private static final Object sWeightCacheLock = new Object();
106
107     /**
108      * Cache for Typeface objects dynamically loaded from assets. Currently max size is 16.
109      */
110     @GuardedBy("sDynamicCacheLock")
111     private static final LruCache<String, Typeface> sDynamicTypefaceCache = new LruCache<>(16);
112     private static final Object sDynamicCacheLock = new Object();
113
114     static Typeface sDefaultTypeface;
115     static final Map<String, Typeface> sSystemFontMap;
116     static final Map<String, FontFamily[]> sSystemFallbackMap;
117
118     /**
119      * @hide
120      */
121     public long native_instance;
122
123     /** @hide */
124     @IntDef(value = {NORMAL, BOLD, ITALIC, BOLD_ITALIC})
125     @Retention(RetentionPolicy.SOURCE)
126     public @interface Style {}
127
128     // 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;
134
135     private @Style int mStyle = 0;
136
137     /**
138      * A maximum value for the weight value.
139      * @hide
140      */
141     public static final int MAX_WEIGHT = 1000;
142
143     private @IntRange(from = 0, to = MAX_WEIGHT) int mWeight = 0;
144
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
147     /** @hide */
148     public static final int RESOLVE_BY_FONT_TABLE = -1;
149     private static final String DEFAULT_FAMILY = "sans-serif";
150
151     // Style value for building typeface.
152     private static final int STYLE_NORMAL = 0;
153     private static final int STYLE_ITALIC = 1;
154
155     private int[] mSupportedAxes;
156     private static final int[] EMPTY_AXES = {};
157
158     private static void setDefault(Typeface t) {
159         sDefaultTypeface = t;
160         nativeSetDefault(t.native_instance);
161     }
162
163     // TODO: Make this public API. (b/64852739)
164     /** @hide */
165     @VisibleForTesting
166     public int getWeight() {
167         return mWeight;
168     }
169
170     /** Returns the typeface's intrinsic style attributes */
171     public @Style int getStyle() {
172         return mStyle;
173     }
174
175     /** Returns true if getStyle() has the BOLD bit set. */
176     public final boolean isBold() {
177         return (mStyle & BOLD) != 0;
178     }
179
180     /** Returns true if getStyle() has the ITALIC bit set. */
181     public final boolean isItalic() {
182         return (mStyle & ITALIC) != 0;
183     }
184
185     /**
186      * @hide
187      * Used by Resources to load a font resource of type font file.
188      */
189     @Nullable
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 */,
195                     DEFAULT_FAMILY);
196             Typeface typeface = sDynamicTypefaceCache.get(key);
197             if (typeface != null) return typeface;
198
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()) {
205                     return null;
206                 }
207                 FontFamily[] families = {fontFamily};
208                 typeface = createFromFamiliesWithDefault(families, DEFAULT_FAMILY,
209                         RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE);
210                 sDynamicTypefaceCache.put(key, typeface);
211                 return typeface;
212             }
213         }
214         return null;
215     }
216
217     /**
218      * @hide
219      * Used by Resources to load a font resource of type xml.
220      */
221     @Nullable
222     public static Typeface createFromResources(
223             FamilyResourceEntry entry, AssetManager mgr, String path) {
224         if (entry instanceof ProviderResourceEntry) {
225             final ProviderResourceEntry providerEntry = (ProviderResourceEntry) entry;
226             // Downloadable font
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));
235                     }
236                     certs.add(byteArraySet);
237                 }
238             }
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;
245         }
246
247         Typeface typeface = findFromCache(mgr, path);
248         if (typeface != null) return typeface;
249
250         // family is FontFamilyFilesResourceEntry
251         final FontFamilyFilesResourceEntry filesEntry = (FontFamilyFilesResourceEntry) entry;
252
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()))) {
259                 return null;
260             }
261         }
262         if (!fontFamily.freeze()) {
263             return null;
264         }
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);
273         }
274         return typeface;
275     }
276
277     /**
278      * Used by resources for cached loading if the font is available.
279      * @hide
280      */
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 */,
285                     DEFAULT_FAMILY);
286             Typeface typeface = sDynamicTypefaceCache.get(key);
287             if (typeface != null) {
288                 return typeface;
289             }
290         }
291         return null;
292     }
293
294     /**
295      * A builder class for creating new Typeface instance.
296      *
297      * <p>
298      * Examples,
299      * 1) Create Typeface from ttf file.
300      * <pre>
301      * <code>
302      * Typeface.Builder buidler = new Typeface.Builder("your_font_file.ttf");
303      * Typeface typeface = builder.build();
304      * </code>
305      * </pre>
306      *
307      * 2) Create Typeface from ttc file in assets directory.
308      * <pre>
309      * <code>
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();
313      * </code>
314      * </pre>
315      *
316      * 3) Create Typeface with variation settings.
317      * <pre>
318      * <code>
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();
324      * </code>
325      * </pre>
326      * </p>
327      */
328     public static final class Builder {
329         /** @hide */
330         public static final int NORMAL_WEIGHT = 400;
331         /** @hide */
332         public static final int BOLD_WEIGHT = 700;
333
334         private int mTtcIndex;
335         private FontVariationAxis[] mAxes;
336
337         private AssetManager mAssetManager;
338         private String mPath;
339         private FileDescriptor mFd;
340
341         private FontsContract.FontInfo[] mFonts;
342         private Map<Uri, ByteBuffer> mFontBuffers;
343
344         private String mFallbackFamilyName;
345
346         private int mWeight = RESOLVE_BY_FONT_TABLE;
347         private int mItalic = RESOLVE_BY_FONT_TABLE;
348
349         /**
350          * Constructs a builder with a file path.
351          *
352          * @param path The file object refers to the font file.
353          */
354         public Builder(@NonNull File path) {
355             mPath = path.getAbsolutePath();
356         }
357
358         /**
359          * Constructs a builder with a file descriptor.
360          *
361          * Caller is responsible for closing the passed file descriptor after {@link #build} is
362          * called.
363          *
364          * @param fd The file descriptor. The passed fd must be mmap-able.
365          */
366         public Builder(@NonNull FileDescriptor fd) {
367             mFd = fd;
368         }
369
370         /**
371          * Constructs a builder with a file path.
372          *
373          * @param path The full path to the font file.
374          */
375         public Builder(@NonNull String path) {
376             mPath = path;
377         }
378
379         /**
380          * Constructs a builder from an asset manager and a file path in an asset directory.
381          *
382          * @param assetManager The application's asset manager
383          * @param path The file name of the font data in the asset directory
384          */
385         public Builder(@NonNull AssetManager assetManager, @NonNull String path) {
386             mAssetManager = Preconditions.checkNotNull(assetManager);
387             mPath = Preconditions.checkStringNotEmpty(path);
388         }
389
390         /**
391          * Constracts a builder from an array of FontsContract.FontInfo.
392          *
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.
398          *
399          * @param fonts The array of {@link FontsContract.FontInfo}
400          * @param buffers The mapping from URI to buffers to be used during building.
401          * @hide
402          */
403         public Builder(@NonNull FontsContract.FontInfo[] fonts,
404                 @NonNull Map<Uri, ByteBuffer> buffers) {
405             mFonts = fonts;
406             mFontBuffers = buffers;
407         }
408
409         /**
410          * Sets weight of the font.
411          *
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.
415          */
416         public Builder setWeight(@IntRange(from = 1, to = 1000) int weight) {
417             mWeight = weight;
418             return this;
419         }
420
421         /**
422          * Sets italic information of the font.
423          *
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}.
427          */
428         public Builder setItalic(boolean italic) {
429             mItalic = italic ? STYLE_ITALIC : STYLE_NORMAL;
430             return this;
431         }
432
433         /**
434          * Sets an index of the font collection. See {@link android.R.attr#ttcIndex}.
435          *
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.
439          */
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.");
444             }
445             mTtcIndex = ttcIndex;
446             return this;
447         }
448
449         /**
450          * Sets a font variation settings.
451          *
452          * @param variationSettings See {@link android.widget.TextView#setFontVariationSettings}.
453          * @throws IllegalArgumentException If given string is not a valid font variation settings
454          *                                  format.
455          */
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.");
460             }
461             if (mAxes != null) {
462                 throw new IllegalStateException("Font variation settings are already set.");
463             }
464             mAxes = FontVariationAxis.fromFontVariationSettings(variationSettings);
465             return this;
466         }
467
468         /**
469          * Sets a font variation settings.
470          *
471          * @param axes An array of font variation axis tag-value pairs.
472          */
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.");
477             }
478             if (mAxes != null) {
479                 throw new IllegalStateException("Font variation settings are already set.");
480             }
481             mAxes = axes;
482             return this;
483         }
484
485         /**
486          * Sets a fallback family name.
487          *
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}.
492          *
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.
501          *
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}
505          *                   is assumed.
506          */
507         public Builder setFallback(@Nullable String familyName) {
508             mFallbackFamilyName = familyName;
509             return this;
510         }
511
512         /**
513          * Creates a unique id for a given AssetManager and asset path.
514          *
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.
520          */
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));
528                 builder.append("-");
529             }
530             builder.append(path);
531             builder.append("-");
532             builder.append(Integer.toString(ttcIndex));
533             builder.append("-");
534             builder.append(Integer.toString(weight));
535             builder.append("-");
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("--");
542             if (axes != null) {
543                 for (FontVariationAxis axis : axes) {
544                     builder.append(axis.getTag());
545                     builder.append("-");
546                     builder.append(Float.toString(axis.getStyleValue()));
547                 }
548             }
549             return builder.toString();
550         }
551
552         private Typeface resolveFallbackTypeface() {
553             if (mFallbackFamilyName == null) {
554                 return null;
555             }
556
557             Typeface base =  sSystemFontMap.get(mFallbackFamilyName);
558             if (base == null) {
559                 base = sDefaultTypeface;
560             }
561
562             if (mWeight == RESOLVE_BY_FONT_TABLE && mItalic == RESOLVE_BY_FONT_TABLE) {
563                 return base;
564             }
565
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);
570         }
571
572         /**
573          * Generates new Typeface from specified configuration.
574          *
575          * @return Newly created Typeface. May return null if some parameters are invalid.
576          */
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);
583
584                     final FontFamily fontFamily = new FontFamily();
585                     if (!fontFamily.addFontFromBuffer(buffer, mTtcIndex, mAxes, mWeight, mItalic)) {
586                         fontFamily.abortCreation();
587                         return resolveFallbackTypeface();
588                     }
589                     if (!fontFamily.freeze()) {
590                         return resolveFallbackTypeface();
591                     }
592                     FontFamily[] families = { fontFamily };
593                     return createFromFamiliesWithDefault(families, mFallbackFamilyName, mWeight,
594                             mItalic);
595                 } catch (IOException e) {
596                     return resolveFallbackTypeface();
597                 }
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();
610                     }
611                     if (!fontFamily.freeze()) {
612                         return resolveFallbackTypeface();
613                     }
614                     FontFamily[] families = { fontFamily };
615                     typeface = createFromFamiliesWithDefault(families, mFallbackFamilyName,
616                             mWeight, mItalic);
617                     sDynamicTypefaceCache.put(key, typeface);
618                     return typeface;
619                 }
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();
625                 }
626                 if (!fontFamily.freeze()) {
627                     return resolveFallbackTypeface();
628                 }
629                 FontFamily[] families = { fontFamily };
630                 return createFromFamiliesWithDefault(families, mFallbackFamilyName, mWeight,
631                         mItalic);
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) {
638                         continue;  // skip
639                     }
640                     final boolean success = fontFamily.addFontFromBuffer(fontBuffer,
641                             font.getTtcIndex(), font.getAxes(), font.getWeight(),
642                             font.isItalic() ? STYLE_ITALIC : STYLE_NORMAL);
643                     if (!success) {
644                         fontFamily.abortCreation();
645                         return null;
646                     }
647                     atLeastOneFont = true;
648                 }
649                 if (!atLeastOneFont) {
650                     // No fonts are avaialble. No need to create new Typeface and returns fallback
651                     // Typeface instead.
652                     fontFamily.abortCreation();
653                     return null;
654                 }
655                 fontFamily.freeze();
656                 FontFamily[] families = { fontFamily };
657                 return createFromFamiliesWithDefault(families, mFallbackFamilyName, mWeight,
658                         mItalic);
659             }
660
661             // Must not reach here.
662             throw new IllegalArgumentException("No source was set.");
663         }
664     }
665
666     /**
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.
671      *
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.
676      */
677     public static Typeface create(String familyName, @Style int style) {
678         return create(sSystemFontMap.get(familyName), style);
679     }
680
681     /**
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.
686      *
687      * <p>
688      * This method is not thread safe on API 27 or before.
689      * This method is thread safe on API 28 or after.
690      * </p>
691      *
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.
697      */
698     public static Typeface create(Typeface family, @Style int style) {
699         if ((style & ~STYLE_MASK) != 0) {
700             style = NORMAL;
701         }
702         if (family == null) {
703             family = sDefaultTypeface;
704         }
705
706         // Return early if we're asked for the same face/style
707         if (family.mStyle == style) {
708             return family;
709         }
710
711         final long ni = family.native_instance;
712
713         Typeface typeface;
714         synchronized (sStyledCacheLock) {
715             SparseArray<Typeface> styles = sStyledTypefaceCache.get(ni);
716
717             if (styles == null) {
718                 styles = new SparseArray<Typeface>(4);
719                 sStyledTypefaceCache.put(ni, styles);
720             } else {
721                 typeface = styles.get(style);
722                 if (typeface != null) {
723                     return typeface;
724                 }
725             }
726
727             typeface = new Typeface(nativeCreateFromTypeface(ni, style));
728             styles.put(style, typeface);
729         }
730         return typeface;
731     }
732
733     /**
734      * Creates a typeface object that best matches the specified existing typeface and the specified
735      * weight and italic style
736      *
737      * <p>
738      * This method is thread safe.
739      * </p>
740      *
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}
747      */
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;
753         }
754         return createWeightStyle(family, weight, italic);
755     }
756
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);
760
761         Typeface typeface;
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);
767             } else {
768                 typeface = innerCache.get(key);
769                 if (typeface != null) {
770                     return typeface;
771                 }
772             }
773
774             typeface = new Typeface(
775                     nativeCreateFromTypefaceWithExactStyle(
776                             base.native_instance, weight, italic));
777             innerCache.put(key, typeface);
778         }
779         return typeface;
780     }
781
782     /** @hide */
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));
787     }
788
789     /**
790      * Returns one of the default typeface objects, based on the specified style
791      *
792      * @return the default typeface that corresponds to the style
793      */
794     public static Typeface defaultFromStyle(@Style int style) {
795         return sDefaults[style];
796     }
797
798     /**
799      * Create a new typeface from the specified font data.
800      *
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.
804      */
805     public static Typeface createFromAsset(AssetManager mgr, String path) {
806         Preconditions.checkNotNull(path); // for backward compatibility
807         Preconditions.checkNotNull(mgr);
808
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);
815         }
816
817         return Typeface.DEFAULT;
818     }
819
820     /**
821      * Creates a unique id for a given font provider and query.
822      */
823     private static String createProviderUid(String authority, String query) {
824         final StringBuilder builder = new StringBuilder();
825         builder.append("provider:");
826         builder.append(authority);
827         builder.append("-");
828         builder.append(query);
829         return builder.toString();
830     }
831
832     /**
833      * Create a new typeface from the specified font file.
834      *
835      * @param file The path to the font data.
836      * @return The new typeface.
837      */
838     public static Typeface createFromFile(@Nullable File file) {
839         // For the compatibility reasons, leaving possible NPE here.
840         // See android.graphics.cts.TypefaceTest#testCreateFromFileByFileReferenceNull
841
842         Typeface typeface = new Builder(file).build();
843         if (typeface != null) return typeface;
844
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());
848         }
849
850         return Typeface.DEFAULT;
851     }
852
853     /**
854      * Create a new typeface from the specified font file.
855      *
856      * @param path The full path to the font data.
857      * @return The new typeface.
858      */
859     public static Typeface createFromFile(@Nullable String path) {
860         Preconditions.checkNotNull(path); // for backward compatibility
861         return createFromFile(new File(path));
862     }
863
864     /**
865      * Create a new typeface from an array of font families.
866      *
867      * @param families array of font families
868      */
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;
873         }
874         return new Typeface(nativeCreateFromArray(
875                 ptrArray, RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE));
876     }
877
878     /**
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
890      */
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);
896         }
897         long[] ptrArray = new long[families.length + fallback.length];
898         for (int i = 0; i < families.length; i++) {
899             ptrArray[i] = families[i].mNativePtr;
900         }
901         for (int i = 0; i < fallback.length; i++) {
902             ptrArray[i + families.length] = fallback[i].mNativePtr;
903         }
904         return new Typeface(nativeCreateFromArray(ptrArray, weight, italic));
905     }
906
907     // don't allow clients to call this directly
908     private Typeface(long ni) {
909         if (ni == 0) {
910             throw new RuntimeException("native typeface cannot be made");
911         }
912
913         native_instance = ni;
914         mStyle = nativeGetStyle(ni);
915         mWeight = nativeGetWeight(ni);
916     }
917
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);
925             return null;
926         }
927     }
928
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.
940                 }
941                 buffer = mmap(fullPath);
942                 cache.put(fullPath, buffer);
943                 if (buffer == null) {
944                     continue;
945                 }
946             }
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());
950             }
951         }
952         if (!family.freeze()) {
953             Log.e(TAG, "Unable to load Family: " + familyName + " : "
954                     + Arrays.toString(languageTags));
955             return null;
956         }
957         return family;
958     }
959
960     private static void pushFamilyToFallback(FontConfig.Family xmlFamily,
961             ArrayMap<String, ArrayList<FontFamily>> fallbackMap,
962             Map<String, ByteBuffer> cache,
963             String fontDir) {
964
965         final String[] languageTags = xmlFamily.getLanguages();
966         final int variant = xmlFamily.getVariant();
967
968         final ArrayList<FontConfig.Font> defaultFonts = new ArrayList<>();
969         final ArrayMap<String, ArrayList<FontConfig.Font>> specificFallbackFonts = new ArrayMap<>();
970
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);
976             } else {
977                 ArrayList<FontConfig.Font> fallback = specificFallbackFonts.get(fallbackName);
978                 if (fallback == null) {
979                     fallback = new ArrayList<>();
980                     specificFallbackFonts.put(fallbackName, fallback);
981                 }
982                 fallback.add(font);
983             }
984         }
985
986         final FontFamily defaultFamily = defaultFonts.isEmpty() ? null : createFontFamily(
987                 xmlFamily.getName(), defaultFonts, languageTags, variant, cache, fontDir);
988
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);
996                 }
997             } else {
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);
1004                 } else {
1005                     // There is no valid for for default fallback. Ignore.
1006                 }
1007             }
1008         }
1009     }
1010
1011     /**
1012      * Build the system fallback from xml file.
1013      *
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
1016      *                slash('/').
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.
1019      * @hide
1020      */
1021     @VisibleForTesting
1022     public static void buildSystemFallback(String xmlPath, String fontDir,
1023             ArrayMap<String, Typeface> fontMap, ArrayMap<String, FontFamily[]> fallbackMap) {
1024         try {
1025             final FileInputStream fontsIn = new FileInputStream(xmlPath);
1026             final FontConfig fontConfig = FontListParser.parse(fontsIn);
1027
1028             final HashMap<String, ByteBuffer> bufferCache = new HashMap<String, ByteBuffer>();
1029             final FontConfig.Family[] xmlFamilies = fontConfig.getFamilies();
1030
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) {
1036                     continue;
1037                 }
1038                 final FontFamily family = createFontFamily(
1039                         xmlFamily.getName(), Arrays.asList(xmlFamily.getFonts()),
1040                         xmlFamily.getLanguages(), xmlFamily.getVariant(), bufferCache, fontDir);
1041                 if (family == null) {
1042                     continue;
1043                 }
1044                 final ArrayList<FontFamily> fallback = new ArrayList<>();
1045                 fallback.add(family);
1046                 fallbackListMap.put(familyName, fallback);
1047             }
1048
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);
1056                 }
1057             }
1058
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()]);
1064
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;
1069                 }
1070                 fontMap.put(fallbackName, new Typeface(nativeCreateFromArray(
1071                         ptrArray, RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE)));
1072             }
1073
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));
1081                 }
1082                 fontMap.put(alias.getName(), newFace);
1083             }
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);
1093         }
1094     }
1095
1096     static {
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,
1100                 systemFallbackMap);
1101         sSystemFontMap = Collections.unmodifiableMap(systemFontMap);
1102         sSystemFallbackMap = Collections.unmodifiableMap(systemFallbackMap);
1103
1104         setDefault(sSystemFontMap.get(DEFAULT_FAMILY));
1105
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);
1112
1113         sDefaults = new Typeface[] {
1114             DEFAULT,
1115             DEFAULT_BOLD,
1116             create((String) null, Typeface.ITALIC),
1117             create((String) null, Typeface.BOLD_ITALIC),
1118         };
1119
1120     }
1121
1122     @Override
1123     protected void finalize() throws Throwable {
1124         try {
1125             nativeUnref(native_instance);
1126             native_instance = 0;  // Other finalizers can still call us.
1127         } finally {
1128             super.finalize();
1129         }
1130     }
1131
1132     @Override
1133     public boolean equals(Object o) {
1134         if (this == o) return true;
1135         if (o == null || getClass() != o.getClass()) return false;
1136
1137         Typeface typeface = (Typeface) o;
1138
1139         return mStyle == typeface.mStyle && native_instance == typeface.native_instance;
1140     }
1141
1142     @Override
1143     public int hashCode() {
1144         /*
1145          * Modified method for hashCode with long native_instance derived from
1146          * http://developer.android.com/reference/java/lang/Object.html
1147          */
1148         int result = 17;
1149         result = 31 * result + (int) (native_instance ^ (native_instance >>> 32));
1150         result = 31 * result + mStyle;
1151         return result;
1152     }
1153
1154     /** @hide */
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;
1162                     }
1163                 }
1164             }
1165         }
1166         return Arrays.binarySearch(mSupportedAxes, axis) >= 0;
1167     }
1168
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);
1182 }