1491b729fa49127c75acb267b95cc5f0ec1b5f1e3Clara Bayarri/*
2491b729fa49127c75acb267b95cc5f0ec1b5f1e3Clara Bayarri * Copyright (C) 2017 The Android Open Source Project
3491b729fa49127c75acb267b95cc5f0ec1b5f1e3Clara Bayarri *
4491b729fa49127c75acb267b95cc5f0ec1b5f1e3Clara Bayarri * Licensed under the Apache License, Version 2.0 (the "License");
5491b729fa49127c75acb267b95cc5f0ec1b5f1e3Clara Bayarri * you may not use this file except in compliance with the License.
6491b729fa49127c75acb267b95cc5f0ec1b5f1e3Clara Bayarri * You may obtain a copy of the License at
7491b729fa49127c75acb267b95cc5f0ec1b5f1e3Clara Bayarri *
8491b729fa49127c75acb267b95cc5f0ec1b5f1e3Clara Bayarri *      http://www.apache.org/licenses/LICENSE-2.0
9491b729fa49127c75acb267b95cc5f0ec1b5f1e3Clara Bayarri *
10491b729fa49127c75acb267b95cc5f0ec1b5f1e3Clara Bayarri * Unless required by applicable law or agreed to in writing, software
11491b729fa49127c75acb267b95cc5f0ec1b5f1e3Clara Bayarri * distributed under the License is distributed on an "AS IS" BASIS,
12491b729fa49127c75acb267b95cc5f0ec1b5f1e3Clara Bayarri * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13491b729fa49127c75acb267b95cc5f0ec1b5f1e3Clara Bayarri * See the License for the specific language governing permissions and
14491b729fa49127c75acb267b95cc5f0ec1b5f1e3Clara Bayarri * limitations under the License.
15491b729fa49127c75acb267b95cc5f0ec1b5f1e3Clara Bayarri */
16491b729fa49127c75acb267b95cc5f0ec1b5f1e3Clara Bayarri
17ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikaspackage androidx.core.graphics;
18491b729fa49127c75acb267b95cc5f0ec1b5f1e3Clara Bayarri
19ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikasimport static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
20491b729fa49127c75acb267b95cc5f0ec1b5f1e3Clara Bayarri
21491b729fa49127c75acb267b95cc5f0ec1b5f1e3Clara Bayarriimport android.content.Context;
22543fd2946ded1593b28553879e74ca4393eddd2eClara Bayarriimport android.content.res.Resources;
23491b729fa49127c75acb267b95cc5f0ec1b5f1e3Clara Bayarriimport android.graphics.Typeface;
2408df2afeec46f363ef5f17146750bdf011412e56Seigo Nonakaimport android.os.CancellationSignal;
259dede51868bbbe16aadcd65e04860bea8ea50e05Aurimas Liutikas
26ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikasimport androidx.annotation.NonNull;
27ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikasimport androidx.annotation.Nullable;
28ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikasimport androidx.annotation.RestrictTo;
29ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikasimport androidx.core.content.res.FontResourcesParserCompat.FontFamilyFilesResourceEntry;
30ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikasimport androidx.core.content.res.FontResourcesParserCompat.FontFileResourceEntry;
31ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikasimport androidx.core.provider.FontsContractCompat.FontInfo;
32491b729fa49127c75acb267b95cc5f0ec1b5f1e3Clara Bayarri
33491b729fa49127c75acb267b95cc5f0ec1b5f1e3Clara Bayarriimport java.io.File;
3408df2afeec46f363ef5f17146750bdf011412e56Seigo Nonakaimport java.io.IOException;
3508df2afeec46f363ef5f17146750bdf011412e56Seigo Nonakaimport java.io.InputStream;
36491b729fa49127c75acb267b95cc5f0ec1b5f1e3Clara Bayarri
37491b729fa49127c75acb267b95cc5f0ec1b5f1e3Clara Bayarri/**
38491b729fa49127c75acb267b95cc5f0ec1b5f1e3Clara Bayarri * Implementation of the Typeface compat methods for API 14 and above.
39491b729fa49127c75acb267b95cc5f0ec1b5f1e3Clara Bayarri * @hide
40491b729fa49127c75acb267b95cc5f0ec1b5f1e3Clara Bayarri */
41491b729fa49127c75acb267b95cc5f0ec1b5f1e3Clara Bayarri@RestrictTo(LIBRARY_GROUP)
427ca26b25eae95dd45532cd092c9189b8bab625d3Jake Whartonclass TypefaceCompatBaseImpl {
43491b729fa49127c75acb267b95cc5f0ec1b5f1e3Clara Bayarri    private static final String TAG = "TypefaceCompatBaseImpl";
44543fd2946ded1593b28553879e74ca4393eddd2eClara Bayarri    private static final String CACHE_FILE_PREFIX = "cached_font_";
45491b729fa49127c75acb267b95cc5f0ec1b5f1e3Clara Bayarri
4608df2afeec46f363ef5f17146750bdf011412e56Seigo Nonaka    private interface StyleExtractor<T> {
4708df2afeec46f363ef5f17146750bdf011412e56Seigo Nonaka        int getWeight(T t);
4808df2afeec46f363ef5f17146750bdf011412e56Seigo Nonaka        boolean isItalic(T t);
4908df2afeec46f363ef5f17146750bdf011412e56Seigo Nonaka    }
5008df2afeec46f363ef5f17146750bdf011412e56Seigo Nonaka
5108df2afeec46f363ef5f17146750bdf011412e56Seigo Nonaka    private static <T> T findBestFont(T[] fonts, int style, StyleExtractor<T> extractor) {
5208df2afeec46f363ef5f17146750bdf011412e56Seigo Nonaka        final int targetWeight = (style & Typeface.BOLD) == 0 ? 400 : 700;
5308df2afeec46f363ef5f17146750bdf011412e56Seigo Nonaka        final boolean isTargetItalic = (style & Typeface.ITALIC) != 0;
5408df2afeec46f363ef5f17146750bdf011412e56Seigo Nonaka
5508df2afeec46f363ef5f17146750bdf011412e56Seigo Nonaka        T best = null;
5608df2afeec46f363ef5f17146750bdf011412e56Seigo Nonaka        int bestScore = Integer.MAX_VALUE;  // smaller is better
5708df2afeec46f363ef5f17146750bdf011412e56Seigo Nonaka
5808df2afeec46f363ef5f17146750bdf011412e56Seigo Nonaka        for (final T font : fonts) {
5908df2afeec46f363ef5f17146750bdf011412e56Seigo Nonaka            final int score = (Math.abs(extractor.getWeight(font) - targetWeight) * 2)
6008df2afeec46f363ef5f17146750bdf011412e56Seigo Nonaka                    + (extractor.isItalic(font) == isTargetItalic ? 0 : 1);
6108df2afeec46f363ef5f17146750bdf011412e56Seigo Nonaka
6208df2afeec46f363ef5f17146750bdf011412e56Seigo Nonaka            if (best == null || bestScore > score) {
6308df2afeec46f363ef5f17146750bdf011412e56Seigo Nonaka                best = font;
6408df2afeec46f363ef5f17146750bdf011412e56Seigo Nonaka                bestScore = score;
6508df2afeec46f363ef5f17146750bdf011412e56Seigo Nonaka            }
660b03693667d95d2202dfbb24866665ff061acce1Seigo Nonaka        }
6708df2afeec46f363ef5f17146750bdf011412e56Seigo Nonaka        return best;
6808df2afeec46f363ef5f17146750bdf011412e56Seigo Nonaka    }
6908df2afeec46f363ef5f17146750bdf011412e56Seigo Nonaka
7008df2afeec46f363ef5f17146750bdf011412e56Seigo Nonaka    protected FontInfo findBestInfo(FontInfo[] fonts, int style) {
7108df2afeec46f363ef5f17146750bdf011412e56Seigo Nonaka        return findBestFont(fonts, style, new StyleExtractor<FontInfo>() {
7208df2afeec46f363ef5f17146750bdf011412e56Seigo Nonaka            @Override
7308df2afeec46f363ef5f17146750bdf011412e56Seigo Nonaka            public int getWeight(FontInfo info) {
7408df2afeec46f363ef5f17146750bdf011412e56Seigo Nonaka                return info.getWeight();
7508df2afeec46f363ef5f17146750bdf011412e56Seigo Nonaka            }
7608df2afeec46f363ef5f17146750bdf011412e56Seigo Nonaka
7708df2afeec46f363ef5f17146750bdf011412e56Seigo Nonaka            @Override
7808df2afeec46f363ef5f17146750bdf011412e56Seigo Nonaka            public boolean isItalic(FontInfo info) {
7908df2afeec46f363ef5f17146750bdf011412e56Seigo Nonaka                return info.isItalic();
8008df2afeec46f363ef5f17146750bdf011412e56Seigo Nonaka            }
8108df2afeec46f363ef5f17146750bdf011412e56Seigo Nonaka        });
8208df2afeec46f363ef5f17146750bdf011412e56Seigo Nonaka    }
8308df2afeec46f363ef5f17146750bdf011412e56Seigo Nonaka
8408df2afeec46f363ef5f17146750bdf011412e56Seigo Nonaka    // Caller must close the stream.
8508df2afeec46f363ef5f17146750bdf011412e56Seigo Nonaka    protected Typeface createFromInputStream(Context context, InputStream is) {
862c4cbf16a9705a4fcb22a8de5cd8795745b01aa4Seigo Nonaka        final File tmpFile = TypefaceCompatUtil.getTempFile(context);
872c4cbf16a9705a4fcb22a8de5cd8795745b01aa4Seigo Nonaka        if (tmpFile == null) {
88491b729fa49127c75acb267b95cc5f0ec1b5f1e3Clara Bayarri            return null;
89491b729fa49127c75acb267b95cc5f0ec1b5f1e3Clara Bayarri        }
900b03693667d95d2202dfbb24866665ff061acce1Seigo Nonaka        try {
9108df2afeec46f363ef5f17146750bdf011412e56Seigo Nonaka            if (!TypefaceCompatUtil.copyToFile(tmpFile, is)) {
922c4cbf16a9705a4fcb22a8de5cd8795745b01aa4Seigo Nonaka                return null;
93491b729fa49127c75acb267b95cc5f0ec1b5f1e3Clara Bayarri            }
942c4cbf16a9705a4fcb22a8de5cd8795745b01aa4Seigo Nonaka            return Typeface.createFromFile(tmpFile.getPath());
952c4cbf16a9705a4fcb22a8de5cd8795745b01aa4Seigo Nonaka        } catch (RuntimeException e) {
962c4cbf16a9705a4fcb22a8de5cd8795745b01aa4Seigo Nonaka            // This was thrown from Typeface.createFromFile when a Typeface could not be loaded,
972c4cbf16a9705a4fcb22a8de5cd8795745b01aa4Seigo Nonaka            // such as due to an invalid ttf or unreadable file. We don't want to throw that
982c4cbf16a9705a4fcb22a8de5cd8795745b01aa4Seigo Nonaka            // exception anymore.
995099c05aeb731f6e4b592e0e7e282761de059cfdSeigo Nonaka            return null;
100f69ef36b9ff270c87e41177551ef4692f9aff965Seigo Nonaka        } finally {
1012c4cbf16a9705a4fcb22a8de5cd8795745b01aa4Seigo Nonaka            tmpFile.delete();
102543fd2946ded1593b28553879e74ca4393eddd2eClara Bayarri        }
103543fd2946ded1593b28553879e74ca4393eddd2eClara Bayarri    }
104543fd2946ded1593b28553879e74ca4393eddd2eClara Bayarri
10508df2afeec46f363ef5f17146750bdf011412e56Seigo Nonaka    public Typeface createFromFontInfo(Context context,
10608df2afeec46f363ef5f17146750bdf011412e56Seigo Nonaka            @Nullable CancellationSignal cancellationSignal, @NonNull FontInfo[] fonts, int style) {
10708df2afeec46f363ef5f17146750bdf011412e56Seigo Nonaka        // When we load from file, we can only load one font so just take the first one.
10808df2afeec46f363ef5f17146750bdf011412e56Seigo Nonaka        if (fonts.length < 1) {
10908df2afeec46f363ef5f17146750bdf011412e56Seigo Nonaka            return null;
11008df2afeec46f363ef5f17146750bdf011412e56Seigo Nonaka        }
11108df2afeec46f363ef5f17146750bdf011412e56Seigo Nonaka        FontInfo font = findBestInfo(fonts, style);
11208df2afeec46f363ef5f17146750bdf011412e56Seigo Nonaka        InputStream is = null;
11308df2afeec46f363ef5f17146750bdf011412e56Seigo Nonaka        try {
11408df2afeec46f363ef5f17146750bdf011412e56Seigo Nonaka            is = context.getContentResolver().openInputStream(font.getUri());
11508df2afeec46f363ef5f17146750bdf011412e56Seigo Nonaka            return createFromInputStream(context, is);
11608df2afeec46f363ef5f17146750bdf011412e56Seigo Nonaka        } catch (IOException e) {
11708df2afeec46f363ef5f17146750bdf011412e56Seigo Nonaka            return null;
11808df2afeec46f363ef5f17146750bdf011412e56Seigo Nonaka        } finally {
11908df2afeec46f363ef5f17146750bdf011412e56Seigo Nonaka            TypefaceCompatUtil.closeQuietly(is);
12008df2afeec46f363ef5f17146750bdf011412e56Seigo Nonaka        }
12108df2afeec46f363ef5f17146750bdf011412e56Seigo Nonaka    }
122f69ef36b9ff270c87e41177551ef4692f9aff965Seigo Nonaka
12308df2afeec46f363ef5f17146750bdf011412e56Seigo Nonaka    private FontFileResourceEntry findBestEntry(FontFamilyFilesResourceEntry entry, int style) {
12408df2afeec46f363ef5f17146750bdf011412e56Seigo Nonaka        return findBestFont(entry.getEntries(), style, new StyleExtractor<FontFileResourceEntry>() {
12508df2afeec46f363ef5f17146750bdf011412e56Seigo Nonaka            @Override
12608df2afeec46f363ef5f17146750bdf011412e56Seigo Nonaka            public int getWeight(FontFileResourceEntry entry) {
12708df2afeec46f363ef5f17146750bdf011412e56Seigo Nonaka                return entry.getWeight();
12808df2afeec46f363ef5f17146750bdf011412e56Seigo Nonaka            }
129f69ef36b9ff270c87e41177551ef4692f9aff965Seigo Nonaka
13008df2afeec46f363ef5f17146750bdf011412e56Seigo Nonaka            @Override
13108df2afeec46f363ef5f17146750bdf011412e56Seigo Nonaka            public boolean isItalic(FontFileResourceEntry entry) {
13208df2afeec46f363ef5f17146750bdf011412e56Seigo Nonaka                return entry.isItalic();
133f69ef36b9ff270c87e41177551ef4692f9aff965Seigo Nonaka            }
13408df2afeec46f363ef5f17146750bdf011412e56Seigo Nonaka        });
135f69ef36b9ff270c87e41177551ef4692f9aff965Seigo Nonaka    }
136f69ef36b9ff270c87e41177551ef4692f9aff965Seigo Nonaka
137543fd2946ded1593b28553879e74ca4393eddd2eClara Bayarri    @Nullable
138b2959d2604c82a2b73e81eac3e19f430e7ac4a1aSeigo Nonaka    public Typeface createFromFontFamilyFilesResourceEntry(Context context,
1392c4cbf16a9705a4fcb22a8de5cd8795745b01aa4Seigo Nonaka            FontFamilyFilesResourceEntry entry, Resources resources, int style) {
14008df2afeec46f363ef5f17146750bdf011412e56Seigo Nonaka        FontFileResourceEntry best = findBestEntry(entry, style);
141f69ef36b9ff270c87e41177551ef4692f9aff965Seigo Nonaka        if (best == null) {
142f69ef36b9ff270c87e41177551ef4692f9aff965Seigo Nonaka            return null;
143f69ef36b9ff270c87e41177551ef4692f9aff965Seigo Nonaka        }
1442c4cbf16a9705a4fcb22a8de5cd8795745b01aa4Seigo Nonaka        return TypefaceCompat.createFromResourcesFontFile(
14511e831738feba2bb6c5338358812a373bda9991aClara Bayarri                context, resources, best.getResourceId(), best.getFileName(), style);
14611e831738feba2bb6c5338358812a373bda9991aClara Bayarri    }
14711e831738feba2bb6c5338358812a373bda9991aClara Bayarri
14811e831738feba2bb6c5338358812a373bda9991aClara Bayarri    /**
14911e831738feba2bb6c5338358812a373bda9991aClara Bayarri     * Used by Resources to load a font resource of type font file.
15011e831738feba2bb6c5338358812a373bda9991aClara Bayarri     */
15111e831738feba2bb6c5338358812a373bda9991aClara Bayarri    @Nullable
15211e831738feba2bb6c5338358812a373bda9991aClara Bayarri    public Typeface createFromResourcesFontFile(
15311e831738feba2bb6c5338358812a373bda9991aClara Bayarri            Context context, Resources resources, int id, String path, int style) {
15411e831738feba2bb6c5338358812a373bda9991aClara Bayarri        final File tmpFile = TypefaceCompatUtil.getTempFile(context);
15511e831738feba2bb6c5338358812a373bda9991aClara Bayarri        if (tmpFile == null) {
15611e831738feba2bb6c5338358812a373bda9991aClara Bayarri            return null;
15711e831738feba2bb6c5338358812a373bda9991aClara Bayarri        }
15811e831738feba2bb6c5338358812a373bda9991aClara Bayarri        try {
15911e831738feba2bb6c5338358812a373bda9991aClara Bayarri            if (!TypefaceCompatUtil.copyToFile(tmpFile, resources, id)) {
16011e831738feba2bb6c5338358812a373bda9991aClara Bayarri                return null;
16111e831738feba2bb6c5338358812a373bda9991aClara Bayarri            }
16211e831738feba2bb6c5338358812a373bda9991aClara Bayarri            return Typeface.createFromFile(tmpFile.getPath());
16311e831738feba2bb6c5338358812a373bda9991aClara Bayarri        } catch (RuntimeException e) {
16411e831738feba2bb6c5338358812a373bda9991aClara Bayarri            // This was thrown from Typeface.createFromFile when a Typeface could not be loaded.
16511e831738feba2bb6c5338358812a373bda9991aClara Bayarri            // such as due to an invalid ttf or unreadable file. We don't want to throw that
16611e831738feba2bb6c5338358812a373bda9991aClara Bayarri            // exception anymore.
16711e831738feba2bb6c5338358812a373bda9991aClara Bayarri            return null;
16811e831738feba2bb6c5338358812a373bda9991aClara Bayarri        } finally {
16911e831738feba2bb6c5338358812a373bda9991aClara Bayarri            tmpFile.delete();
17011e831738feba2bb6c5338358812a373bda9991aClara Bayarri        }
171543fd2946ded1593b28553879e74ca4393eddd2eClara Bayarri    }
172491b729fa49127c75acb267b95cc5f0ec1b5f1e3Clara Bayarri}
173