OSDN Git Service

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