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