Typeface.java revision 69754bf66dae9d047d5a0ff2c71820aa35b9cc70
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    public static void create(@NonNull FontRequest request, @NonNull FontRequestCallback callback) {
296        // Check the cache first
297        // TODO: would the developer want to avoid a cache hit and always ask for the freshest
298        // result?
299        Typeface cachedTypeface = findFromCache(
300                request.getProviderAuthority(), request.getQuery());
301        if (cachedTypeface != null) {
302            sHandler.post(() -> callback.onTypefaceRetrieved(cachedTypeface));
303            return;
304        }
305        synchronized (sLock) {
306            if (sFontsContract == null) {
307                throw new RuntimeException("Context not initialized, can't query provider");
308            }
309            final ResultReceiver receiver = new ResultReceiver(null) {
310                @Override
311                public void onReceiveResult(int resultCode, Bundle resultData) {
312                    sHandler.post(() -> receiveResult(request, callback, resultCode, resultData));
313                }
314            };
315            sFontsContract.getFont(request, receiver);
316        }
317    }
318
319    private static Typeface findFromCache(String providerAuthority, String query) {
320        synchronized (sDynamicTypefaceCache) {
321            final String key = createProviderUid(providerAuthority, query);
322            Typeface typeface = sDynamicTypefaceCache.get(key);
323            if (typeface != null) {
324                return typeface;
325            }
326        }
327        return null;
328    }
329
330    private static void receiveResult(FontRequest request, FontRequestCallback callback,
331            int resultCode, Bundle resultData) {
332        Typeface cachedTypeface = findFromCache(
333                request.getProviderAuthority(), request.getQuery());
334        if (cachedTypeface != null) {
335            // We already know the result.
336            // Probably the requester requests the same font again in a short interval.
337            callback.onTypefaceRetrieved(cachedTypeface);
338            return;
339        }
340        if (resultCode != FontsContract.Columns.RESULT_CODE_OK) {
341            callback.onTypefaceRequestFailed(resultCode);
342            return;
343        }
344        if (resultData == null) {
345            callback.onTypefaceRequestFailed(
346                    FontRequestCallback.FAIL_REASON_FONT_NOT_FOUND);
347            return;
348        }
349        List<FontResult> resultList =
350                resultData.getParcelableArrayList(FontsContract.PARCEL_FONT_RESULTS);
351        if (resultList == null || resultList.isEmpty()) {
352            callback.onTypefaceRequestFailed(
353                    FontRequestCallback.FAIL_REASON_FONT_NOT_FOUND);
354            return;
355        }
356        FontFamily fontFamily = new FontFamily();
357        for (int i = 0; i < resultList.size(); ++i) {
358            FontResult result = resultList.get(i);
359            ParcelFileDescriptor fd = result.getFileDescriptor();
360            if (fd == null) {
361                callback.onTypefaceRequestFailed(
362                        FontRequestCallback.FAIL_REASON_FONT_LOAD_ERROR);
363                return;
364            }
365            try (FileInputStream is = new FileInputStream(fd.getFileDescriptor())) {
366                FileChannel fileChannel = is.getChannel();
367                long fontSize = fileChannel.size();
368                ByteBuffer fontBuffer = fileChannel.map(
369                        FileChannel.MapMode.READ_ONLY, 0, fontSize);
370                int weight = result.getWeight();
371                int italic = result.getItalic() ? STYLE_ITALIC : STYLE_NORMAL;
372                FontVariationAxis[] axes = null;
373                try {
374                    axes = FontVariationAxis.fromFontVariationSettings(
375                            result.getFontVariationSettings());
376                } catch (FontVariationAxis.InvalidFormatException e) {
377                    // TODO: Nice to pass FontVariationAxis[] directly instead of string.
378                }
379                if (!fontFamily.addFontFromBuffer(fontBuffer, result.getTtcIndex(),
380                        axes, weight, italic)) {
381                    Log.e(TAG, "Error creating font " + request.getQuery());
382                    callback.onTypefaceRequestFailed(
383                            FontRequestCallback.FAIL_REASON_FONT_LOAD_ERROR);
384                    return;
385                }
386            } catch (IOException e) {
387                Log.e(TAG, "Error reading font " + request.getQuery(), e);
388                callback.onTypefaceRequestFailed(
389                        FontRequestCallback.FAIL_REASON_FONT_LOAD_ERROR);
390                return;
391            } finally {
392                IoUtils.closeQuietly(fd);
393            }
394        }
395        fontFamily.freeze();
396        Typeface typeface = Typeface.createFromFamiliesWithDefault(new FontFamily[] { fontFamily });
397        synchronized (sDynamicTypefaceCache) {
398            String key = createProviderUid(request.getProviderAuthority(), request.getQuery());
399            sDynamicTypefaceCache.put(key, typeface);
400        }
401        callback.onTypefaceRetrieved(typeface);
402    }
403
404    /**
405     * Interface used to receive asynchronously fetched typefaces.
406     */
407    public interface FontRequestCallback {
408        /**
409         * Constant returned by {@link #onTypefaceRequestFailed(int)} signaling that the given
410         * provider was not found on the device.
411         */
412        int FAIL_REASON_PROVIDER_NOT_FOUND = FontsContract.RESULT_CODE_PROVIDER_NOT_FOUND;
413        /**
414         * Constant returned by {@link #onTypefaceRequestFailed(int)} signaling that the given
415         * provider must be authenticated and the given certificates do not match its signature.
416         */
417        int FAIL_REASON_WRONG_CERTIFICATES = FontsContract.RESULT_CODE_WRONG_CERTIFICATES;
418        /**
419         * Constant returned by {@link #onTypefaceRequestFailed(int)} signaling that the font
420         * returned by the provider was not loaded properly.
421         */
422        int FAIL_REASON_FONT_LOAD_ERROR = -3;
423        /**
424         * Constant returned by {@link #onTypefaceRequestFailed(int)} signaling that the font
425         * provider did not return any results for the given query.
426         */
427        int FAIL_REASON_FONT_NOT_FOUND = FontsContract.Columns.RESULT_CODE_FONT_NOT_FOUND;
428        /**
429         * Constant returned by {@link #onTypefaceRequestFailed(int)} signaling that the font
430         * provider found the queried font, but it is currently unavailable.
431         */
432        int FAIL_REASON_FONT_UNAVAILABLE = FontsContract.Columns.RESULT_CODE_FONT_UNAVAILABLE;
433        /**
434         * Constant returned by {@link #onTypefaceRequestFailed(int)} signaling that the given
435         * query was not supported by the provider.
436         */
437        int FAIL_REASON_MALFORMED_QUERY = FontsContract.Columns.RESULT_CODE_MALFORMED_QUERY;
438
439        /** @hide */
440        @IntDef({ FAIL_REASON_PROVIDER_NOT_FOUND, FAIL_REASON_FONT_LOAD_ERROR,
441                FAIL_REASON_FONT_NOT_FOUND, FAIL_REASON_FONT_UNAVAILABLE,
442                FAIL_REASON_MALFORMED_QUERY })
443        @Retention(RetentionPolicy.SOURCE)
444        @interface FontRequestFailReason {}
445
446        /**
447         * Called then a Typeface request done via {@link Typeface#create(FontRequest,
448         * FontRequestCallback)} is complete. Note that this method will not be called if
449         * {@link #onTypefaceRequestFailed(int)} is called instead.
450         * @param typeface  The Typeface object retrieved.
451         */
452        void onTypefaceRetrieved(Typeface typeface);
453
454        /**
455         * Called when a Typeface request done via {@link Typeface#create(FontRequest,
456         * FontRequestCallback)} fails.
457         * @param reason One of {@link #FAIL_REASON_PROVIDER_NOT_FOUND},
458         *               {@link #FAIL_REASON_FONT_NOT_FOUND},
459         *               {@link #FAIL_REASON_FONT_LOAD_ERROR},
460         *               {@link #FAIL_REASON_FONT_UNAVAILABLE} or
461         *               {@link #FAIL_REASON_MALFORMED_QUERY}.
462         */
463        void onTypefaceRequestFailed(@FontRequestFailReason int reason);
464    }
465
466    private static final FontRequestCallback NO_OP_REQUEST_CALLBACK = new FontRequestCallback() {
467        @Override
468        public void onTypefaceRetrieved(Typeface typeface) {
469            // Do nothing.
470        }
471
472        @Override
473        public void onTypefaceRequestFailed(@FontRequestFailReason int reason) {
474            // Do nothing.
475        }
476    };
477
478    /**
479     * A builder class for creating new Typeface instance.
480     *
481     * <p>
482     * Examples,
483     * 1) Create Typeface from ttf file.
484     * <pre>
485     * <code>
486     * Typeface.Builder buidler = new Typeface.Builder("your_font_file.ttf");
487     * Typeface typeface = builder.build();
488     * </code>
489     * </pre>
490     *
491     * 2) Create Typeface from ttc file in assets directory.
492     * <pre>
493     * <code>
494     * Typeface.Builder buidler = new Typeface.Builder(getAssets(), "your_font_file.ttc");
495     * builder.setTtcIndex(2);  // Set index of font collection.
496     * Typeface typeface = builder.build();
497     * </code>
498     * </pre>
499     *
500     * 3) Create Typeface with variation settings.
501     * <pre>
502     * <code>
503     * Typeface.Builder buidler = new Typeface.Builder("your_font_file.ttf");
504     * builder.setFontVariationSettings("'wght' 700, 'slnt' 20, 'ital' 1");
505     * builder.setWeight(700);  // Tell the system that this is a bold font.
506     * builder.setItalic(true);  // Tell the system that this is an italic style font.
507     * Typeface typeface = builder.build();
508     * </code>
509     * </pre>
510     * </p>
511     */
512    public static final class Builder {
513        /** @hide */
514        public static final int NORMAL_WEIGHT = 400;
515        /** @hide */
516        public static final int BOLD_WEIGHT = 700;
517
518        private int mTtcIndex;
519        private FontVariationAxis[] mAxes;
520
521        private AssetManager mAssetManager;
522        private String mPath;
523        private FileDescriptor mFd;
524
525        private FontsContract.FontInfo[] mFonts;
526        private Map<Uri, ByteBuffer> mFontBuffers;
527        private String mFallbackFamilyName;
528
529        private int mWeight = RESOLVE_BY_FONT_TABLE;
530        private int mItalic = RESOLVE_BY_FONT_TABLE;
531
532        /**
533         * Constructs a builder with a file path.
534         *
535         * @param path The file object refers to the font file.
536         */
537        public Builder(@NonNull File path) {
538            mPath = path.getAbsolutePath();
539        }
540
541        /**
542         * Constructs a builder with a file descriptor.
543         *
544         * @param fd The file descriptor. The passed fd must be mmap-able.
545         */
546        public Builder(@NonNull FileDescriptor fd) {
547            mFd = fd;
548        }
549
550        /**
551         * Constructs a builder with a file path.
552         *
553         * @param path The full path to the font file.
554         */
555        public Builder(@NonNull String path) {
556            mPath = path;
557        }
558
559        /**
560         * Constructs a builder from an asset manager and a file path in an asset directory.
561         *
562         * @param assetManager The application's asset manager
563         * @param path The file name of the font data in the asset directory
564         */
565        public Builder(@NonNull AssetManager assetManager, @NonNull String path) {
566            mAssetManager = Preconditions.checkNotNull(assetManager);
567            mPath = Preconditions.checkStringNotEmpty(path);
568        }
569
570        /**
571         * Constracts a builder from an array of FontsContract.FontInfo.
572         *
573         * Since {@link FontsContract.FontInfo} holds information about TTC indices and
574         * variation settings, there is no need to call {@link #setTtcIndex} or
575         * {@link #setFontVariationSettings}. Similary, {@link FontsContract.FontInfo} holds
576         * weight and italic information, so {@link #setWeight} and {@link #setItalic} are used
577         * for style matching during font selection.
578         *
579         * @param results The array of {@link FontsContract.FontInfo}
580         * @param buffers The mapping from URI to buffers to be used during building.
581         * @hide
582         */
583        public Builder(@NonNull FontsContract.FontInfo[] fonts,
584                @NonNull Map<Uri, ByteBuffer> buffers) {
585            mFonts = fonts;
586            mFontBuffers = buffers;
587        }
588
589        /**
590         * Sets weight of the font.
591         *
592         * Tells the system the weight of the given font. If not provided, the system will resolve
593         * the weight value by reading font tables.
594         * @param weight a weight value.
595         */
596        public Builder setWeight(@IntRange(from = 1, to = 1000) int weight) {
597            mWeight = weight;
598            return this;
599        }
600
601        /**
602         * Sets italic information of the font.
603         *
604         * Tells the system the style of the given font. If not provided, the system will resolve
605         * the style by reading font tables.
606         * @param italic {@code true} if the font is italic. Otherwise {@code false}.
607         */
608        public Builder setItalic(boolean italic) {
609            mItalic = italic ? STYLE_ITALIC : STYLE_NORMAL;
610            return this;
611        }
612
613        /**
614         * Sets an index of the font collection.
615         *
616         * Can not be used for Typeface source. build() method will return null for invalid index.
617         * @param ttcIndex An index of the font collection. If the font source is not font
618         *                 collection, do not call this method or specify 0.
619         */
620        public Builder setTtcIndex(@IntRange(from = 0) int ttcIndex) {
621            if (mFonts != null) {
622                throw new IllegalArgumentException(
623                        "TTC index can not be specified for FontResult source.");
624            }
625            mTtcIndex = ttcIndex;
626            return this;
627        }
628
629        /**
630         * Sets a font variation settings.
631         *
632         * @param variationSettings See {@link android.widget.TextView#setFontVariationSettings}.
633         * @throws InvalidFormatException If given string is not a valid font variation settings
634         *                                format.
635         */
636        public Builder setFontVariationSettings(@Nullable String variationSettings)
637                throws InvalidFormatException {
638            if (mFonts != null) {
639                throw new IllegalArgumentException(
640                        "Font variation settings can not be specified for FontResult source.");
641            }
642            if (mAxes != null) {
643                throw new IllegalStateException("Font variation settings are already set.");
644            }
645            mAxes = FontVariationAxis.fromFontVariationSettings(variationSettings);
646            return this;
647        }
648
649        /**
650         * Sets a font variation settings.
651         *
652         * @param axes An array of font variation axis tag-value pairs.
653         */
654        public Builder setFontVariationSettings(@Nullable FontVariationAxis[] axes) {
655            if (mFonts != null) {
656                throw new IllegalArgumentException(
657                        "Font variation settings can not be specified for FontResult source.");
658            }
659            if (mAxes != null) {
660                throw new IllegalStateException("Font variation settings are already set.");
661            }
662            mAxes = axes;
663            return this;
664        }
665
666        /**
667         * Creates a unique id for a given AssetManager and asset path.
668         *
669         * @param mgr  AssetManager instance
670         * @param path The path for the asset.
671         * @param ttcIndex The TTC index for the font.
672         * @param axes The font variation settings.
673         * @return Unique id for a given AssetManager and asset path.
674         */
675        private static String createAssetUid(final AssetManager mgr, String path, int ttcIndex,
676                @Nullable FontVariationAxis[] axes) {
677            final SparseArray<String> pkgs = mgr.getAssignedPackageIdentifiers();
678            final StringBuilder builder = new StringBuilder();
679            final int size = pkgs.size();
680            for (int i = 0; i < size; i++) {
681                builder.append(pkgs.valueAt(i));
682                builder.append("-");
683            }
684            builder.append(path);
685            builder.append("-");
686            builder.append(Integer.toString(ttcIndex));
687            builder.append("-");
688            if (axes != null) {
689                for (FontVariationAxis axis : axes) {
690                    builder.append(axis.getTag());
691                    builder.append("-");
692                    builder.append(Float.toString(axis.getStyleValue()));
693                }
694            }
695            return builder.toString();
696        }
697
698        /**
699         * Generates new Typeface from specified configuration.
700         *
701         * @return Newly created Typeface. May return null if some parameters are invalid.
702         */
703        public Typeface build() {
704            if (mFd != null) {  // set source by setSourceFromFile(FileDescriptor)
705                try (FileInputStream fis = new FileInputStream(mFd)) {
706                    FileChannel channel = fis.getChannel();
707                    long size = channel.size();
708                    ByteBuffer buffer = channel.map(FileChannel.MapMode.READ_ONLY, 0, size);
709
710                    final FontFamily fontFamily = new FontFamily();
711                    if (!fontFamily.addFontFromBuffer(buffer, mTtcIndex, mAxes, mWeight, mItalic)) {
712                        fontFamily.abortCreation();
713                        return null;
714                    }
715                    fontFamily.freeze();
716                    FontFamily[] families = { fontFamily };
717                    return createFromFamiliesWithDefault(families);
718                } catch (IOException e) {
719                    return null;
720                }
721            } else if (mAssetManager != null) {  // set source by setSourceFromAsset()
722                final String key = createAssetUid(mAssetManager, mPath, mTtcIndex, mAxes);
723                synchronized (sDynamicTypefaceCache) {
724                    Typeface typeface = sDynamicTypefaceCache.get(key);
725                    if (typeface != null) return typeface;
726                    final FontFamily fontFamily = new FontFamily();
727                    if (!fontFamily.addFontFromAssetManager(mAssetManager, mPath, mTtcIndex,
728                            true /* isAsset */, mTtcIndex, mWeight, mItalic, mAxes)) {
729                        fontFamily.abortCreation();
730                        return null;
731                    }
732                    fontFamily.freeze();
733                    FontFamily[] families = { fontFamily };
734                    typeface = createFromFamiliesWithDefault(families);
735                    sDynamicTypefaceCache.put(key, typeface);
736                    return typeface;
737                }
738            } else if (mPath != null) {  // set source by setSourceFromFile(File)
739                final FontFamily fontFamily = new FontFamily();
740                if (!fontFamily.addFont(mPath, mTtcIndex, mAxes, mWeight, mItalic)) {
741                    fontFamily.abortCreation();
742                    return null;
743                }
744                fontFamily.freeze();
745                FontFamily[] families = { fontFamily };
746                return createFromFamiliesWithDefault(families);
747            } else if (mFonts != null) {
748                final FontFamily fontFamily = new FontFamily();
749                boolean atLeastOneFont = false;
750                for (FontsContract.FontInfo font : mFonts) {
751                    final ByteBuffer fontBuffer = mFontBuffers.get(font.getUri());
752                    if (fontBuffer == null) {
753                        continue;  // skip
754                    }
755                    final boolean success = fontFamily.addFontFromBuffer(fontBuffer,
756                            font.getTtcIndex(), font.getAxes(), font.getWeight(),
757                            font.isItalic() ? STYLE_ITALIC : STYLE_NORMAL);
758                    if (!success) {
759                        fontFamily.abortCreation();
760                        return null;
761                    }
762                    atLeastOneFont = true;
763                }
764                if (!atLeastOneFont) {
765                    // No fonts are avaialble. No need to create new Typeface and returns fallback
766                    // Typeface instead.
767                    fontFamily.abortCreation();
768                    return null;
769                }
770                fontFamily.freeze();
771                FontFamily[] families = { fontFamily };
772                return createFromFamiliesWithDefault(families);
773            }
774
775            // Must not reach here.
776            throw new IllegalArgumentException("No source was set.");
777        }
778    }
779
780    /**
781     * Create a typeface object given a family name, and option style information.
782     * If null is passed for the name, then the "default" font will be chosen.
783     * The resulting typeface object can be queried (getStyle()) to discover what
784     * its "real" style characteristics are.
785     *
786     * @param familyName May be null. The name of the font family.
787     * @param style  The style (normal, bold, italic) of the typeface.
788     *               e.g. NORMAL, BOLD, ITALIC, BOLD_ITALIC
789     * @return The best matching typeface.
790     */
791    public static Typeface create(String familyName, int style) {
792        if (sSystemFontMap != null) {
793            return create(sSystemFontMap.get(familyName), style);
794        }
795        return null;
796    }
797
798    /**
799     * Create a typeface object that best matches the specified existing
800     * typeface and the specified Style. Use this call if you want to pick a new
801     * style from the same family of an existing typeface object. If family is
802     * null, this selects from the default font's family.
803     *
804     * @param family May be null. The name of the existing type face.
805     * @param style  The style (normal, bold, italic) of the typeface.
806     *               e.g. NORMAL, BOLD, ITALIC, BOLD_ITALIC
807     * @return The best matching typeface.
808     */
809    public static Typeface create(Typeface family, int style) {
810        if (style < 0 || style > 3) {
811            style = 0;
812        }
813        long ni = 0;
814        if (family != null) {
815            // Return early if we're asked for the same face/style
816            if (family.mStyle == style) {
817                return family;
818            }
819
820            ni = family.native_instance;
821        }
822
823        Typeface typeface;
824        SparseArray<Typeface> styles = sTypefaceCache.get(ni);
825
826        if (styles != null) {
827            typeface = styles.get(style);
828            if (typeface != null) {
829                return typeface;
830            }
831        }
832
833        typeface = new Typeface(nativeCreateFromTypeface(ni, style));
834        if (styles == null) {
835            styles = new SparseArray<Typeface>(4);
836            sTypefaceCache.put(ni, styles);
837        }
838        styles.put(style, typeface);
839
840        return typeface;
841    }
842
843    /** @hide */
844    public static Typeface createFromTypefaceWithVariation(Typeface family,
845            List<FontVariationAxis> axes) {
846        final long ni = family == null ? 0 : family.native_instance;
847        return new Typeface(nativeCreateFromTypefaceWithVariation(ni, axes));
848    }
849
850    /**
851     * Returns one of the default typeface objects, based on the specified style
852     *
853     * @return the default typeface that corresponds to the style
854     */
855    public static Typeface defaultFromStyle(int style) {
856        return sDefaults[style];
857    }
858
859    /**
860     * Create a new typeface from the specified font data.
861     *
862     * @param mgr  The application's asset manager
863     * @param path The file name of the font data in the assets directory
864     * @return The new typeface.
865     */
866    public static Typeface createFromAsset(AssetManager mgr, String path) {
867        if (path == null) {
868            throw new NullPointerException();  // for backward compatibility
869        }
870        if (sFallbackFonts != null) {
871            Typeface typeface = new Builder(mgr, path).build();
872            if (typeface != null) {
873                return typeface;
874            }
875        }
876        // For the compatibility reasons, throw runtime exception if failed to create Typeface.
877        throw new RuntimeException("Font asset not found " + path);
878    }
879
880    /**
881     * Creates a unique id for a given font provider and query.
882     */
883    private static String createProviderUid(String authority, String query) {
884        final StringBuilder builder = new StringBuilder();
885        builder.append("provider:");
886        builder.append(authority);
887        builder.append("-");
888        builder.append(query);
889        return builder.toString();
890    }
891
892    /**
893     * Create a new typeface from the specified font file.
894     *
895     * @param path The path to the font data.
896     * @return The new typeface.
897     */
898    public static Typeface createFromFile(@Nullable File path) {
899        // For the compatibility reasons, leaving possible NPE here.
900        // See android.graphics.cts.TypefaceTest#testCreateFromFileByFileReferenceNull
901        return createFromFile(path.getAbsolutePath());
902    }
903
904    /**
905     * Create a new typeface from the specified font file.
906     *
907     * @param path The full path to the font data.
908     * @return The new typeface.
909     */
910    public static Typeface createFromFile(@Nullable String path) {
911        if (path == null) {
912            // For the compatibility reasons, need to throw NPE if the argument is null.
913            // See android.graphics.cts.TypefaceTest#testCreateFromFileByFileNameNull
914            throw new NullPointerException();
915        }
916        if (sFallbackFonts != null) {
917            Typeface typeface = new Builder(path).build();
918            if (typeface != null) {
919                // For the compatibility reasons, throw runtime exception if failed to create
920                // Typeface.
921                return typeface;
922            }
923        }
924        throw new RuntimeException("Font not found " + path);
925    }
926
927    /**
928     * Create a new typeface from an array of font families.
929     *
930     * @param families array of font families
931     */
932    private static Typeface createFromFamilies(FontFamily[] families) {
933        long[] ptrArray = new long[families.length];
934        for (int i = 0; i < families.length; i++) {
935            ptrArray[i] = families[i].mNativePtr;
936        }
937        return new Typeface(nativeCreateFromArray(ptrArray));
938    }
939
940    /**
941     * Create a new typeface from an array of font families, including
942     * also the font families in the fallback list.
943     *
944     * @param families array of font families
945     */
946    private static Typeface createFromFamiliesWithDefault(FontFamily[] families) {
947        long[] ptrArray = new long[families.length + sFallbackFonts.length];
948        for (int i = 0; i < families.length; i++) {
949            ptrArray[i] = families[i].mNativePtr;
950        }
951        for (int i = 0; i < sFallbackFonts.length; i++) {
952            ptrArray[i + families.length] = sFallbackFonts[i].mNativePtr;
953        }
954        return new Typeface(nativeCreateFromArray(ptrArray));
955    }
956
957    // don't allow clients to call this directly
958    private Typeface(long ni) {
959        if (ni == 0) {
960            throw new RuntimeException("native typeface cannot be made");
961        }
962
963        native_instance = ni;
964        mStyle = nativeGetStyle(ni);
965    }
966
967    private static FontFamily makeFamilyFromParsed(FontConfig.Family family,
968            Map<String, ByteBuffer> bufferForPath) {
969        FontFamily fontFamily = new FontFamily(family.getLanguage(), family.getVariant());
970        for (FontConfig.Font font : family.getFonts()) {
971            String fullPathName = "/system/fonts/" + font.getFontName();
972            ByteBuffer fontBuffer = bufferForPath.get(fullPathName);
973            if (fontBuffer == null) {
974                try (FileInputStream file = new FileInputStream(fullPathName)) {
975                    FileChannel fileChannel = file.getChannel();
976                    long fontSize = fileChannel.size();
977                    fontBuffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, fontSize);
978                    bufferForPath.put(fullPathName, fontBuffer);
979                } catch (IOException e) {
980                    Log.e(TAG, "Error mapping font file " + fullPathName);
981                    continue;
982                }
983            }
984            if (!fontFamily.addFontFromBuffer(fontBuffer, font.getTtcIndex(), font.getAxes(),
985                    font.getWeight(), font.isItalic() ? STYLE_ITALIC : STYLE_NORMAL)) {
986                Log.e(TAG, "Error creating font " + fullPathName + "#" + font.getTtcIndex());
987            }
988        }
989        fontFamily.freeze();
990        return fontFamily;
991    }
992
993    /*
994     * (non-Javadoc)
995     *
996     * This should only be called once, from the static class initializer block.
997     */
998    private static void init() {
999        // Load font config and initialize Minikin state
1000        File systemFontConfigLocation = getSystemFontConfigLocation();
1001        File configFilename = new File(systemFontConfigLocation, FONTS_CONFIG);
1002        try {
1003            FileInputStream fontsIn = new FileInputStream(configFilename);
1004            FontConfig fontConfig = FontListParser.parse(fontsIn);
1005
1006            Map<String, ByteBuffer> bufferForPath = new HashMap<String, ByteBuffer>();
1007
1008            List<FontFamily> familyList = new ArrayList<FontFamily>();
1009            // Note that the default typeface is always present in the fallback list;
1010            // this is an enhancement from pre-Minikin behavior.
1011            for (int i = 0; i < fontConfig.getFamilies().length; i++) {
1012                FontConfig.Family f = fontConfig.getFamilies()[i];
1013                if (i == 0 || f.getName() == null) {
1014                    familyList.add(makeFamilyFromParsed(f, bufferForPath));
1015                }
1016            }
1017            sFallbackFonts = familyList.toArray(new FontFamily[familyList.size()]);
1018            setDefault(Typeface.createFromFamilies(sFallbackFonts));
1019
1020            Map<String, Typeface> systemFonts = new HashMap<String, Typeface>();
1021            for (int i = 0; i < fontConfig.getFamilies().length; i++) {
1022                Typeface typeface;
1023                FontConfig.Family f = fontConfig.getFamilies()[i];
1024                if (f.getName() != null) {
1025                    if (i == 0) {
1026                        // The first entry is the default typeface; no sense in
1027                        // duplicating the corresponding FontFamily.
1028                        typeface = sDefaultTypeface;
1029                    } else {
1030                        FontFamily fontFamily = makeFamilyFromParsed(f, bufferForPath);
1031                        FontFamily[] families = { fontFamily };
1032                        typeface = Typeface.createFromFamiliesWithDefault(families);
1033                    }
1034                    systemFonts.put(f.getName(), typeface);
1035                }
1036            }
1037            for (FontConfig.Alias alias : fontConfig.getAliases()) {
1038                Typeface base = systemFonts.get(alias.getToName());
1039                Typeface newFace = base;
1040                int weight = alias.getWeight();
1041                if (weight != 400) {
1042                    newFace = new Typeface(nativeCreateWeightAlias(base.native_instance, weight));
1043                }
1044                systemFonts.put(alias.getName(), newFace);
1045            }
1046            sSystemFontMap = systemFonts;
1047
1048        } catch (RuntimeException e) {
1049            Log.w(TAG, "Didn't create default family (most likely, non-Minikin build)", e);
1050            // TODO: normal in non-Minikin case, remove or make error when Minikin-only
1051        } catch (FileNotFoundException e) {
1052            Log.e(TAG, "Error opening " + configFilename, e);
1053        } catch (IOException e) {
1054            Log.e(TAG, "Error reading " + configFilename, e);
1055        } catch (XmlPullParserException e) {
1056            Log.e(TAG, "XML parse exception for " + configFilename, e);
1057        }
1058    }
1059
1060    static {
1061        init();
1062        // Set up defaults and typefaces exposed in public API
1063        DEFAULT         = create((String) null, 0);
1064        DEFAULT_BOLD    = create((String) null, Typeface.BOLD);
1065        SANS_SERIF      = create("sans-serif", 0);
1066        SERIF           = create("serif", 0);
1067        MONOSPACE       = create("monospace", 0);
1068
1069        sDefaults = new Typeface[] {
1070            DEFAULT,
1071            DEFAULT_BOLD,
1072            create((String) null, Typeface.ITALIC),
1073            create((String) null, Typeface.BOLD_ITALIC),
1074        };
1075
1076    }
1077
1078    private static File getSystemFontConfigLocation() {
1079        return new File("/system/etc/");
1080    }
1081
1082    @Override
1083    protected void finalize() throws Throwable {
1084        try {
1085            nativeUnref(native_instance);
1086            native_instance = 0;  // Other finalizers can still call us.
1087        } finally {
1088            super.finalize();
1089        }
1090    }
1091
1092    @Override
1093    public boolean equals(Object o) {
1094        if (this == o) return true;
1095        if (o == null || getClass() != o.getClass()) return false;
1096
1097        Typeface typeface = (Typeface) o;
1098
1099        return mStyle == typeface.mStyle && native_instance == typeface.native_instance;
1100    }
1101
1102    @Override
1103    public int hashCode() {
1104        /*
1105         * Modified method for hashCode with long native_instance derived from
1106         * http://developer.android.com/reference/java/lang/Object.html
1107         */
1108        int result = 17;
1109        result = 31 * result + (int) (native_instance ^ (native_instance >>> 32));
1110        result = 31 * result + mStyle;
1111        return result;
1112    }
1113
1114    /** @hide */
1115    public boolean isSupportedAxes(int axis) {
1116        if (mSupportedAxes == null) {
1117            synchronized (this) {
1118                if (mSupportedAxes == null) {
1119                    mSupportedAxes = nativeGetSupportedAxes(native_instance);
1120                    if (mSupportedAxes == null) {
1121                        mSupportedAxes = EMPTY_AXES;
1122                    }
1123                }
1124            }
1125        }
1126        return Arrays.binarySearch(mSupportedAxes, axis) > 0;
1127    }
1128
1129    private static native long nativeCreateFromTypeface(long native_instance, int style);
1130    // TODO: clean up: change List<FontVariationAxis> to FontVariationAxis[]
1131    private static native long nativeCreateFromTypefaceWithVariation(
1132            long native_instance, List<FontVariationAxis> axes);
1133    private static native long nativeCreateWeightAlias(long native_instance, int weight);
1134    private static native void nativeUnref(long native_instance);
1135    private static native int  nativeGetStyle(long native_instance);
1136    private static native long nativeCreateFromArray(long[] familyArray);
1137    private static native void nativeSetDefault(long native_instance);
1138    private static native int[] nativeGetSupportedAxes(long native_instance);
1139}
1140