1 /*
2  * Copyright (C) 2006 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.graphics;
18 
19 import static android.content.res.FontResourcesParser.FamilyResourceEntry;
20 import static android.content.res.FontResourcesParser.FontFamilyFilesResourceEntry;
21 import static android.content.res.FontResourcesParser.FontFileResourceEntry;
22 import static android.content.res.FontResourcesParser.ProviderResourceEntry;
23 
24 import android.annotation.IntDef;
25 import android.annotation.IntRange;
26 import android.annotation.NonNull;
27 import android.annotation.Nullable;
28 import android.compat.annotation.UnsupportedAppUsage;
29 import android.content.res.AssetManager;
30 import android.graphics.fonts.Font;
31 import android.graphics.fonts.FontFamily;
32 import android.graphics.fonts.FontStyle;
33 import android.graphics.fonts.FontVariationAxis;
34 import android.graphics.fonts.SystemFonts;
35 import android.os.Build;
36 import android.os.ParcelFileDescriptor;
37 import android.provider.FontRequest;
38 import android.provider.FontsContract;
39 import android.text.FontConfig;
40 import android.util.Base64;
41 import android.util.LongSparseArray;
42 import android.util.LruCache;
43 import android.util.SparseArray;
44 
45 import com.android.internal.annotations.GuardedBy;
46 import com.android.internal.annotations.VisibleForTesting;
47 import com.android.internal.util.Preconditions;
48 
49 import dalvik.annotation.optimization.CriticalNative;
50 
51 import libcore.util.NativeAllocationRegistry;
52 
53 import java.io.File;
54 import java.io.FileDescriptor;
55 import java.io.IOException;
56 import java.io.InputStream;
57 import java.lang.annotation.Retention;
58 import java.lang.annotation.RetentionPolicy;
59 import java.util.ArrayList;
60 import java.util.Arrays;
61 import java.util.Collections;
62 import java.util.HashMap;
63 import java.util.List;
64 import java.util.Map;
65 
66 /**
67  * The Typeface class specifies the typeface and intrinsic style of a font.
68  * This is used in the paint, along with optionally Paint settings like
69  * textSize, textSkewX, textScaleX to specify
70  * how text appears when drawn (and measured).
71  */
72 public class Typeface {
73 
74     private static String TAG = "Typeface";
75 
76     private static final NativeAllocationRegistry sRegistry =
77             NativeAllocationRegistry.createMalloced(
78             Typeface.class.getClassLoader(), nativeGetReleaseFunc());
79 
80     /** The default NORMAL typeface object */
81     public static final Typeface DEFAULT;
82     /**
83      * The default BOLD typeface object. Note: this may be not actually be
84      * bold, depending on what fonts are installed. Call getStyle() to know
85      * for sure.
86      */
87     public static final Typeface DEFAULT_BOLD;
88     /** The NORMAL style of the default sans serif typeface. */
89     public static final Typeface SANS_SERIF;
90     /** The NORMAL style of the default serif typeface. */
91     public static final Typeface SERIF;
92     /** The NORMAL style of the default monospace typeface. */
93     public static final Typeface MONOSPACE;
94 
95     /**
96      * The default {@link Typeface}s for different text styles.
97      * Call {@link #defaultFromStyle(int)} to get the default typeface for the given text style.
98      * It shouldn't be changed for app wide typeface settings. Please use theme and font XML for
99      * the same purpose.
100      */
101     @UnsupportedAppUsage(trackingBug = 123769446)
102     static Typeface[] sDefaults;
103 
104     /**
105      * Cache for Typeface objects for style variant. Currently max size is 3.
106      */
107     @GuardedBy("sStyledCacheLock")
108     private static final LongSparseArray<SparseArray<Typeface>> sStyledTypefaceCache =
109             new LongSparseArray<>(3);
110     private static final Object sStyledCacheLock = new Object();
111 
112     /**
113      * Cache for Typeface objects for weight variant. Currently max size is 3.
114      */
115     @GuardedBy("sWeightCacheLock")
116     private static final LongSparseArray<SparseArray<Typeface>> sWeightTypefaceCache =
117             new LongSparseArray<>(3);
118     private static final Object sWeightCacheLock = new Object();
119 
120     /**
121      * Cache for Typeface objects dynamically loaded from assets. Currently max size is 16.
122      */
123     @GuardedBy("sDynamicCacheLock")
124     private static final LruCache<String, Typeface> sDynamicTypefaceCache = new LruCache<>(16);
125     private static final Object sDynamicCacheLock = new Object();
126 
127     static Typeface sDefaultTypeface;
128 
129     // Following two fields are not used but left for hiddenapi private list
130     /**
131      * sSystemFontMap is read only and unmodifiable.
132      * Use public API {@link #create(String, int)} to get the typeface for given familyName.
133      */
134     @UnsupportedAppUsage(trackingBug = 123769347)
135     static final Map<String, Typeface> sSystemFontMap;
136 
137     // We cannot support sSystemFallbackMap since we will migrate to public FontFamily API.
138     /**
139      * @deprecated Use {@link android.graphics.fonts.FontFamily} instead.
140      */
141     @UnsupportedAppUsage(trackingBug = 123768928)
142     @Deprecated
143     static final Map<String, android.graphics.FontFamily[]> sSystemFallbackMap =
144             Collections.emptyMap();
145 
146     /**
147      * @hide
148      */
149     @UnsupportedAppUsage
150     public long native_instance;
151 
152     /** @hide */
153     @IntDef(value = {NORMAL, BOLD, ITALIC, BOLD_ITALIC})
154     @Retention(RetentionPolicy.SOURCE)
155     public @interface Style {}
156 
157     // Style
158     public static final int NORMAL = 0;
159     public static final int BOLD = 1;
160     public static final int ITALIC = 2;
161     public static final int BOLD_ITALIC = 3;
162     /** @hide */ public static final int STYLE_MASK = 0x03;
163 
164     @UnsupportedAppUsage
165     private @Style int mStyle = 0;
166 
167     private @IntRange(from = 0, to = FontStyle.FONT_WEIGHT_MAX) int mWeight = 0;
168 
169     // Value for weight and italic. Indicates the value is resolved by font metadata.
170     // Must be the same as the C++ constant in core/jni/android/graphics/FontFamily.cpp
171     /** @hide */
172     public static final int RESOLVE_BY_FONT_TABLE = -1;
173     private static final String DEFAULT_FAMILY = "sans-serif";
174 
175     // Style value for building typeface.
176     private static final int STYLE_NORMAL = 0;
177     private static final int STYLE_ITALIC = 1;
178 
179     private int[] mSupportedAxes;
180     private static final int[] EMPTY_AXES = {};
181 
182     /**
183      * Please use font in xml and also your application global theme to change the default Typeface.
184      * android:textViewStyle and its attribute android:textAppearance can be used in order to change
185      * typeface and other text related properties.
186      */
187     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
setDefault(Typeface t)188     private static void setDefault(Typeface t) {
189         sDefaultTypeface = t;
190         nativeSetDefault(t.native_instance);
191     }
192 
193     /** Returns the typeface's weight value */
getWeight()194     public @IntRange(from = 0, to = 1000) int getWeight() {
195         return mWeight;
196     }
197 
198     /** Returns the typeface's intrinsic style attributes */
getStyle()199     public @Style int getStyle() {
200         return mStyle;
201     }
202 
203     /** Returns true if getStyle() has the BOLD bit set. */
isBold()204     public final boolean isBold() {
205         return (mStyle & BOLD) != 0;
206     }
207 
208     /** Returns true if getStyle() has the ITALIC bit set. */
isItalic()209     public final boolean isItalic() {
210         return (mStyle & ITALIC) != 0;
211     }
212 
213     /**
214      * @hide
215      * Used by Resources to load a font resource of type xml.
216      */
217     @Nullable
createFromResources( FamilyResourceEntry entry, AssetManager mgr, String path)218     public static Typeface createFromResources(
219             FamilyResourceEntry entry, AssetManager mgr, String path) {
220         if (entry instanceof ProviderResourceEntry) {
221             final ProviderResourceEntry providerEntry = (ProviderResourceEntry) entry;
222             // Downloadable font
223             List<List<String>> givenCerts = providerEntry.getCerts();
224             List<List<byte[]>> certs = new ArrayList<>();
225             if (givenCerts != null) {
226                 for (int i = 0; i < givenCerts.size(); i++) {
227                     List<String> certSet = givenCerts.get(i);
228                     List<byte[]> byteArraySet = new ArrayList<>();
229                     for (int j = 0; j < certSet.size(); j++) {
230                         byteArraySet.add(Base64.decode(certSet.get(j), Base64.DEFAULT));
231                     }
232                     certs.add(byteArraySet);
233                 }
234             }
235             // Downloaded font and it wasn't cached, request it again and return a
236             // default font instead (nothing we can do now).
237             FontRequest request = new FontRequest(providerEntry.getAuthority(),
238                     providerEntry.getPackage(), providerEntry.getQuery(), certs);
239             Typeface typeface = FontsContract.getFontSync(request);
240             return typeface == null ? DEFAULT : typeface;
241         }
242 
243         Typeface typeface = findFromCache(mgr, path);
244         if (typeface != null) return typeface;
245 
246         // family is FontFamilyFilesResourceEntry
247         final FontFamilyFilesResourceEntry filesEntry = (FontFamilyFilesResourceEntry) entry;
248 
249         try {
250             FontFamily.Builder familyBuilder = null;
251             for (final FontFileResourceEntry fontFile : filesEntry.getEntries()) {
252                 final Font.Builder fontBuilder = new Font.Builder(mgr, fontFile.getFileName(),
253                         false /* isAsset */, 0 /* cookie */)
254                         .setTtcIndex(fontFile.getTtcIndex())
255                         .setFontVariationSettings(fontFile.getVariationSettings());
256                 if (fontFile.getWeight() != Typeface.RESOLVE_BY_FONT_TABLE) {
257                     fontBuilder.setWeight(fontFile.getWeight());
258                 }
259                 if (fontFile.getItalic() != Typeface.RESOLVE_BY_FONT_TABLE) {
260                     fontBuilder.setSlant(fontFile.getItalic() == FontFileResourceEntry.ITALIC
261                             ?  FontStyle.FONT_SLANT_ITALIC : FontStyle.FONT_SLANT_UPRIGHT);
262                 }
263 
264                 if (familyBuilder == null) {
265                     familyBuilder = new FontFamily.Builder(fontBuilder.build());
266                 } else {
267                     familyBuilder.addFont(fontBuilder.build());
268                 }
269             }
270             if (familyBuilder == null) {
271                 return Typeface.DEFAULT;
272             }
273             final FontFamily family = familyBuilder.build();
274             final FontStyle normal = new FontStyle(FontStyle.FONT_WEIGHT_NORMAL,
275                     FontStyle.FONT_SLANT_UPRIGHT);
276             Font bestFont = family.getFont(0);
277             int bestScore = normal.getMatchScore(bestFont.getStyle());
278             for (int i = 1; i < family.getSize(); ++i) {
279                 final Font candidate = family.getFont(i);
280                 final int score = normal.getMatchScore(candidate.getStyle());
281                 if (score < bestScore) {
282                     bestFont = candidate;
283                     bestScore = score;
284                 }
285             }
286             typeface = new Typeface.CustomFallbackBuilder(family)
287                     .setStyle(bestFont.getStyle())
288                     .build();
289         } catch (IllegalArgumentException e) {
290             // To be a compatible behavior with API28 or before, catch IllegalArgumentExcetpion
291             // thrown by native code and returns null.
292             return null;
293         } catch (IOException e) {
294             typeface = Typeface.DEFAULT;
295         }
296         synchronized (sDynamicCacheLock) {
297             final String key = Builder.createAssetUid(mgr, path, 0 /* ttcIndex */,
298                     null /* axes */, RESOLVE_BY_FONT_TABLE /* weight */,
299                     RESOLVE_BY_FONT_TABLE /* italic */, DEFAULT_FAMILY);
300             sDynamicTypefaceCache.put(key, typeface);
301         }
302         return typeface;
303     }
304 
305     /**
306      * Used by resources for cached loading if the font is available.
307      * @hide
308      */
findFromCache(AssetManager mgr, String path)309     public static Typeface findFromCache(AssetManager mgr, String path) {
310         synchronized (sDynamicCacheLock) {
311             final String key = Builder.createAssetUid(mgr, path, 0 /* ttcIndex */, null /* axes */,
312                     RESOLVE_BY_FONT_TABLE /* weight */, RESOLVE_BY_FONT_TABLE /* italic */,
313                     DEFAULT_FAMILY);
314             Typeface typeface = sDynamicTypefaceCache.get(key);
315             if (typeface != null) {
316                 return typeface;
317             }
318         }
319         return null;
320     }
321 
322     /**
323      * A builder class for creating new Typeface instance.
324      *
325      * <p>
326      * Examples,
327      * 1) Create Typeface from ttf file.
328      * <pre>
329      * <code>
330      * Typeface.Builder buidler = new Typeface.Builder("your_font_file.ttf");
331      * Typeface typeface = builder.build();
332      * </code>
333      * </pre>
334      *
335      * 2) Create Typeface from ttc file in assets directory.
336      * <pre>
337      * <code>
338      * Typeface.Builder buidler = new Typeface.Builder(getAssets(), "your_font_file.ttc");
339      * builder.setTtcIndex(2);  // Set index of font collection.
340      * Typeface typeface = builder.build();
341      * </code>
342      * </pre>
343      *
344      * 3) Create Typeface with variation settings.
345      * <pre>
346      * <code>
347      * Typeface.Builder buidler = new Typeface.Builder("your_font_file.ttf");
348      * builder.setFontVariationSettings("'wght' 700, 'slnt' 20, 'ital' 1");
349      * builder.setWeight(700);  // Tell the system that this is a bold font.
350      * builder.setItalic(true);  // Tell the system that this is an italic style font.
351      * Typeface typeface = builder.build();
352      * </code>
353      * </pre>
354      * </p>
355      */
356     public static final class Builder {
357         /** @hide */
358         public static final int NORMAL_WEIGHT = 400;
359         /** @hide */
360         public static final int BOLD_WEIGHT = 700;
361 
362         // Kept for generating asset cache key.
363         private final AssetManager mAssetManager;
364         private final String mPath;
365 
366         private final @Nullable Font.Builder mFontBuilder;
367 
368         private String mFallbackFamilyName;
369 
370         private int mWeight = RESOLVE_BY_FONT_TABLE;
371         private int mItalic = RESOLVE_BY_FONT_TABLE;
372 
373         /**
374          * Constructs a builder with a file path.
375          *
376          * @param path The file object refers to the font file.
377          */
Builder(@onNull File path)378         public Builder(@NonNull File path) {
379             mFontBuilder = new Font.Builder(path);
380             mAssetManager = null;
381             mPath = null;
382         }
383 
384         /**
385          * Constructs a builder with a file descriptor.
386          *
387          * Caller is responsible for closing the passed file descriptor after {@link #build} is
388          * called.
389          *
390          * @param fd The file descriptor. The passed fd must be mmap-able.
391          */
Builder(@onNull FileDescriptor fd)392         public Builder(@NonNull FileDescriptor fd) {
393             Font.Builder builder;
394             try {
395                 builder = new Font.Builder(ParcelFileDescriptor.dup(fd));
396             } catch (IOException e) {
397                 // We cannot tell the error to developer at this moment since we cannot change the
398                 // public API signature. Instead, silently fallbacks to system fallback in the build
399                 // method as the same as other error cases.
400                 builder = null;
401             }
402             mFontBuilder = builder;
403             mAssetManager = null;
404             mPath = null;
405         }
406 
407         /**
408          * Constructs a builder with a file path.
409          *
410          * @param path The full path to the font file.
411          */
Builder(@onNull String path)412         public Builder(@NonNull String path) {
413             mFontBuilder = new Font.Builder(new File(path));
414             mAssetManager = null;
415             mPath = null;
416         }
417 
418         /**
419          * Constructs a builder from an asset manager and a file path in an asset directory.
420          *
421          * @param assetManager The application's asset manager
422          * @param path The file name of the font data in the asset directory
423          */
Builder(@onNull AssetManager assetManager, @NonNull String path)424         public Builder(@NonNull AssetManager assetManager, @NonNull String path) {
425             this(assetManager, path, true /* is asset */, 0 /* cookie */);
426         }
427 
428         /**
429          * Constructs a builder from an asset manager and a file path in an asset directory.
430          *
431          * @param assetManager The application's asset manager
432          * @param path The file name of the font data in the asset directory
433          * @param cookie a cookie for the asset
434          * @hide
435          */
Builder(@onNull AssetManager assetManager, @NonNull String path, boolean isAsset, int cookie)436         public Builder(@NonNull AssetManager assetManager, @NonNull String path, boolean isAsset,
437                 int cookie) {
438             mFontBuilder = new Font.Builder(assetManager, path, isAsset, cookie);
439             mAssetManager = assetManager;
440             mPath = path;
441         }
442 
443         /**
444          * Sets weight of the font.
445          *
446          * Tells the system the weight of the given font. If not provided, the system will resolve
447          * the weight value by reading font tables.
448          * @param weight a weight value.
449          */
setWeight(@ntRangefrom = 1, to = 1000) int weight)450         public Builder setWeight(@IntRange(from = 1, to = 1000) int weight) {
451             mWeight = weight;
452             mFontBuilder.setWeight(weight);
453             return this;
454         }
455 
456         /**
457          * Sets italic information of the font.
458          *
459          * Tells the system the style of the given font. If not provided, the system will resolve
460          * the style by reading font tables.
461          * @param italic {@code true} if the font is italic. Otherwise {@code false}.
462          */
setItalic(boolean italic)463         public Builder setItalic(boolean italic) {
464             mItalic = italic ? FontStyle.FONT_SLANT_ITALIC : FontStyle.FONT_SLANT_UPRIGHT;
465             mFontBuilder.setSlant(mItalic);
466             return this;
467         }
468 
469         /**
470          * Sets an index of the font collection. See {@link android.R.attr#ttcIndex}.
471          *
472          * Can not be used for Typeface source. build() method will return null for invalid index.
473          * @param ttcIndex An index of the font collection. If the font source is not font
474          *                 collection, do not call this method or specify 0.
475          */
setTtcIndex(@ntRangefrom = 0) int ttcIndex)476         public Builder setTtcIndex(@IntRange(from = 0) int ttcIndex) {
477             mFontBuilder.setTtcIndex(ttcIndex);
478             return this;
479         }
480 
481         /**
482          * Sets a font variation settings.
483          *
484          * @param variationSettings See {@link android.widget.TextView#setFontVariationSettings}.
485          * @throws IllegalArgumentException If given string is not a valid font variation settings
486          *                                  format.
487          */
setFontVariationSettings(@ullable String variationSettings)488         public Builder setFontVariationSettings(@Nullable String variationSettings) {
489             mFontBuilder.setFontVariationSettings(variationSettings);
490             return this;
491         }
492 
493         /**
494          * Sets a font variation settings.
495          *
496          * @param axes An array of font variation axis tag-value pairs.
497          */
setFontVariationSettings(@ullable FontVariationAxis[] axes)498         public Builder setFontVariationSettings(@Nullable FontVariationAxis[] axes) {
499             mFontBuilder.setFontVariationSettings(axes);
500             return this;
501         }
502 
503         /**
504          * Sets a fallback family name.
505          *
506          * By specifying a fallback family name, a fallback Typeface will be returned if the
507          * {@link #build} method fails to create a Typeface from the provided font. The fallback
508          * family will be resolved with the provided weight and italic information specified by
509          * {@link #setWeight} and {@link #setItalic}.
510          *
511          * If {@link #setWeight} is not called, the fallback family keeps the default weight.
512          * Similary, if {@link #setItalic} is not called, the fallback family keeps the default
513          * italic information. For example, calling {@code builder.setFallback("sans-serif-light")}
514          * is equivalent to calling {@code builder.setFallback("sans-serif").setWeight(300)} in
515          * terms of fallback. The default weight and italic information are overridden by calling
516          * {@link #setWeight} and {@link #setItalic}. For example, if a Typeface is constructed
517          * using {@code builder.setFallback("sans-serif-light").setWeight(700)}, the fallback text
518          * will render as sans serif bold.
519          *
520          * @param familyName A family name to be used for fallback if the provided font can not be
521          *                   used. By passing {@code null}, build() returns {@code null}.
522          *                   If {@link #setFallback} is not called on the builder, {@code null}
523          *                   is assumed.
524          */
setFallback(@ullable String familyName)525         public Builder setFallback(@Nullable String familyName) {
526             mFallbackFamilyName = familyName;
527             return this;
528         }
529 
530         /**
531          * Creates a unique id for a given AssetManager and asset path.
532          *
533          * @param mgr  AssetManager instance
534          * @param path The path for the asset.
535          * @param ttcIndex The TTC index for the font.
536          * @param axes The font variation settings.
537          * @return Unique id for a given AssetManager and asset path.
538          */
createAssetUid(final AssetManager mgr, String path, int ttcIndex, @Nullable FontVariationAxis[] axes, int weight, int italic, String fallback)539         private static String createAssetUid(final AssetManager mgr, String path, int ttcIndex,
540                 @Nullable FontVariationAxis[] axes, int weight, int italic, String fallback) {
541             final SparseArray<String> pkgs = mgr.getAssignedPackageIdentifiers();
542             final StringBuilder builder = new StringBuilder();
543             final int size = pkgs.size();
544             for (int i = 0; i < size; i++) {
545                 builder.append(pkgs.valueAt(i));
546                 builder.append("-");
547             }
548             builder.append(path);
549             builder.append("-");
550             builder.append(Integer.toString(ttcIndex));
551             builder.append("-");
552             builder.append(Integer.toString(weight));
553             builder.append("-");
554             builder.append(Integer.toString(italic));
555             // Family name may contain hyphen. Use double hyphen for avoiding key conflicts before
556             // and after appending falblack name.
557             builder.append("--");
558             builder.append(fallback);
559             builder.append("--");
560             if (axes != null) {
561                 for (FontVariationAxis axis : axes) {
562                     builder.append(axis.getTag());
563                     builder.append("-");
564                     builder.append(Float.toString(axis.getStyleValue()));
565                 }
566             }
567             return builder.toString();
568         }
569 
resolveFallbackTypeface()570         private Typeface resolveFallbackTypeface() {
571             if (mFallbackFamilyName == null) {
572                 return null;
573             }
574 
575             final Typeface base =  getSystemDefaultTypeface(mFallbackFamilyName);
576             if (mWeight == RESOLVE_BY_FONT_TABLE && mItalic == RESOLVE_BY_FONT_TABLE) {
577                 return base;
578             }
579 
580             final int weight = (mWeight == RESOLVE_BY_FONT_TABLE) ? base.mWeight : mWeight;
581             final boolean italic =
582                     (mItalic == RESOLVE_BY_FONT_TABLE) ? (base.mStyle & ITALIC) != 0 : mItalic == 1;
583             return createWeightStyle(base, weight, italic);
584         }
585 
586         /**
587          * Generates new Typeface from specified configuration.
588          *
589          * @return Newly created Typeface. May return null if some parameters are invalid.
590          */
build()591         public Typeface build() {
592             if (mFontBuilder == null) {
593                 return resolveFallbackTypeface();
594             }
595             try {
596                 final Font font = mFontBuilder.build();
597                 final String key = mAssetManager == null ? null : createAssetUid(
598                         mAssetManager, mPath, font.getTtcIndex(), font.getAxes(),
599                         mWeight, mItalic,
600                         mFallbackFamilyName == null ? DEFAULT_FAMILY : mFallbackFamilyName);
601                 if (key != null) {
602                     // Dynamic cache lookup is only for assets.
603                     synchronized (sDynamicCacheLock) {
604                         final Typeface typeface = sDynamicTypefaceCache.get(key);
605                         if (typeface != null) {
606                             return typeface;
607                         }
608                     }
609                 }
610                 final FontFamily family = new FontFamily.Builder(font).build();
611                 final int weight = mWeight == RESOLVE_BY_FONT_TABLE
612                         ? font.getStyle().getWeight() : mWeight;
613                 final int slant = mItalic == RESOLVE_BY_FONT_TABLE
614                         ? font.getStyle().getSlant() : mItalic;
615                 final CustomFallbackBuilder builder = new CustomFallbackBuilder(family)
616                         .setStyle(new FontStyle(weight, slant));
617                 if (mFallbackFamilyName != null) {
618                     builder.setSystemFallback(mFallbackFamilyName);
619                 }
620                 final Typeface typeface = builder.build();
621                 if (key != null) {
622                     synchronized (sDynamicCacheLock) {
623                         sDynamicTypefaceCache.put(key, typeface);
624                     }
625                 }
626                 return typeface;
627             } catch (IOException | IllegalArgumentException e) {
628                 return resolveFallbackTypeface();
629             }
630         }
631     }
632 
633     /**
634      * A builder class for creating new Typeface instance.
635      *
636      * There are two font fallback mechanisms, custom font fallback and system font fallback.
637      * The custom font fallback is a simple ordered list. The text renderer tries to see if it can
638      * render a character with the first font and if that font does not support the character, try
639      * next one and so on. It will keep trying until end of the custom fallback chain. The maximum
640      * length of the custom fallback chain is 64.
641      * The system font fallback is a system pre-defined fallback chain. The system fallback is
642      * processed only when no matching font is found in the custom font fallback.
643      *
644      * <p>
645      * Examples,
646      * 1) Create Typeface from single ttf file.
647      * <pre>
648      * <code>
649      * Font font = new Font.Builder("your_font_file.ttf").build();
650      * FontFamily family = new FontFamily.Builder(font).build();
651      * Typeface typeface = new Typeface.CustomFallbackBuilder(family).build();
652      * </code>
653      * </pre>
654      *
655      * 2) Create Typeface from multiple font files and select bold style by default.
656      * <pre>
657      * <code>
658      * Font regularFont = new Font.Builder("regular.ttf").build();
659      * Font boldFont = new Font.Builder("bold.ttf").build();
660      * FontFamily family = new FontFamily.Builder(regularFont)
661      *     .addFont(boldFont).build();
662      * Typeface typeface = new Typeface.CustomFallbackBuilder(family)
663      *     .setWeight(Font.FONT_WEIGHT_BOLD)  // Set bold style as the default style.
664      *                                        // If the font family doesn't have bold style font,
665      *                                        // system will select the closest font.
666      *     .build();
667      * </code>
668      * </pre>
669      *
670      * 3) Create Typeface from single ttf file and if that font does not have glyph for the
671      * characters, use "serif" font family instead.
672      * <pre>
673      * <code>
674      * Font font = new Font.Builder("your_font_file.ttf").build();
675      * FontFamily family = new FontFamily.Builder(font).build();
676      * Typeface typeface = new Typeface.CustomFallbackBuilder(family)
677      *     .setSystemFallback("serif")  // Set serif font family as the fallback.
678      *     .build();
679      * </code>
680      * </pre>
681      * 4) Create Typeface from single ttf file and set another ttf file for the fallback.
682      * <pre>
683      * <code>
684      * Font font = new Font.Builder("English.ttf").build();
685      * FontFamily family = new FontFamily.Builder(font).build();
686      *
687      * Font fallbackFont = new Font.Builder("Arabic.ttf").build();
688      * FontFamily fallbackFamily = new FontFamily.Builder(fallbackFont).build();
689      * Typeface typeface = new Typeface.CustomFallbackBuilder(family)
690      *     .addCustomFallback(fallbackFamily)  // Specify fallback family.
691      *     .setSystemFallback("serif")  // Set serif font family as the fallback.
692      *     .build();
693      * </code>
694      * </pre>
695      * </p>
696      */
697     public static final class CustomFallbackBuilder {
698         private static final int MAX_CUSTOM_FALLBACK = 64;
699         private final ArrayList<FontFamily> mFamilies = new ArrayList<>();
700         private String mFallbackName = null;
701         private @Nullable FontStyle mStyle;
702 
703         /**
704          * Returns the maximum capacity of custom fallback families.
705          *
706          * This includes the the first font family passed to the constructor.
707          * It is guaranteed that the value will be greater than or equal to 64.
708          *
709          * @return the maximum number of font families for the custom fallback
710          */
getMaxCustomFallbackCount()711         public static @IntRange(from = 64) int getMaxCustomFallbackCount() {
712             return MAX_CUSTOM_FALLBACK;
713         }
714 
715         /**
716          * Constructs a builder with a font family.
717          *
718          * @param family a family object
719          */
CustomFallbackBuilder(@onNull FontFamily family)720         public CustomFallbackBuilder(@NonNull FontFamily family) {
721             Preconditions.checkNotNull(family);
722             mFamilies.add(family);
723         }
724 
725         /**
726          * Sets a system fallback by name.
727          *
728          * You can specify generic font familiy names or OEM specific family names. If the system
729          * don't have a specified fallback, the default fallback is used instead.
730          * For more information about generic font families, see <a
731          * href="https://www.w3.org/TR/css-fonts-4/#generic-font-families">CSS specification</a>
732          *
733          * For more information about fallback, see class description.
734          *
735          * @param familyName a family name to be used for fallback if the provided fonts can not be
736          *                   used
737          */
setSystemFallback(@onNull String familyName)738         public @NonNull CustomFallbackBuilder setSystemFallback(@NonNull String familyName) {
739             Preconditions.checkNotNull(familyName);
740             mFallbackName = familyName;
741             return this;
742         }
743 
744         /**
745          * Sets a font style of the Typeface.
746          *
747          * If the font family doesn't have a font of given style, system will select the closest
748          * font from font family. For example, if a font family has fonts of 300 weight and 700
749          * weight then setWeight(400) is called, system will select the font of 300 weight.
750          *
751          * @param style a font style
752          */
setStyle(@onNull FontStyle style)753         public @NonNull CustomFallbackBuilder setStyle(@NonNull FontStyle style) {
754             mStyle = style;
755             return this;
756         }
757 
758         /**
759          * Append a font family to the end of the custom font fallback.
760          *
761          * You can set up to 64 custom fallback families including the first font family you passed
762          * to the constructor.
763          * For more information about fallback, see class description.
764          *
765          * @param family a fallback family
766          * @throws IllegalArgumentException if you give more than 64 custom fallback families
767          */
addCustomFallback(@onNull FontFamily family)768         public @NonNull CustomFallbackBuilder addCustomFallback(@NonNull FontFamily family) {
769             Preconditions.checkNotNull(family);
770             Preconditions.checkArgument(mFamilies.size() < getMaxCustomFallbackCount(),
771                     "Custom fallback limit exceeded(" + getMaxCustomFallbackCount() + ")");
772             mFamilies.add(family);
773             return this;
774         }
775 
776         /**
777          * Create the Typeface based on the configured values.
778          *
779          * @return the Typeface object
780          */
781         public @NonNull Typeface build() {
782             final int userFallbackSize = mFamilies.size();
783             final FontFamily[] fallback = SystemFonts.getSystemFallback(mFallbackName);
784             final long[] ptrArray = new long[fallback.length + userFallbackSize];
785             for (int i = 0; i < userFallbackSize; ++i) {
786                 ptrArray[i] = mFamilies.get(i).getNativePtr();
787             }
788             for (int i = 0; i < fallback.length; ++i) {
789                 ptrArray[i + userFallbackSize] = fallback[i].getNativePtr();
790             }
791             final int weight = mStyle == null ? 400 : mStyle.getWeight();
792             final int italic =
793                     (mStyle == null || mStyle.getSlant() == FontStyle.FONT_SLANT_UPRIGHT) ?  0 : 1;
794             return new Typeface(nativeCreateFromArray(ptrArray, weight, italic));
795         }
796     }
797 
798     /**
799      * Create a typeface object given a family name, and option style information.
800      * If null is passed for the name, then the "default" font will be chosen.
801      * The resulting typeface object can be queried (getStyle()) to discover what
802      * its "real" style characteristics are.
803      *
804      * @param familyName May be null. The name of the font family.
805      * @param style  The style (normal, bold, italic) of the typeface.
806      *               e.g. NORMAL, BOLD, ITALIC, BOLD_ITALIC
807      * @return The best matching typeface.
808      */
809     public static Typeface create(String familyName, @Style int style) {
810         return create(getSystemDefaultTypeface(familyName), style);
811     }
812 
813     /**
814      * Create a typeface object that best matches the specified existing
815      * typeface and the specified Style. Use this call if you want to pick a new
816      * style from the same family of an existing typeface object. If family is
817      * null, this selects from the default font's family.
818      *
819      * <p>
820      * This method is not thread safe on API 27 or before.
821      * This method is thread safe on API 28 or after.
822      * </p>
823      *
824      * @param family An existing {@link Typeface} object. In case of {@code null}, the default
825      *               typeface is used instead.
826      * @param style  The style (normal, bold, italic) of the typeface.
827      *               e.g. NORMAL, BOLD, ITALIC, BOLD_ITALIC
828      * @return The best matching typeface.
829      */
830     public static Typeface create(Typeface family, @Style int style) {
831         if ((style & ~STYLE_MASK) != 0) {
832             style = NORMAL;
833         }
834         if (family == null) {
835             family = sDefaultTypeface;
836         }
837 
838         // Return early if we're asked for the same face/style
839         if (family.mStyle == style) {
840             return family;
841         }
842 
843         final long ni = family.native_instance;
844 
845         Typeface typeface;
846         synchronized (sStyledCacheLock) {
847             SparseArray<Typeface> styles = sStyledTypefaceCache.get(ni);
848 
849             if (styles == null) {
850                 styles = new SparseArray<Typeface>(4);
851                 sStyledTypefaceCache.put(ni, styles);
852             } else {
853                 typeface = styles.get(style);
854                 if (typeface != null) {
855                     return typeface;
856                 }
857             }
858 
859             typeface = new Typeface(nativeCreateFromTypeface(ni, style));
860             styles.put(style, typeface);
861         }
862         return typeface;
863     }
864 
865     /**
866      * Creates a typeface object that best matches the specified existing typeface and the specified
867      * weight and italic style
868      * <p>Below are numerical values and corresponding common weight names.</p>
869      * <table>
870      * <thead>
871      * <tr><th>Value</th><th>Common weight name</th></tr>
872      * </thead>
873      * <tbody>
874      * <tr><td>100</td><td>Thin</td></tr>
875      * <tr><td>200</td><td>Extra Light</td></tr>
876      * <tr><td>300</td><td>Light</td></tr>
877      * <tr><td>400</td><td>Normal</td></tr>
878      * <tr><td>500</td><td>Medium</td></tr>
879      * <tr><td>600</td><td>Semi Bold</td></tr>
880      * <tr><td>700</td><td>Bold</td></tr>
881      * <tr><td>800</td><td>Extra Bold</td></tr>
882      * <tr><td>900</td><td>Black</td></tr>
883      * </tbody>
884      * </table>
885      *
886      * <p>
887      * This method is thread safe.
888      * </p>
889      *
890      * @param family An existing {@link Typeface} object. In case of {@code null}, the default
891      *               typeface is used instead.
892      * @param weight The desired weight to be drawn.
893      * @param italic {@code true} if italic style is desired to be drawn. Otherwise, {@code false}
894      * @return A {@link Typeface} object for drawing specified weight and italic style. Never
895      *         returns {@code null}
896      *
897      * @see #getWeight()
898      * @see #isItalic()
899      */
900     public static @NonNull Typeface create(@Nullable Typeface family,
901             @IntRange(from = 1, to = 1000) int weight, boolean italic) {
902         Preconditions.checkArgumentInRange(weight, 0, 1000, "weight");
903         if (family == null) {
904             family = sDefaultTypeface;
905         }
906         return createWeightStyle(family, weight, italic);
907     }
908 
909     private static @NonNull Typeface createWeightStyle(@NonNull Typeface base,
910             @IntRange(from = 1, to = 1000) int weight, boolean italic) {
911         final int key = (weight << 1) | (italic ? 1 : 0);
912 
913         Typeface typeface;
914         synchronized(sWeightCacheLock) {
915             SparseArray<Typeface> innerCache = sWeightTypefaceCache.get(base.native_instance);
916             if (innerCache == null) {
917                 innerCache = new SparseArray<>(4);
918                 sWeightTypefaceCache.put(base.native_instance, innerCache);
919             } else {
920                 typeface = innerCache.get(key);
921                 if (typeface != null) {
922                     return typeface;
923                 }
924             }
925 
926             typeface = new Typeface(
927                     nativeCreateFromTypefaceWithExactStyle(base.native_instance, weight, italic));
928             innerCache.put(key, typeface);
929         }
930         return typeface;
931     }
932 
933     /** @hide */
934     public static Typeface createFromTypefaceWithVariation(@Nullable Typeface family,
935             @NonNull List<FontVariationAxis> axes) {
936         final Typeface base = family == null ? Typeface.DEFAULT : family;
937         return new Typeface(nativeCreateFromTypefaceWithVariation(base.native_instance, axes));
938     }
939 
940     /**
941      * Returns one of the default typeface objects, based on the specified style
942      *
943      * @return the default typeface that corresponds to the style
944      */
945     public static Typeface defaultFromStyle(@Style int style) {
946         return sDefaults[style];
947     }
948 
949     /**
950      * Create a new typeface from the specified font data.
951      *
952      * @param mgr  The application's asset manager
953      * @param path The file name of the font data in the assets directory
954      * @return The new typeface.
955      */
956     public static Typeface createFromAsset(AssetManager mgr, String path) {
957         Preconditions.checkNotNull(path); // for backward compatibility
958         Preconditions.checkNotNull(mgr);
959 
960         Typeface typeface = new Builder(mgr, path).build();
961         if (typeface != null) return typeface;
962         // check if the file exists, and throw an exception for backward compatibility
963         try (InputStream inputStream = mgr.open(path)) {
964         } catch (IOException e) {
965             throw new RuntimeException("Font asset not found " + path);
966         }
967 
968         return Typeface.DEFAULT;
969     }
970 
971     /**
972      * Creates a unique id for a given font provider and query.
973      */
974     private static String createProviderUid(String authority, String query) {
975         final StringBuilder builder = new StringBuilder();
976         builder.append("provider:");
977         builder.append(authority);
978         builder.append("-");
979         builder.append(query);
980         return builder.toString();
981     }
982 
983     /**
984      * Create a new typeface from the specified font file.
985      *
986      * @param file The path to the font data.
987      * @return The new typeface.
988      */
989     public static Typeface createFromFile(@Nullable File file) {
990         // For the compatibility reasons, leaving possible NPE here.
991         // See android.graphics.cts.TypefaceTest#testCreateFromFileByFileReferenceNull
992 
993         Typeface typeface = new Builder(file).build();
994         if (typeface != null) return typeface;
995 
996         // check if the file exists, and throw an exception for backward compatibility
997         if (!file.exists()) {
998             throw new RuntimeException("Font asset not found " + file.getAbsolutePath());
999         }
1000 
1001         return Typeface.DEFAULT;
1002     }
1003 
1004     /**
1005      * Create a new typeface from the specified font file.
1006      *
1007      * @param path The full path to the font data.
1008      * @return The new typeface.
1009      */
1010     public static Typeface createFromFile(@Nullable String path) {
1011         Preconditions.checkNotNull(path); // for backward compatibility
1012         return createFromFile(new File(path));
1013     }
1014 
1015     /**
1016      * Create a new typeface from an array of font families.
1017      *
1018      * @param families array of font families
1019      * @deprecated
1020      */
1021     @Deprecated
1022     @UnsupportedAppUsage(trackingBug = 123768928)
1023     private static Typeface createFromFamilies(android.graphics.FontFamily[] families) {
1024         long[] ptrArray = new long[families.length];
1025         for (int i = 0; i < families.length; i++) {
1026             ptrArray[i] = families[i].mNativePtr;
1027         }
1028         return new Typeface(nativeCreateFromArray(
1029                 ptrArray, RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE));
1030     }
1031 
1032     /**
1033      * Create a new typeface from an array of android.graphics.fonts.FontFamily.
1034      *
1035      * @param families array of font families
1036      */
1037     private static Typeface createFromFamilies(@Nullable FontFamily[] families) {
1038         final long[] ptrArray = new long[families.length];
1039         for (int i = 0; i < families.length; ++i) {
1040             ptrArray[i] = families[i].getNativePtr();
1041         }
1042         return new Typeface(nativeCreateFromArray(ptrArray,
1043                   RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE));
1044     }
1045 
1046     /**
1047      * This method is used by supportlib-v27.
1048      *
1049      * @deprecated Use {@link android.graphics.fonts.FontFamily} instead.
1050      */
1051     @UnsupportedAppUsage(trackingBug = 123768395)
1052     @Deprecated
1053     private static Typeface createFromFamiliesWithDefault(
1054             android.graphics.FontFamily[] families, int weight, int italic) {
1055         return createFromFamiliesWithDefault(families, DEFAULT_FAMILY, weight, italic);
1056     }
1057 
1058     /**
1059      * Create a new typeface from an array of font families, including
1060      * also the font families in the fallback list.
1061      * @param fallbackName the family name. If given families don't support characters, the
1062      *               characters will be rendered with this family.
1063      * @param weight the weight for this family. In that case, the table information in the first
1064      *               family's font is used. If the first family has multiple fonts, the closest to
1065      *               the regular weight and upright font is used.
1066      * @param italic the italic information for this family. In that case, the table information in
1067      *               the first family's font is used. If the first family has multiple fonts, the
1068      *               closest to the regular weight and upright font is used.
1069      * @param families array of font families
1070      *
1071      * @deprecated Use {@link android.graphics.fonts.FontFamily} instead.
1072      */
1073     @UnsupportedAppUsage(trackingBug = 123768928)
1074     @Deprecated
1075     private static Typeface createFromFamiliesWithDefault(android.graphics.FontFamily[] families,
1076                 String fallbackName, int weight, int italic) {
1077         android.graphics.fonts.FontFamily[] fallback = SystemFonts.getSystemFallback(fallbackName);
1078         long[] ptrArray = new long[families.length + fallback.length];
1079         for (int i = 0; i < families.length; i++) {
1080             ptrArray[i] = families[i].mNativePtr;
1081         }
1082         for (int i = 0; i < fallback.length; i++) {
1083             ptrArray[i + families.length] = fallback[i].getNativePtr();
1084         }
1085         return new Typeface(nativeCreateFromArray(ptrArray, weight, italic));
1086     }
1087 
1088     // don't allow clients to call this directly
1089     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
1090     private Typeface(long ni) {
1091         if (ni == 0) {
1092             throw new RuntimeException("native typeface cannot be made");
1093         }
1094 
1095         native_instance = ni;
1096         sRegistry.registerNativeAllocation(this, native_instance);
1097         mStyle = nativeGetStyle(ni);
1098         mWeight = nativeGetWeight(ni);
1099     }
1100 
1101     private static Typeface getSystemDefaultTypeface(@NonNull String familyName) {
1102         Typeface tf = sSystemFontMap.get(familyName);
1103         return tf == null ? Typeface.DEFAULT : tf;
1104     }
1105 
1106     /** @hide */
1107     @VisibleForTesting
1108     public static void initSystemDefaultTypefaces(Map<String, Typeface> systemFontMap,
1109             Map<String, FontFamily[]> fallbacks,
1110             FontConfig.Alias[] aliases) {
1111         for (Map.Entry<String, FontFamily[]> entry : fallbacks.entrySet()) {
1112             systemFontMap.put(entry.getKey(), createFromFamilies(entry.getValue()));
1113         }
1114 
1115         for (FontConfig.Alias alias : aliases) {
1116             if (systemFontMap.containsKey(alias.getName())) {
1117                 continue; // If alias and named family are conflict, use named family.
1118             }
1119             final Typeface base = systemFontMap.get(alias.getToName());
1120             if (base == null) {
1121                 // The missing target is a valid thing, some configuration don't have font files,
1122                 // e.g. wear devices. Just skip this alias.
1123                 continue;
1124             }
1125             final int weight = alias.getWeight();
1126             final Typeface newFace = weight == 400 ? base :
1127                     new Typeface(nativeCreateWeightAlias(base.native_instance, weight));
1128             systemFontMap.put(alias.getName(), newFace);
1129         }
1130     }
1131 
1132     private static void registerGenericFamilyNative(@NonNull String familyName,
1133             @Nullable Typeface typeface) {
1134         if (typeface != null) {
1135             nativeRegisterGenericFamily(familyName, typeface.native_instance);
1136         }
1137     }
1138 
1139     static {
1140         final HashMap<String, Typeface> systemFontMap = new HashMap<>();
1141         initSystemDefaultTypefaces(systemFontMap, SystemFonts.getRawSystemFallbackMap(),
1142                 SystemFonts.getAliases());
1143         sSystemFontMap = Collections.unmodifiableMap(systemFontMap);
1144 
1145         // We can't assume DEFAULT_FAMILY available on Roboletric.
1146         if (sSystemFontMap.containsKey(DEFAULT_FAMILY)) {
1147             setDefault(sSystemFontMap.get(DEFAULT_FAMILY));
1148         }
1149 
1150         // Set up defaults and typefaces exposed in public API
1151         DEFAULT         = create((String) null, 0);
1152         DEFAULT_BOLD    = create((String) null, Typeface.BOLD);
1153         SANS_SERIF      = create("sans-serif", 0);
1154         SERIF           = create("serif", 0);
1155         MONOSPACE       = create("monospace", 0);
1156 
1157         sDefaults = new Typeface[] {
1158             DEFAULT,
1159             DEFAULT_BOLD,
1160             create((String) null, Typeface.ITALIC),
1161             create((String) null, Typeface.BOLD_ITALIC),
1162         };
1163 
1164         // A list of generic families to be registered in native.
1165         // https://www.w3.org/TR/css-fonts-4/#generic-font-families
1166         String[] genericFamilies = {
1167             "serif", "sans-serif", "cursive", "fantasy", "monospace", "system-ui"
1168         };
1169 
1170         for (String genericFamily : genericFamilies) {
1171             registerGenericFamilyNative(genericFamily, systemFontMap.get(genericFamily));
1172         }
1173     }
1174 
1175     @Override
1176     public boolean equals(Object o) {
1177         if (this == o) return true;
1178         if (o == null || getClass() != o.getClass()) return false;
1179 
1180         Typeface typeface = (Typeface) o;
1181 
1182         return mStyle == typeface.mStyle && native_instance == typeface.native_instance;
1183     }
1184 
1185     @Override
1186     public int hashCode() {
1187         /*
1188          * Modified method for hashCode with long native_instance derived from
1189          * http://developer.android.com/reference/java/lang/Object.html
1190          */
1191         int result = 17;
1192         result = 31 * result + (int) (native_instance ^ (native_instance >>> 32));
1193         result = 31 * result + mStyle;
1194         return result;
1195     }
1196 
1197     /** @hide */
1198     public boolean isSupportedAxes(int axis) {
1199         if (mSupportedAxes == null) {
1200             synchronized (this) {
1201                 if (mSupportedAxes == null) {
1202                     mSupportedAxes = nativeGetSupportedAxes(native_instance);
1203                     if (mSupportedAxes == null) {
1204                         mSupportedAxes = EMPTY_AXES;
1205                     }
1206                 }
1207             }
1208         }
1209         return Arrays.binarySearch(mSupportedAxes, axis) >= 0;
1210     }
1211 
1212     private static native long nativeCreateFromTypeface(long native_instance, int style);
1213     private static native long nativeCreateFromTypefaceWithExactStyle(
1214             long native_instance, int weight, boolean italic);
1215     // TODO: clean up: change List<FontVariationAxis> to FontVariationAxis[]
1216     private static native long nativeCreateFromTypefaceWithVariation(
1217             long native_instance, List<FontVariationAxis> axes);
1218     @UnsupportedAppUsage
1219     private static native long nativeCreateWeightAlias(long native_instance, int weight);
1220     @UnsupportedAppUsage
1221     private static native long nativeCreateFromArray(long[] familyArray, int weight, int italic);
1222     private static native int[] nativeGetSupportedAxes(long native_instance);
1223 
1224     @CriticalNative
1225     private static native void nativeSetDefault(long nativePtr);
1226 
1227     @CriticalNative
1228     private static native int  nativeGetStyle(long nativePtr);
1229 
1230     @CriticalNative
1231     private static native int  nativeGetWeight(long nativePtr);
1232 
1233     @CriticalNative
1234     private static native long nativeGetReleaseFunc();
1235 
1236     private static native void nativeRegisterGenericFamily(String str, long nativePtr);
1237 }
1238