blob: 1bdd1ccead0e199c6f4253080b065331cecb2b8a [file] [log] [blame]
/*
* Copyright (C) 2008 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 com.android.layoutlib.bridge;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
import android.graphics.Typeface;
import java.awt.Font;
import java.awt.FontFormatException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
/**
* Provides {@link Font} object to the layout lib.
* <p/>
* The fonts are loaded from the SDK directory. Family/style mapping is done by parsing the
* fonts.xml file located alongside the ttf files.
*/
public final class FontLoader {
private static final String FONTS_DEFINITIONS = "fonts.xml";
private static final String NODE_FONTS = "fonts";
private static final String NODE_FONT = "font";
private static final String NODE_NAME = "name";
private static final String ATTR_TTF = "ttf";
private static final String[] NODE_LEVEL = { NODE_FONTS, NODE_FONT, NODE_NAME };
private static final String FONT_EXT = ".ttf";
private static final String[] FONT_STYLE_DEFAULT = { "", "-Regular" };
private static final String[] FONT_STYLE_BOLD = { "-Bold" };
private static final String[] FONT_STYLE_ITALIC = { "-Italic" };
private static final String[] FONT_STYLE_BOLDITALIC = { "-BoldItalic" };
// list of font style, in the order matching the Typeface Font style
private static final String[][] FONT_STYLES = {
FONT_STYLE_DEFAULT,
FONT_STYLE_BOLD,
FONT_STYLE_ITALIC,
FONT_STYLE_BOLDITALIC
};
private final Map<String, String> mFamilyToTtf = new HashMap<String, String>();
private final Map<String, Map<Integer, Font>> mTtfToFontMap =
new HashMap<String, Map<Integer, Font>>();
public static FontLoader create(String fontOsLocation) {
try {
SAXParserFactory parserFactory = SAXParserFactory.newInstance();
parserFactory.setNamespaceAware(true);
SAXParser parser = parserFactory.newSAXParser();
File f = new File(fontOsLocation + File.separator + FONTS_DEFINITIONS);
FontDefinitionParser definitionParser = new FontDefinitionParser(
fontOsLocation + File.separator);
parser.parse(new FileInputStream(f), definitionParser);
return definitionParser.getFontLoader();
} catch (ParserConfigurationException e) {
// return null below
} catch (SAXException e) {
// return null below
} catch (FileNotFoundException e) {
// return null below
} catch (IOException e) {
// return null below
}
return null;
}
private FontLoader(List<FontInfo> fontList) {
for (FontInfo info : fontList) {
for (String family : info.families) {
mFamilyToTtf.put(family, info.ttf);
}
}
}
public synchronized Font getFont(String family, int[] style) {
if (family == null) {
return null;
}
// get the ttf name from the family
String ttf = mFamilyToTtf.get(family);
if (ttf == null) {
return null;
}
// get the font from the ttf
Map<Integer, Font> styleMap = mTtfToFontMap.get(ttf);
if (styleMap == null) {
styleMap = new HashMap<Integer, Font>();
mTtfToFontMap.put(ttf, styleMap);
}
Font f = styleMap.get(style);
if (f != null) {
return f;
}
// if it doesn't exist, we create it, and we can't, we try with a simpler style
switch (style[0]) {
case Typeface.NORMAL:
f = getFont(ttf, FONT_STYLES[Typeface.NORMAL]);
break;
case Typeface.BOLD:
case Typeface.ITALIC:
f = getFont(ttf, FONT_STYLES[style[0]]);
if (f == null) {
f = getFont(ttf, FONT_STYLES[Typeface.NORMAL]);
style[0] = Typeface.NORMAL;
}
break;
case Typeface.BOLD_ITALIC:
f = getFont(ttf, FONT_STYLES[style[0]]);
if (f == null) {
f = getFont(ttf, FONT_STYLES[Typeface.BOLD]);
if (f != null) {
style[0] = Typeface.BOLD;
} else {
f = getFont(ttf, FONT_STYLES[Typeface.ITALIC]);
if (f != null) {
style[0] = Typeface.ITALIC;
} else {
f = getFont(ttf, FONT_STYLES[Typeface.NORMAL]);
style[0] = Typeface.NORMAL;
}
}
}
break;
}
if (f != null) {
styleMap.put(style[0], f);
return f;
}
return null;
}
private Font getFont(String ttf, String[] fontFileSuffix) {
for (String suffix : fontFileSuffix) {
String name = ttf + suffix + FONT_EXT;
File f = new File(name);
if (f.isFile()) {
try {
Font font = Font.createFont(Font.TRUETYPE_FONT, f);
if (font != null) {
return font;
}
} catch (FontFormatException e) {
// skip this font name
} catch (IOException e) {
// skip this font name
}
}
}
return null;
}
private final static class FontInfo {
String ttf;
final Set<String> families;
FontInfo() {
families = new HashSet<String>();
}
}
private final static class FontDefinitionParser extends DefaultHandler {
private final String mOsFontsLocation;
private int mDepth = 0;
private FontInfo mFontInfo = null;
private final StringBuilder mBuilder = new StringBuilder();
private final List<FontInfo> mFontList = new ArrayList<FontInfo>();
private FontDefinitionParser(String osFontsLocation) {
super();
mOsFontsLocation = osFontsLocation;
}
FontLoader getFontLoader() {
return new FontLoader(mFontList);
}
/* (non-Javadoc)
* @see org.xml.sax.helpers.DefaultHandler#startElement(java.lang.String, java.lang.String, java.lang.String, org.xml.sax.Attributes)
*/
@Override
public void startElement(String uri, String localName, String name, Attributes attributes)
throws SAXException {
if (localName.equals(NODE_LEVEL[mDepth])) {
mDepth++;
if (mDepth == 2) { // font level.
String ttf = attributes.getValue(ATTR_TTF);
if (ttf != null) {
mFontInfo = new FontInfo();
mFontInfo.ttf = mOsFontsLocation + ttf;
mFontList.add(mFontInfo);
}
}
}
super.startElement(uri, localName, name, attributes);
}
/* (non-Javadoc)
* @see org.xml.sax.helpers.DefaultHandler#characters(char[], int, int)
*/
@SuppressWarnings("unused")
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
if (mFontInfo != null) {
mBuilder.append(ch, start, length);
}
}
/* (non-Javadoc)
* @see org.xml.sax.helpers.DefaultHandler#endElement(java.lang.String, java.lang.String, java.lang.String)
*/
@SuppressWarnings("unused")
@Override
public void endElement(String uri, String localName, String name) throws SAXException {
if (localName.equals(NODE_LEVEL[mDepth-1])) {
mDepth--;
if (mDepth == 2) { // end of a <name> node
if (mFontInfo != null) {
String family = trimXmlWhitespaces(mBuilder.toString());
mFontInfo.families.add(family);
mBuilder.setLength(0);
}
} else if (mDepth == 1) { // end of a <font> node
mFontInfo = null;
}
}
}
private String trimXmlWhitespaces(String value) {
if (value == null) {
return null;
}
// look for carriage return and replace all whitespace around it by just 1 space.
int index;
while ((index = value.indexOf('\n')) != -1) {
// look for whitespace on each side
int left = index - 1;
while (left >= 0) {
if (Character.isWhitespace(value.charAt(left))) {
left--;
} else {
break;
}
}
int right = index + 1;
int count = value.length();
while (right < count) {
if (Character.isWhitespace(value.charAt(right))) {
right++;
} else {
break;
}
}
// remove all between left and right (non inclusive) and replace by a single space.
String leftString = null;
if (left >= 0) {
leftString = value.substring(0, left + 1);
}
String rightString = null;
if (right < count) {
rightString = value.substring(right);
}
if (leftString != null) {
value = leftString;
if (rightString != null) {
value += " " + rightString;
}
} else {
value = rightString != null ? rightString : "";
}
}
// now we un-escape the string
int length = value.length();
char[] buffer = value.toCharArray();
for (int i = 0 ; i < length ; i++) {
if (buffer[i] == '\\') {
if (buffer[i+1] == 'n') {
// replace the char with \n
buffer[i+1] = '\n';
}
// offset the rest of the buffer since we go from 2 to 1 char
System.arraycopy(buffer, i+1, buffer, i, length - i - 1);
length--;
}
}
return new String(buffer, 0, length);
}
}
}