| /* |
| * 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); |
| } |
| |
| } |
| } |