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