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