OSDN Git Service

8595165aab278ced9a3fbac3cd42613f4055636e
[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.IntRange;
25 import android.annotation.NonNull;
26 import android.annotation.Nullable;
27 import android.content.res.AssetManager;
28 import android.graphics.fonts.FontVariationAxis;
29 import android.net.Uri;
30 import android.provider.FontRequest;
31 import android.provider.FontsContract;
32 import android.text.FontConfig;
33 import android.util.ArrayMap;
34 import android.util.Base64;
35 import android.util.Log;
36 import android.util.LongSparseArray;
37 import android.util.LruCache;
38 import android.util.SparseArray;
39
40 import com.android.internal.annotations.GuardedBy;
41 import com.android.internal.annotations.VisibleForTesting;
42 import com.android.internal.util.Preconditions;
43
44 import org.xmlpull.v1.XmlPullParserException;
45
46 import java.io.File;
47 import java.io.FileDescriptor;
48 import java.io.FileInputStream;
49 import java.io.FileNotFoundException;
50 import java.io.IOException;
51 import java.io.InputStream;
52 import java.nio.ByteBuffer;
53 import java.nio.channels.FileChannel;
54 import java.util.ArrayList;
55 import java.util.Arrays;
56 import java.util.Collections;
57 import java.util.HashMap;
58 import java.util.List;
59 import java.util.Map;
60
61 /**
62  * The Typeface class specifies the typeface and intrinsic style of a font.
63  * This is used in the paint, along with optionally Paint settings like
64  * textSize, textSkewX, textScaleX to specify
65  * how text appears when drawn (and measured).
66  */
67 public class Typeface {
68
69     private static String TAG = "Typeface";
70
71     /** The default NORMAL typeface object */
72     public static final Typeface DEFAULT;
73     /**
74      * The default BOLD typeface object. Note: this may be not actually be
75      * bold, depending on what fonts are installed. Call getStyle() to know
76      * for sure.
77      */
78     public static final Typeface DEFAULT_BOLD;
79     /** The NORMAL style of the default sans serif typeface. */
80     public static final Typeface SANS_SERIF;
81     /** The NORMAL style of the default serif typeface. */
82     public static final Typeface SERIF;
83     /** The NORMAL style of the default monospace typeface. */
84     public static final Typeface MONOSPACE;
85
86     static Typeface[] sDefaults;
87
88     /**
89      * Cache for Typeface objects for style variant. Currently max size is 3.
90      */
91     @GuardedBy("sStyledCacheLock")
92     private static final LongSparseArray<SparseArray<Typeface>> sStyledTypefaceCache =
93             new LongSparseArray<>(3);
94     private static final Object sStyledCacheLock = new Object();
95
96     /**
97      * Cache for Typeface objects for weight variant. Currently max size is 3.
98      */
99     @GuardedBy("sWeightCacheLock")
100     private static final LongSparseArray<SparseArray<Typeface>> sWeightTypefaceCache =
101             new LongSparseArray<>(3);
102     private static final Object sWeightCacheLock = new Object();
103
104     /**
105      * Cache for Typeface objects dynamically loaded from assets. Currently max size is 16.
106      */
107     @GuardedBy("sDynamicCacheLock")
108     private static final LruCache<String, Typeface> sDynamicTypefaceCache = new LruCache<>(16);
109     private static final Object sDynamicCacheLock = new Object();
110
111     static Typeface sDefaultTypeface;
112     static final Map<String, Typeface> sSystemFontMap;
113     static final Map<String, FontFamily[]> sSystemFallbackMap;
114
115     /**
116      * @hide
117      */
118     public long native_instance;
119
120     // Style
121     public static final int NORMAL = 0;
122     public static final int BOLD = 1;
123     public static final int ITALIC = 2;
124     public static final int BOLD_ITALIC = 3;
125     /** @hide */ public static final int STYLE_MASK = 0x03;
126
127     private int mStyle = 0;
128     private int mWeight = 0;
129
130     // Value for weight and italic. Indicates the value is resolved by font metadata.
131     // Must be the same as the C++ constant in core/jni/android/graphics/FontFamily.cpp
132     /** @hide */
133     public static final int RESOLVE_BY_FONT_TABLE = -1;
134     private static final String DEFAULT_FAMILY = "sans-serif";
135
136     // Style value for building typeface.
137     private static final int STYLE_NORMAL = 0;
138     private static final int STYLE_ITALIC = 1;
139
140     private int[] mSupportedAxes;
141     private static final int[] EMPTY_AXES = {};
142
143     private static void setDefault(Typeface t) {
144         sDefaultTypeface = t;
145         nativeSetDefault(t.native_instance);
146     }
147
148     // TODO: Make this public API. (b/64852739)
149     /** @hide */
150     @VisibleForTesting
151     public int getWeight() {
152         return mWeight;
153     }
154
155     /** Returns the typeface's intrinsic style attributes */
156     public int getStyle() {
157         return mStyle;
158     }
159
160     /** Returns true if getStyle() has the BOLD bit set. */
161     public final boolean isBold() {
162         return (mStyle & BOLD) != 0;
163     }
164
165     /** Returns true if getStyle() has the ITALIC bit set. */
166     public final boolean isItalic() {
167         return (mStyle & ITALIC) != 0;
168     }
169
170     /**
171      * @hide
172      * Used by Resources to load a font resource of type font file.
173      */
174     @Nullable
175     public static Typeface createFromResources(AssetManager mgr, String path, int cookie) {
176         synchronized (sDynamicCacheLock) {
177             final String key = Builder.createAssetUid(
178                     mgr, path, 0 /* ttcIndex */, null /* axes */,
179                     RESOLVE_BY_FONT_TABLE /* weight */, RESOLVE_BY_FONT_TABLE /* italic */,
180                     DEFAULT_FAMILY);
181             Typeface typeface = sDynamicTypefaceCache.get(key);
182             if (typeface != null) return typeface;
183
184             FontFamily fontFamily = new FontFamily();
185             // TODO: introduce ttc index and variation settings to resource type font.
186             if (fontFamily.addFontFromAssetManager(mgr, path, cookie, false /* isAsset */,
187                     0 /* ttcIndex */, RESOLVE_BY_FONT_TABLE /* weight */,
188                     RESOLVE_BY_FONT_TABLE /* italic */, null /* axes */)) {
189                 if (!fontFamily.freeze()) {
190                     return null;
191                 }
192                 FontFamily[] families = {fontFamily};
193                 typeface = createFromFamiliesWithDefault(families, DEFAULT_FAMILY,
194                         RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE);
195                 sDynamicTypefaceCache.put(key, typeface);
196                 return typeface;
197             }
198         }
199         return null;
200     }
201
202     /**
203      * @hide
204      * Used by Resources to load a font resource of type xml.
205      */
206     @Nullable
207     public static Typeface createFromResources(
208             FamilyResourceEntry entry, AssetManager mgr, String path) {
209         if (entry instanceof ProviderResourceEntry) {
210             final ProviderResourceEntry providerEntry = (ProviderResourceEntry) entry;
211             // Downloadable font
212             List<List<String>> givenCerts = providerEntry.getCerts();
213             List<List<byte[]>> certs = new ArrayList<>();
214             if (givenCerts != null) {
215                 for (int i = 0; i < givenCerts.size(); i++) {
216                     List<String> certSet = givenCerts.get(i);
217                     List<byte[]> byteArraySet = new ArrayList<>();
218                     for (int j = 0; j < certSet.size(); j++) {
219                         byteArraySet.add(Base64.decode(certSet.get(j), Base64.DEFAULT));
220                     }
221                     certs.add(byteArraySet);
222                 }
223             }
224             // Downloaded font and it wasn't cached, request it again and return a
225             // default font instead (nothing we can do now).
226             FontRequest request = new FontRequest(providerEntry.getAuthority(),
227                     providerEntry.getPackage(), providerEntry.getQuery(), certs);
228             Typeface typeface = FontsContract.getFontSync(request);
229             return typeface == null ? DEFAULT : typeface;
230         }
231
232         Typeface typeface = findFromCache(mgr, path);
233         if (typeface != null) return typeface;
234
235         // family is FontFamilyFilesResourceEntry
236         final FontFamilyFilesResourceEntry filesEntry = (FontFamilyFilesResourceEntry) entry;
237
238         FontFamily fontFamily = new FontFamily();
239         for (final FontFileResourceEntry fontFile : filesEntry.getEntries()) {
240             if (!fontFamily.addFontFromAssetManager(mgr, fontFile.getFileName(),
241                     0 /* resourceCookie */, false /* isAsset */, fontFile.getTtcIndex(),
242                     fontFile.getWeight(), fontFile.getItalic(),
243                     FontVariationAxis.fromFontVariationSettings(fontFile.getVariationSettings()))) {
244                 return null;
245             }
246         }
247         if (!fontFamily.freeze()) {
248             return null;
249         }
250         FontFamily[] familyChain = { fontFamily };
251         typeface = createFromFamiliesWithDefault(familyChain, DEFAULT_FAMILY,
252                 RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE);
253         synchronized (sDynamicCacheLock) {
254             final String key = Builder.createAssetUid(mgr, path, 0 /* ttcIndex */,
255                     null /* axes */, RESOLVE_BY_FONT_TABLE /* weight */,
256                     RESOLVE_BY_FONT_TABLE /* italic */, DEFAULT_FAMILY);
257             sDynamicTypefaceCache.put(key, typeface);
258         }
259         return typeface;
260     }
261
262     /**
263      * Used by resources for cached loading if the font is available.
264      * @hide
265      */
266     public static Typeface findFromCache(AssetManager mgr, String path) {
267         synchronized (sDynamicCacheLock) {
268             final String key = Builder.createAssetUid(mgr, path, 0 /* ttcIndex */, null /* axes */,
269                     RESOLVE_BY_FONT_TABLE /* weight */, RESOLVE_BY_FONT_TABLE /* italic */,
270                     DEFAULT_FAMILY);
271             Typeface typeface = sDynamicTypefaceCache.get(key);
272             if (typeface != null) {
273                 return typeface;
274             }
275         }
276         return null;
277     }
278
279     /**
280      * A builder class for creating new Typeface instance.
281      *
282      * <p>
283      * Examples,
284      * 1) Create Typeface from ttf file.
285      * <pre>
286      * <code>
287      * Typeface.Builder buidler = new Typeface.Builder("your_font_file.ttf");
288      * Typeface typeface = builder.build();
289      * </code>
290      * </pre>
291      *
292      * 2) Create Typeface from ttc file in assets directory.
293      * <pre>
294      * <code>
295      * Typeface.Builder buidler = new Typeface.Builder(getAssets(), "your_font_file.ttc");
296      * builder.setTtcIndex(2);  // Set index of font collection.
297      * Typeface typeface = builder.build();
298      * </code>
299      * </pre>
300      *
301      * 3) Create Typeface with variation settings.
302      * <pre>
303      * <code>
304      * Typeface.Builder buidler = new Typeface.Builder("your_font_file.ttf");
305      * builder.setFontVariationSettings("'wght' 700, 'slnt' 20, 'ital' 1");
306      * builder.setWeight(700);  // Tell the system that this is a bold font.
307      * builder.setItalic(true);  // Tell the system that this is an italic style font.
308      * Typeface typeface = builder.build();
309      * </code>
310      * </pre>
311      * </p>
312      */
313     public static final class Builder {
314         /** @hide */
315         public static final int NORMAL_WEIGHT = 400;
316         /** @hide */
317         public static final int BOLD_WEIGHT = 700;
318
319         private int mTtcIndex;
320         private FontVariationAxis[] mAxes;
321
322         private AssetManager mAssetManager;
323         private String mPath;
324         private FileDescriptor mFd;
325
326         private FontsContract.FontInfo[] mFonts;
327         private Map<Uri, ByteBuffer> mFontBuffers;
328
329         private String mFallbackFamilyName;
330
331         private int mWeight = RESOLVE_BY_FONT_TABLE;
332         private int mItalic = RESOLVE_BY_FONT_TABLE;
333
334         /**
335          * Constructs a builder with a file path.
336          *
337          * @param path The file object refers to the font file.
338          */
339         public Builder(@NonNull File path) {
340             mPath = path.getAbsolutePath();
341         }
342
343         /**
344          * Constructs a builder with a file descriptor.
345          *
346          * Caller is responsible for closing the passed file descriptor after {@link #build} is
347          * called.
348          *
349          * @param fd The file descriptor. The passed fd must be mmap-able.
350          */
351         public Builder(@NonNull FileDescriptor fd) {
352             mFd = fd;
353         }
354
355         /**
356          * Constructs a builder with a file path.
357          *
358          * @param path The full path to the font file.
359          */
360         public Builder(@NonNull String path) {
361             mPath = path;
362         }
363
364         /**
365          * Constructs a builder from an asset manager and a file path in an asset directory.
366          *
367          * @param assetManager The application's asset manager
368          * @param path The file name of the font data in the asset directory
369          */
370         public Builder(@NonNull AssetManager assetManager, @NonNull String path) {
371             mAssetManager = Preconditions.checkNotNull(assetManager);
372             mPath = Preconditions.checkStringNotEmpty(path);
373         }
374
375         /**
376          * Constracts a builder from an array of FontsContract.FontInfo.
377          *
378          * Since {@link FontsContract.FontInfo} holds information about TTC indices and
379          * variation settings, there is no need to call {@link #setTtcIndex} or
380          * {@link #setFontVariationSettings}. Similary, {@link FontsContract.FontInfo} holds
381          * weight and italic information, so {@link #setWeight} and {@link #setItalic} are used
382          * for style matching during font selection.
383          *
384          * @param fonts The array of {@link FontsContract.FontInfo}
385          * @param buffers The mapping from URI to buffers to be used during building.
386          * @hide
387          */
388         public Builder(@NonNull FontsContract.FontInfo[] fonts,
389                 @NonNull Map<Uri, ByteBuffer> buffers) {
390             mFonts = fonts;
391             mFontBuffers = buffers;
392         }
393
394         /**
395          * Sets weight of the font.
396          *
397          * Tells the system the weight of the given font. If not provided, the system will resolve
398          * the weight value by reading font tables.
399          * @param weight a weight value.
400          */
401         public Builder setWeight(@IntRange(from = 1, to = 1000) int weight) {
402             mWeight = weight;
403             return this;
404         }
405
406         /**
407          * Sets italic information of the font.
408          *
409          * Tells the system the style of the given font. If not provided, the system will resolve
410          * the style by reading font tables.
411          * @param italic {@code true} if the font is italic. Otherwise {@code false}.
412          */
413         public Builder setItalic(boolean italic) {
414             mItalic = italic ? STYLE_ITALIC : STYLE_NORMAL;
415             return this;
416         }
417
418         /**
419          * Sets an index of the font collection. See {@link android.R.attr#ttcIndex}.
420          *
421          * Can not be used for Typeface source. build() method will return null for invalid index.
422          * @param ttcIndex An index of the font collection. If the font source is not font
423          *                 collection, do not call this method or specify 0.
424          */
425         public Builder setTtcIndex(@IntRange(from = 0) int ttcIndex) {
426             if (mFonts != null) {
427                 throw new IllegalArgumentException(
428                         "TTC index can not be specified for FontResult source.");
429             }
430             mTtcIndex = ttcIndex;
431             return this;
432         }
433
434         /**
435          * Sets a font variation settings.
436          *
437          * @param variationSettings See {@link android.widget.TextView#setFontVariationSettings}.
438          * @throws IllegalArgumentException If given string is not a valid font variation settings
439          *                                  format.
440          */
441         public Builder setFontVariationSettings(@Nullable String variationSettings) {
442             if (mFonts != null) {
443                 throw new IllegalArgumentException(
444                         "Font variation settings can not be specified for FontResult source.");
445             }
446             if (mAxes != null) {
447                 throw new IllegalStateException("Font variation settings are already set.");
448             }
449             mAxes = FontVariationAxis.fromFontVariationSettings(variationSettings);
450             return this;
451         }
452
453         /**
454          * Sets a font variation settings.
455          *
456          * @param axes An array of font variation axis tag-value pairs.
457          */
458         public Builder setFontVariationSettings(@Nullable FontVariationAxis[] axes) {
459             if (mFonts != null) {
460                 throw new IllegalArgumentException(
461                         "Font variation settings can not be specified for FontResult source.");
462             }
463             if (mAxes != null) {
464                 throw new IllegalStateException("Font variation settings are already set.");
465             }
466             mAxes = axes;
467             return this;
468         }
469
470         /**
471          * Sets a fallback family name.
472          *
473          * By specifying a fallback family name, a fallback Typeface will be returned if the
474          * {@link #build} method fails to create a Typeface from the provided font. The fallback
475          * family will be resolved with the provided weight and italic information specified by
476          * {@link #setWeight} and {@link #setItalic}.
477          *
478          * If {@link #setWeight} is not called, the fallback family keeps the default weight.
479          * Similary, if {@link #setItalic} is not called, the fallback family keeps the default
480          * italic information. For example, calling {@code builder.setFallback("sans-serif-light")}
481          * is equivalent to calling {@code builder.setFallback("sans-serif").setWeight(300)} in
482          * terms of fallback. The default weight and italic information are overridden by calling
483          * {@link #setWeight} and {@link #setItalic}. For example, if a Typeface is constructed
484          * using {@code builder.setFallback("sans-serif-light").setWeight(700)}, the fallback text
485          * will render as sans serif bold.
486          *
487          * @param familyName A family name to be used for fallback if the provided font can not be
488          *                   used. By passing {@code null}, build() returns {@code null}.
489          *                   If {@link #setFallback} is not called on the builder, {@code null}
490          *                   is assumed.
491          */
492         public Builder setFallback(@Nullable String familyName) {
493             mFallbackFamilyName = familyName;
494             return this;
495         }
496
497         /**
498          * Creates a unique id for a given AssetManager and asset path.
499          *
500          * @param mgr  AssetManager instance
501          * @param path The path for the asset.
502          * @param ttcIndex The TTC index for the font.
503          * @param axes The font variation settings.
504          * @return Unique id for a given AssetManager and asset path.
505          */
506         private static String createAssetUid(final AssetManager mgr, String path, int ttcIndex,
507                 @Nullable FontVariationAxis[] axes, int weight, int italic, String fallback) {
508             final SparseArray<String> pkgs = mgr.getAssignedPackageIdentifiers();
509             final StringBuilder builder = new StringBuilder();
510             final int size = pkgs.size();
511             for (int i = 0; i < size; i++) {
512                 builder.append(pkgs.valueAt(i));
513                 builder.append("-");
514             }
515             builder.append(path);
516             builder.append("-");
517             builder.append(Integer.toString(ttcIndex));
518             builder.append("-");
519             builder.append(Integer.toString(weight));
520             builder.append("-");
521             builder.append(Integer.toString(italic));
522             // Family name may contain hyphen. Use double hyphen for avoiding key conflicts before
523             // and after appending falblack name.
524             builder.append("--");
525             builder.append(fallback);
526             builder.append("--");
527             if (axes != null) {
528                 for (FontVariationAxis axis : axes) {
529                     builder.append(axis.getTag());
530                     builder.append("-");
531                     builder.append(Float.toString(axis.getStyleValue()));
532                 }
533             }
534             return builder.toString();
535         }
536
537         private Typeface resolveFallbackTypeface() {
538             if (mFallbackFamilyName == null) {
539                 return null;
540             }
541
542             Typeface base =  sSystemFontMap.get(mFallbackFamilyName);
543             if (base == null) {
544                 base = sDefaultTypeface;
545             }
546
547             if (mWeight == RESOLVE_BY_FONT_TABLE && mItalic == RESOLVE_BY_FONT_TABLE) {
548                 return base;
549             }
550
551             final int weight = (mWeight == RESOLVE_BY_FONT_TABLE) ? base.mWeight : mWeight;
552             final boolean italic =
553                     (mItalic == RESOLVE_BY_FONT_TABLE) ? (base.mStyle & ITALIC) != 0 : mItalic == 1;
554             return createWeightStyle(base, weight, italic);
555         }
556
557         /**
558          * Generates new Typeface from specified configuration.
559          *
560          * @return Newly created Typeface. May return null if some parameters are invalid.
561          */
562         public Typeface build() {
563             if (mFd != null) {  // Builder is created with file descriptor.
564                 try (FileInputStream fis = new FileInputStream(mFd)) {
565                     FileChannel channel = fis.getChannel();
566                     long size = channel.size();
567                     ByteBuffer buffer = channel.map(FileChannel.MapMode.READ_ONLY, 0, size);
568
569                     final FontFamily fontFamily = new FontFamily();
570                     if (!fontFamily.addFontFromBuffer(buffer, mTtcIndex, mAxes, mWeight, mItalic)) {
571                         fontFamily.abortCreation();
572                         return resolveFallbackTypeface();
573                     }
574                     if (!fontFamily.freeze()) {
575                         return resolveFallbackTypeface();
576                     }
577                     FontFamily[] families = { fontFamily };
578                     return createFromFamiliesWithDefault(families, mFallbackFamilyName, mWeight,
579                             mItalic);
580                 } catch (IOException e) {
581                     return resolveFallbackTypeface();
582                 }
583             } else if (mAssetManager != null) {  // Builder is created with asset manager.
584                 final String key = createAssetUid(
585                         mAssetManager, mPath, mTtcIndex, mAxes, mWeight, mItalic,
586                         mFallbackFamilyName);
587                 synchronized (sDynamicCacheLock) {
588                     Typeface typeface = sDynamicTypefaceCache.get(key);
589                     if (typeface != null) return typeface;
590                     final FontFamily fontFamily = new FontFamily();
591                     if (!fontFamily.addFontFromAssetManager(mAssetManager, mPath, mTtcIndex,
592                             true /* isAsset */, mTtcIndex, mWeight, mItalic, mAxes)) {
593                         fontFamily.abortCreation();
594                         return resolveFallbackTypeface();
595                     }
596                     if (!fontFamily.freeze()) {
597                         return resolveFallbackTypeface();
598                     }
599                     FontFamily[] families = { fontFamily };
600                     typeface = createFromFamiliesWithDefault(families, mFallbackFamilyName,
601                             mWeight, mItalic);
602                     sDynamicTypefaceCache.put(key, typeface);
603                     return typeface;
604                 }
605             } else if (mPath != null) {  // Builder is created with file path.
606                 final FontFamily fontFamily = new FontFamily();
607                 if (!fontFamily.addFont(mPath, mTtcIndex, mAxes, mWeight, mItalic)) {
608                     fontFamily.abortCreation();
609                     return resolveFallbackTypeface();
610                 }
611                 if (!fontFamily.freeze()) {
612                     return resolveFallbackTypeface();
613                 }
614                 FontFamily[] families = { fontFamily };
615                 return createFromFamiliesWithDefault(families, mFallbackFamilyName, mWeight,
616                         mItalic);
617             } else if (mFonts != null) {
618                 final FontFamily fontFamily = new FontFamily();
619                 boolean atLeastOneFont = false;
620                 for (FontsContract.FontInfo font : mFonts) {
621                     final ByteBuffer fontBuffer = mFontBuffers.get(font.getUri());
622                     if (fontBuffer == null) {
623                         continue;  // skip
624                     }
625                     final boolean success = fontFamily.addFontFromBuffer(fontBuffer,
626                             font.getTtcIndex(), font.getAxes(), font.getWeight(),
627                             font.isItalic() ? STYLE_ITALIC : STYLE_NORMAL);
628                     if (!success) {
629                         fontFamily.abortCreation();
630                         return null;
631                     }
632                     atLeastOneFont = true;
633                 }
634                 if (!atLeastOneFont) {
635                     // No fonts are avaialble. No need to create new Typeface and returns fallback
636                     // Typeface instead.
637                     fontFamily.abortCreation();
638                     return null;
639                 }
640                 fontFamily.freeze();
641                 FontFamily[] families = { fontFamily };
642                 return createFromFamiliesWithDefault(families, mFallbackFamilyName, mWeight,
643                         mItalic);
644             }
645
646             // Must not reach here.
647             throw new IllegalArgumentException("No source was set.");
648         }
649     }
650
651     /**
652      * Create a typeface object given a family name, and option style information.
653      * If null is passed for the name, then the "default" font will be chosen.
654      * The resulting typeface object can be queried (getStyle()) to discover what
655      * its "real" style characteristics are.
656      *
657      * @param familyName May be null. The name of the font family.
658      * @param style  The style (normal, bold, italic) of the typeface.
659      *               e.g. NORMAL, BOLD, ITALIC, BOLD_ITALIC
660      * @return The best matching typeface.
661      */
662     public static Typeface create(String familyName, int style) {
663         return create(sSystemFontMap.get(familyName), style);
664     }
665
666     /**
667      * Create a typeface object that best matches the specified existing
668      * typeface and the specified Style. Use this call if you want to pick a new
669      * style from the same family of an existing typeface object. If family is
670      * null, this selects from the default font's family.
671      *
672      * <p>
673      * This method is not thread safe on API 27 or before.
674      * This method is thread safe on API 28 or after.
675      * </p>
676      *
677      * @param family An existing {@link Typeface} object. In case of {@code null}, the default
678      *               typeface is used instead.
679      * @param style  The style (normal, bold, italic) of the typeface.
680      *               e.g. NORMAL, BOLD, ITALIC, BOLD_ITALIC
681      * @return The best matching typeface.
682      */
683     public static Typeface create(Typeface family, int style) {
684         if ((style & ~STYLE_MASK) != 0) {
685             style = NORMAL;
686         }
687         if (family == null) {
688             family = sDefaultTypeface;
689         }
690
691         // Return early if we're asked for the same face/style
692         if (family.mStyle == style) {
693             return family;
694         }
695
696         final long ni = family.native_instance;
697
698         Typeface typeface;
699         synchronized (sStyledCacheLock) {
700             SparseArray<Typeface> styles = sStyledTypefaceCache.get(ni);
701
702             if (styles == null) {
703                 styles = new SparseArray<Typeface>(4);
704                 sStyledTypefaceCache.put(ni, styles);
705             } else {
706                 typeface = styles.get(style);
707                 if (typeface != null) {
708                     return typeface;
709                 }
710             }
711
712             typeface = new Typeface(nativeCreateFromTypeface(ni, style));
713             styles.put(style, typeface);
714         }
715         return typeface;
716     }
717
718     /**
719      * Creates a typeface object that best matches the specified existing typeface and the specified
720      * weight and italic style
721      *
722      * <p>
723      * This method is thread safe.
724      * </p>
725      *
726      * @param family An existing {@link Typeface} object. In case of {@code null}, the default
727      *               typeface is used instead.
728      * @param weight The desired weight to be drawn.
729      * @param italic {@code true} if italic style is desired to be drawn. Otherwise, {@code false}
730      * @return A {@link Typeface} object for drawing specified weight and italic style. Never
731      *         returns {@code null}
732      */
733     public static @NonNull Typeface create(@Nullable Typeface family,
734             @IntRange(from = 1, to = 1000) int weight, boolean italic) {
735         Preconditions.checkArgumentInRange(weight, 0, 1000, "weight");
736         if (family == null) {
737             family = sDefaultTypeface;
738         }
739         return createWeightStyle(family, weight, italic);
740     }
741
742     private static @NonNull Typeface createWeightStyle(@NonNull Typeface base,
743             @IntRange(from = 1, to = 1000) int weight, boolean italic) {
744         final int key = (weight << 1) | (italic ? 1 : 0);
745
746         Typeface typeface;
747         synchronized(sWeightCacheLock) {
748             SparseArray<Typeface> innerCache = sWeightTypefaceCache.get(base.native_instance);
749             if (innerCache == null) {
750                 innerCache = new SparseArray<>(4);
751                 sWeightTypefaceCache.put(base.native_instance, innerCache);
752             } else {
753                 typeface = innerCache.get(key);
754                 if (typeface != null) {
755                     return typeface;
756                 }
757             }
758
759             typeface = new Typeface(
760                     nativeCreateFromTypefaceWithExactStyle(
761                             base.native_instance, weight, italic));
762             innerCache.put(key, typeface);
763         }
764         return typeface;
765     }
766
767     /** @hide */
768     public static Typeface createFromTypefaceWithVariation(@Nullable Typeface family,
769             @NonNull List<FontVariationAxis> axes) {
770         final long ni = family == null ? 0 : family.native_instance;
771         return new Typeface(nativeCreateFromTypefaceWithVariation(ni, axes));
772     }
773
774     /**
775      * Returns one of the default typeface objects, based on the specified style
776      *
777      * @return the default typeface that corresponds to the style
778      */
779     public static Typeface defaultFromStyle(int style) {
780         return sDefaults[style];
781     }
782
783     /**
784      * Create a new typeface from the specified font data.
785      *
786      * @param mgr  The application's asset manager
787      * @param path The file name of the font data in the assets directory
788      * @return The new typeface.
789      */
790     public static Typeface createFromAsset(AssetManager mgr, String path) {
791         Preconditions.checkNotNull(path); // for backward compatibility
792         Preconditions.checkNotNull(mgr);
793
794         Typeface typeface = new Builder(mgr, path).build();
795         if (typeface != null) return typeface;
796         // check if the file exists, and throw an exception for backward compatibility
797         try (InputStream inputStream = mgr.open(path)) {
798         } catch (IOException e) {
799             throw new RuntimeException("Font asset not found " + path);
800         }
801
802         return Typeface.DEFAULT;
803     }
804
805     /**
806      * Creates a unique id for a given font provider and query.
807      */
808     private static String createProviderUid(String authority, String query) {
809         final StringBuilder builder = new StringBuilder();
810         builder.append("provider:");
811         builder.append(authority);
812         builder.append("-");
813         builder.append(query);
814         return builder.toString();
815     }
816
817     /**
818      * Create a new typeface from the specified font file.
819      *
820      * @param file The path to the font data.
821      * @return The new typeface.
822      */
823     public static Typeface createFromFile(@Nullable File file) {
824         // For the compatibility reasons, leaving possible NPE here.
825         // See android.graphics.cts.TypefaceTest#testCreateFromFileByFileReferenceNull
826
827         Typeface typeface = new Builder(file).build();
828         if (typeface != null) return typeface;
829
830         // check if the file exists, and throw an exception for backward compatibility
831         if (!file.exists()) {
832             throw new RuntimeException("Font asset not found " + file.getAbsolutePath());
833         }
834
835         return Typeface.DEFAULT;
836     }
837
838     /**
839      * Create a new typeface from the specified font file.
840      *
841      * @param path The full path to the font data.
842      * @return The new typeface.
843      */
844     public static Typeface createFromFile(@Nullable String path) {
845         Preconditions.checkNotNull(path); // for backward compatibility
846         return createFromFile(new File(path));
847     }
848
849     /**
850      * Create a new typeface from an array of font families.
851      *
852      * @param families array of font families
853      */
854     private static Typeface createFromFamilies(FontFamily[] families) {
855         long[] ptrArray = new long[families.length];
856         for (int i = 0; i < families.length; i++) {
857             ptrArray[i] = families[i].mNativePtr;
858         }
859         return new Typeface(nativeCreateFromArray(
860                 ptrArray, RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE));
861     }
862
863     /**
864      * Create a new typeface from an array of font families, including
865      * also the font families in the fallback list.
866      * @param fallbackName the family name. If given families don't support characters, the
867      *               characters will be rendered with this family.
868      * @param weight the weight for this family. In that case, the table information in the first
869      *               family's font is used. If the first family has multiple fonts, the closest to
870      *               the regular weight and upright font is used.
871      * @param italic the italic information for this family. In that case, the table information in
872      *               the first family's font is used. If the first family has multiple fonts, the
873      *               closest to the regular weight and upright font is used.
874      * @param families array of font families
875      */
876     private static Typeface createFromFamiliesWithDefault(FontFamily[] families,
877                 String fallbackName, int weight, int italic) {
878         FontFamily[] fallback = sSystemFallbackMap.get(fallbackName);
879         if (fallback == null) {
880             fallback = sSystemFallbackMap.get(DEFAULT_FAMILY);
881         }
882         long[] ptrArray = new long[families.length + fallback.length];
883         for (int i = 0; i < families.length; i++) {
884             ptrArray[i] = families[i].mNativePtr;
885         }
886         for (int i = 0; i < fallback.length; i++) {
887             ptrArray[i + families.length] = fallback[i].mNativePtr;
888         }
889         return new Typeface(nativeCreateFromArray(ptrArray, weight, italic));
890     }
891
892     // don't allow clients to call this directly
893     private Typeface(long ni) {
894         if (ni == 0) {
895             throw new RuntimeException("native typeface cannot be made");
896         }
897
898         native_instance = ni;
899         mStyle = nativeGetStyle(ni);
900         mWeight = nativeGetWeight(ni);
901     }
902
903     private static @Nullable ByteBuffer mmap(String fullPath) {
904         try (FileInputStream file = new FileInputStream(fullPath)) {
905             final FileChannel fileChannel = file.getChannel();
906             final long fontSize = fileChannel.size();
907             return fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, fontSize);
908         } catch (IOException e) {
909             Log.e(TAG, "Error mapping font file " + fullPath);
910             return null;
911         }
912     }
913
914     private static @Nullable FontFamily createFontFamily(
915             String familyName, List<FontConfig.Font> fonts, String[] languageTags, int variant,
916             Map<String, ByteBuffer> cache, String fontDir) {
917         final FontFamily family = new FontFamily(languageTags, variant);
918         for (int i = 0; i < fonts.size(); i++) {
919             final FontConfig.Font font = fonts.get(i);
920             final String fullPath = fontDir + font.getFontName();
921             ByteBuffer buffer = cache.get(fullPath);
922             if (buffer == null) {
923                 if (cache.containsKey(fullPath)) {
924                     continue;  // Already failed to mmap. Skip it.
925                 }
926                 buffer = mmap(fullPath);
927                 cache.put(fullPath, buffer);
928                 if (buffer == null) {
929                     continue;
930                 }
931             }
932             if (!family.addFontFromBuffer(buffer, font.getTtcIndex(), font.getAxes(),
933                     font.getWeight(), font.isItalic() ? STYLE_ITALIC : STYLE_NORMAL)) {
934                 Log.e(TAG, "Error creating font " + fullPath + "#" + font.getTtcIndex());
935             }
936         }
937         if (!family.freeze()) {
938             Log.e(TAG, "Unable to load Family: " + familyName + " : "
939                     + Arrays.toString(languageTags));
940             return null;
941         }
942         return family;
943     }
944
945     private static void pushFamilyToFallback(FontConfig.Family xmlFamily,
946             ArrayMap<String, ArrayList<FontFamily>> fallbackMap,
947             Map<String, ByteBuffer> cache,
948             String fontDir) {
949
950         final String[] languageTags = xmlFamily.getLanguages();
951         final int variant = xmlFamily.getVariant();
952
953         final ArrayList<FontConfig.Font> defaultFonts = new ArrayList<>();
954         final ArrayMap<String, ArrayList<FontConfig.Font>> specificFallbackFonts = new ArrayMap<>();
955
956         // Collect default fallback and specific fallback fonts.
957         for (final FontConfig.Font font : xmlFamily.getFonts()) {
958             final String fallbackName = font.getFallbackFor();
959             if (fallbackName == null) {
960                 defaultFonts.add(font);
961             } else {
962                 ArrayList<FontConfig.Font> fallback = specificFallbackFonts.get(fallbackName);
963                 if (fallback == null) {
964                     fallback = new ArrayList<>();
965                     specificFallbackFonts.put(fallbackName, fallback);
966                 }
967                 fallback.add(font);
968             }
969         }
970
971         final FontFamily defaultFamily = defaultFonts.isEmpty() ? null : createFontFamily(
972                 xmlFamily.getName(), defaultFonts, languageTags, variant, cache, fontDir);
973
974         // Insert family into fallback map.
975         for (int i = 0; i < fallbackMap.size(); i++) {
976             final ArrayList<FontConfig.Font> fallback =
977                     specificFallbackFonts.get(fallbackMap.keyAt(i));
978             if (fallback == null) {
979                 if (defaultFamily != null) {
980                     fallbackMap.valueAt(i).add(defaultFamily);
981                 }
982             } else {
983                 final FontFamily family = createFontFamily(
984                         xmlFamily.getName(), fallback, languageTags, variant, cache, fontDir);
985                 if (family != null) {
986                     fallbackMap.valueAt(i).add(family);
987                 } else if (defaultFamily != null) {
988                     fallbackMap.valueAt(i).add(defaultFamily);
989                 } else {
990                     // There is no valid for for default fallback. Ignore.
991                 }
992             }
993         }
994     }
995
996     /**
997      * Build the system fallback from xml file.
998      *
999      * @param xmlPath A full path string to the fonts.xml file.
1000      * @param fontDir A full path string to the system font directory. This must end with
1001      *                slash('/').
1002      * @param fontMap An output system font map. Caller must pass empty map.
1003      * @param fallbackMap An output system fallback map. Caller must pass empty map.
1004      * @hide
1005      */
1006     @VisibleForTesting
1007     public static void buildSystemFallback(String xmlPath, String fontDir,
1008             ArrayMap<String, Typeface> fontMap, ArrayMap<String, FontFamily[]> fallbackMap) {
1009         try {
1010             final FileInputStream fontsIn = new FileInputStream(xmlPath);
1011             final FontConfig fontConfig = FontListParser.parse(fontsIn);
1012
1013             final HashMap<String, ByteBuffer> bufferCache = new HashMap<String, ByteBuffer>();
1014             final FontConfig.Family[] xmlFamilies = fontConfig.getFamilies();
1015
1016             final ArrayMap<String, ArrayList<FontFamily>> fallbackListMap = new ArrayMap<>();
1017             // First traverse families which have a 'name' attribute to create fallback map.
1018             for (final FontConfig.Family xmlFamily : xmlFamilies) {
1019                 final String familyName = xmlFamily.getName();
1020                 if (familyName == null) {
1021                     continue;
1022                 }
1023                 final FontFamily family = createFontFamily(
1024                         xmlFamily.getName(), Arrays.asList(xmlFamily.getFonts()),
1025                         xmlFamily.getLanguages(), xmlFamily.getVariant(), bufferCache, fontDir);
1026                 if (family == null) {
1027                     continue;
1028                 }
1029                 final ArrayList<FontFamily> fallback = new ArrayList<>();
1030                 fallback.add(family);
1031                 fallbackListMap.put(familyName, fallback);
1032             }
1033
1034             // Then, add fallback fonts to the each fallback map.
1035             for (int i = 0; i < xmlFamilies.length; i++) {
1036                 final FontConfig.Family xmlFamily = xmlFamilies[i];
1037                 // The first family (usually the sans-serif family) is always placed immediately
1038                 // after the primary family in the fallback.
1039                 if (i == 0 || xmlFamily.getName() == null) {
1040                     pushFamilyToFallback(xmlFamily, fallbackListMap, bufferCache, fontDir);
1041                 }
1042             }
1043
1044             // Build the font map and fallback map.
1045             for (int i = 0; i < fallbackListMap.size(); i++) {
1046                 final String fallbackName = fallbackListMap.keyAt(i);
1047                 final List<FontFamily> familyList = fallbackListMap.valueAt(i);
1048                 final FontFamily[] families = familyList.toArray(new FontFamily[familyList.size()]);
1049
1050                 fallbackMap.put(fallbackName, families);
1051                 final long[] ptrArray = new long[families.length];
1052                 for (int j = 0; j < families.length; j++) {
1053                     ptrArray[j] = families[j].mNativePtr;
1054                 }
1055                 fontMap.put(fallbackName, new Typeface(nativeCreateFromArray(
1056                         ptrArray, RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE)));
1057             }
1058
1059             // Insert alias to font maps.
1060             for (final FontConfig.Alias alias : fontConfig.getAliases()) {
1061                 Typeface base = fontMap.get(alias.getToName());
1062                 Typeface newFace = base;
1063                 int weight = alias.getWeight();
1064                 if (weight != 400) {
1065                     newFace = new Typeface(nativeCreateWeightAlias(base.native_instance, weight));
1066                 }
1067                 fontMap.put(alias.getName(), newFace);
1068             }
1069         } catch (RuntimeException e) {
1070             Log.w(TAG, "Didn't create default family (most likely, non-Minikin build)", e);
1071             // TODO: normal in non-Minikin case, remove or make error when Minikin-only
1072         } catch (FileNotFoundException e) {
1073             Log.e(TAG, "Error opening " + xmlPath, e);
1074         } catch (IOException e) {
1075             Log.e(TAG, "Error reading " + xmlPath, e);
1076         } catch (XmlPullParserException e) {
1077             Log.e(TAG, "XML parse exception for " + xmlPath, e);
1078         }
1079     }
1080
1081     static {
1082         final ArrayMap<String, Typeface> systemFontMap = new ArrayMap<>();
1083         final ArrayMap<String, FontFamily[]> systemFallbackMap = new ArrayMap<>();
1084         buildSystemFallback("/system/etc/fonts.xml", "/system/fonts/", systemFontMap,
1085                 systemFallbackMap);
1086         sSystemFontMap = Collections.unmodifiableMap(systemFontMap);
1087         sSystemFallbackMap = Collections.unmodifiableMap(systemFallbackMap);
1088
1089         setDefault(sSystemFontMap.get(DEFAULT_FAMILY));
1090
1091         // Set up defaults and typefaces exposed in public API
1092         DEFAULT         = create((String) null, 0);
1093         DEFAULT_BOLD    = create((String) null, Typeface.BOLD);
1094         SANS_SERIF      = create("sans-serif", 0);
1095         SERIF           = create("serif", 0);
1096         MONOSPACE       = create("monospace", 0);
1097
1098         sDefaults = new Typeface[] {
1099             DEFAULT,
1100             DEFAULT_BOLD,
1101             create((String) null, Typeface.ITALIC),
1102             create((String) null, Typeface.BOLD_ITALIC),
1103         };
1104
1105     }
1106
1107     @Override
1108     protected void finalize() throws Throwable {
1109         try {
1110             nativeUnref(native_instance);
1111             native_instance = 0;  // Other finalizers can still call us.
1112         } finally {
1113             super.finalize();
1114         }
1115     }
1116
1117     @Override
1118     public boolean equals(Object o) {
1119         if (this == o) return true;
1120         if (o == null || getClass() != o.getClass()) return false;
1121
1122         Typeface typeface = (Typeface) o;
1123
1124         return mStyle == typeface.mStyle && native_instance == typeface.native_instance;
1125     }
1126
1127     @Override
1128     public int hashCode() {
1129         /*
1130          * Modified method for hashCode with long native_instance derived from
1131          * http://developer.android.com/reference/java/lang/Object.html
1132          */
1133         int result = 17;
1134         result = 31 * result + (int) (native_instance ^ (native_instance >>> 32));
1135         result = 31 * result + mStyle;
1136         return result;
1137     }
1138
1139     /** @hide */
1140     public boolean isSupportedAxes(int axis) {
1141         if (mSupportedAxes == null) {
1142             synchronized (this) {
1143                 if (mSupportedAxes == null) {
1144                     mSupportedAxes = nativeGetSupportedAxes(native_instance);
1145                     if (mSupportedAxes == null) {
1146                         mSupportedAxes = EMPTY_AXES;
1147                     }
1148                 }
1149             }
1150         }
1151         return Arrays.binarySearch(mSupportedAxes, axis) >= 0;
1152     }
1153
1154     private static native long nativeCreateFromTypeface(long native_instance, int style);
1155     private static native long nativeCreateFromTypefaceWithExactStyle(
1156             long native_instance, int weight, boolean italic);
1157     // TODO: clean up: change List<FontVariationAxis> to FontVariationAxis[]
1158     private static native long nativeCreateFromTypefaceWithVariation(
1159             long native_instance, List<FontVariationAxis> axes);
1160     private static native long nativeCreateWeightAlias(long native_instance, int weight);
1161     private static native void nativeUnref(long native_instance);
1162     private static native int  nativeGetStyle(long native_instance);
1163     private static native int  nativeGetWeight(long native_instance);
1164     private static native long nativeCreateFromArray(long[] familyArray, int weight, int italic);
1165     private static native void nativeSetDefault(long native_instance);
1166     private static native int[] nativeGetSupportedAxes(long native_instance);
1167 }