• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.annotation.TestApi;
29 import android.annotation.UiThread;
30 import android.compat.annotation.UnsupportedAppUsage;
31 import android.content.res.AssetManager;
32 import android.graphics.fonts.Font;
33 import android.graphics.fonts.FontFamily;
34 import android.graphics.fonts.FontStyle;
35 import android.graphics.fonts.FontVariationAxis;
36 import android.graphics.fonts.SystemFonts;
37 import android.icu.util.ULocale;
38 import android.os.Build;
39 import android.os.ParcelFileDescriptor;
40 import android.os.SharedMemory;
41 import android.os.SystemProperties;
42 import android.os.Trace;
43 import android.provider.FontRequest;
44 import android.provider.FontsContract;
45 import android.ravenwood.annotation.RavenwoodReplace;
46 import android.system.ErrnoException;
47 import android.system.OsConstants;
48 import android.text.FontConfig;
49 import android.util.ArrayMap;
50 import android.util.Base64;
51 import android.util.Log;
52 import android.util.LongSparseArray;
53 import android.util.LruCache;
54 import android.util.Pair;
55 import android.util.SparseArray;
56 
57 import com.android.internal.annotations.GuardedBy;
58 import com.android.internal.annotations.VisibleForTesting;
59 import com.android.internal.util.Preconditions;
60 import com.android.text.flags.Flags;
61 
62 import dalvik.annotation.optimization.CriticalNative;
63 import dalvik.annotation.optimization.FastNative;
64 
65 import libcore.util.NativeAllocationRegistry;
66 
67 import java.io.ByteArrayOutputStream;
68 import java.io.File;
69 import java.io.FileDescriptor;
70 import java.io.IOException;
71 import java.io.InputStream;
72 import java.lang.annotation.Retention;
73 import java.lang.annotation.RetentionPolicy;
74 import java.nio.ByteBuffer;
75 import java.nio.ByteOrder;
76 import java.util.ArrayList;
77 import java.util.Arrays;
78 import java.util.Collections;
79 import java.util.Comparator;
80 import java.util.List;
81 import java.util.Map;
82 import java.util.Objects;
83 import java.util.function.BiConsumer;
84 
85 /**
86  * The Typeface class specifies the typeface and intrinsic style of a font.
87  * This is used in the paint, along with optionally Paint settings like
88  * textSize, textSkewX, textScaleX to specify
89  * how text appears when drawn (and measured).
90  */
91 @android.ravenwood.annotation.RavenwoodKeepWholeClass
92 public class Typeface {
93 
94     private static String TAG = "Typeface";
95 
96     /** @hide */
97     public static final boolean ENABLE_LAZY_TYPEFACE_INITIALIZATION = true;
98 
99     private static class NoImagePreloadHolder {
100         static final NativeAllocationRegistry sRegistry =
101                 NativeAllocationRegistry.createMalloced(
102                         Typeface.class.getClassLoader(), nativeGetReleaseFunc());
103     }
104 
105     /** The default NORMAL typeface object */
106     public static final Typeface DEFAULT = null;
107     /**
108      * The default BOLD typeface object. Note: this may be not actually be
109      * bold, depending on what fonts are installed. Call getStyle() to know
110      * for sure.
111      */
112     public static final Typeface DEFAULT_BOLD = null;
113     /** The NORMAL style of the default sans serif typeface. */
114     public static final Typeface SANS_SERIF = null;
115     /** The NORMAL style of the default serif typeface. */
116     public static final Typeface SERIF = null;
117     /** The NORMAL style of the default monospace typeface. */
118     public static final Typeface MONOSPACE = null;
119 
120     /**
121      * The default {@link Typeface}s for different text styles.
122      * Call {@link #defaultFromStyle(int)} to get the default typeface for the given text style.
123      * It shouldn't be changed for app wide typeface settings. Please use theme and font XML for
124      * the same purpose.
125      */
126     @GuardedBy("SYSTEM_FONT_MAP_LOCK")
127     @UnsupportedAppUsage(trackingBug = 123769446)
128     static Typeface[] sDefaults;
129 
130     /**
131      * Cache for Typeface objects for style variant. Currently max size is 3.
132      */
133     @GuardedBy("sStyledCacheLock")
134     private static final LongSparseArray<SparseArray<Typeface>> sStyledTypefaceCache =
135             new LongSparseArray<>(3);
136     private static final Object sStyledCacheLock = new Object();
137 
138     /**
139      * Cache for Typeface objects for weight variant. Currently max size is 3.
140      */
141     @GuardedBy("sWeightCacheLock")
142     private static final LongSparseArray<SparseArray<Typeface>> sWeightTypefaceCache =
143             new LongSparseArray<>(3);
144     private static final Object sWeightCacheLock = new Object();
145 
146     /**
147      * Cache for Typeface objects dynamically loaded from assets. Currently max size is 16.
148      */
149     @GuardedBy("sDynamicCacheLock")
150     private static final LruCache<String, Typeface> sDynamicTypefaceCache = new LruCache<>(16);
151     private static final Object sDynamicCacheLock = new Object();
152 
153     private static final LruCache<Long, LruCache<String, Typeface>> sVariableCache =
154             new LruCache<>(16);
155     private static final Object sVariableCacheLock = new Object();
156 
157     /** @hide */
158     @VisibleForTesting
clearTypefaceCachesForTestingPurpose()159     public static void clearTypefaceCachesForTestingPurpose() {
160         synchronized (sWeightCacheLock) {
161             sWeightTypefaceCache.clear();
162         }
163         synchronized (sDynamicCacheLock) {
164             sDynamicTypefaceCache.evictAll();
165         }
166         synchronized (sVariableCacheLock) {
167             sVariableCache.evictAll();
168         }
169     }
170 
171     @GuardedBy("SYSTEM_FONT_MAP_LOCK")
172     static Typeface sDefaultTypeface;
173 
174     /**
175      * sSystemFontMap is read only and unmodifiable.
176      * Use public API {@link #create(String, int)} to get the typeface for given familyName.
177      */
178     @GuardedBy("SYSTEM_FONT_MAP_LOCK")
179     @UnsupportedAppUsage(trackingBug = 123769347)
180     static final Map<String, Typeface> sSystemFontMap = new ArrayMap<>();
181 
182     // DirectByteBuffer object to hold sSystemFontMap's backing memory mapping.
183     static ByteBuffer sSystemFontMapBuffer = null;
184     static SharedMemory sSystemFontMapSharedMemory = null;
185 
186     // Lock to guard sSystemFontMap and derived default or public typefaces.
187     // sStyledCacheLock may be held while this lock is held. Holding them in the reverse order may
188     // introduce deadlock.
189     private static final Object SYSTEM_FONT_MAP_LOCK = new Object();
190 
191     // This field is used but left for hiddenapi private list
192     // We cannot support sSystemFallbackMap since we will migrate to public FontFamily API.
193     /**
194      * @deprecated Use {@link android.graphics.fonts.FontFamily} instead.
195      */
196     @UnsupportedAppUsage(trackingBug = 123768928)
197     @Deprecated
198     static final Map<String, android.graphics.FontFamily[]> sSystemFallbackMap =
199             Collections.emptyMap();
200 
201     /**
202      * Returns the shared memory that used for creating Typefaces.
203      *
204      * @return A SharedMemory used for creating Typeface. Maybe null if the lazy initialization is
205      *         disabled or inside SystemServer or Zygote.
206      * @hide
207      */
208     @TestApi
getSystemFontMapSharedMemory()209     public static @Nullable SharedMemory getSystemFontMapSharedMemory() {
210         if (ENABLE_LAZY_TYPEFACE_INITIALIZATION) {
211             Objects.requireNonNull(sSystemFontMapSharedMemory);
212         }
213         return sSystemFontMapSharedMemory;
214     }
215 
216     /**
217      * @hide
218      */
219     @UnsupportedAppUsage
220     public final long native_instance;
221 
222     private final Typeface mDerivedFrom;
223 
224     private final String mSystemFontFamilyName;
225 
226     private final Runnable mCleaner;
227 
228     /** @hide */
229     @IntDef(value = {NORMAL, BOLD, ITALIC, BOLD_ITALIC})
230     @Retention(RetentionPolicy.SOURCE)
231     public @interface Style {}
232 
233     // Style
234     public static final int NORMAL = 0;
235     public static final int BOLD = 1;
236     public static final int ITALIC = 2;
237     public static final int BOLD_ITALIC = 3;
238     /** @hide */ public static final int STYLE_MASK = 0x03;
239 
240     @UnsupportedAppUsage
241     private @Style final int mStyle;
242 
243     private @IntRange(from = 0, to = FontStyle.FONT_WEIGHT_MAX) final int mWeight;
244 
245     private boolean mIsVariationInstance;
246 
247     // Value for weight and italic. Indicates the value is resolved by font metadata.
248     // Must be the same as the C++ constant in core/jni/android/graphics/FontFamily.cpp
249     /** @hide */
250     public static final int RESOLVE_BY_FONT_TABLE = -1;
251     /**
252      * The key of the default font family.
253      * @hide
254      */
255     public static final String DEFAULT_FAMILY = "sans-serif";
256 
257     // Style value for building typeface.
258     private static final int STYLE_NORMAL = 0;
259     private static final int STYLE_ITALIC = 1;
260 
261     @GuardedBy("this")
262     private int[] mSupportedAxes;
263     private static final int[] EMPTY_AXES = {};
264 
265     /**
266      * Please use font in xml and also your application global theme to change the default Typeface.
267      * android:textViewStyle and its attribute android:textAppearance can be used in order to change
268      * typeface and other text related properties.
269      */
270     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
setDefault(Typeface t)271     private static void setDefault(Typeface t) {
272         synchronized (SYSTEM_FONT_MAP_LOCK) {
273             sDefaultTypeface = t;
274             nativeSetDefault(t.native_instance);
275         }
276     }
277 
getDefault()278     private static Typeface getDefault() {
279         synchronized (SYSTEM_FONT_MAP_LOCK) {
280             return sDefaultTypeface;
281         }
282     }
283 
284     /** Returns the typeface's weight value */
getWeight()285     public @IntRange(from = 0, to = 1000) int getWeight() {
286         return mWeight;
287     }
288 
289     /** @hide */
isVariationInstance()290     public boolean isVariationInstance() {
291         return mIsVariationInstance;
292     }
293 
294     /** Returns the typeface's intrinsic style attributes */
getStyle()295     public @Style int getStyle() {
296         return mStyle;
297     }
298 
299     /** Returns true if getStyle() has the BOLD bit set. */
isBold()300     public final boolean isBold() {
301         return (mStyle & BOLD) != 0;
302     }
303 
304     /** Returns true if getStyle() has the ITALIC bit set. */
isItalic()305     public final boolean isItalic() {
306         return (mStyle & ITALIC) != 0;
307     }
308 
309     /**
310      * Returns the Typeface used for creating this Typeface.
311      *
312      * Maybe null if this is not derived from other Typeface.
313      * TODO(b/357707916): Make this public API.
314      * @hide
315      */
316     @VisibleForTesting
getDerivedFrom()317     public final @Nullable Typeface getDerivedFrom() {
318         return mDerivedFrom;
319     }
320 
321     /**
322      * Returns the system font family name if the typeface was created from a system font family,
323      * otherwise returns null.
324      */
getSystemFontFamilyName()325     public final @Nullable String getSystemFontFamilyName() {
326         return mSystemFontFamilyName;
327     }
328 
329     /**
330      * Returns true if the system has the font family with the name [familyName]. For example
331      * querying with "sans-serif" would check if the "sans-serif" family is defined in the system
332      * and return true if does.
333      *
334      * @param familyName The name of the font family, cannot be null. If null, exception will be
335      *                   thrown.
336      */
hasFontFamily(@onNull String familyName)337     private static boolean hasFontFamily(@NonNull String familyName) {
338         Objects.requireNonNull(familyName, "familyName cannot be null");
339         synchronized (SYSTEM_FONT_MAP_LOCK) {
340             return sSystemFontMap.containsKey(familyName);
341         }
342     }
343 
344     /**
345      * @hide
346      * Used by Resources to load a font resource of type xml.
347      */
348     @Nullable
createFromResources( FamilyResourceEntry entry, AssetManager mgr, String path)349     public static Typeface createFromResources(
350             FamilyResourceEntry entry, AssetManager mgr, String path) {
351         if (entry instanceof ProviderResourceEntry) {
352             final ProviderResourceEntry providerEntry = (ProviderResourceEntry) entry;
353 
354             String systemFontFamilyName = providerEntry.getSystemFontFamilyName();
355             if (systemFontFamilyName != null && hasFontFamily(systemFontFamilyName)) {
356                 return Typeface.create(systemFontFamilyName, NORMAL);
357             }
358             // Downloadable font
359             List<List<String>> givenCerts = providerEntry.getCerts();
360             List<List<byte[]>> certs = new ArrayList<>();
361             if (givenCerts != null) {
362                 for (int i = 0; i < givenCerts.size(); i++) {
363                     List<String> certSet = givenCerts.get(i);
364                     List<byte[]> byteArraySet = new ArrayList<>();
365                     for (int j = 0; j < certSet.size(); j++) {
366                         byteArraySet.add(Base64.decode(certSet.get(j), Base64.DEFAULT));
367                     }
368                     certs.add(byteArraySet);
369                 }
370             }
371             // Downloaded font and it wasn't cached, request it again and return a
372             // default font instead (nothing we can do now).
373             FontRequest request = new FontRequest(providerEntry.getAuthority(),
374                     providerEntry.getPackage(), providerEntry.getQuery(), certs);
375             Typeface typeface = FontsContract.getFontSync(request);
376             return typeface == null ? DEFAULT : typeface;
377         }
378 
379         Typeface typeface = findFromCache(mgr, path);
380         if (typeface != null) return typeface;
381 
382         // family is FontFamilyFilesResourceEntry
383         final FontFamilyFilesResourceEntry filesEntry = (FontFamilyFilesResourceEntry) entry;
384 
385         try {
386             FontFamily.Builder familyBuilder = null;
387             for (final FontFileResourceEntry fontFile : filesEntry.getEntries()) {
388                 final Font.Builder fontBuilder = new Font.Builder(mgr, fontFile.getFileName(),
389                         false /* isAsset */, AssetManager.COOKIE_UNKNOWN)
390                         .setTtcIndex(fontFile.getTtcIndex())
391                         .setFontVariationSettings(fontFile.getVariationSettings());
392                 if (fontFile.getWeight() != Typeface.RESOLVE_BY_FONT_TABLE) {
393                     fontBuilder.setWeight(fontFile.getWeight());
394                 }
395                 if (fontFile.getItalic() != Typeface.RESOLVE_BY_FONT_TABLE) {
396                     fontBuilder.setSlant(fontFile.getItalic() == FontFileResourceEntry.ITALIC
397                             ?  FontStyle.FONT_SLANT_ITALIC : FontStyle.FONT_SLANT_UPRIGHT);
398                 }
399 
400                 if (familyBuilder == null) {
401                     familyBuilder = new FontFamily.Builder(fontBuilder.build());
402                 } else {
403                     familyBuilder.addFont(fontBuilder.build());
404                 }
405             }
406             if (familyBuilder == null) {
407                 return Typeface.DEFAULT;
408             }
409             final FontFamily family = familyBuilder.build();
410             final FontStyle normal = new FontStyle(FontStyle.FONT_WEIGHT_NORMAL,
411                     FontStyle.FONT_SLANT_UPRIGHT);
412             Font bestFont = family.getFont(0);
413             int bestScore = normal.getMatchScore(bestFont.getStyle());
414             for (int i = 1; i < family.getSize(); ++i) {
415                 final Font candidate = family.getFont(i);
416                 final int score = normal.getMatchScore(candidate.getStyle());
417                 if (score < bestScore) {
418                     bestFont = candidate;
419                     bestScore = score;
420                 }
421             }
422             typeface = new Typeface.CustomFallbackBuilder(family)
423                     .setStyle(bestFont.getStyle())
424                     .build();
425         } catch (IllegalArgumentException e) {
426             // To be a compatible behavior with API28 or before, catch IllegalArgumentExcetpion
427             // thrown by native code and returns null.
428             return null;
429         } catch (IOException e) {
430             typeface = Typeface.DEFAULT;
431         }
432         synchronized (sDynamicCacheLock) {
433             final String key = Builder.createAssetUid(mgr, path, 0 /* ttcIndex */,
434                     null /* axes */, RESOLVE_BY_FONT_TABLE /* weight */,
435                     RESOLVE_BY_FONT_TABLE /* italic */, DEFAULT_FAMILY);
436             sDynamicTypefaceCache.put(key, typeface);
437         }
438         return typeface;
439     }
440 
441     /**
442      * Used by resources for cached loading if the font is available.
443      * @hide
444      */
findFromCache(AssetManager mgr, String path)445     public static Typeface findFromCache(AssetManager mgr, String path) {
446         synchronized (sDynamicCacheLock) {
447             final String key = Builder.createAssetUid(mgr, path, 0 /* ttcIndex */, null /* axes */,
448                     RESOLVE_BY_FONT_TABLE /* weight */, RESOLVE_BY_FONT_TABLE /* italic */,
449                     DEFAULT_FAMILY);
450             Typeface typeface = sDynamicTypefaceCache.get(key);
451             if (typeface != null) {
452                 return typeface;
453             }
454         }
455         return null;
456     }
457 
458     /**
459      * A builder class for creating new Typeface instance.
460      *
461      * <p>
462      * Examples,
463      * 1) Create Typeface from ttf file.
464      * <pre>
465      * <code>
466      * Typeface.Builder builder = new Typeface.Builder("your_font_file.ttf");
467      * Typeface typeface = builder.build();
468      * </code>
469      * </pre>
470      *
471      * 2) Create Typeface from ttc file in assets directory.
472      * <pre>
473      * <code>
474      * Typeface.Builder builder = new Typeface.Builder(getAssets(), "your_font_file.ttc");
475      * builder.setTtcIndex(2);  // Set index of font collection.
476      * Typeface typeface = builder.build();
477      * </code>
478      * </pre>
479      *
480      * 3) Create Typeface with variation settings.
481      * <pre>
482      * <code>
483      * Typeface.Builder builder = new Typeface.Builder("your_font_file.ttf");
484      * builder.setFontVariationSettings("'wght' 700, 'slnt' 20, 'ital' 1");
485      * builder.setWeight(700);  // Tell the system that this is a bold font.
486      * builder.setItalic(true);  // Tell the system that this is an italic style font.
487      * Typeface typeface = builder.build();
488      * </code>
489      * </pre>
490      * </p>
491      */
492     public static final class Builder {
493         /** @hide */
494         public static final int NORMAL_WEIGHT = 400;
495         /** @hide */
496         public static final int BOLD_WEIGHT = 700;
497 
498         // Kept for generating asset cache key.
499         private final AssetManager mAssetManager;
500         private final String mPath;
501 
502         private final @Nullable Font.Builder mFontBuilder;
503 
504         private String mFallbackFamilyName;
505 
506         private int mWeight = RESOLVE_BY_FONT_TABLE;
507         private int mItalic = RESOLVE_BY_FONT_TABLE;
508 
509         /**
510          * Constructs a builder with a file path.
511          *
512          * @param path The file object refers to the font file.
513          */
Builder(@onNull File path)514         public Builder(@NonNull File path) {
515             mFontBuilder = new Font.Builder(path);
516             mAssetManager = null;
517             mPath = null;
518         }
519 
520         /**
521          * Constructs a builder with a file descriptor.
522          *
523          * Caller is responsible for closing the passed file descriptor after {@link #build} is
524          * called.
525          *
526          * @param fd The file descriptor. The passed fd must be mmap-able.
527          */
Builder(@onNull FileDescriptor fd)528         public Builder(@NonNull FileDescriptor fd) {
529             Font.Builder builder;
530             try {
531                 builder = new Font.Builder(ParcelFileDescriptor.dup(fd));
532             } catch (IOException e) {
533                 // We cannot tell the error to developer at this moment since we cannot change the
534                 // public API signature. Instead, silently fallbacks to system fallback in the build
535                 // method as the same as other error cases.
536                 builder = null;
537             }
538             mFontBuilder = builder;
539             mAssetManager = null;
540             mPath = null;
541         }
542 
543         /**
544          * Constructs a builder with a file path.
545          *
546          * @param path The full path to the font file.
547          */
Builder(@onNull String path)548         public Builder(@NonNull String path) {
549             mFontBuilder = new Font.Builder(new File(path));
550             mAssetManager = null;
551             mPath = null;
552         }
553 
554         /**
555          * Constructs a builder from an asset manager and a file path in an asset directory.
556          *
557          * @param assetManager The application's asset manager
558          * @param path The file name of the font data in the asset directory
559          */
Builder(@onNull AssetManager assetManager, @NonNull String path)560         public Builder(@NonNull AssetManager assetManager, @NonNull String path) {
561             this(assetManager, path, true /* is asset */, 0 /* cookie */);
562         }
563 
564         /**
565          * Constructs a builder from an asset manager and a file path in an asset directory.
566          *
567          * @param assetManager The application's asset manager
568          * @param path The file name of the font data in the asset directory
569          * @param cookie a cookie for the asset
570          * @hide
571          */
Builder(@onNull AssetManager assetManager, @NonNull String path, boolean isAsset, int cookie)572         public Builder(@NonNull AssetManager assetManager, @NonNull String path, boolean isAsset,
573                 int cookie) {
574             mFontBuilder = new Font.Builder(assetManager, path, isAsset, cookie);
575             mAssetManager = assetManager;
576             mPath = path;
577         }
578 
579         /**
580          * Sets weight of the font.
581          *
582          * Tells the system the weight of the given font. If not provided, the system will resolve
583          * the weight value by reading font tables.
584          * @param weight a weight value.
585          */
setWeight(@ntRangefrom = 1, to = 1000) int weight)586         public Builder setWeight(@IntRange(from = 1, to = 1000) int weight) {
587             mWeight = weight;
588             mFontBuilder.setWeight(weight);
589             return this;
590         }
591 
592         /**
593          * Sets italic information of the font.
594          *
595          * Tells the system the style of the given font. If not provided, the system will resolve
596          * the style by reading font tables.
597          * @param italic {@code true} if the font is italic. Otherwise {@code false}.
598          */
setItalic(boolean italic)599         public Builder setItalic(boolean italic) {
600             mItalic = italic ? FontStyle.FONT_SLANT_ITALIC : FontStyle.FONT_SLANT_UPRIGHT;
601             mFontBuilder.setSlant(mItalic);
602             return this;
603         }
604 
605         /**
606          * Sets an index of the font collection. See {@link android.R.attr#ttcIndex}.
607          *
608          * Can not be used for Typeface source. build() method will return null for invalid index.
609          * @param ttcIndex An index of the font collection. If the font source is not font
610          *                 collection, do not call this method or specify 0.
611          */
setTtcIndex(@ntRangefrom = 0) int ttcIndex)612         public Builder setTtcIndex(@IntRange(from = 0) int ttcIndex) {
613             mFontBuilder.setTtcIndex(ttcIndex);
614             return this;
615         }
616 
617         /**
618          * Sets a font variation settings.
619          *
620          * @param variationSettings See {@link android.widget.TextView#setFontVariationSettings}.
621          * @throws IllegalArgumentException If given string is not a valid font variation settings
622          *                                  format.
623          */
setFontVariationSettings(@ullable String variationSettings)624         public Builder setFontVariationSettings(@Nullable String variationSettings) {
625             mFontBuilder.setFontVariationSettings(variationSettings);
626             return this;
627         }
628 
629         /**
630          * Sets a font variation settings.
631          *
632          * @param axes An array of font variation axis tag-value pairs.
633          */
setFontVariationSettings(@ullable FontVariationAxis[] axes)634         public Builder setFontVariationSettings(@Nullable FontVariationAxis[] axes) {
635             mFontBuilder.setFontVariationSettings(axes);
636             return this;
637         }
638 
639         /**
640          * Sets a fallback family name.
641          *
642          * By specifying a fallback family name, a fallback Typeface will be returned if the
643          * {@link #build} method fails to create a Typeface from the provided font. The fallback
644          * family will be resolved with the provided weight and italic information specified by
645          * {@link #setWeight} and {@link #setItalic}.
646          *
647          * If {@link #setWeight} is not called, the fallback family keeps the default weight.
648          * Similarly, if {@link #setItalic} is not called, the fallback family keeps the default
649          * italic information. For example, calling {@code builder.setFallback("sans-serif-light")}
650          * is equivalent to calling {@code builder.setFallback("sans-serif").setWeight(300)} in
651          * terms of fallback. The default weight and italic information are overridden by calling
652          * {@link #setWeight} and {@link #setItalic}. For example, if a Typeface is constructed
653          * using {@code builder.setFallback("sans-serif-light").setWeight(700)}, the fallback text
654          * will render as sans serif bold.
655          *
656          * @param familyName A family name to be used for fallback if the provided font can not be
657          *                   used. By passing {@code null}, build() returns {@code null}.
658          *                   If {@link #setFallback} is not called on the builder, {@code null}
659          *                   is assumed.
660          */
setFallback(@ullable String familyName)661         public Builder setFallback(@Nullable String familyName) {
662             mFallbackFamilyName = familyName;
663             return this;
664         }
665 
666         /**
667          * Creates a unique id for a given AssetManager and asset path.
668          *
669          * @param mgr  AssetManager instance
670          * @param path The path for the asset.
671          * @param ttcIndex The TTC index for the font.
672          * @param axes The font variation settings.
673          * @return Unique id for a given AssetManager and asset path.
674          */
createAssetUid(final AssetManager mgr, String path, int ttcIndex, @Nullable FontVariationAxis[] axes, int weight, int italic, String fallback)675         private static String createAssetUid(final AssetManager mgr, String path, int ttcIndex,
676                 @Nullable FontVariationAxis[] axes, int weight, int italic, String fallback) {
677             final SparseArray<String> pkgs = mgr.getAssignedPackageIdentifiers();
678             final StringBuilder builder = new StringBuilder();
679             final int size = pkgs.size();
680             for (int i = 0; i < size; i++) {
681                 builder.append(pkgs.valueAt(i));
682                 builder.append("-");
683             }
684             builder.append(path);
685             builder.append("-");
686             builder.append(Integer.toString(ttcIndex));
687             builder.append("-");
688             builder.append(Integer.toString(weight));
689             builder.append("-");
690             builder.append(Integer.toString(italic));
691             // Family name may contain hyphen. Use double hyphen for avoiding key conflicts before
692             // and after appending falblack name.
693             builder.append("--");
694             builder.append(fallback);
695             builder.append("--");
696             if (axes != null) {
697                 for (FontVariationAxis axis : axes) {
698                     builder.append(axis.getTag());
699                     builder.append("-");
700                     builder.append(Float.toString(axis.getStyleValue()));
701                 }
702             }
703             return builder.toString();
704         }
705 
resolveFallbackTypeface()706         private Typeface resolveFallbackTypeface() {
707             if (mFallbackFamilyName == null) {
708                 return null;
709             }
710 
711             final Typeface base =  getSystemDefaultTypeface(mFallbackFamilyName);
712             if (mWeight == RESOLVE_BY_FONT_TABLE && mItalic == RESOLVE_BY_FONT_TABLE) {
713                 return base;
714             }
715 
716             final int weight = (mWeight == RESOLVE_BY_FONT_TABLE) ? base.mWeight : mWeight;
717             final boolean italic =
718                     (mItalic == RESOLVE_BY_FONT_TABLE) ? (base.mStyle & ITALIC) != 0 : mItalic == 1;
719             return createWeightStyle(base, weight, italic);
720         }
721 
722         /**
723          * Generates new Typeface from specified configuration.
724          *
725          * @return Newly created Typeface. May return null if some parameters are invalid.
726          */
build()727         public Typeface build() {
728             if (mFontBuilder == null) {
729                 return resolveFallbackTypeface();
730             }
731             try {
732                 final Font font = mFontBuilder.build();
733                 final String key = mAssetManager == null ? null : createAssetUid(
734                         mAssetManager, mPath, font.getTtcIndex(), font.getAxes(),
735                         mWeight, mItalic,
736                         mFallbackFamilyName == null ? DEFAULT_FAMILY : mFallbackFamilyName);
737                 if (key != null) {
738                     // Dynamic cache lookup is only for assets.
739                     synchronized (sDynamicCacheLock) {
740                         final Typeface typeface = sDynamicTypefaceCache.get(key);
741                         if (typeface != null) {
742                             return typeface;
743                         }
744                     }
745                 }
746                 final FontFamily family = new FontFamily.Builder(font).build();
747                 final int weight = mWeight == RESOLVE_BY_FONT_TABLE
748                         ? font.getStyle().getWeight() : mWeight;
749                 final int slant = mItalic == RESOLVE_BY_FONT_TABLE
750                         ? font.getStyle().getSlant() : mItalic;
751                 final CustomFallbackBuilder builder = new CustomFallbackBuilder(family)
752                         .setStyle(new FontStyle(weight, slant));
753                 if (mFallbackFamilyName != null) {
754                     builder.setSystemFallback(mFallbackFamilyName);
755                 }
756                 final Typeface typeface = builder.build();
757                 if (key != null) {
758                     synchronized (sDynamicCacheLock) {
759                         sDynamicTypefaceCache.put(key, typeface);
760                     }
761                 }
762                 return typeface;
763             } catch (IOException | IllegalArgumentException e) {
764                 return resolveFallbackTypeface();
765             }
766         }
767     }
768 
769     /**
770      * A builder class for creating new Typeface instance.
771      *
772      * There are two font fallback mechanisms, custom font fallback and system font fallback.
773      * The custom font fallback is a simple ordered list. The text renderer tries to see if it can
774      * render a character with the first font and if that font does not support the character, try
775      * next one and so on. It will keep trying until end of the custom fallback chain. The maximum
776      * length of the custom fallback chain is 64.
777      * The system font fallback is a system pre-defined fallback chain. The system fallback is
778      * processed only when no matching font is found in the custom font fallback.
779      *
780      * <p>
781      * Examples,
782      * 1) Create Typeface from single ttf file.
783      * <pre>
784      * <code>
785      * Font font = new Font.Builder("your_font_file.ttf").build();
786      * FontFamily family = new FontFamily.Builder(font).build();
787      * Typeface typeface = new Typeface.CustomFallbackBuilder(family).build();
788      * </code>
789      * </pre>
790      *
791      * 2) Create Typeface from multiple font files and select bold style by default.
792      * <pre>
793      * <code>
794      * Font regularFont = new Font.Builder("regular.ttf").build();
795      * Font boldFont = new Font.Builder("bold.ttf").build();
796      * FontFamily family = new FontFamily.Builder(regularFont)
797      *     .addFont(boldFont).build();
798      * Typeface typeface = new Typeface.CustomFallbackBuilder(family)
799      *     .setWeight(Font.FONT_WEIGHT_BOLD)  // Set bold style as the default style.
800      *                                        // If the font family doesn't have bold style font,
801      *                                        // system will select the closest font.
802      *     .build();
803      * </code>
804      * </pre>
805      *
806      * 3) Create Typeface from single ttf file and if that font does not have glyph for the
807      * characters, use "serif" font family instead.
808      * <pre>
809      * <code>
810      * Font font = new Font.Builder("your_font_file.ttf").build();
811      * FontFamily family = new FontFamily.Builder(font).build();
812      * Typeface typeface = new Typeface.CustomFallbackBuilder(family)
813      *     .setSystemFallback("serif")  // Set serif font family as the fallback.
814      *     .build();
815      * </code>
816      * </pre>
817      * 4) Create Typeface from single ttf file and set another ttf file for the fallback.
818      * <pre>
819      * <code>
820      * Font font = new Font.Builder("English.ttf").build();
821      * FontFamily family = new FontFamily.Builder(font).build();
822      *
823      * Font fallbackFont = new Font.Builder("Arabic.ttf").build();
824      * FontFamily fallbackFamily = new FontFamily.Builder(fallbackFont).build();
825      * Typeface typeface = new Typeface.CustomFallbackBuilder(family)
826      *     .addCustomFallback(fallbackFamily)  // Specify fallback family.
827      *     .setSystemFallback("serif")  // Set serif font family as the fallback.
828      *     .build();
829      * </code>
830      * </pre>
831      * </p>
832      */
833     public static final class CustomFallbackBuilder {
834         private static final int MAX_CUSTOM_FALLBACK = 64;
835         private final ArrayList<FontFamily> mFamilies = new ArrayList<>();
836         private String mFallbackName = null;
837         private @Nullable FontStyle mStyle;
838 
839         /**
840          * Returns the maximum capacity of custom fallback families.
841          *
842          * This includes the first font family passed to the constructor.
843          * It is guaranteed that the value will be greater than or equal to 64.
844          *
845          * @return the maximum number of font families for the custom fallback
846          */
getMaxCustomFallbackCount()847         public static @IntRange(from = 64) int getMaxCustomFallbackCount() {
848             return MAX_CUSTOM_FALLBACK;
849         }
850 
851         /**
852          * Constructs a builder with a font family.
853          *
854          * @param family a family object
855          */
CustomFallbackBuilder(@onNull FontFamily family)856         public CustomFallbackBuilder(@NonNull FontFamily family) {
857             Preconditions.checkNotNull(family);
858             mFamilies.add(family);
859         }
860 
861         /**
862          * Sets a system fallback by name.
863          *
864          * You can specify generic font family names or OEM specific family names. If the system
865          * don't have a specified fallback, the default fallback is used instead.
866          * For more information about generic font families, see <a
867          * href="https://www.w3.org/TR/css-fonts-4/#generic-font-families">CSS specification</a>
868          *
869          * For more information about fallback, see class description.
870          *
871          * @param familyName a family name to be used for fallback if the provided fonts can not be
872          *                   used
873          */
setSystemFallback(@onNull String familyName)874         public @NonNull CustomFallbackBuilder setSystemFallback(@NonNull String familyName) {
875             Preconditions.checkNotNull(familyName);
876             mFallbackName = familyName;
877             return this;
878         }
879 
880         /**
881          * Sets a font style of the Typeface.
882          *
883          * If the font family doesn't have a font of given style, system will select the closest
884          * font from font family. For example, if a font family has fonts of 300 weight and 700
885          * weight then setWeight(400) is called, system will select the font of 300 weight.
886          *
887          * @param style a font style
888          */
setStyle(@onNull FontStyle style)889         public @NonNull CustomFallbackBuilder setStyle(@NonNull FontStyle style) {
890             mStyle = style;
891             return this;
892         }
893 
894         /**
895          * Append a font family to the end of the custom font fallback.
896          *
897          * You can set up to 64 custom fallback families including the first font family you passed
898          * to the constructor.
899          * For more information about fallback, see class description.
900          *
901          * @param family a fallback family
902          * @throws IllegalArgumentException if you give more than 64 custom fallback families
903          */
addCustomFallback(@onNull FontFamily family)904         public @NonNull CustomFallbackBuilder addCustomFallback(@NonNull FontFamily family) {
905             Preconditions.checkNotNull(family);
906             Preconditions.checkArgument(mFamilies.size() < getMaxCustomFallbackCount(),
907                     "Custom fallback limit exceeded(%d)", getMaxCustomFallbackCount());
908             mFamilies.add(family);
909             return this;
910         }
911 
912         /**
913          * Create the Typeface based on the configured values.
914          *
915          * @return the Typeface object
916          */
917         public @NonNull Typeface build() {
918             final int userFallbackSize = mFamilies.size();
919             final Typeface fallbackTypeface = getSystemDefaultTypeface(mFallbackName);
920             final long[] ptrArray = new long[userFallbackSize];
921             for (int i = 0; i < userFallbackSize; ++i) {
922                 ptrArray[i] = mFamilies.get(i).getNativePtr();
923             }
924             final int weight = mStyle == null ? 400 : mStyle.getWeight();
925             final int italic =
926                     (mStyle == null || mStyle.getSlant() == FontStyle.FONT_SLANT_UPRIGHT) ?  0 : 1;
927             return new Typeface(nativeCreateFromArray(
928                     ptrArray, fallbackTypeface.native_instance, weight, italic), null);
929         }
930     }
931 
932     /**
933      * Create a typeface object given a family name, and option style information.
934      * If null is passed for the name, then the "default" font will be chosen.
935      * The resulting typeface object can be queried (getStyle()) to discover what
936      * its "real" style characteristics are.
937      *
938      * @param familyName May be null. The name of the font family.
939      * @param style  The style (normal, bold, italic) of the typeface.
940      *               e.g. NORMAL, BOLD, ITALIC, BOLD_ITALIC
941      * @return The best matching typeface.
942      */
943     public static Typeface create(String familyName, @Style int style) {
944         return create(getSystemDefaultTypeface(familyName), style);
945     }
946 
947     /**
948      * Create a typeface object that best matches the specified existing
949      * typeface and the specified Style. Use this call if you want to pick a new
950      * style from the same family of an existing typeface object. If family is
951      * null, this selects from the default font's family.
952      *
953      * <p>
954      * This method is not thread safe on API 27 or before.
955      * This method is thread safe on API 28 or after.
956      * </p>
957      *
958      * @param family An existing {@link Typeface} object. In case of {@code null}, the default
959      *               typeface is used instead.
960      * @param style  The style (normal, bold, italic) of the typeface.
961      *               e.g. NORMAL, BOLD, ITALIC, BOLD_ITALIC
962      * @return The best matching typeface.
963      */
964     public static Typeface create(Typeface family, @Style int style) {
965         if ((style & ~STYLE_MASK) != 0) {
966             style = NORMAL;
967         }
968         if (family == null) {
969             family = getDefault();
970         }
971 
972         // Return early if we're asked for the same face/style
973         if (family.mStyle == style) {
974             return family;
975         }
976 
977         final long ni = family.native_instance;
978 
979         Typeface typeface;
980         synchronized (sStyledCacheLock) {
981             SparseArray<Typeface> styles = sStyledTypefaceCache.get(ni);
982 
983             if (styles == null) {
984                 styles = new SparseArray<Typeface>(4);
985                 sStyledTypefaceCache.put(ni, styles);
986             } else {
987                 typeface = styles.get(style);
988                 if (typeface != null) {
989                     return typeface;
990                 }
991             }
992 
993             typeface = new Typeface(nativeCreateFromTypeface(ni, style),
994                     family.getSystemFontFamilyName());
995             styles.put(style, typeface);
996         }
997         return typeface;
998     }
999 
1000     /**
1001      * Creates a typeface object that best matches the specified existing typeface and the specified
1002      * weight and italic style
1003      * <p>Below are numerical values and corresponding common weight names.</p>
1004      * <table>
1005      * <thead>
1006      * <tr><th>Value</th><th>Common weight name</th></tr>
1007      * </thead>
1008      * <tbody>
1009      * <tr><td>100</td><td>Thin</td></tr>
1010      * <tr><td>200</td><td>Extra Light</td></tr>
1011      * <tr><td>300</td><td>Light</td></tr>
1012      * <tr><td>400</td><td>Normal</td></tr>
1013      * <tr><td>500</td><td>Medium</td></tr>
1014      * <tr><td>600</td><td>Semi Bold</td></tr>
1015      * <tr><td>700</td><td>Bold</td></tr>
1016      * <tr><td>800</td><td>Extra Bold</td></tr>
1017      * <tr><td>900</td><td>Black</td></tr>
1018      * </tbody>
1019      * </table>
1020      *
1021      * <p>
1022      * This method is thread safe.
1023      * </p>
1024      *
1025      * @param family An existing {@link Typeface} object. In case of {@code null}, the default
1026      *               typeface is used instead.
1027      * @param weight The desired weight to be drawn.
1028      * @param italic {@code true} if italic style is desired to be drawn. Otherwise, {@code false}
1029      * @return A {@link Typeface} object for drawing specified weight and italic style. Never
1030      *         returns {@code null}
1031      *
1032      * @see #getWeight()
1033      * @see #isItalic()
1034      */
1035     public static @NonNull Typeface create(@Nullable Typeface family,
1036             @IntRange(from = 1, to = 1000) int weight, boolean italic) {
1037         Preconditions.checkArgumentInRange(weight, 0, 1000, "weight");
1038         if (family == null) {
1039             family = getDefault();
1040         }
1041         return createWeightStyle(family, weight, italic);
1042     }
1043 
1044     private static @NonNull Typeface createWeightStyle(@NonNull Typeface base,
1045             @IntRange(from = 1, to = 1000) int weight, boolean italic) {
1046         final int key = (weight << 1) | (italic ? 1 : 0);
1047 
1048         Typeface typeface;
1049         synchronized(sWeightCacheLock) {
1050             SparseArray<Typeface> innerCache = sWeightTypefaceCache.get(base.native_instance);
1051             if (innerCache == null) {
1052                 innerCache = new SparseArray<>(4);
1053                 sWeightTypefaceCache.put(base.native_instance, innerCache);
1054             } else {
1055                 typeface = innerCache.get(key);
1056                 if (typeface != null) {
1057                     return typeface;
1058                 }
1059             }
1060 
1061             typeface = new Typeface(
1062                     nativeCreateFromTypefaceWithExactStyle(base.native_instance, weight, italic),
1063                     base.getSystemFontFamilyName());
1064             innerCache.put(key, typeface);
1065         }
1066         return typeface;
1067     }
1068 
1069     private static String axesToVarKey(@NonNull List<FontVariationAxis> axes) {
1070         // The given list can be mutated because it is allocated in Paint#setFontVariationSettings.
1071         // Currently, Paint#setFontVariationSettings is the only code path reaches this method.
1072         axes.sort(Comparator.comparingInt(FontVariationAxis::getOpenTypeTagValue));
1073         StringBuilder sb = new StringBuilder();
1074         for (int i = 0; i < axes.size(); ++i) {
1075             final FontVariationAxis fva = axes.get(i);
1076             sb.append(fva.getTag());
1077             sb.append(fva.getStyleValue());
1078         }
1079         return sb.toString();
1080     }
1081 
1082     /**
1083      * TODO(b/357707916): Make this public API.
1084      * @hide
1085      */
1086     public static Typeface createFromTypefaceWithVariation(@Nullable Typeface family,
1087             @NonNull List<FontVariationAxis> axes) {
1088         if (Flags.typefaceCacheForVarSettings()) {
1089             final Typeface target = (family == null) ? Typeface.DEFAULT : family;
1090             final Typeface base = (target.mDerivedFrom == null) ? target : target.mDerivedFrom;
1091 
1092             final String key = axesToVarKey(axes);
1093 
1094             synchronized (sVariableCacheLock) {
1095                 LruCache<String, Typeface> innerCache = sVariableCache.get(base.native_instance);
1096                 if (innerCache == null) {
1097                     // Cache up to 16 var instance per root Typeface
1098                     innerCache = new LruCache<>(16);
1099                     sVariableCache.put(base.native_instance, innerCache);
1100                 } else {
1101                     Typeface cached = innerCache.get(key);
1102                     if (cached != null) {
1103                         return cached;
1104                     }
1105                 }
1106                 Typeface typeface = new Typeface(
1107                         nativeCreateFromTypefaceWithVariation(base.native_instance, axes),
1108                         base.getSystemFontFamilyName(), base);
1109                 innerCache.put(key, typeface);
1110                 return typeface;
1111             }
1112         }
1113 
1114         final Typeface base = family == null ? Typeface.DEFAULT : family;
1115         Typeface typeface = new Typeface(
1116                 nativeCreateFromTypefaceWithVariation(base.native_instance, axes),
1117                 base.getSystemFontFamilyName());
1118         return typeface;
1119     }
1120 
1121     /**
1122      * Returns one of the default typeface objects, based on the specified style
1123      *
1124      * @return the default typeface that corresponds to the style
1125      */
1126     public static Typeface defaultFromStyle(@Style int style) {
1127         synchronized (SYSTEM_FONT_MAP_LOCK) {
1128             return sDefaults[style];
1129         }
1130     }
1131 
1132     /**
1133      * Create a new typeface from the specified font data.
1134      *
1135      * @param mgr  The application's asset manager
1136      * @param path The file name of the font data in the assets directory
1137      * @return The new typeface.
1138      */
1139     public static Typeface createFromAsset(AssetManager mgr, String path) {
1140         Preconditions.checkNotNull(path); // for backward compatibility
1141         Preconditions.checkNotNull(mgr);
1142 
1143         Typeface typeface = new Builder(mgr, path).build();
1144         if (typeface != null) return typeface;
1145         // check if the file exists, and throw an exception for backward compatibility
1146         try (InputStream inputStream = mgr.open(path)) {
1147         } catch (IOException e) {
1148             throw new RuntimeException("Font asset not found " + path);
1149         }
1150 
1151         return Typeface.DEFAULT;
1152     }
1153 
1154     /**
1155      * Creates a unique id for a given font provider and query.
1156      */
1157     private static String createProviderUid(String authority, String query) {
1158         final StringBuilder builder = new StringBuilder();
1159         builder.append("provider:");
1160         builder.append(authority);
1161         builder.append("-");
1162         builder.append(query);
1163         return builder.toString();
1164     }
1165 
1166     /**
1167      * Create a new typeface from the specified font file.
1168      *
1169      * @param file The path to the font data.
1170      * @return The new typeface.
1171      */
1172     public static Typeface createFromFile(@Nullable File file) {
1173         // For the compatibility reasons, leaving possible NPE here.
1174         // See android.graphics.cts.TypefaceTest#testCreateFromFileByFileReferenceNull
1175 
1176         Typeface typeface = new Builder(file).build();
1177         if (typeface != null) return typeface;
1178 
1179         // check if the file exists, and throw an exception for backward compatibility
1180         if (!file.exists()) {
1181             throw new RuntimeException("Font asset not found " + file.getAbsolutePath());
1182         }
1183 
1184         return Typeface.DEFAULT;
1185     }
1186 
1187     /**
1188      * Create a new typeface from the specified font file.
1189      *
1190      * @param path The full path to the font data.
1191      * @return The new typeface.
1192      */
1193     public static Typeface createFromFile(@Nullable String path) {
1194         Preconditions.checkNotNull(path); // for backward compatibility
1195         return createFromFile(new File(path));
1196     }
1197 
1198     /**
1199      * Create a new typeface from an array of font families.
1200      *
1201      * @param families array of font families
1202      * @deprecated
1203      */
1204     @Deprecated
1205     @UnsupportedAppUsage(trackingBug = 123768928)
1206     private static Typeface createFromFamilies(android.graphics.FontFamily[] families) {
1207         long[] ptrArray = new long[families.length];
1208         for (int i = 0; i < families.length; i++) {
1209             ptrArray[i] = families[i].mNativePtr;
1210         }
1211         return new Typeface(nativeCreateFromArray(
1212                 ptrArray, 0, RESOLVE_BY_FONT_TABLE,
1213                 RESOLVE_BY_FONT_TABLE), null);
1214     }
1215 
1216     /**
1217      * Create a new typeface from an array of android.graphics.fonts.FontFamily.
1218      *
1219      * @param families array of font families
1220      */
1221     private static Typeface createFromFamilies(@NonNull String familyName,
1222             @Nullable FontFamily[] families) {
1223         final long[] ptrArray = new long[families.length];
1224         for (int i = 0; i < families.length; ++i) {
1225             ptrArray[i] = families[i].getNativePtr();
1226         }
1227         return new Typeface(nativeCreateFromArray(ptrArray, 0,
1228                   RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE), familyName);
1229     }
1230 
1231     /**
1232      * This method is used by supportlib-v27.
1233      *
1234      * @deprecated Use {@link android.graphics.fonts.FontFamily} instead.
1235      */
1236     @UnsupportedAppUsage(trackingBug = 123768395)
1237     @Deprecated
1238     private static Typeface createFromFamiliesWithDefault(
1239             android.graphics.FontFamily[] families, int weight, int italic) {
1240         return createFromFamiliesWithDefault(families, DEFAULT_FAMILY, weight, italic);
1241     }
1242 
1243     /**
1244      * Create a new typeface from an array of font families, including
1245      * also the font families in the fallback list.
1246      * @param fallbackName the family name. If given families don't support characters, the
1247      *               characters will be rendered with this family.
1248      * @param weight the weight for this family. In that case, the table information in the first
1249      *               family's font is used. If the first family has multiple fonts, the closest to
1250      *               the regular weight and upright font is used.
1251      * @param italic the italic information for this family. In that case, the table information in
1252      *               the first family's font is used. If the first family has multiple fonts, the
1253      *               closest to the regular weight and upright font is used.
1254      * @param families array of font families
1255      *
1256      * @deprecated Use {@link android.graphics.fonts.FontFamily} instead.
1257      */
1258     @UnsupportedAppUsage(trackingBug = 123768928)
1259     @Deprecated
1260     private static Typeface createFromFamiliesWithDefault(android.graphics.FontFamily[] families,
1261                 String fallbackName, int weight, int italic) {
1262         final Typeface fallbackTypeface = getSystemDefaultTypeface(fallbackName);
1263         long[] ptrArray = new long[families.length];
1264         for (int i = 0; i < families.length; i++) {
1265             ptrArray[i] = families[i].mNativePtr;
1266         }
1267         return new Typeface(nativeCreateFromArray(
1268                 ptrArray, fallbackTypeface.native_instance, weight, italic), null);
1269     }
1270 
1271     // don't allow clients to call this directly
1272     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
1273     private Typeface(long ni) {
1274         this(ni, null, null);
1275     }
1276 
1277 
1278     // don't allow clients to call this directly
1279     // This is kept for robolectric.
1280     private Typeface(long ni, @Nullable String systemFontFamilyName) {
1281         this(ni, systemFontFamilyName, null);
1282     }
1283 
1284     // don't allow clients to call this directly
1285     private Typeface(long ni, @Nullable String systemFontFamilyName,
1286             @Nullable Typeface derivedFrom) {
1287         if (ni == 0) {
1288             throw new RuntimeException("native typeface cannot be made");
1289         }
1290 
1291         native_instance = ni;
1292         mCleaner = NoImagePreloadHolder.sRegistry.registerNativeAllocation(this, native_instance);
1293         mStyle = nativeGetStyle(ni);
1294         mWeight = nativeGetWeight(ni);
1295         mIsVariationInstance = nativeIsVariationInstance(ni);
1296         mSystemFontFamilyName = systemFontFamilyName;
1297         mDerivedFrom = derivedFrom;
1298     }
1299 
1300     /**
1301      * Releases the underlying native object.
1302      *
1303      * <p>For testing only. Do not use the instance after this method is called.
1304      * It is safe to call this method twice or more on the same instance.
1305      * @hide
1306      */
1307     @TestApi
1308     public void releaseNativeObjectForTest() {
1309         mCleaner.run();
1310     }
1311 
1312     private static Typeface getSystemDefaultTypeface(@NonNull String familyName) {
1313         Typeface tf = sSystemFontMap.get(familyName);
1314         return tf == null ? Typeface.DEFAULT : tf;
1315     }
1316 
1317     /** @hide */
1318     @VisibleForTesting
1319     public static void initSystemDefaultTypefaces(Map<String, FontFamily[]> fallbacks,
1320             List<FontConfig.Alias> aliases,
1321             Map<String, Typeface> outSystemFontMap) {
1322         for (Map.Entry<String, FontFamily[]> entry : fallbacks.entrySet()) {
1323             outSystemFontMap.put(entry.getKey(),
1324                     createFromFamilies(entry.getKey(), entry.getValue()));
1325         }
1326 
1327         for (int i = 0; i < aliases.size(); ++i) {
1328             final FontConfig.Alias alias = aliases.get(i);
1329             if (outSystemFontMap.containsKey(alias.getName())) {
1330                 continue; // If alias and named family are conflict, use named family.
1331             }
1332             final Typeface base = outSystemFontMap.get(alias.getOriginal());
1333             if (base == null) {
1334                 // The missing target is a valid thing, some configuration don't have font files,
1335                 // e.g. wear devices. Just skip this alias.
1336                 continue;
1337             }
1338             final int weight = alias.getWeight();
1339             final Typeface newFace = weight == 400 ? base : new Typeface(
1340                     nativeCreateWeightAlias(base.native_instance, weight), alias.getName());
1341             outSystemFontMap.put(alias.getName(), newFace);
1342         }
1343     }
1344 
1345     private static void registerGenericFamilyNative(@NonNull String familyName,
1346             @Nullable Typeface typeface) {
1347         if (typeface != null) {
1348             nativeRegisterGenericFamily(familyName, typeface.native_instance);
1349         }
1350     }
1351 
1352     /**
1353      * Create a serialized system font mappings.
1354      *
1355      * @hide
1356      */
1357     @TestApi
1358     public static @NonNull SharedMemory serializeFontMap(@NonNull Map<String, Typeface> fontMap)
1359             throws IOException, ErrnoException {
1360         long[] nativePtrs = new long[fontMap.size()];
1361         // The name table will not be large, so let's create a byte array in memory.
1362         ByteArrayOutputStream namesBytes = new ByteArrayOutputStream();
1363         int i = 0;
1364         for (Map.Entry<String, Typeface> entry : fontMap.entrySet()) {
1365             nativePtrs[i++] = entry.getValue().native_instance;
1366             writeString(namesBytes, entry.getKey());
1367         }
1368         // int (typefacesBytesCount), typefaces, namesBytes
1369         final int typefaceBytesCountSize = Integer.BYTES;
1370         int typefacesBytesCount = nativeWriteTypefaces(null, typefaceBytesCountSize, nativePtrs);
1371         SharedMemory sharedMemory = SharedMemory.create(
1372                 "fontMap", typefaceBytesCountSize + typefacesBytesCount + namesBytes.size());
1373         ByteBuffer writableBuffer = sharedMemory.mapReadWrite().order(ByteOrder.BIG_ENDIAN);
1374         try {
1375             writableBuffer.putInt(typefacesBytesCount);
1376             int writtenBytesCount =
1377                     nativeWriteTypefaces(writableBuffer, writableBuffer.position(), nativePtrs);
1378             if (writtenBytesCount != typefacesBytesCount) {
1379                 throw new IOException(String.format("Unexpected bytes written: %d, expected: %d",
1380                         writtenBytesCount, typefacesBytesCount));
1381             }
1382             writableBuffer.position(writableBuffer.position() + writtenBytesCount);
1383             writableBuffer.put(namesBytes.toByteArray());
1384         } finally {
1385             SharedMemory.unmap(writableBuffer);
1386         }
1387         sharedMemory.setProtect(OsConstants.PROT_READ);
1388         return sharedMemory;
1389     }
1390 
1391     // buffer's byte order should be BIG_ENDIAN.
1392     /**
1393      * Deserialize the font mapping from the serialized byte buffer.
1394      *
1395      * <p>Warning: the given {@code buffer} must outlive generated Typeface
1396      * objects in {@code out}. In production code, this is guaranteed by
1397      * storing the buffer in {@link #sSystemFontMapBuffer}.
1398      * If you call this method in a test, please make sure to destroy the
1399      * generated Typeface objects by calling
1400      * {@link #releaseNativeObjectForTest()}.
1401      *
1402      * @hide
1403      */
1404     @TestApi
1405     public static @NonNull long[] deserializeFontMap(
1406             @NonNull ByteBuffer buffer, @NonNull Map<String, Typeface> out)
1407             throws IOException {
1408         int typefacesBytesCount = buffer.getInt();
1409         // Note: Do not call buffer.slice(), as nativeReadTypefaces() expects
1410         // that buffer.address() is page-aligned.
1411         long[] nativePtrs = nativeReadTypefaces(buffer, buffer.position());
1412         if (nativePtrs == null) {
1413             throw new IOException("Could not read typefaces");
1414         }
1415         out.clear();
1416         buffer.position(buffer.position() + typefacesBytesCount);
1417         for (long nativePtr : nativePtrs) {
1418             String name = readString(buffer);
1419             out.put(name, new Typeface(nativePtr, name));
1420         }
1421         return nativePtrs;
1422     }
1423 
1424     private static String readString(ByteBuffer buffer) {
1425         int length = buffer.getInt();
1426         byte[] bytes = new byte[length];
1427         buffer.get(bytes);
1428         return new String(bytes);
1429     }
1430 
1431     private static void writeString(ByteArrayOutputStream bos, String value) throws IOException {
1432         byte[] bytes = value.getBytes();
1433         writeInt(bos, bytes.length);
1434         bos.write(bytes);
1435     }
1436 
1437     private static void writeInt(ByteArrayOutputStream bos, int value) {
1438         // Write in the big endian order.
1439         bos.write((value >> 24) & 0xFF);
1440         bos.write((value >> 16) & 0xFF);
1441         bos.write((value >> 8) & 0xFF);
1442         bos.write(value & 0xFF);
1443     }
1444 
1445     /** @hide */
1446     public static Map<String, Typeface> getSystemFontMap() {
1447         synchronized (SYSTEM_FONT_MAP_LOCK) {
1448             return sSystemFontMap;
1449         }
1450     }
1451 
1452     /**
1453      * Deserialize font map and set it as system font map. This method should be called at most once
1454      * per process.
1455      */
1456     /** @hide */
1457     @UiThread
1458     public static void setSystemFontMap(@Nullable SharedMemory sharedMemory)
1459             throws IOException, ErrnoException {
1460         if (sSystemFontMapBuffer != null) {
1461             // Apps can re-send BIND_APPLICATION message from their code. This is a work around to
1462             // detect it and avoid crashing.
1463             if (sharedMemory == null || sharedMemory == sSystemFontMapSharedMemory) {
1464                 return;
1465             }
1466             throw new UnsupportedOperationException(
1467                     "Once set, buffer-based system font map cannot be updated");
1468         }
1469         sSystemFontMapSharedMemory = sharedMemory;
1470         Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, "setSystemFontMap");
1471         try {
1472             if (sharedMemory == null) {
1473                 // FontManagerService is not started. This may happen in FACTORY_TEST_LOW_LEVEL
1474                 // mode for example.
1475                 loadPreinstalledSystemFontMap();
1476                 return;
1477             }
1478             sSystemFontMapBuffer = sharedMemory.mapReadOnly().order(ByteOrder.BIG_ENDIAN);
1479             Map<String, Typeface> systemFontMap = new ArrayMap<>();
1480             long[] nativePtrs = deserializeFontMap(sSystemFontMapBuffer, systemFontMap);
1481 
1482             // Initialize native font APIs. The native font API will read fonts.xml by itself if
1483             // Typeface is initialized with loadPreinstalledSystemFontMap.
1484             for (long ptr : nativePtrs) {
1485                 nativeAddFontCollections(ptr);
1486             }
1487             setSystemFontMap(systemFontMap);
1488         } finally {
1489             Trace.traceEnd(Trace.TRACE_TAG_GRAPHICS);
1490         }
1491     }
1492 
1493     /** @hide */
1494     @VisibleForTesting
1495     public static void setSystemFontMap(Map<String, Typeface> systemFontMap) {
1496         synchronized (SYSTEM_FONT_MAP_LOCK) {
1497             sSystemFontMap.clear();
1498             sSystemFontMap.putAll(systemFontMap);
1499 
1500             // We can't assume DEFAULT_FAMILY available on Roboletric.
1501             if (sSystemFontMap.containsKey(DEFAULT_FAMILY)) {
1502                 setDefault(sSystemFontMap.get(DEFAULT_FAMILY));
1503             }
1504 
1505             // Set up defaults and typefaces exposed in public API
1506             // Use sDefaultTypeface here, because create(String, int) uses DEFAULT as fallback.
1507             nativeForceSetStaticFinalField("DEFAULT", create(sDefaultTypeface, 0));
1508             nativeForceSetStaticFinalField("DEFAULT_BOLD", create(sDefaultTypeface, Typeface.BOLD));
1509             nativeForceSetStaticFinalField("SANS_SERIF", create("sans-serif", 0));
1510             nativeForceSetStaticFinalField("SERIF", create("serif", 0));
1511             nativeForceSetStaticFinalField("MONOSPACE", create("monospace", 0));
1512 
1513             sDefaults = new Typeface[]{
1514                 DEFAULT,
1515                 DEFAULT_BOLD,
1516                 create((String) null, Typeface.ITALIC),
1517                 create((String) null, Typeface.BOLD_ITALIC),
1518             };
1519 
1520             // A list of generic families to be registered in native.
1521             // https://www.w3.org/TR/css-fonts-4/#generic-font-families
1522             String[] genericFamilies = {
1523                 "serif", "sans-serif", "cursive", "fantasy", "monospace", "system-ui"
1524             };
1525 
1526             for (String genericFamily : genericFamilies) {
1527                 registerGenericFamilyNative(genericFamily, systemFontMap.get(genericFamily));
1528             }
1529         }
1530     }
1531 
1532     /**
1533      * Change default typefaces for testing purpose.
1534      *
1535      * Note: The existing TextView or Paint instance still holds the old Typeface.
1536      *
1537      * @param defaults array of [default, default_bold, default_italic, default_bolditalic].
1538      * @param genericFamilies array of [sans-serif, serif, monospace]
1539      * @return return the old defaults and genericFamilies
1540      * @hide
1541      */
1542     @TestApi
1543     @NonNull
1544     public static Pair<List<Typeface>, List<Typeface>> changeDefaultFontForTest(
1545             @NonNull List<Typeface> defaults,
1546             @NonNull List<Typeface> genericFamilies
1547     ) {
1548         synchronized (SYSTEM_FONT_MAP_LOCK) {
1549             List<Typeface> oldDefaults = Arrays.asList(sDefaults);
1550             sDefaults = defaults.toArray(new Typeface[4]);
1551             setDefault(defaults.get(0));
1552 
1553             ArrayList<Typeface> oldGenerics = new ArrayList<>();
1554             BiConsumer<Typeface, String> swapTypeface = (typeface, key) -> {
1555                 oldGenerics.add(sSystemFontMap.get(key));
1556                 sSystemFontMap.put(key, typeface);
1557             };
1558 
1559             Typeface sansSerif = genericFamilies.get(0);
1560             swapTypeface.accept(sansSerif, "sans-serif");
1561             swapTypeface.accept(Typeface.create(sansSerif, 100, false), "sans-serif-thin");
1562             swapTypeface.accept(Typeface.create(sansSerif, 300, false), "sans-serif-light");
1563             swapTypeface.accept(Typeface.create(sansSerif, 500, false), "sans-serif-medium");
1564             swapTypeface.accept(Typeface.create(sansSerif, 700, false), "sans-serif-bold");
1565             swapTypeface.accept(Typeface.create(sansSerif, 900, false), "sans-serif-black");
1566 
1567             swapTypeface.accept(genericFamilies.get(1), "serif");
1568             swapTypeface.accept(genericFamilies.get(2), "monospace");
1569 
1570             return new Pair<>(oldDefaults, oldGenerics);
1571         }
1572     }
1573 
1574     static {
1575         staticInitializer();
1576     }
1577 
1578     @RavenwoodReplace(reason = "Prevent circular reference on host side JVM", bug = 337329128)
1579     private static void staticInitializer() {
1580         init();
1581     }
1582 
1583     private static void staticInitializer$ravenwood() {
1584         /* no-op */
1585     }
1586 
1587     /** @hide */
1588     public static void init() {
1589         // Preload Roboto-Regular.ttf in Zygote for improving app launch performance.
1590         preloadFontFile(SystemFonts.SYSTEM_FONT_DIR + "Roboto-Regular.ttf");
1591         preloadFontFile(SystemFonts.SYSTEM_FONT_DIR + "RobotoStatic-Regular.ttf");
1592 
1593         String locale = SystemProperties.get("persist.sys.locale", "en-US");
1594         String script = ULocale.addLikelySubtags(ULocale.forLanguageTag(locale)).getScript();
1595 
1596         // The feature flag cannot be referred from Zygote. Use legacy fonts.xml for preloading font
1597         // files.
1598         // TODO(nona): Use new XML file once the feature is fully launched.
1599         FontConfig config = SystemFonts.getSystemPreinstalledFontConfigFromLegacyXml();
1600         for (int i = 0; i < config.getFontFamilies().size(); ++i) {
1601             FontConfig.FontFamily family = config.getFontFamilies().get(i);
1602             if (!family.getLocaleList().isEmpty()) {
1603                 nativeRegisterLocaleList(family.getLocaleList().toLanguageTags());
1604             }
1605             boolean loadFamily = false;
1606             for (int j = 0; j < family.getLocaleList().size(); ++j) {
1607                 String fontScript = ULocale.addLikelySubtags(
1608                         ULocale.forLocale(family.getLocaleList().get(j))).getScript();
1609                 loadFamily = fontScript.equals(script);
1610                 if (loadFamily) {
1611                     break;
1612                 }
1613             }
1614             if (loadFamily) {
1615                 for (int j = 0; j < family.getFontList().size(); ++j) {
1616                     preloadFontFile(family.getFontList().get(j).getFile().getAbsolutePath());
1617                 }
1618             }
1619         }
1620     }
1621 
1622     private static void preloadFontFile(String filePath) {
1623         File file = new File(filePath);
1624         if (file.exists()) {
1625             Log.i(TAG, "Preloading " + file.getAbsolutePath());
1626             nativeWarmUpCache(filePath);
1627         }
1628     }
1629 
1630     /** @hide */
1631     @VisibleForTesting
1632     public static void destroySystemFontMap() {
1633         synchronized (SYSTEM_FONT_MAP_LOCK) {
1634             for (Typeface typeface : sSystemFontMap.values()) {
1635                 typeface.releaseNativeObjectForTest();
1636             }
1637             sSystemFontMap.clear();
1638             if (sSystemFontMapBuffer != null) {
1639                 SharedMemory.unmap(sSystemFontMapBuffer);
1640             }
1641             sSystemFontMapBuffer = null;
1642             sSystemFontMapSharedMemory = null;
1643             synchronized (sStyledCacheLock) {
1644                 destroyTypefaceCacheLocked(sStyledTypefaceCache);
1645             }
1646             synchronized (sWeightCacheLock) {
1647                 destroyTypefaceCacheLocked(sWeightTypefaceCache);
1648             }
1649         }
1650     }
1651 
1652     private static void destroyTypefaceCacheLocked(LongSparseArray<SparseArray<Typeface>> cache) {
1653         for (int i = 0; i < cache.size(); i++) {
1654             SparseArray<Typeface> array = cache.valueAt(i);
1655             for (int j = 0; j < array.size(); j++) {
1656                 array.valueAt(j).releaseNativeObjectForTest();
1657             }
1658         }
1659         cache.clear();
1660     }
1661 
1662     /** @hide */
1663     public static void loadPreinstalledSystemFontMap() {
1664         final FontConfig fontConfig = SystemFonts.getSystemPreinstalledFontConfig();
1665         final Map<String, FontFamily[]> fallback = SystemFonts.buildSystemFallback(fontConfig);
1666         final Map<String, Typeface> typefaceMap =
1667                 SystemFonts.buildSystemTypefaces(fontConfig, fallback);
1668         setSystemFontMap(typefaceMap);
1669     }
1670 
1671     /**
1672      * {@link #loadPreinstalledSystemFontMap()} does not actually initialize the native
1673      * system font APIs. Add a new method to actually load the font files without going
1674      * through SharedMemory.
1675      *
1676      * @hide
1677      */
1678     public static void loadNativeSystemFonts() {
1679         synchronized (SYSTEM_FONT_MAP_LOCK) {
1680             for (var type : sSystemFontMap.values()) {
1681                 nativeAddFontCollections(type.native_instance);
1682             }
1683         }
1684     }
1685 
1686     static {
1687         if (!ENABLE_LAZY_TYPEFACE_INITIALIZATION) {
1688             loadPreinstalledSystemFontMap();
1689         }
1690     }
1691 
1692     @Override
1693     public boolean equals(Object o) {
1694         if (this == o) return true;
1695         if (o == null || getClass() != o.getClass()) return false;
1696 
1697         Typeface typeface = (Typeface) o;
1698 
1699         return mStyle == typeface.mStyle && native_instance == typeface.native_instance;
1700     }
1701 
1702     @Override
1703     public int hashCode() {
1704         /*
1705          * Modified method for hashCode with long native_instance derived from
1706          * http://developer.android.com/reference/java/lang/Object.html
1707          */
1708         int result = 17;
1709         result = 31 * result + (int) (native_instance ^ (native_instance >>> 32));
1710         result = 31 * result + mStyle;
1711         return result;
1712     }
1713 
1714     /** @hide */
1715     public boolean isSupportedAxes(int axis) {
1716         synchronized (this) {
1717             if (mSupportedAxes == null) {
1718                 mSupportedAxes = nativeGetSupportedAxes(native_instance);
1719                 if (mSupportedAxes == null) {
1720                     mSupportedAxes = EMPTY_AXES;
1721                 }
1722             }
1723         }
1724         return Arrays.binarySearch(mSupportedAxes, axis) >= 0;
1725     }
1726 
1727     private static native long nativeCreateFromTypeface(long native_instance, int style);
1728     private static native long nativeCreateFromTypefaceWithExactStyle(
1729             long native_instance, int weight, boolean italic);
1730     // TODO: clean up: change List<FontVariationAxis> to FontVariationAxis[]
1731     private static native long nativeCreateFromTypefaceWithVariation(
1732             long native_instance, List<FontVariationAxis> axes);
1733     @UnsupportedAppUsage
1734     private static native long nativeCreateWeightAlias(long native_instance, int weight);
1735     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
1736     private static native long nativeCreateFromArray(
1737             long[] familyArray, long fallbackTypeface, int weight, int italic);
1738     private static native int[] nativeGetSupportedAxes(long native_instance);
1739 
1740     @CriticalNative
1741     private static native void nativeSetDefault(long nativePtr);
1742 
1743     @CriticalNative
1744     private static native int  nativeGetStyle(long nativePtr);
1745 
1746     @CriticalNative
1747     private static native int  nativeGetWeight(long nativePtr);
1748 
1749     @CriticalNative
1750     private static native boolean nativeIsVariationInstance(long nativePtr);
1751 
1752     @CriticalNative
1753     private static native long nativeGetReleaseFunc();
1754 
1755     private static native void nativeRegisterGenericFamily(String str, long nativePtr);
1756 
1757     private static native int nativeWriteTypefaces(
1758             @Nullable ByteBuffer buffer, int position, @NonNull long[] nativePtrs);
1759 
1760     private static native
1761             @Nullable long[] nativeReadTypefaces(@NonNull ByteBuffer buffer, int position);
1762 
1763     private static native void nativeForceSetStaticFinalField(String fieldName, Typeface typeface);
1764 
1765     @CriticalNative
1766     private static native void nativeAddFontCollections(long nativePtr);
1767 
1768     private static native void nativeWarmUpCache(String fileName);
1769 
1770     @FastNative
1771     private static native void nativeRegisterLocaleList(String locales);
1772 }
1773