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