Typeface.java revision fb483cc90c27a9c0fcafa28343a8fd644f8384a4
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                // TODO: this method should be
362                // create(fd, ttcIndex, fontVariationSettings, style).
363                if (!fontFamily.addFontFromBuffer(fontBuffer, result.getTtcIndex(),
364                                null, 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    /** @hide TODO: Make this API public. */
494    public static final class Builder {
495        /**
496         * Value for weight and italic.
497         *
498         * Indicates the value is resolved by font metadata.
499         */
500        // Must be same with C++ constant in core/jni/android/graphics/FontFamily.cpp
501        public static final int RESOLVE_BY_FONT_TABLE = -1;
502
503        /**
504         * Value for italic.
505         *
506         * Indicates the font style is not italic.
507         */
508        public static final int NORMAL = 0;
509
510        /**
511         * Value for italic.
512         *
513         * Indicates the font style is italic.
514         */
515        public static final int ITALIC = 1;
516
517        private int mTtcIndex;
518        private FontConfig.Axis[] mAxes;
519
520        private AssetManager mAssetManager;
521        private String mPath;
522        private FileDescriptor mFd;
523        private @IntRange(from = -1) int mWeight = RESOLVE_BY_FONT_TABLE;
524
525        /** @hide */
526        @Retention(SOURCE)
527        @IntDef({RESOLVE_BY_FONT_TABLE, NORMAL, ITALIC})
528        public @interface Italic {}
529        private @Italic int mItalic = RESOLVE_BY_FONT_TABLE;
530
531        private boolean mHasSourceSet = false;
532        private boolean mRecycled = false;
533
534        /** Use Builder.obtain() instead */
535        private void Builder() {}
536
537        private static AtomicReference<Builder> mCache = new AtomicReference<>();
538
539        /**
540         * Returns Typeface.Builder from pool.
541         */
542        public static Builder obtain() {
543            final Builder builder = mCache.getAndSet(null);
544            if (builder != null) {
545                builder.mRecycled = false;
546                return builder;
547            }
548            return new Builder();
549        }
550
551        /**
552         * Resets the internal states.
553         */
554        public void reset() {
555            checkNotRecycled();
556            mTtcIndex = 0;
557            mAxes = null;
558
559            mAssetManager = null;
560            mPath = null;
561            mFd = null;
562
563            mWeight = RESOLVE_BY_FONT_TABLE;
564            mItalic = RESOLVE_BY_FONT_TABLE;
565
566            mHasSourceSet = false;
567        }
568
569        /**
570         * Returns the instance to the pool.
571         */
572        public void recycle() {
573            reset();
574            mRecycled = true;
575
576            mCache.compareAndSet(null, this);
577        }
578
579        private void checkNotRecycled() {
580            if (mRecycled) {
581                throw new IllegalStateException("Don't use Builder after calling recycle()");
582            }
583        }
584
585        private void checkSingleFontSource() {
586            if (mHasSourceSet) {
587                throw new IllegalStateException("Typeface can only built with single font source.");
588            }
589        }
590
591        /**
592         * Sets a font file as a source of Typeface.
593         *
594         * @param path The file object refers to the font file.
595         */
596        public Builder setSourceFromFile(@NonNull File path) {
597            return setSourceFromFilePath(path.getAbsolutePath());
598        }
599
600        /**
601         * Sets a font file as a source of Typeface.
602         *
603         * @param fd The file descriptor. The passed fd must be mmap-able.
604         */
605        public Builder setSourceFromFile(@NonNull FileDescriptor fd) {
606            checkNotRecycled();
607            checkSingleFontSource();
608            mFd = fd;
609            mHasSourceSet = true;
610            return this;
611        }
612
613        /**
614         * Sets a font file as a source of Typeface.
615         *
616         * @param path The full path to the font file.
617         */
618        public Builder setSourceFromFilePath(@NonNull String path) {
619            checkNotRecycled();
620            checkSingleFontSource();
621            mPath = path;
622            mHasSourceSet = true;
623            return this;
624        }
625
626        /**
627         * Sets an asset entry as a source of Typeface.
628         *
629         * @param assetManager The application's asset manager
630         * @param path The file name of the font data in the asset directory
631         */
632        public Builder setSourceFromAsset(@NonNull AssetManager assetManager,
633                @NonNull String path) {
634            checkNotRecycled();
635            checkSingleFontSource();
636            mAssetManager = Preconditions.checkNotNull(assetManager);
637            mPath = Preconditions.checkStringNotEmpty(path);
638            mHasSourceSet = true;
639            return this;
640        }
641
642        /**
643         * Sets weight of the font.
644         *
645         * By passing {@link #RESOLVE_BY_FONT_TABLE}, weight value is resolved by OS/2 table in
646         * font file if possible.
647         * @param weight a weight value or {@link #RESOLVE_BY_FONT_TABLE}
648         */
649        public Builder setWeight(@IntRange(from = -1) int weight) {
650            checkNotRecycled();
651            mWeight = weight;
652            return this;
653        }
654
655        /**
656         * Sets italic information of the font.
657         *
658         * By passing {@link #RESOLVE_BY_FONT_TABLE}, italic or normal is determined by OS/2 table
659         * in font file if possible.
660         * @param italic One of {@link #NORMAL}, {@link #ITALIC}, {@link #RESOLVE_BY_FONT_TABLE}.
661         *                 will be used.
662         */
663        public Builder setItalic(@Italic int italic) {
664            checkNotRecycled();
665            mItalic = italic;
666            return this;
667        }
668
669        /**
670         * Sets an idex of the font collection.
671         *
672         * Can not be used for Typeface source. build() method will return null for invalid index.
673         * @param ttcIndex An index of the font collection. If the font source is not font
674         *                 collection, do not call this method or specify 0.
675         */
676        public Builder setTtcIndex(@IntRange(from = 0) int ttcIndex) {
677            checkNotRecycled();
678            mTtcIndex = ttcIndex;
679            return this;
680        }
681
682        /**
683         * Sets a font variation settings.
684         *
685         * @param variationSettings See {@link android.widget.TextView#setFontVariationSettings}.
686         */
687        public Builder setFontVariationSettings(@Nullable String variationSettings) {
688            checkNotRecycled();
689            if (mAxes != null) {
690                throw new IllegalStateException("Font variation settings are already set.");
691            }
692            final List<FontConfig.Axis> axesList = FontListParser.parseFontVariationSettings(
693                    variationSettings);
694            mAxes = axesList.toArray(new FontConfig.Axis[axesList.size()]);
695            return this;
696        }
697
698        /**
699         * Sets a font variation settings.
700         *
701         * @param axes An array of font variation axis tag-value pairs.
702         */
703        public Builder setFontVariationSettings(@Nullable FontConfig.Axis[] axes) {
704            checkNotRecycled();
705            if (mAxes != null) {
706                throw new IllegalStateException("Font variation settings are already set.");
707            }
708            mAxes = axes;
709            return this;
710        }
711
712        /**
713         * Creates a unique id for a given AssetManager and asset path.
714         *
715         * @param mgr  AssetManager instance
716         * @param path The path for the asset.
717         * @param ttcIndex The TTC index for the font.
718         * @param axes The font variation settings.
719         * @return Unique id for a given AssetManager and asset path.
720         */
721        private static String createAssetUid(final AssetManager mgr, String path, int ttcIndex,
722                @Nullable FontConfig.Axis[] axes) {
723            final SparseArray<String> pkgs = mgr.getAssignedPackageIdentifiers();
724            final StringBuilder builder = new StringBuilder();
725            final int size = pkgs.size();
726            for (int i = 0; i < size; i++) {
727                builder.append(pkgs.valueAt(i));
728                builder.append("-");
729            }
730            builder.append(path);
731            builder.append("-");
732            builder.append(Integer.toString(ttcIndex));
733            builder.append("-");
734            if (axes != null) {
735                for (FontConfig.Axis axis : axes) {
736                    builder.append(Integer.toHexString(axis.getTag()));
737                    builder.append("-");
738                    builder.append(Float.toString(axis.getStyleValue()));
739                }
740            }
741            return builder.toString();
742        }
743
744        /**
745         * Generates new Typeface from specified configuration.
746         *
747         * @return Newly created Typeface. May return null if some parameters are invalid.
748         */
749        public Typeface build() {
750            checkNotRecycled();
751            if (!mHasSourceSet) {
752                return null;
753            }
754
755            if (mFd != null) {  // set source by setSourceFromFile(FileDescriptor)
756                try (FileInputStream fis = new FileInputStream(mFd)) {
757                    FileChannel channel = fis.getChannel();
758                    long size = channel.size();
759                    ByteBuffer buffer = channel.map(FileChannel.MapMode.READ_ONLY, 0, size);
760
761                    final FontFamily fontFamily = new FontFamily();
762                    if (!fontFamily.addFontFromBuffer(buffer, mTtcIndex, mAxes, mWeight, mItalic)) {
763                        fontFamily.abortCreation();
764                        return null;
765                    }
766                    fontFamily.freeze();
767                    FontFamily[] families = { fontFamily };
768                    return createFromFamiliesWithDefault(families);
769                } catch (IOException e) {
770                    return null;
771                }
772            } else if (mAssetManager != null) {  // set source by setSourceFromAsset()
773                final String key = createAssetUid(mAssetManager, mPath, mTtcIndex, mAxes);
774                synchronized (sDynamicTypefaceCache) {
775                    Typeface typeface = sDynamicTypefaceCache.get(key);
776                    if (typeface != null) return typeface;
777                    final FontFamily fontFamily = new FontFamily();
778                    if (!fontFamily.addFontFromAssetManager(mAssetManager, mPath, mTtcIndex,
779                            true /* isAsset */, mTtcIndex, mWeight, mItalic, mAxes)) {
780                        fontFamily.abortCreation();
781                        return null;
782                    }
783                    fontFamily.freeze();
784                    FontFamily[] families = { fontFamily };
785                    typeface = createFromFamiliesWithDefault(families);
786                    sDynamicTypefaceCache.put(key, typeface);
787                    return typeface;
788                }
789            } else if (mPath != null) {  // set source by setSourceFromFile(File)
790                final FontFamily fontFamily = new FontFamily();
791                if (!fontFamily.addFont(mPath, mTtcIndex, mAxes, mWeight, mItalic)) {
792                    fontFamily.abortCreation();
793                    return null;
794                }
795                fontFamily.freeze();
796                FontFamily[] families = { fontFamily };
797                return createFromFamiliesWithDefault(families);
798            } else {
799                throw new IllegalArgumentException("No source was set.");
800            }
801        }
802    }
803
804    /**
805     * Create a typeface object given a family name, and option style information.
806     * If null is passed for the name, then the "default" font will be chosen.
807     * The resulting typeface object can be queried (getStyle()) to discover what
808     * its "real" style characteristics are.
809     *
810     * @param familyName May be null. The name of the font family.
811     * @param style  The style (normal, bold, italic) of the typeface.
812     *               e.g. NORMAL, BOLD, ITALIC, BOLD_ITALIC
813     * @return The best matching typeface.
814     */
815    public static Typeface create(String familyName, int style) {
816        if (sSystemFontMap != null) {
817            return create(sSystemFontMap.get(familyName), style);
818        }
819        return null;
820    }
821
822    /**
823     * Create a typeface object that best matches the specified existing
824     * typeface and the specified Style. Use this call if you want to pick a new
825     * style from the same family of an existing typeface object. If family is
826     * null, this selects from the default font's family.
827     *
828     * @param family May be null. The name of the existing type face.
829     * @param style  The style (normal, bold, italic) of the typeface.
830     *               e.g. NORMAL, BOLD, ITALIC, BOLD_ITALIC
831     * @return The best matching typeface.
832     */
833    public static Typeface create(Typeface family, int style) {
834        if (style < 0 || style > 3) {
835            style = 0;
836        }
837        long ni = 0;
838        if (family != null) {
839            // Return early if we're asked for the same face/style
840            if (family.mStyle == style) {
841                return family;
842            }
843
844            ni = family.native_instance;
845        }
846
847        Typeface typeface;
848        SparseArray<Typeface> styles = sTypefaceCache.get(ni);
849
850        if (styles != null) {
851            typeface = styles.get(style);
852            if (typeface != null) {
853                return typeface;
854            }
855        }
856
857        typeface = new Typeface(nativeCreateFromTypeface(ni, style));
858        if (styles == null) {
859            styles = new SparseArray<Typeface>(4);
860            sTypefaceCache.put(ni, styles);
861        }
862        styles.put(style, typeface);
863
864        return typeface;
865    }
866
867    /** @hide */
868    public static Typeface createFromTypefaceWithVariation(Typeface family,
869            List<FontConfig.Axis> axes) {
870        final long ni = family == null ? 0 : family.native_instance;
871        return new Typeface(nativeCreateFromTypefaceWithVariation(ni, axes));
872    }
873
874    /**
875     * Returns one of the default typeface objects, based on the specified style
876     *
877     * @return the default typeface that corresponds to the style
878     */
879    public static Typeface defaultFromStyle(int style) {
880        return sDefaults[style];
881    }
882
883    /**
884     * Create a new typeface from the specified font data.
885     *
886     * @param mgr  The application's asset manager
887     * @param path The file name of the font data in the assets directory
888     * @return The new typeface.
889     */
890    public static Typeface createFromAsset(AssetManager mgr, String path) {
891        if (path == null) {
892            throw new NullPointerException();  // for backward compatibility
893        }
894        if (sFallbackFonts != null) {
895            final Builder builder = Builder.obtain();
896            try {
897                builder.setSourceFromAsset(mgr, path);
898                Typeface typeface = builder.build();
899                if (typeface != null) {
900                    return typeface;
901                }
902            } finally {
903                builder.recycle();
904            }
905        }
906        // For the compatibility reasons, throw runtime exception if failed to create Typeface.
907        throw new RuntimeException("Font asset not found " + path);
908    }
909
910    /**
911     * Creates a unique id for a given font provider and query.
912     */
913    private static String createProviderUid(String authority, String query) {
914        final StringBuilder builder = new StringBuilder();
915        builder.append("provider:");
916        builder.append(authority);
917        builder.append("-");
918        builder.append(query);
919        return builder.toString();
920    }
921
922    /**
923     * Create a new typeface from the specified font file.
924     *
925     * @param path The path to the font data.
926     * @return The new typeface.
927     */
928    public static Typeface createFromFile(@Nullable File path) {
929        // For the compatibility reasons, leaving possible NPE here.
930        // See android.graphics.cts.TypefaceTest#testCreateFromFileByFileReferenceNull
931        return createFromFile(path.getAbsolutePath());
932    }
933
934    /**
935     * Create a new typeface from the specified font file.
936     *
937     * @param path The full path to the font data.
938     * @return The new typeface.
939     */
940    public static Typeface createFromFile(@Nullable String path) {
941        if (path == null) {
942            // For the compatibility reasons, need to throw NPE if the argument is null.
943            // See android.graphics.cts.TypefaceTest#testCreateFromFileByFileNameNull
944            throw new NullPointerException();
945        }
946        if (sFallbackFonts != null) {
947            final Builder builder = Builder.obtain();
948            try {
949                builder.setSourceFromFilePath(path);
950                Typeface typeface = builder.build();
951                if (typeface != null) {
952                    // For the compatibility reasons, throw runtime exception if failed to create
953                    // Typeface.
954                    return typeface;
955                }
956            } finally {
957                builder.recycle();
958            }
959        }
960        throw new RuntimeException("Font not found " + path);
961    }
962
963    /**
964     * Create a new typeface from an array of font families.
965     *
966     * @param families array of font families
967     */
968    private static Typeface createFromFamilies(FontFamily[] families) {
969        long[] ptrArray = new long[families.length];
970        for (int i = 0; i < families.length; i++) {
971            ptrArray[i] = families[i].mNativePtr;
972        }
973        return new Typeface(nativeCreateFromArray(ptrArray));
974    }
975
976    /**
977     * Create a new typeface from an array of font families, including
978     * also the font families in the fallback list.
979     *
980     * @param families array of font families
981     */
982    private static Typeface createFromFamiliesWithDefault(FontFamily[] families) {
983        long[] ptrArray = new long[families.length + sFallbackFonts.length];
984        for (int i = 0; i < families.length; i++) {
985            ptrArray[i] = families[i].mNativePtr;
986        }
987        for (int i = 0; i < sFallbackFonts.length; i++) {
988            ptrArray[i + families.length] = sFallbackFonts[i].mNativePtr;
989        }
990        return new Typeface(nativeCreateFromArray(ptrArray));
991    }
992
993    // don't allow clients to call this directly
994    private Typeface(long ni) {
995        if (ni == 0) {
996            throw new RuntimeException("native typeface cannot be made");
997        }
998
999        native_instance = ni;
1000        mStyle = nativeGetStyle(ni);
1001    }
1002
1003    private static FontFamily makeFamilyFromParsed(FontConfig.Family family,
1004            Map<String, ByteBuffer> bufferForPath) {
1005        FontFamily fontFamily = new FontFamily(family.getLanguage(), family.getVariant());
1006        for (FontConfig.Font font : family.getFonts()) {
1007            ByteBuffer fontBuffer = bufferForPath.get(font.getFontName());
1008            if (fontBuffer == null) {
1009                try (FileInputStream file = new FileInputStream(font.getFontName())) {
1010                    FileChannel fileChannel = file.getChannel();
1011                    long fontSize = fileChannel.size();
1012                    fontBuffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, fontSize);
1013                    bufferForPath.put(font.getFontName(), fontBuffer);
1014                } catch (IOException e) {
1015                    Log.e(TAG, "Error mapping font file " + font.getFontName());
1016                    continue;
1017                }
1018            }
1019            if (!fontFamily.addFontFromBuffer(fontBuffer, font.getTtcIndex(), font.getAxes(),
1020                    font.getWeight(), font.isItalic() ? Builder.ITALIC : Builder.NORMAL)) {
1021                Log.e(TAG, "Error creating font " + font.getFontName() + "#" + font.getTtcIndex());
1022            }
1023        }
1024        fontFamily.freeze();
1025        return fontFamily;
1026    }
1027
1028    /*
1029     * (non-Javadoc)
1030     *
1031     * This should only be called once, from the static class initializer block.
1032     */
1033    private static void init() {
1034        // Load font config and initialize Minikin state
1035        File systemFontConfigLocation = getSystemFontConfigLocation();
1036        File configFilename = new File(systemFontConfigLocation, FONTS_CONFIG);
1037        try {
1038            FileInputStream fontsIn = new FileInputStream(configFilename);
1039            FontConfig fontConfig = FontListParser.parse(fontsIn);
1040
1041            Map<String, ByteBuffer> bufferForPath = new HashMap<String, ByteBuffer>();
1042
1043            List<FontFamily> familyList = new ArrayList<FontFamily>();
1044            // Note that the default typeface is always present in the fallback list;
1045            // this is an enhancement from pre-Minikin behavior.
1046            for (int i = 0; i < fontConfig.getFamilies().length; i++) {
1047                FontConfig.Family f = fontConfig.getFamilies()[i];
1048                if (i == 0 || f.getName() == null) {
1049                    familyList.add(makeFamilyFromParsed(f, bufferForPath));
1050                }
1051            }
1052            sFallbackFonts = familyList.toArray(new FontFamily[familyList.size()]);
1053            setDefault(Typeface.createFromFamilies(sFallbackFonts));
1054
1055            Map<String, Typeface> systemFonts = new HashMap<String, Typeface>();
1056            for (int i = 0; i < fontConfig.getFamilies().length; i++) {
1057                Typeface typeface;
1058                FontConfig.Family f = fontConfig.getFamilies()[i];
1059                if (f.getName() != null) {
1060                    if (i == 0) {
1061                        // The first entry is the default typeface; no sense in
1062                        // duplicating the corresponding FontFamily.
1063                        typeface = sDefaultTypeface;
1064                    } else {
1065                        FontFamily fontFamily = makeFamilyFromParsed(f, bufferForPath);
1066                        FontFamily[] families = { fontFamily };
1067                        typeface = Typeface.createFromFamiliesWithDefault(families);
1068                    }
1069                    systemFonts.put(f.getName(), typeface);
1070                }
1071            }
1072            for (FontConfig.Alias alias : fontConfig.getAliases()) {
1073                Typeface base = systemFonts.get(alias.getToName());
1074                Typeface newFace = base;
1075                int weight = alias.getWeight();
1076                if (weight != 400) {
1077                    newFace = new Typeface(nativeCreateWeightAlias(base.native_instance, weight));
1078                }
1079                systemFonts.put(alias.getName(), newFace);
1080            }
1081            sSystemFontMap = systemFonts;
1082
1083        } catch (RuntimeException e) {
1084            Log.w(TAG, "Didn't create default family (most likely, non-Minikin build)", e);
1085            // TODO: normal in non-Minikin case, remove or make error when Minikin-only
1086        } catch (FileNotFoundException e) {
1087            Log.e(TAG, "Error opening " + configFilename, e);
1088        } catch (IOException e) {
1089            Log.e(TAG, "Error reading " + configFilename, e);
1090        } catch (XmlPullParserException e) {
1091            Log.e(TAG, "XML parse exception for " + configFilename, e);
1092        }
1093    }
1094
1095    static {
1096        init();
1097        // Set up defaults and typefaces exposed in public API
1098        DEFAULT         = create((String) null, 0);
1099        DEFAULT_BOLD    = create((String) null, Typeface.BOLD);
1100        SANS_SERIF      = create("sans-serif", 0);
1101        SERIF           = create("serif", 0);
1102        MONOSPACE       = create("monospace", 0);
1103
1104        sDefaults = new Typeface[] {
1105            DEFAULT,
1106            DEFAULT_BOLD,
1107            create((String) null, Typeface.ITALIC),
1108            create((String) null, Typeface.BOLD_ITALIC),
1109        };
1110
1111    }
1112
1113    private static File getSystemFontConfigLocation() {
1114        return new File("/system/etc/");
1115    }
1116
1117    @Override
1118    protected void finalize() throws Throwable {
1119        try {
1120            nativeUnref(native_instance);
1121            native_instance = 0;  // Other finalizers can still call us.
1122        } finally {
1123            super.finalize();
1124        }
1125    }
1126
1127    @Override
1128    public boolean equals(Object o) {
1129        if (this == o) return true;
1130        if (o == null || getClass() != o.getClass()) return false;
1131
1132        Typeface typeface = (Typeface) o;
1133
1134        return mStyle == typeface.mStyle && native_instance == typeface.native_instance;
1135    }
1136
1137    @Override
1138    public int hashCode() {
1139        /*
1140         * Modified method for hashCode with long native_instance derived from
1141         * http://developer.android.com/reference/java/lang/Object.html
1142         */
1143        int result = 17;
1144        result = 31 * result + (int) (native_instance ^ (native_instance >>> 32));
1145        result = 31 * result + mStyle;
1146        return result;
1147    }
1148
1149    /** @hide */
1150    public boolean isSupportedAxes(int axis) {
1151        if (mSupportedAxes == null) {
1152            synchronized (this) {
1153                if (mSupportedAxes == null) {
1154                    mSupportedAxes = nativeGetSupportedAxes(native_instance);
1155                    if (mSupportedAxes == null) {
1156                        mSupportedAxes = EMPTY_AXES;
1157                    }
1158                }
1159            }
1160        }
1161        return Arrays.binarySearch(mSupportedAxes, axis) > 0;
1162    }
1163
1164    private static native long nativeCreateFromTypeface(long native_instance, int style);
1165    // TODO: clean up: change List<FontConfig.Axis> to FontConfig.Axis[]
1166    private static native long nativeCreateFromTypefaceWithVariation(
1167            long native_instance, List<FontConfig.Axis> axes);
1168    private static native long nativeCreateWeightAlias(long native_instance, int weight);
1169    private static native void nativeUnref(long native_instance);
1170    private static native int  nativeGetStyle(long native_instance);
1171    private static native long nativeCreateFromArray(long[] familyArray);
1172    private static native void nativeSetDefault(long native_instance);
1173    private static native int[] nativeGetSupportedAxes(long native_instance);
1174}
1175