package org.holoeverywhere; import android.content.Context; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.graphics.Typeface; import android.os.Build; import android.view.View; import android.view.ViewGroup; import java.io.File; import java.io.FileOutputStream; import java.io.InputStream; import java.io.OutputStream; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; public class FontLoader { public static final FontCollector ROBOTO; public static final Font ROBOTO_BLACK; public static final Font ROBOTO_BLACKITALIC; public static final Font ROBOTO_BOLD; public static final Font ROBOTO_BOLDCONDENSED; public static final Font ROBOTO_BOLDCONDENSEDITALIC; public static final Font ROBOTO_BOLDITALIC; public static final Font ROBOTO_CONDENSED; public static final Font ROBOTO_CONDENSEDITALIC; public static final Font ROBOTO_ITALIC; public static final Font ROBOTO_LIGHT; public static final Font ROBOTO_LIGHTITALIC; public static final Font ROBOTO_MEDIUM; public static final Font ROBOTO_MEDIUMITALIC; public static final Font ROBOTO_REGULAR; public static final Font ROBOTO_THIN; public static final Font ROBOTO_THINITALIC; public static final int TEXT_STYLE_BLACK; public static final int TEXT_STYLE_BOLD; public static final int TEXT_STYLE_CONDENDSED; public static final int TEXT_STYLE_ITALIC; public static final int TEXT_STYLE_LIGHT; public static final int TEXT_STYLE_MEDIUM; public static final int TEXT_STYLE_NORMAL; public static final int TEXT_STYLE_THIN; static { sFontStyleMapping = new HashMap<String, Integer>(); TEXT_STYLE_NORMAL = 0; TEXT_STYLE_BOLD = registerTextStyle("bold"); TEXT_STYLE_ITALIC = registerTextStyle("italic"); TEXT_STYLE_BLACK = registerTextStyle("black"); TEXT_STYLE_CONDENDSED = registerTextStyle("condensed"); TEXT_STYLE_LIGHT = registerTextStyle("light"); TEXT_STYLE_MEDIUM = registerTextStyle("medium"); TEXT_STYLE_THIN = registerTextStyle("thin"); ROBOTO_REGULAR = new RobotoRawFont(R.raw.roboto_regular) .setFontStyle(TEXT_STYLE_NORMAL); ROBOTO_BOLD = new RobotoRawFont(R.raw.roboto_bold) .setFontStyle(TEXT_STYLE_BOLD); ROBOTO_ITALIC = new RobotoRawFont(R.raw.roboto_italic) .setFontStyle(TEXT_STYLE_ITALIC); ROBOTO_BOLDITALIC = new RobotoRawFont(R.raw.roboto_bolditalic) .setFontStyle(TEXT_STYLE_BOLD | TEXT_STYLE_ITALIC); ROBOTO_CONDENSED = new RobotoRawFont(R.raw.roboto_condensed) .setFontStyle(TEXT_STYLE_CONDENDSED); ROBOTO_BLACK = new RobotoRawLazyFont("roboto_black") .setFontStyle(TEXT_STYLE_BLACK); ROBOTO_BLACKITALIC = new RobotoRawLazyFont("roboto_blackitalic") .setFontStyle(TEXT_STYLE_BLACK | TEXT_STYLE_ITALIC); ROBOTO_BOLDCONDENSED = new RobotoRawLazyFont("roboto_boldcondensed") .setFontStyle(TEXT_STYLE_BOLD | TEXT_STYLE_CONDENDSED); ROBOTO_BOLDCONDENSEDITALIC = new RobotoRawLazyFont("roboto_boldcondenseditalic") .setFontStyle(TEXT_STYLE_BOLD | TEXT_STYLE_CONDENDSED | TEXT_STYLE_ITALIC); ROBOTO_CONDENSEDITALIC = new RobotoRawLazyFont("roboto_condenseditalic") .setFontStyle(TEXT_STYLE_CONDENDSED | TEXT_STYLE_ITALIC); ROBOTO_LIGHT = new RobotoRawLazyFont("roboto_light") .setFontStyle(TEXT_STYLE_LIGHT); ROBOTO_LIGHTITALIC = new RobotoRawLazyFont("roboto_lightitalic") .setFontStyle(TEXT_STYLE_LIGHT | TEXT_STYLE_ITALIC); ROBOTO_MEDIUM = new RobotoRawLazyFont("roboto_medium") .setFontStyle(TEXT_STYLE_MEDIUM); ROBOTO_MEDIUMITALIC = new RobotoRawLazyFont("roboto_mediumitalic") .setFontStyle(TEXT_STYLE_MEDIUM | TEXT_STYLE_ITALIC); ROBOTO_THIN = new RobotoRawLazyFont("roboto_thin") .setFontStyle(TEXT_STYLE_THIN); ROBOTO_THINITALIC = new RobotoRawLazyFont("roboto_thinitalic") .setFontStyle(TEXT_STYLE_THIN | TEXT_STYLE_ITALIC); sDefaultFont = ROBOTO = new FontCollector().allowAnyFontFamily(); ROBOTO.register(ROBOTO_REGULAR).asDefaultFont(); ROBOTO.register(ROBOTO_BOLD); ROBOTO.register(ROBOTO_ITALIC); ROBOTO.register(ROBOTO_BOLDITALIC); ROBOTO.register(ROBOTO_BLACK); ROBOTO.register(ROBOTO_BLACKITALIC); ROBOTO.register(ROBOTO_BOLDCONDENSED); ROBOTO.register(ROBOTO_BOLDCONDENSEDITALIC); ROBOTO.register(ROBOTO_CONDENSED); ROBOTO.register(ROBOTO_CONDENSEDITALIC); ROBOTO.register(ROBOTO_LIGHT); ROBOTO.register(ROBOTO_LIGHTITALIC); ROBOTO.register(ROBOTO_MEDIUM); ROBOTO.register(ROBOTO_MEDIUMITALIC); ROBOTO.register(ROBOTO_THIN); ROBOTO.register(ROBOTO_THINITALIC); } private static final Map<String, Integer> sFontStyleMapping; private static Font sDefaultFont; private static List<String> sFontStyleKeys; private static int sNextTextStyleOffset = 0; private FontLoader() { } public static <T extends View> T apply(T view, Font font) { if (view == null || font == null || view.isInEditMode()) { return view; } font.mContext = view.getContext(); applyInternal(view, font); font.mContext = null; return view; } public static <T extends View> T applyDefaultFont(T view) { return apply(view, sDefaultFont); } private static void applyInternal(View view, Font font) { if (view instanceof ViewGroup) { final ViewGroup vg = (ViewGroup) view; final int childCount = vg.getChildCount(); for (int i = 0; i < childCount; i++) { applyInternal(vg.getChildAt(i), font); } } if (view instanceof FontStyleProvider) { final FontStyleProvider provider = (FontStyleProvider) view; final int fontStyle = provider.getFontStyle(); final String fontFamily = provider.getFontFamily(); if (view.getTag(R.id.fontLoaderFont) == font && equals(view.getTag(R.id.fontLoaderFontStyle), fontStyle) && equals(view.getTag(R.id.fontLoaderFontFamily), fontFamily)) { return; } provider.setTypeface(font.getTypeface(fontFamily, fontStyle)); view.setTag(R.id.fontLoaderFont, font); view.setTag(R.id.fontLoaderFontStyle, fontStyle); view.setTag(R.id.fontLoaderFontFamily, fontFamily); } } private static boolean equals(Object o1, Object o2) { return o1 == null ? o2 == null : o1.equals(o2); } public static Font getDefaultFont() { return sDefaultFont; } public static void setDefaultFont(Font defaultFont) { sDefaultFont = defaultFont; } public static Object[] parseFontStyle(String string) { String fontFamily = null; int c = string.lastIndexOf('-'); if (c > 0) { fontFamily = string.substring(0, c).toLowerCase(Locale.ENGLISH); string = string.substring(c + 1); } if (sFontStyleKeys == null) { sFontStyleKeys = new ArrayList<String>(sFontStyleMapping.keySet()); } int textStyle = TEXT_STYLE_NORMAL; for (int i = 0; i < sFontStyleKeys.size(); i++) { final String key = sFontStyleKeys.get(i); if (string.contains(key)) { textStyle |= sFontStyleMapping.get(key); } } return new Object[]{ textStyle, fontFamily == null && textStyle == TEXT_STYLE_NORMAL ? string : fontFamily }; } public static int registerTextStyle(String modifier) { if (sNextTextStyleOffset >= 32) { throw new IllegalStateException("Too much text styles!"); } final int flag = 1 << sNextTextStyleOffset++; sFontStyleMapping.put(modifier.toLowerCase(Locale.ENGLISH), flag); sFontStyleKeys = null; return flag; } public static interface FontStyleProvider { public String getFontFamily(); public int getFontStyle(); public void setFontStyle(String fontFamily, int fontStyle); public void setTypeface(Typeface typeface); } public static class Font implements Cloneable { private Context mContext; private String mFontFamily; private int mFontStyle; private boolean mLockModifing = false; private Typeface mTypeface; private boolean mTypefaceLoaded = false; public Font() { } public Font(Font font) { mContext = font.mContext; mFontStyle = font.mFontStyle; mTypeface = font.mTypeface; mTypefaceLoaded = font.mTypefaceLoaded; mFontFamily = font.mFontFamily; } protected final void assertContext() { if (mContext == null) { throw new IllegalStateException( "Cannot load typeface without attaching font instance to FontLoader"); } } protected final void assertModifing() { if (mLockModifing) { throw new IllegalStateException( "Cannot modify typeface after attaching to FontCollector"); } } public boolean available(Context context, String fontFamily, int fontStyle) { return mFontFamily == null ? fontFamily == null : mFontFamily.equals(fontFamily) && mFontStyle == fontStyle; } @Override public Font clone() { return new Font(this); } public final Context getContext() { return mContext; } public String getFontFamily() { return mFontFamily; } public Font setFontFamily(String fontFamily) { assertModifing(); mFontFamily = fontFamily; return this; } public int getFontStyle() { return mFontStyle; } public Font setFontStyle(int fontStyle) { mFontStyle = fontStyle; return this; } protected Typeface getTypeface(String fontFamily, int fontStyle) { if (!mTypefaceLoaded) { mTypeface = loadTypeface(); mTypefaceLoaded = true; } return mTypeface; } public Typeface getTypeface(Context context) { if (!mTypefaceLoaded) { mContext = context; mTypeface = loadTypeface(); mContext = null; mTypefaceLoaded = true; } return mTypeface; } public Typeface loadTypeface() { return null; } public void lock() { mLockModifing = true; } protected final void resetTypeface() { mTypeface = null; mTypefaceLoaded = false; } } public static class FontCollector extends Font { private static final String DEFAULT_FONT_FAMILY = "FONT-FAMILY-DEFAULT"; private final List<Font> mFonts; private boolean mAllowAnyFontFamily; private Font mDefaultFont; private Font mLastUsedFont; public FontCollector() { mFonts = new ArrayList<Font>(); } public FontCollector(Font font) { super(font); if (font instanceof FontCollector) { FontCollector fontCollector = (FontCollector) font; mFonts = new ArrayList<Font>(fontCollector.mFonts); mAllowAnyFontFamily = fontCollector.mAllowAnyFontFamily; if (fontCollector.mDefaultFont != null) { mDefaultFont = fontCollector.mDefaultFont.clone(); } } else { mFonts = new ArrayList<Font>(); } } public FontCollector allowAnyFontFamily() { mAllowAnyFontFamily = true; return this; } public FontCollector asDefaultFont() { mDefaultFont = mLastUsedFont; return this; } @Override public FontCollector clone() { return new FontCollector(this); } public Font getDefaultFont() { return mDefaultFont; } public FontCollector setDefaultFont(Font defaultFont) { mDefaultFont = defaultFont; if (defaultFont != null) { setFontFamily(defaultFont.getFontFamily()); setFontStyle(defaultFont.getFontStyle()); } return this; } private Typeface getTypeface(Font font, String fontFamily, int fontStyle) { font.mContext = getContext(); final Typeface typeface = font.getTypeface(fontFamily, fontStyle); font.mContext = null; return typeface; } @Override public boolean available(Context context, String fontFamily, int fontStyle) { final Font font = findFont(fontFamily, fontStyle); return font != null && font.available(context, fontFamily, fontStyle); } private Font findFont(String fontFamily, int fontStyle) { if (fontFamily == null) { fontFamily = DEFAULT_FONT_FAMILY; } for (int i = 0; i < mFonts.size(); i++) { Font font = mFonts.get(i); if ((mAllowAnyFontFamily || fontFamily.equals(font.mFontFamily)) && font.mFontStyle == fontStyle) { return font; } } if (mDefaultFont != null) { mDefaultFont.mContext = getContext(); return mDefaultFont; } return null; } @Override protected Typeface getTypeface(String fontFamily, int fontStyle) { final Font font = findFont(fontFamily, fontStyle); return font != null ? getTypeface(font, fontFamily, fontStyle) : null; } public FontCollector register(Font font) { if (font == null) { return this; } font.lock(); mFonts.add(font); mLastUsedFont = font; return this; } public FontCollector unregister(Font font) { mFonts.remove(font); return this; } public FontCollector unregister(String fontFamily, int fontStyle) { for (int i = 0; i < mFonts.size(); i++) { final Font font = mFonts.get(i); if (FontLoader.equals(fontFamily, font.mFontFamily) && font.mFontStyle == fontStyle) { mFonts.remove(font); return this; } } return this; } } public static class RawFont extends Font { private static long sApplicationInstallDate = -1; private int mRawResourceId; public RawFont(Font font) { super(font); if (font instanceof RawFont) { mRawResourceId = ((RawFont) font).mRawResourceId; } } public RawFont(int rawResourceId) { mRawResourceId = rawResourceId; } @Override public boolean available(Context context, String fontFamily, int fontStyle) { boolean result = false; try { final InputStream is = context.getResources().openRawResource(mRawResourceId); is.close(); result = true; } catch (Exception e) { } return result && super.available(context, fontFamily, fontStyle); } @Override public RawFont clone() { return new RawFont(this); } @Override public Typeface loadTypeface() { assertContext(); return loadRawTypeface(); } protected Typeface loadRawTypeface() { final File fontFile = new File(getContext().getCacheDir(), "font_0x" + Integer.toHexString(mRawResourceId)); if (fontFile.exists()) { if (sApplicationInstallDate == -1) { try { final Context context = getContext(); final PackageInfo ai = context.getPackageManager().getPackageInfo(context.getPackageName(), 0); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) { sApplicationInstallDate = Math.max(ai.lastUpdateTime, ai.firstInstallTime); } else { sApplicationInstallDate = new File(ai.applicationInfo.sourceDir).lastModified(); } } catch (PackageManager.NameNotFoundException e) { e.printStackTrace(); sApplicationInstallDate = System.currentTimeMillis(); } } if (fontFile.lastModified() < sApplicationInstallDate) { fontFile.delete(); return loadTypeface(fontFile, false); } return loadTypeface(fontFile, true); } return loadTypeface(fontFile, false); } private Typeface loadTypeface(File file, boolean allowFileReusage) { if (file.exists() && allowFileReusage) { try { return tryToLoadRawTypeface(file); } catch (Exception e) { e.printStackTrace(); } } try { InputStream is = getContext().getResources().openRawResource(mRawResourceId); OutputStream os = new FileOutputStream(file); byte[] buffer = new byte[1024]; int c; while ((c = is.read(buffer)) > 0) { os.write(buffer, 0, c); } os.flush(); os.close(); is.close(); return tryToLoadRawTypeface(file); } catch (Exception e) { e.printStackTrace(); return null; } } private Typeface tryToLoadRawTypeface(File file) throws Exception { Typeface typeface = Typeface.createFromFile(file); if (typeface == null) { throw new NullPointerException(); } return typeface; } public void setRawResourceId(int rawResourceId) { mRawResourceId = rawResourceId; resetTypeface(); } } public static class RawLazyFont extends RawFont { private String mRawResourceName; public RawLazyFont(Font font) { super(font); if (font instanceof RawLazyFont) { mRawResourceName = ((RawLazyFont) font).mRawResourceName; } } public RawLazyFont(String rawResourceName) { super(0); mRawResourceName = rawResourceName; } @Override public boolean available(Context context, String fontFamily, int fontStyle) { boolean result = false; try { setRawResourceId(context); result = true; } catch (Exception e) { } return result && super.available(context, fontFamily, fontStyle); } @Override public RawLazyFont clone() { return new RawLazyFont(this); } @Override public Typeface loadTypeface() { assertContext(); setRawResourceId(getContext()); return loadRawTypeface(); } private void setRawResourceId(Context context) { final int id = context.getResources().getIdentifier(mRawResourceName, "raw", context.getPackageName()); if (id == 0) { throw new IllegalStateException("Could not find font in raw resources: " + mRawResourceName); } setRawResourceId(id); } } private static final class RobotoRawFont extends RawFont { public RobotoRawFont(int rawResourceId) { super(rawResourceId); setFontFamily("roboto"); } } private static final class RobotoRawLazyFont extends RawLazyFont { public RobotoRawLazyFont(String rawResourceName) { super(rawResourceName); setFontFamily("roboto"); } } }