/* * Copyright (C) 2015-2017 Emanuel Moecklin * * 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 com.onegravity.rteditor.fonts; import android.content.Context; import android.content.res.AssetManager; import android.content.res.Resources; import android.graphics.Typeface; import com.onegravity.rteditor.utils.io.FilenameUtils; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.Locale; import java.util.Map; import java.util.SortedSet; import java.util.TreeMap; /** * This class retrieves fonts from the assets and the Android system folders. */ public class FontManager { private static final String[] FONT_DIRS = {"/system/fonts", "/system/font", "/data/fonts"}; private static final Map<String, String> ASSET_FONTS_BY_NAME = new TreeMap<String, String>(); private static final Map<String, String> SYSTEM_FONTS_BY_PATH = new TreeMap<String, String>(); private static final Map<String, String> SYSTEM_FONTS_BY_NAME = new TreeMap<String, String>(); /* * Don't load the same font more than once -> cache them here. */ private static final RTTypefaceSet ALL_FONTS = new RTTypefaceSet() { /** * @return The RTTypeface with the specified name or false if no such RTTypeface exists. */ RTTypeface get(String fontName) { for (RTTypeface typeface : this) { if (typeface.getName().equals(fontName)) { return typeface; } } return null; } /** * @return True if the collections contains an RTTypeface with the specified name, false otherwise. */ boolean contains(String fontName) { return get(fontName) != null; } }; /** * Use this method to preload fonts asynchronously e.g. when the app starts up. */ public static void preLoadFonts(final Context context) { new Thread(new Runnable() { @Override public void run() { synchronized (ASSET_FONTS_BY_NAME) { getAssetFonts(context); } synchronized (SYSTEM_FONTS_BY_NAME) { getSystemFonts(); } } }).start(); } /** * Retrieve the fonts from the asset and the system folder. * * @return A Map mapping the name of the font to the Typeface. * If the name can't be retrieved the file name will be used (e.g. arial.ttf). */ public static SortedSet<RTTypeface> getFonts(Context context) { /* * Fonts from the assets folder */ Map<String, String> assetFonts = getAssetFonts(context); AssetManager assets = context.getResources().getAssets(); for (String fontName : assetFonts.keySet()) { String filePath = assetFonts.get(fontName); if (!ALL_FONTS.contains(fontName)) { try { Typeface typeface = Typeface.createFromAsset(assets, filePath); ALL_FONTS.add(new RTTypeface(fontName, typeface)); } catch (Exception e) { // this can happen if we don't have access to the font or it's not a font or... } } } /* * Fonts from the system */ Map<String, String> systemFonts = getSystemFonts(); for (String fontName : systemFonts.keySet()) { String filePath = systemFonts.get(fontName); if (!ALL_FONTS.contains(fontName)) { try { Typeface typeface = Typeface.createFromFile(filePath); ALL_FONTS.add(new RTTypeface(fontName, typeface)); } catch (Exception e) { // this can happen if we don't have access to the font or it's not a font or... } } } return ALL_FONTS; } /** * Returns the RTTypeface for a specific font identified by name. * This assumes that the fonts have already been loaded. * * @return The RTTypeface for a specific font name or Null of no such font exists. */ public static RTTypeface getTypeface(String fontName) { return ALL_FONTS.get(fontName); } /** * Retrieve the fonts from the asset folder. * * @return A Map mapping name of the font to the file path. * If the name can't be retrieved the file name will be used (e.g. arial.ttf). */ private static Map<String, String> getAssetFonts(Context context) { synchronized (ASSET_FONTS_BY_NAME) { /* * Let's do this only once because it's expensive and the result won't change in any case. */ if (ASSET_FONTS_BY_NAME.isEmpty()) { AssetManager assets = context.getResources().getAssets(); Collection<String> fontFiles = AssetIndex.getAssetIndex(context); if (fontFiles == null || fontFiles.isEmpty()) { fontFiles = listFontFiles(context.getResources()); } for (String filePath : fontFiles) { if (filePath.toLowerCase(Locale.getDefault()).endsWith("ttf")) { String fontName = TTFAnalyzer.getFontName(assets, filePath); if (fontName == null) { fontName = getFileName(filePath); } ASSET_FONTS_BY_NAME.put(fontName, filePath); } } } return ASSET_FONTS_BY_NAME; } } private static Collection<String> listFontFiles(Resources res) { Collection<String> fonts = new ArrayList<String>(); listFontFiles(res.getAssets(), fonts, ""); return fonts; } private static void listFontFiles(AssetManager assets, Collection<String> fonts, String path) { try { String[] list = assets.list(path); if (list != null && list.length > 0) { // it's a folder for (String file : list) { String prefix = "".equals(path) ? "" : path + File.separator; listFontFiles(assets, fonts, prefix + file); } } else if (path.endsWith("ttf")) { // it's a font file fonts.add(path); } } catch (IOException ignore) { } } /** * Retrieve the fonts from the system folders. * * @return A Map mapping name of the font to the file path. * If the name can't be retrieved the file name will be used (e.g. arial.ttf). */ private static Map<String, String> getSystemFonts() { synchronized (SYSTEM_FONTS_BY_NAME) { for (String fontDir : FONT_DIRS) { File dir = new File(fontDir); if (!dir.exists()) continue; File[] files = dir.listFiles(); if (files == null) continue; for (File file : files) { String filePath = file.getAbsolutePath(); if (!SYSTEM_FONTS_BY_PATH.containsKey(filePath)) { String fontName = TTFAnalyzer.getFontName(file.getAbsolutePath()); if (fontName == null) { fontName = getFileName(filePath); } SYSTEM_FONTS_BY_PATH.put(filePath, fontName); SYSTEM_FONTS_BY_NAME.put(fontName, filePath); } } } return SYSTEM_FONTS_BY_NAME; } } private static String getFileName(String path) { return FilenameUtils.getBaseName(path).replace(File.pathSeparator, ""); } }