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