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