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