/*####################################################### * * Maintained by Gregor Santner, 2018- * https://gsantner.net/ * * License: Apache 2.0 / Commercial * https://github.com/gsantner/opoc/#licensing * https://www.apache.org/licenses/LICENSE-2.0 * #########################################################*/ /* * Define element in Preferences-XML: <net.gsantner.opoc.preference.FontPreferenceCompat android:icon="@drawable/ic_title_black_24dp" android:defaultValue="@string/default_font_family" android:key="@string/pref_key__font_family" android:title="@string/pref_title__font_choice" /> */ package net.gsantner.opoc.preference; import android.annotation.TargetApi; import android.content.Context; import android.graphics.Paint; import android.graphics.Typeface; import android.os.Build; import android.os.Environment; import android.support.annotation.Nullable; import android.support.v4.content.ContextCompat; import android.support.v7.preference.ListPreference; import android.text.Spannable; import android.text.SpannableString; import android.text.TextPaint; import android.text.TextUtils; import android.text.style.MetricAffectingSpan; import android.text.style.RelativeSizeSpan; import android.util.AttributeSet; import java.io.File; import java.io.FilenameFilter; import java.util.ArrayList; import java.util.Arrays; import java.util.List; /** * A {@link ListPreference} that displays a list of fonts to select from * This list contains fonts that are bundled with android * <p> * Apply to TextView: * setTypeface(Typeface.create(settings.getFontFamilyAsString(), Typeface.NORMAL)); */ @SuppressWarnings({"unused", "SpellCheckingInspection", "WeakerAccess"}) public class FontPreferenceCompat extends ListPreference { public static File additionalyCheckedFolder = null; public static final FilenameFilter FONT_FILENAME_FILTER = (file, s) -> s.toLowerCase().endsWith(".ttf") || s.toLowerCase().endsWith(".otf"); private final static String ANDROID_ASSET_DIR = "/android_asset/"; private String _defaultValue; private String[] _fontNames = { "Roboto Regular", "Roboto Light", "Roboto Bold", "Roboto Medium", "Monospace", "Noto Serif", "Cutive Mono", "Roboto Condensed", "Roboto Thin", "Roboto Black", "Coming Soon", "Carrois Gothic", "Dancing Script" }; private String[] _fontValues = { "sans-serif-regular", "sans-serif-light", "sans-serif-bold", "sans-serif-medium", "monospace", "serif", "serif-monospace", "sans-serif-condensed", "sans-serif-thin", "sans-serif-black", "casual", "sans-serif-smallcaps", "cursive" }; public FontPreferenceCompat(Context context) { super(context); loadFonts(context, null); } public FontPreferenceCompat(Context context, AttributeSet attrs) { super(context, attrs); loadFonts(context, attrs); } @TargetApi(Build.VERSION_CODES.LOLLIPOP) public FontPreferenceCompat(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); loadFonts(context, attrs); } @TargetApi(Build.VERSION_CODES.LOLLIPOP) public FontPreferenceCompat(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); loadFonts(context, attrs); } private void loadFonts(Context context) { loadFonts(context, null); } private void loadFonts(Context context, @Nullable AttributeSet attrs) { _defaultValue = _fontValues[0]; if (attrs != null) { for (int i = 0; i < attrs.getAttributeCount(); i++) { String attrName = attrs.getAttributeName(i); String attrValue = attrs.getAttributeValue(i); if (attrName.equalsIgnoreCase("defaultValue")) { if (attrValue.startsWith("@")) { int resId = Integer.valueOf(attrValue.substring(1)); attrValue = getContext().getString(resId); } _defaultValue = attrValue; break; } } } for (File file : getAdditionalFonts()) { _fontNames = appendToArray(_fontNames, file.getName().replace(".ttf", "").replace(".TTF", "")); _fontValues = appendToArray(_fontValues, file.getAbsolutePath()); } Spannable[] fontText = new Spannable[_fontNames.length]; for (int i = 0; i < _fontNames.length; i++) { fontText[i] = new SpannableString(_fontNames[i] + "\n" + _fontValues[i]); fontText[i].setSpan(new TypefaceObjectSpan(typeface(getContext(), _fontValues[i], null)), 0, _fontNames[i].length(), 0); fontText[i].setSpan(new RelativeSizeSpan(0.7f), _fontNames[i].length() + 1, fontText[i].length(), 0); } setDefaultValue(_defaultValue); setEntries(fontText); setEntryValues(_fontValues); } public static Typeface typeface(Context context, String familyOrFilepath, Integer typefaceStyle) { if (typefaceStyle == null) { typefaceStyle = Typeface.NORMAL; } if (!familyOrFilepath.startsWith("/")) { return Typeface.create(familyOrFilepath, typefaceStyle); } else { try { if (familyOrFilepath.startsWith(ANDROID_ASSET_DIR)) { return Typeface.createFromAsset(context.getAssets(), familyOrFilepath.substring(ANDROID_ASSET_DIR.length())); } else { return Typeface.createFromFile(familyOrFilepath); } } catch (RuntimeException exception) { return typeface(context, "sans-serif-regular", typefaceStyle); } } } @Override public CharSequence getSummary() { String prefix = TextUtils.isEmpty(super.getSummary()) ? "" : super.getSummary() + "\n\n"; String fontText = TextUtils.isEmpty(getValue()) ? _defaultValue : getValue(); for (int i = 0; i < _fontValues.length; i++) { if (_fontValues[i].equals(fontText)) { fontText = _fontNames[i] + " (" + fontText + ")"; break; } } fontText = fontText.replace("★", ""); return prefix + fontText; } public String[] getFontNames() { return _fontNames; } public void setFontNames(String[] fontNames) { _fontNames = fontNames; } public String[] getFontValues() { return _fontValues; } public void setFontValues(String[] fontValues) { _fontValues = fontValues; } @SuppressWarnings("ResultOfMethodCallIgnored") public List<File> getAdditionalFonts() { final ArrayList<File> additionalFonts = new ArrayList<>(); // Bundled fonts try { //noinspection ConstantConditions for (String filename : getContext().getAssets().list("fonts")) { additionalFonts.add(new File(ANDROID_ASSET_DIR + "fonts", filename)); } } catch (Exception ignored) { } // Directories that are additionally checked out for fonts final List<File> checkedDirs = new ArrayList<>(Arrays.asList( new File(getContext().getFilesDir(), ".app/fonts"), new File(getContext().getFilesDir(), ".app/Fonts"), additionalyCheckedFolder, new File(Environment.getExternalStorageDirectory(), "fonts"), new File(Environment.getExternalStorageDirectory(), "Fonts") )); // Also check external storage directories, at the respective root and data directory for (File externalFileDir : ContextCompat.getExternalFilesDirs(getContext(), null)) { if (externalFileDir == null || externalFileDir.getAbsolutePath() == null) { continue; } checkedDirs.add(new File(externalFileDir.getAbsolutePath().replaceFirst("/Android/data/.*$", "/fonts"))); checkedDirs.add(new File(externalFileDir.getAbsolutePath().replaceFirst("/Android/data/.*$", "/Fonts"))); checkedDirs.add(new File(externalFileDir.getAbsolutePath(), "/fonts")); checkedDirs.add(new File(externalFileDir.getAbsolutePath(), "/Fonts")); } // Check all directories for fonts for (File checkedDir : checkedDirs) { if (checkedDir != null && checkedDir.exists()) { File[] checkedDirFiles = checkedDir.listFiles(FONT_FILENAME_FILTER); if (checkedDirFiles != null) { for (File font : checkedDirFiles) { if (!additionalFonts.contains(new File(font.getAbsolutePath().replace("/Fonts/", "/fonts/")))) { additionalFonts.add(font); } } } } } return additionalFonts; } private static String[] appendToArray(String[] arr, String append) { List<String> arro = new ArrayList<>(Arrays.asList(arr)); arro.add(append); return arro.toArray(new String[arr.length + 1]); } public class TypefaceObjectSpan extends MetricAffectingSpan { private final Typeface _typeface; public TypefaceObjectSpan(final Typeface typeface) { _typeface = typeface; } @Override public void updateDrawState(final TextPaint drawState) { apply(drawState); } @Override public void updateMeasureState(final TextPaint paint) { apply(paint); } private void apply(final Paint paint) { final Typeface oldTypeface = paint.getTypeface(); final int oldStyle = oldTypeface != null ? oldTypeface.getStyle() : 0; final int fakeStyle = oldStyle & ~_typeface.getStyle(); if ((fakeStyle & Typeface.BOLD) != 0) { paint.setFakeBoldText(true); } if ((fakeStyle & Typeface.ITALIC) != 0) { paint.setTextSkewX(-0.25f); } paint.setTypeface(_typeface); } } }