Typeface.java revision b44abf290190ceee037f24c47493a34de45fa3f4
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 android.annotation.IntDef;
20import android.annotation.NonNull;
21import android.annotation.Nullable;
22import android.content.res.AssetManager;
23import android.graphics.fonts.FontRequest;
24import android.graphics.fonts.FontResult;
25import android.os.Bundle;
26import android.os.Handler;
27import android.os.ParcelFileDescriptor;
28import android.os.ResultReceiver;
29import android.provider.FontsContract;
30import android.text.FontConfig;
31import android.util.Log;
32import android.util.LongSparseArray;
33import android.util.LruCache;
34import android.util.SparseArray;
35import android.graphics.FontListParser;
36
37import com.android.internal.annotations.GuardedBy;
38
39import libcore.io.IoUtils;
40
41import org.xmlpull.v1.XmlPullParserException;
42
43import java.io.File;
44import java.io.FileInputStream;
45import java.io.FileNotFoundException;
46import java.io.IOException;
47import java.lang.annotation.Retention;
48import java.lang.annotation.RetentionPolicy;
49import java.nio.ByteBuffer;
50import java.nio.channels.FileChannel;
51import java.util.ArrayList;
52import java.util.HashMap;
53import java.util.List;
54import java.util.Map;
55
56/**
57 * The Typeface class specifies the typeface and intrinsic style of a font.
58 * This is used in the paint, along with optionally Paint settings like
59 * textSize, textSkewX, textScaleX to specify
60 * how text appears when drawn (and measured).
61 */
62public class Typeface {
63
64    private static String TAG = "Typeface";
65
66    /** The default NORMAL typeface object */
67    public static final Typeface DEFAULT;
68    /**
69     * The default BOLD typeface object. Note: this may be not actually be
70     * bold, depending on what fonts are installed. Call getStyle() to know
71     * for sure.
72     */
73    public static final Typeface DEFAULT_BOLD;
74    /** The NORMAL style of the default sans serif typeface. */
75    public static final Typeface SANS_SERIF;
76    /** The NORMAL style of the default serif typeface. */
77    public static final Typeface SERIF;
78    /** The NORMAL style of the default monospace typeface. */
79    public static final Typeface MONOSPACE;
80
81    static Typeface[] sDefaults;
82    private static final LongSparseArray<SparseArray<Typeface>> sTypefaceCache =
83            new LongSparseArray<>(3);
84    @GuardedBy("sLock")
85    private static FontsContract sFontsContract;
86    @GuardedBy("sLock")
87    private static Handler mHandler;
88
89    /**
90     * Cache for Typeface objects dynamically loaded from assets. Currently max size is 16.
91     */
92    private static final LruCache<String, Typeface> sDynamicTypefaceCache = new LruCache<>(16);
93
94    static Typeface sDefaultTypeface;
95    static Map<String, Typeface> sSystemFontMap;
96    static FontFamily[] sFallbackFonts;
97    private static final Object sLock = new Object();
98
99    static final String FONTS_CONFIG = "fonts.xml";
100
101    /**
102     * @hide
103     */
104    public long native_instance;
105
106    // Style
107    public static final int NORMAL = 0;
108    public static final int BOLD = 1;
109    public static final int ITALIC = 2;
110    public static final int BOLD_ITALIC = 3;
111
112    private int mStyle = 0;
113
114    private static void setDefault(Typeface t) {
115        sDefaultTypeface = t;
116        nativeSetDefault(t.native_instance);
117    }
118
119    /** Returns the typeface's intrinsic style attributes */
120    public int getStyle() {
121        return mStyle;
122    }
123
124    /** Returns true if getStyle() has the BOLD bit set. */
125    public final boolean isBold() {
126        return (mStyle & BOLD) != 0;
127    }
128
129    /** Returns true if getStyle() has the ITALIC bit set. */
130    public final boolean isItalic() {
131        return (mStyle & ITALIC) != 0;
132    }
133
134    /**
135     * @hide
136     * Used by Resources to load a font resource of type font file.
137     */
138    @Nullable
139    public static Typeface createFromResources(AssetManager mgr, String path, int cookie) {
140        if (sFallbackFonts != null) {
141            synchronized (sDynamicTypefaceCache) {
142                final String key = createAssetUid(mgr, path);
143                Typeface typeface = sDynamicTypefaceCache.get(key);
144                if (typeface != null) return typeface;
145
146                FontFamily fontFamily = new FontFamily();
147                if (fontFamily.addFontFromAssetManager(mgr, path, cookie, false /* isAsset */,
148                        0 /* use OS/2 table to determine weight and italic */, false)) {
149                    fontFamily.freeze();
150                    FontFamily[] families = {fontFamily};
151                    typeface = createFromFamiliesWithDefault(families);
152                    sDynamicTypefaceCache.put(key, typeface);
153                    return typeface;
154                }
155            }
156        }
157        return null;
158    }
159
160    /**
161     * @hide
162     * Used by Resources to load a font resource of type xml.
163     */
164    @Nullable
165    public static Typeface createFromResources(FontConfig config, AssetManager mgr, String path) {
166        if (sFallbackFonts != null) {
167            Typeface typeface = findFromCache(mgr, path);
168            if (typeface != null) return typeface;
169
170            List<FontConfig.Family> families = config.getFamilies();
171            if (families == null || families.isEmpty()) {
172                throw new RuntimeException(
173                        "Font resource " + path + " contained no font families.");
174            }
175            if (families.size() > 1) {
176                throw new RuntimeException(
177                        "Font resource " + path + " contained more than one family.");
178            }
179            FontConfig.Family family = families.get(0);
180            if (family.getProviderAuthority() != null && family.getQuery() != null) {
181                // Downloadable font
182                typeface = findFromCache(
183                        family.getProviderAuthority(), family.getQuery());
184                if (typeface != null) {
185                    return typeface;
186                }
187                // Downloaded font and it wasn't cached, request it again and return a
188                // default font instead (nothing we can do now).
189                create(new FontRequest(family.getProviderAuthority(), family.getQuery()),
190                        NO_OP_REQUEST_CALLBACK);
191                return DEFAULT;
192            }
193
194            FontFamily fontFamily = new FontFamily();
195            List<FontConfig.Font> fonts = family.getFonts();
196            if (fonts == null || fonts.isEmpty()) {
197                throw new RuntimeException("Font resource " + path + " contained no fonts.");
198            }
199            for (int i = 0; i < fonts.size(); i++) {
200                FontConfig.Font font = fonts.get(i);
201                // TODO: Use style and weight info
202                if (!fontFamily.addFontFromAssetManager(mgr, font.getFontName(),
203                        0 /* resourceCookie */, false /* isAsset */, font.getWeight(),
204                        font.isItalic())) {
205                    return null;
206                }
207            }
208            fontFamily.freeze();
209            FontFamily[] familyChain = { fontFamily };
210            typeface = createFromFamiliesWithDefault(familyChain);
211            synchronized (sDynamicTypefaceCache) {
212                final String key = createAssetUid(mgr, path);
213                sDynamicTypefaceCache.put(key, typeface);
214            }
215            return typeface;
216        }
217        return null;
218    }
219
220    /**
221     * Used by resources for cached loading if the font is available.
222     * @hide
223     */
224    public static Typeface findFromCache(AssetManager mgr, String path) {
225        synchronized (sDynamicTypefaceCache) {
226            final String key = createAssetUid(mgr, path);
227            Typeface typeface = sDynamicTypefaceCache.get(key);
228            if (typeface != null) {
229                return typeface;
230            }
231        }
232        return null;
233    }
234
235    /**
236     * Create a typeface object given a font request. The font will be asynchronously fetched,
237     * therefore the result is delivered to the given callback. See {@link FontRequest}.
238     * Only one of the methods in callback will be invoked, depending on whether the request
239     * succeeds or fails. These calls will happen on the main thread.
240     * @param request A {@link FontRequest} object that identifies the provider and query for the
241     *                request. May not be null.
242     * @param callback A callback that will be triggered when results are obtained. May not be null.
243     */
244    public static void create(@NonNull FontRequest request, @NonNull FontRequestCallback callback) {
245        // Check the cache first
246        // TODO: would the developer want to avoid a cache hit and always ask for the freshest
247        // result?
248        Typeface cachedTypeface = findFromCache(
249                request.getProviderAuthority(), request.getQuery());
250        if (cachedTypeface != null) {
251            mHandler.post(() -> callback.onTypefaceRetrieved(cachedTypeface));
252            return;
253        }
254        synchronized (sLock) {
255            if (sFontsContract == null) {
256                sFontsContract = new FontsContract();
257                mHandler = new Handler();
258            }
259            final ResultReceiver receiver = new ResultReceiver(null) {
260                @Override
261                public void onReceiveResult(int resultCode, Bundle resultData) {
262                    mHandler.post(() -> receiveResult(request, callback, resultCode, resultData));
263                }
264            };
265            sFontsContract.getFont(request, receiver);
266        }
267    }
268
269    private static Typeface findFromCache(String providerAuthority, String query) {
270        synchronized (sDynamicTypefaceCache) {
271            final String key = createProviderUid(providerAuthority, query);
272            Typeface typeface = sDynamicTypefaceCache.get(key);
273            if (typeface != null) {
274                return typeface;
275            }
276        }
277        return null;
278    }
279
280    private static void receiveResult(FontRequest request, FontRequestCallback callback,
281            int resultCode, Bundle resultData) {
282        Typeface cachedTypeface = findFromCache(
283                request.getProviderAuthority(), request.getQuery());
284        if (cachedTypeface != null) {
285            // We already know the result.
286            // Probably the requester requests the same font again in a short interval.
287            callback.onTypefaceRetrieved(cachedTypeface);
288            return;
289        }
290        if (resultCode == FontsContract.RESULT_CODE_PROVIDER_NOT_FOUND) {
291            callback.onTypefaceRequestFailed(
292                    FontRequestCallback.FAIL_REASON_PROVIDER_NOT_FOUND);
293            return;
294        }
295        if (resultCode == FontsContract.RESULT_CODE_FONT_NOT_FOUND
296                || resultData == null) {
297            callback.onTypefaceRequestFailed(
298                    FontRequestCallback.FAIL_REASON_FONT_NOT_FOUND);
299            return;
300        }
301        List<FontResult> resultList =
302                resultData.getParcelableArrayList(FontsContract.PARCEL_FONT_RESULTS);
303        if (resultList == null || resultList.isEmpty()) {
304            callback.onTypefaceRequestFailed(
305                    FontRequestCallback.FAIL_REASON_FONT_NOT_FOUND);
306            return;
307        }
308        FontFamily fontFamily = new FontFamily();
309        for (int i = 0; i < resultList.size(); ++i) {
310            FontResult result = resultList.get(i);
311            ParcelFileDescriptor fd = result.getFileDescriptor();
312            if (fd == null) {
313                callback.onTypefaceRequestFailed(
314                        FontRequestCallback.FAIL_REASON_FONT_LOAD_ERROR);
315                return;
316            }
317            try (FileInputStream is = new FileInputStream(fd.getFileDescriptor())) {
318                FileChannel fileChannel = is.getChannel();
319                long fontSize = fileChannel.size();
320                ByteBuffer fontBuffer = fileChannel.map(
321                        FileChannel.MapMode.READ_ONLY, 0, fontSize);
322                int style = result.getStyle();
323                int weight = (style & BOLD) != 0 ? 700 : 400;
324                // TODO: this method should be
325                // create(fd, ttcIndex, fontVariationSettings, style).
326                if (!fontFamily.addFontWeightStyle(fontBuffer, result.getTtcIndex(),
327                                null, weight, (style & ITALIC) != 0)) {
328                    Log.e(TAG, "Error creating font " + request.getQuery());
329                    callback.onTypefaceRequestFailed(
330                            FontRequestCallback.FAIL_REASON_FONT_LOAD_ERROR);
331                    return;
332                }
333            } catch (IOException e) {
334                Log.e(TAG, "Error reading font " + request.getQuery(), e);
335                callback.onTypefaceRequestFailed(
336                        FontRequestCallback.FAIL_REASON_FONT_LOAD_ERROR);
337                return;
338            } finally {
339                IoUtils.closeQuietly(fd);
340            }
341        }
342        fontFamily.freeze();
343        Typeface typeface = Typeface.createFromFamiliesWithDefault(new FontFamily[] { fontFamily });
344        synchronized (sDynamicTypefaceCache) {
345            String key = createProviderUid(request.getProviderAuthority(), request.getQuery());
346            sDynamicTypefaceCache.put(key, typeface);
347        }
348        callback.onTypefaceRetrieved(typeface);
349    }
350
351    /**
352     * Interface used to receive asynchronously fetched typefaces.
353     */
354    public interface FontRequestCallback {
355        /**
356         * Constant returned by {@link #onTypefaceRequestFailed(int)} signaling that the given
357         * provider was not found on the device.
358         */
359        int FAIL_REASON_PROVIDER_NOT_FOUND = 0;
360        /**
361         * Constant returned by {@link #onTypefaceRequestFailed(int)} signaling that the font
362         * returned by the provider was not loaded properly.
363         */
364        int FAIL_REASON_FONT_LOAD_ERROR = 1;
365        /**
366         * Constant returned by {@link #onTypefaceRequestFailed(int)} signaling that the given
367         * provider did not return any results for the given query.
368         */
369        int FAIL_REASON_FONT_NOT_FOUND = 2;
370
371        /** @hide */
372        @IntDef({FAIL_REASON_PROVIDER_NOT_FOUND, FAIL_REASON_FONT_LOAD_ERROR,
373                FAIL_REASON_FONT_NOT_FOUND})
374        @Retention(RetentionPolicy.SOURCE)
375        @interface FontRequestFailReason {}
376
377        /**
378         * Called then a Typeface request done via {@link Typeface#create(FontRequest,
379         * FontRequestCallback)} is complete. Note that this method will not be called if
380         * {@link #onTypefaceRequestFailed(int)} is called instead.
381         * @param typeface  The Typeface object retrieved.
382         */
383        void onTypefaceRetrieved(Typeface typeface);
384
385        /**
386         * Called when a Typeface request done via {@link Typeface#create(FontRequest,
387         * FontRequestCallback)} fails.
388         * @param reason One of {@link #FAIL_REASON_PROVIDER_NOT_FOUND},
389         *               {@link #FAIL_REASON_FONT_NOT_FOUND} or
390         *               {@link #FAIL_REASON_FONT_LOAD_ERROR}.
391         */
392        void onTypefaceRequestFailed(@FontRequestFailReason int reason);
393    }
394
395    private static final FontRequestCallback NO_OP_REQUEST_CALLBACK = new FontRequestCallback() {
396        @Override
397        public void onTypefaceRetrieved(Typeface typeface) {
398            // Do nothing.
399        }
400
401        @Override
402        public void onTypefaceRequestFailed(@FontRequestFailReason int reason) {
403            // Do nothing.
404        }
405    };
406
407    /**
408     * Create a typeface object given a family name, and option style information.
409     * If null is passed for the name, then the "default" font will be chosen.
410     * The resulting typeface object can be queried (getStyle()) to discover what
411     * its "real" style characteristics are.
412     *
413     * @param familyName May be null. The name of the font family.
414     * @param style  The style (normal, bold, italic) of the typeface.
415     *               e.g. NORMAL, BOLD, ITALIC, BOLD_ITALIC
416     * @return The best matching typeface.
417     */
418    public static Typeface create(String familyName, int style) {
419        if (sSystemFontMap != null) {
420            return create(sSystemFontMap.get(familyName), style);
421        }
422        return null;
423    }
424
425    /**
426     * Create a typeface object that best matches the specified existing
427     * typeface and the specified Style. Use this call if you want to pick a new
428     * style from the same family of an existing typeface object. If family is
429     * null, this selects from the default font's family.
430     *
431     * @param family May be null. The name of the existing type face.
432     * @param style  The style (normal, bold, italic) of the typeface.
433     *               e.g. NORMAL, BOLD, ITALIC, BOLD_ITALIC
434     * @return The best matching typeface.
435     */
436    public static Typeface create(Typeface family, int style) {
437        if (style < 0 || style > 3) {
438            style = 0;
439        }
440        long ni = 0;
441        if (family != null) {
442            // Return early if we're asked for the same face/style
443            if (family.mStyle == style) {
444                return family;
445            }
446
447            ni = family.native_instance;
448        }
449
450        Typeface typeface;
451        SparseArray<Typeface> styles = sTypefaceCache.get(ni);
452
453        if (styles != null) {
454            typeface = styles.get(style);
455            if (typeface != null) {
456                return typeface;
457            }
458        }
459
460        typeface = new Typeface(nativeCreateFromTypeface(ni, style));
461        if (styles == null) {
462            styles = new SparseArray<Typeface>(4);
463            sTypefaceCache.put(ni, styles);
464        }
465        styles.put(style, typeface);
466
467        return typeface;
468    }
469
470    /** @hide */
471    public static Typeface createFromTypefaceWithVariation(Typeface family,
472            String fontVariationSettings) {
473        final long ni = family == null ? 0 : family.native_instance;
474        ArrayList<FontConfig.Axis> axes =
475                FontListParser.parseFontVariationSettings(fontVariationSettings);
476        return new Typeface(nativeCreateFromTypefaceWithVariation(ni, axes));
477    }
478
479    /**
480     * Returns one of the default typeface objects, based on the specified style
481     *
482     * @return the default typeface that corresponds to the style
483     */
484    public static Typeface defaultFromStyle(int style) {
485        return sDefaults[style];
486    }
487
488    /**
489     * Create a new typeface from the specified font data.
490     *
491     * @param mgr  The application's asset manager
492     * @param path The file name of the font data in the assets directory
493     * @return The new typeface.
494     */
495    public static Typeface createFromAsset(AssetManager mgr, String path) {
496        if (sFallbackFonts != null) {
497            synchronized (sDynamicTypefaceCache) {
498                final String key = createAssetUid(mgr, path);
499                Typeface typeface = sDynamicTypefaceCache.get(key);
500                if (typeface != null) return typeface;
501
502                FontFamily fontFamily = new FontFamily();
503                if (fontFamily.addFontFromAssetManager(mgr, path, 0, true /* isAsset */,
504                        0 /* use OS/2 table to determine weight and italic */, false)) {
505                    fontFamily.freeze();
506                    FontFamily[] families = { fontFamily };
507                    typeface = createFromFamiliesWithDefault(families);
508                    sDynamicTypefaceCache.put(key, typeface);
509                    return typeface;
510                } else {
511                    fontFamily.abortCreation();
512                }
513            }
514        }
515        throw new RuntimeException("Font asset not found " + path);
516    }
517
518    /**
519     * Creates a unique id for a given AssetManager and asset path.
520     *
521     * @param mgr  AssetManager instance
522     * @param path The path for the asset.
523     * @return Unique id for a given AssetManager and asset path.
524     */
525    private static String createAssetUid(final AssetManager mgr, String path) {
526        final SparseArray<String> pkgs = mgr.getAssignedPackageIdentifiers();
527        final StringBuilder builder = new StringBuilder();
528        builder.append("asset:");
529        final int size = pkgs.size();
530        for (int i = 0; i < size; i++) {
531            builder.append(pkgs.valueAt(i));
532            builder.append("-");
533        }
534        builder.append(path);
535        return builder.toString();
536    }
537
538    /**
539     * Creates a unique id for a given font provider and query.
540     */
541    private static String createProviderUid(String authority, String query) {
542        final StringBuilder builder = new StringBuilder();
543        builder.append("provider:");
544        builder.append(authority);
545        builder.append("-");
546        builder.append(query);
547        return builder.toString();
548    }
549
550    /**
551     * Create a new typeface from the specified font file.
552     *
553     * @param path The path to the font data.
554     * @return The new typeface.
555     */
556    public static Typeface createFromFile(File path) {
557        return createFromFile(path.getAbsolutePath());
558    }
559
560    /**
561     * Create a new typeface from the specified font file.
562     *
563     * @param path The full path to the font data.
564     * @return The new typeface.
565     */
566    public static Typeface createFromFile(String path) {
567        if (sFallbackFonts != null) {
568            FontFamily fontFamily = new FontFamily();
569            if (fontFamily.addFont(path, 0 /* ttcIndex */)) {
570                fontFamily.freeze();
571                FontFamily[] families = { fontFamily };
572                return createFromFamiliesWithDefault(families);
573            } else {
574                fontFamily.abortCreation();
575            }
576        }
577        throw new RuntimeException("Font not found " + path);
578    }
579
580    /**
581     * Create a new typeface from an array of font families.
582     *
583     * @param families array of font families
584     * @hide
585     */
586    public static Typeface createFromFamilies(FontFamily[] families) {
587        long[] ptrArray = new long[families.length];
588        for (int i = 0; i < families.length; i++) {
589            ptrArray[i] = families[i].mNativePtr;
590        }
591        return new Typeface(nativeCreateFromArray(ptrArray));
592    }
593
594    /**
595     * Create a new typeface from an array of font families, including
596     * also the font families in the fallback list.
597     *
598     * @param families array of font families
599     * @hide
600     */
601    public static Typeface createFromFamiliesWithDefault(FontFamily[] families) {
602        long[] ptrArray = new long[families.length + sFallbackFonts.length];
603        for (int i = 0; i < families.length; i++) {
604            ptrArray[i] = families[i].mNativePtr;
605        }
606        for (int i = 0; i < sFallbackFonts.length; i++) {
607            ptrArray[i + families.length] = sFallbackFonts[i].mNativePtr;
608        }
609        return new Typeface(nativeCreateFromArray(ptrArray));
610    }
611
612    // don't allow clients to call this directly
613    private Typeface(long ni) {
614        if (ni == 0) {
615            throw new RuntimeException("native typeface cannot be made");
616        }
617
618        native_instance = ni;
619        mStyle = nativeGetStyle(ni);
620    }
621
622    private static FontFamily makeFamilyFromParsed(FontConfig.Family family,
623            Map<String, ByteBuffer> bufferForPath) {
624        FontFamily fontFamily = new FontFamily(family.getLanguage(), family.getVariant());
625        for (FontConfig.Font font : family.getFonts()) {
626            ByteBuffer fontBuffer = bufferForPath.get(font.getFontName());
627            if (fontBuffer == null) {
628                try (FileInputStream file = new FileInputStream(font.getFontName())) {
629                    FileChannel fileChannel = file.getChannel();
630                    long fontSize = fileChannel.size();
631                    fontBuffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, fontSize);
632                    bufferForPath.put(font.getFontName(), fontBuffer);
633                } catch (IOException e) {
634                    Log.e(TAG, "Error mapping font file " + font.getFontName());
635                    continue;
636                }
637            }
638            if (!fontFamily.addFontWeightStyle(fontBuffer, font.getTtcIndex(), font.getAxes(),
639                    font.getWeight(), font.isItalic())) {
640                Log.e(TAG, "Error creating font " + font.getFontName() + "#" + font.getTtcIndex());
641            }
642        }
643        fontFamily.freeze();
644        return fontFamily;
645    }
646
647    /*
648     * (non-Javadoc)
649     *
650     * This should only be called once, from the static class initializer block.
651     */
652    private static void init() {
653        // Load font config and initialize Minikin state
654        File systemFontConfigLocation = getSystemFontConfigLocation();
655        File configFilename = new File(systemFontConfigLocation, FONTS_CONFIG);
656        try {
657            FileInputStream fontsIn = new FileInputStream(configFilename);
658            FontConfig fontConfig = FontListParser.parse(fontsIn);
659
660            Map<String, ByteBuffer> bufferForPath = new HashMap<String, ByteBuffer>();
661
662            List<FontFamily> familyList = new ArrayList<FontFamily>();
663            // Note that the default typeface is always present in the fallback list;
664            // this is an enhancement from pre-Minikin behavior.
665            for (int i = 0; i < fontConfig.getFamilies().size(); i++) {
666                FontConfig.Family f = fontConfig.getFamilies().get(i);
667                if (i == 0 || f.getName() == null) {
668                    familyList.add(makeFamilyFromParsed(f, bufferForPath));
669                }
670            }
671            sFallbackFonts = familyList.toArray(new FontFamily[familyList.size()]);
672            setDefault(Typeface.createFromFamilies(sFallbackFonts));
673
674            Map<String, Typeface> systemFonts = new HashMap<String, Typeface>();
675            for (int i = 0; i < fontConfig.getFamilies().size(); i++) {
676                Typeface typeface;
677                FontConfig.Family f = fontConfig.getFamilies().get(i);
678                if (f.getName() != null) {
679                    if (i == 0) {
680                        // The first entry is the default typeface; no sense in
681                        // duplicating the corresponding FontFamily.
682                        typeface = sDefaultTypeface;
683                    } else {
684                        FontFamily fontFamily = makeFamilyFromParsed(f, bufferForPath);
685                        FontFamily[] families = { fontFamily };
686                        typeface = Typeface.createFromFamiliesWithDefault(families);
687                    }
688                    systemFonts.put(f.getName(), typeface);
689                }
690            }
691            for (FontConfig.Alias alias : fontConfig.getAliases()) {
692                Typeface base = systemFonts.get(alias.getToName());
693                Typeface newFace = base;
694                int weight = alias.getWeight();
695                if (weight != 400) {
696                    newFace = new Typeface(nativeCreateWeightAlias(base.native_instance, weight));
697                }
698                systemFonts.put(alias.getName(), newFace);
699            }
700            sSystemFontMap = systemFonts;
701
702        } catch (RuntimeException e) {
703            Log.w(TAG, "Didn't create default family (most likely, non-Minikin build)", e);
704            // TODO: normal in non-Minikin case, remove or make error when Minikin-only
705        } catch (FileNotFoundException e) {
706            Log.e(TAG, "Error opening " + configFilename, e);
707        } catch (IOException e) {
708            Log.e(TAG, "Error reading " + configFilename, e);
709        } catch (XmlPullParserException e) {
710            Log.e(TAG, "XML parse exception for " + configFilename, e);
711        }
712    }
713
714    static {
715        init();
716        // Set up defaults and typefaces exposed in public API
717        DEFAULT         = create((String) null, 0);
718        DEFAULT_BOLD    = create((String) null, Typeface.BOLD);
719        SANS_SERIF      = create("sans-serif", 0);
720        SERIF           = create("serif", 0);
721        MONOSPACE       = create("monospace", 0);
722
723        sDefaults = new Typeface[] {
724            DEFAULT,
725            DEFAULT_BOLD,
726            create((String) null, Typeface.ITALIC),
727            create((String) null, Typeface.BOLD_ITALIC),
728        };
729
730    }
731
732    private static File getSystemFontConfigLocation() {
733        return new File("/system/etc/");
734    }
735
736    @Override
737    protected void finalize() throws Throwable {
738        try {
739            nativeUnref(native_instance);
740            native_instance = 0;  // Other finalizers can still call us.
741        } finally {
742            super.finalize();
743        }
744    }
745
746    @Override
747    public boolean equals(Object o) {
748        if (this == o) return true;
749        if (o == null || getClass() != o.getClass()) return false;
750
751        Typeface typeface = (Typeface) o;
752
753        return mStyle == typeface.mStyle && native_instance == typeface.native_instance;
754    }
755
756    @Override
757    public int hashCode() {
758        /*
759         * Modified method for hashCode with long native_instance derived from
760         * http://developer.android.com/reference/java/lang/Object.html
761         */
762        int result = 17;
763        result = 31 * result + (int) (native_instance ^ (native_instance >>> 32));
764        result = 31 * result + mStyle;
765        return result;
766    }
767
768    private static native long nativeCreateFromTypeface(long native_instance, int style);
769    private static native long nativeCreateFromTypefaceWithVariation(
770            long native_instance, List<FontConfig.Axis> axes);
771    private static native long nativeCreateWeightAlias(long native_instance, int weight);
772    private static native void nativeUnref(long native_instance);
773    private static native int  nativeGetStyle(long native_instance);
774    private static native long nativeCreateFromArray(long[] familyArray);
775    private static native void nativeSetDefault(long native_instance);
776}
777