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