| /* |
| * Copyright (C) 2017 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package androidx.core.graphics; |
| |
| import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX; |
| |
| import android.annotation.SuppressLint; |
| import android.content.Context; |
| import android.content.res.Resources; |
| import android.graphics.Typeface; |
| import android.os.CancellationSignal; |
| import android.util.Log; |
| |
| import androidx.annotation.NonNull; |
| import androidx.annotation.Nullable; |
| import androidx.annotation.RestrictTo; |
| import androidx.core.content.res.FontResourcesParserCompat.FontFamilyFilesResourceEntry; |
| import androidx.core.content.res.FontResourcesParserCompat.FontFileResourceEntry; |
| import androidx.core.provider.FontsContractCompat.FontInfo; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.lang.reflect.Field; |
| |
| /** |
| * Implementation of the Typeface compat methods for API 14 and above. |
| * @hide |
| */ |
| @RestrictTo(LIBRARY_GROUP_PREFIX) |
| class TypefaceCompatBaseImpl { |
| private static final String TAG = "TypefaceCompatBaseImpl"; |
| private static final int INVALID_KEY = 0; |
| |
| /** |
| * Maps a unique identifier from a Typeface to it's family |
| */ |
| @SuppressLint("BanConcurrentHashMap") |
| private java.util.concurrent.ConcurrentHashMap<Long, FontFamilyFilesResourceEntry> |
| mFontFamilies = new java.util.concurrent.ConcurrentHashMap<>(); |
| |
| private interface StyleExtractor<T> { |
| int getWeight(T t); |
| boolean isItalic(T t); |
| } |
| |
| private static <T> T findBestFont(T[] fonts, int style, StyleExtractor<T> extractor) { |
| final int targetWeight = (style & Typeface.BOLD) == 0 ? 400 : 700; |
| final boolean isTargetItalic = (style & Typeface.ITALIC) != 0; |
| |
| T best = null; |
| int bestScore = Integer.MAX_VALUE; // smaller is better |
| |
| for (final T font : fonts) { |
| final int score = (Math.abs(extractor.getWeight(font) - targetWeight) * 2) |
| + (extractor.isItalic(font) == isTargetItalic ? 0 : 1); |
| |
| if (best == null || bestScore > score) { |
| best = font; |
| bestScore = score; |
| } |
| } |
| return best; |
| } |
| |
| private static long getUniqueKey(@Nullable final Typeface typeface) { |
| if (typeface == null) { |
| return INVALID_KEY; |
| } |
| |
| try { |
| final Field field = Typeface.class.getDeclaredField("native_instance"); |
| field.setAccessible(true); |
| final Number num = (Number) field.get(typeface); |
| return num.longValue(); |
| } catch (NoSuchFieldException e) { |
| Log.e(TAG, "Could not retrieve font from family.", e); |
| return INVALID_KEY; |
| } catch (IllegalAccessException e) { |
| Log.e(TAG, "Could not retrieve font from family.", e); |
| return INVALID_KEY; |
| } |
| } |
| |
| protected FontInfo findBestInfo(FontInfo[] fonts, int style) { |
| return findBestFont(fonts, style, new StyleExtractor<FontInfo>() { |
| @Override |
| public int getWeight(FontInfo info) { |
| return info.getWeight(); |
| } |
| |
| @Override |
| public boolean isItalic(FontInfo info) { |
| return info.isItalic(); |
| } |
| }); |
| } |
| |
| // Caller must close the stream. |
| protected Typeface createFromInputStream(Context context, InputStream is) { |
| final File tmpFile = TypefaceCompatUtil.getTempFile(context); |
| if (tmpFile == null) { |
| return null; |
| } |
| try { |
| if (!TypefaceCompatUtil.copyToFile(tmpFile, is)) { |
| return null; |
| } |
| return Typeface.createFromFile(tmpFile.getPath()); |
| } catch (RuntimeException e) { |
| // This was thrown from Typeface.createFromFile when a Typeface could not be loaded, |
| // such as due to an invalid ttf or unreadable file. We don't want to throw that |
| // exception anymore. |
| return null; |
| } finally { |
| tmpFile.delete(); |
| } |
| } |
| |
| @Nullable |
| public Typeface createFromFontInfo(Context context, |
| @Nullable CancellationSignal cancellationSignal, @NonNull FontInfo[] fonts, int style) { |
| // When we load from file, we can only load one font so just take the first one. |
| if (fonts.length < 1) { |
| return null; |
| } |
| FontInfo font = findBestInfo(fonts, style); |
| InputStream is = null; |
| try { |
| is = context.getContentResolver().openInputStream(font.getUri()); |
| return createFromInputStream(context, is); |
| } catch (IOException e) { |
| return null; |
| } finally { |
| TypefaceCompatUtil.closeQuietly(is); |
| } |
| } |
| |
| private FontFileResourceEntry findBestEntry(FontFamilyFilesResourceEntry entry, int style) { |
| return findBestFont(entry.getEntries(), style, new StyleExtractor<FontFileResourceEntry>() { |
| @Override |
| public int getWeight(FontFileResourceEntry entry) { |
| return entry.getWeight(); |
| } |
| |
| @Override |
| public boolean isItalic(FontFileResourceEntry entry) { |
| return entry.isItalic(); |
| } |
| }); |
| } |
| |
| @Nullable |
| public Typeface createFromFontFamilyFilesResourceEntry(Context context, |
| FontFamilyFilesResourceEntry entry, Resources resources, int style) { |
| FontFileResourceEntry best = findBestEntry(entry, style); |
| if (best == null) { |
| return null; |
| } |
| final Typeface typeface = TypefaceCompat.createFromResourcesFontFile( |
| context, resources, best.getResourceId(), best.getFileName(), style); |
| |
| addFontFamily(typeface, entry); |
| |
| return typeface; |
| } |
| |
| /** |
| * Used by Resources to load a font resource of type font file. |
| */ |
| @Nullable |
| public Typeface createFromResourcesFontFile( |
| Context context, Resources resources, int id, String path, int style) { |
| final File tmpFile = TypefaceCompatUtil.getTempFile(context); |
| if (tmpFile == null) { |
| return null; |
| } |
| try { |
| if (!TypefaceCompatUtil.copyToFile(tmpFile, resources, id)) { |
| return null; |
| } |
| return Typeface.createFromFile(tmpFile.getPath()); |
| } catch (RuntimeException e) { |
| // This was thrown from Typeface.createFromFile when a Typeface could not be loaded. |
| // such as due to an invalid ttf or unreadable file. We don't want to throw that |
| // exception anymore. |
| return null; |
| } finally { |
| tmpFile.delete(); |
| } |
| } |
| |
| /** |
| * Retrieves the font family resource entries given a unique identifier for a Typeface |
| */ |
| @Nullable |
| FontFamilyFilesResourceEntry getFontFamily(final Typeface typeface) { |
| final long key = getUniqueKey(typeface); |
| if (key == INVALID_KEY) { |
| return null; |
| } |
| return mFontFamilies.get(key); |
| } |
| |
| private void addFontFamily(final Typeface typeface, final FontFamilyFilesResourceEntry entry) { |
| final long key = getUniqueKey(typeface); |
| if (key != INVALID_KEY) { |
| mFontFamilies.put(key, entry); |
| } |
| } |
| } |