package com.sage42.android.view.fonts;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import android.content.Context;
import android.content.res.Resources.NotFoundException;
import android.content.res.TypedArray;
import android.graphics.Typeface;
import android.util.AttributeSet;
import android.util.Log;
import com.sage42.android.view.R;
/**
* Copyright (C) 2013- Sage 42 App Sdn Bhd
*
* 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.
*
* @author Corey Scott (corey.scott@sage42.com)
*
*/
public class FontManager
{
private static final String TAG = FontManager.class.getSimpleName();
private final Map<String, Font> mFonts;
// singleton instance
private static FontManager mInstance;
protected FontManager()
{
// enforce singleton
super();
this.mFonts = new HashMap<String, Font>();
}
public static synchronized FontManager getInstance()
{
if (FontManager.mInstance == null)
{
FontManager.mInstance = new FontManager();
}
return FontManager.mInstance;
}
@SuppressWarnings("synthetic-access")
private Font addFont(final Context context, final String fontName, final Integer normalResId,
final Integer italicResId)
{
final String fontNameLower = fontName.toLowerCase(Locale.US);
final Font font = new Font();
// a list of font-family names supported.
font.families = new ArrayList<String>();
font.families.add(fontNameLower);
// a list of files specifying the different styles.
font.styles = new ArrayList<FontStyle>();
if (normalResId != null)
{
final FontStyle fontStyle = new FontStyle();
fontStyle.font = this.getFontFromRes(context, normalResId);
fontStyle.style = Typeface.NORMAL;
font.styles.add(fontStyle);
Log.d(FontManager.TAG, "Loaded (Normal): " + fontNameLower); //$NON-NLS-1$
}
if (italicResId != null)
{
final FontStyle fontStyle = new FontStyle();
fontStyle.font = this.getFontFromRes(context, italicResId);
fontStyle.style = Typeface.ITALIC;
font.styles.add(fontStyle);
Log.d(FontManager.TAG, "Loaded (Italic): " + fontNameLower); //$NON-NLS-1$
}
return font;
}
public Typeface get(final Context context, final String fontFamily, final int inStyle)
{
final String fontFamilyLower = fontFamily.toLowerCase(Locale.US);
if (!this.mFonts.containsKey(fontFamilyLower))
{
// attempt to load font JIT at runtime
this.loadFont(context, fontFamilyLower);
// return null, if load failed
if (!this.mFonts.containsKey(fontFamilyLower))
{
Log.e(FontManager.TAG, "Failed to load: " + fontFamilyLower); //$NON-NLS-1$
return null;
}
}
// default to normal style if not was supplied
final int style = (inStyle >= 0) ? inStyle : Typeface.NORMAL;
final Font font = this.mFonts.get(fontFamilyLower);
for (final FontStyle fontStyle : font.styles)
{
if (fontStyle.style == style)
{
return fontStyle.font;
}
}
return null;
}
private void loadFont(final Context context, final String fontFamily)
{
final String fontFamilyLower = fontFamily.toLowerCase(Locale.US);
RobotoTypes type = RobotoTypes.getByFamilyName(fontFamily);
if (type == null)
{
Log.e(FontManager.TAG, "Failed to load font, unknown fontFamily: " + fontFamilyLower); //$NON-NLS-1$
}
else
{
this.mFonts.put(fontFamilyLower,
this.addFont(context, type.getFontName(), type.getNormalResId(), type.getItalicResId()));
}
}
/**
* Extend this class and override this method for custom error handling.
*
* @param exception
*/
public void logError(final Exception exception)
{
Log.e(FontManager.TAG, exception.getMessage(), exception);
}
private Typeface getFontFromRes(final Context context, final int resource)
{
Typeface typeface = null;
InputStream inputStream = null;
try
{
inputStream = context.getResources().openRawResource(resource);
if (inputStream == null)
{
return null;
}
final String outPath = context.getCacheDir() + "/tmp" + System.currentTimeMillis() + ".raw"; //$NON-NLS-1$ //$NON-NLS-2$
final byte[] buffer = new byte[inputStream.available()];
final BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(outPath));
int length = 0;
while ((length = inputStream.read(buffer)) > 0)
{
bos.write(buffer, 0, length);
}
bos.close();
typeface = Typeface.createFromFile(outPath);
// clean up
new File(outPath).delete();
}
catch (final NotFoundException exception)
{
this.logError(exception);
}
catch (final IOException exception)
{
this.logError(exception);
}
finally
{
// clean up
if (inputStream != null)
{
try
{
inputStream.close();
}
catch (final IOException exception)
{
this.logError(exception);
}
}
}
return typeface;
}
public void addCustomFont(final Context context, final String fontFamily, final Integer normalFontRes,
final Integer italicFontRes)
{
final String fontFamilyLower = fontFamily.toLowerCase(Locale.US);
if (this.mFonts.containsKey(fontFamilyLower))
{
// not need to load it again
return;
}
this.mFonts.put(fontFamilyLower, this.addFont(context, fontFamilyLower, normalFontRes, italicFontRes));
}
public static Typeface extractTypeface(final Context context, final AttributeSet attrs)
{
// Fonts work as a combination of particular family and the style.
final TypedArray args = context.obtainStyledAttributes(attrs, R.styleable.fonts);
final String family = args.getString(R.styleable.fonts_fontFamily);
final int style = args.getInt(R.styleable.fonts_android_textStyle, -1);
args.recycle();
if (family == null)
{
return null;
}
// Set the typeface based on the family and the style combination.
return FontManager.getInstance().get(context, family, style);
}
private static class FontStyle
{
int style;
Typeface font;
}
private static class Font
{
// different font-family names that this Font will respond to.
List<String> families;
// different styles for this font.
List<FontStyle> styles;
}
}