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