2 * Copyright (C) 2008 The Android Open Source Project
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package com.android.layoutlib.bridge;
19 import org.xml.sax.Attributes;
20 import org.xml.sax.SAXException;
21 import org.xml.sax.helpers.DefaultHandler;
23 import android.graphics.Typeface;
26 import java.awt.FontFormatException;
28 import java.io.FileInputStream;
29 import java.io.FileNotFoundException;
30 import java.io.IOException;
31 import java.util.ArrayList;
32 import java.util.Collections;
33 import java.util.HashMap;
34 import java.util.HashSet;
35 import java.util.List;
39 import javax.xml.parsers.ParserConfigurationException;
40 import javax.xml.parsers.SAXParser;
41 import javax.xml.parsers.SAXParserFactory;
44 * Provides {@link Font} object to the layout lib.
46 * The fonts are loaded from the SDK directory. Family/style mapping is done by parsing the
47 * fonts.xml file located alongside the ttf files.
49 public final class FontLoader {
50 private static final String FONTS_DEFINITIONS = "fonts.xml";
52 private static final String NODE_FONTS = "fonts";
53 private static final String NODE_FONT = "font";
54 private static final String NODE_NAME = "name";
55 private static final String NODE_FALLBACK = "fallback";
57 private static final String ATTR_TTF = "ttf";
59 private static final String FONT_EXT = ".ttf";
61 private static final String[] FONT_STYLE_DEFAULT = { "", "-Regular" };
62 private static final String[] FONT_STYLE_BOLD = { "-Bold" };
63 private static final String[] FONT_STYLE_ITALIC = { "-Italic" };
64 private static final String[] FONT_STYLE_BOLDITALIC = { "-BoldItalic" };
66 // list of font style, in the order matching the Typeface Font style
67 private static final String[][] FONT_STYLES = {
74 private final Map<String, String> mFamilyToTtf = new HashMap<String, String>();
75 private final Map<String, Map<Integer, Font>> mTtfToFontMap =
76 new HashMap<String, Map<Integer, Font>>();
78 private List<Font> mFallBackFonts = null;
80 public static FontLoader create(String fontOsLocation) {
82 SAXParserFactory parserFactory = SAXParserFactory.newInstance();
83 parserFactory.setNamespaceAware(true);
85 SAXParser parser = parserFactory.newSAXParser();
86 File f = new File(fontOsLocation + File.separator + FONTS_DEFINITIONS);
88 FontDefinitionParser definitionParser = new FontDefinitionParser(
89 fontOsLocation + File.separator);
90 parser.parse(new FileInputStream(f), definitionParser);
92 return definitionParser.getFontLoader();
93 } catch (ParserConfigurationException e) {
95 } catch (SAXException e) {
97 } catch (FileNotFoundException e) {
99 } catch (IOException e) {
106 private FontLoader(List<FontInfo> fontList, List<String> fallBackList) {
107 for (FontInfo info : fontList) {
108 for (String family : info.families) {
109 mFamilyToTtf.put(family, info.ttf);
113 ArrayList<Font> list = new ArrayList<Font>();
114 for (String path : fallBackList) {
115 File f = new File(path + FONT_EXT);
118 Font font = Font.createFont(Font.TRUETYPE_FONT, f);
122 } catch (FontFormatException e) {
123 // skip this font name
124 } catch (IOException e) {
125 // skip this font name
130 mFallBackFonts = Collections.unmodifiableList(list);
133 public List<Font> getFallBackFonts() {
134 return mFallBackFonts;
137 public synchronized Font getFont(String family, int[] style) {
138 if (family == null) {
142 // get the ttf name from the family
143 String ttf = mFamilyToTtf.get(family);
149 // get the font from the ttf
150 Map<Integer, Font> styleMap = mTtfToFontMap.get(ttf);
152 if (styleMap == null) {
153 styleMap = new HashMap<Integer, Font>();
154 mTtfToFontMap.put(ttf, styleMap);
157 Font f = styleMap.get(style);
163 // if it doesn't exist, we create it, and we can't, we try with a simpler style
165 case Typeface.NORMAL:
166 f = getFont(ttf, FONT_STYLES[Typeface.NORMAL]);
169 case Typeface.ITALIC:
170 f = getFont(ttf, FONT_STYLES[style[0]]);
172 f = getFont(ttf, FONT_STYLES[Typeface.NORMAL]);
173 style[0] = Typeface.NORMAL;
176 case Typeface.BOLD_ITALIC:
177 f = getFont(ttf, FONT_STYLES[style[0]]);
179 f = getFont(ttf, FONT_STYLES[Typeface.BOLD]);
181 style[0] = Typeface.BOLD;
183 f = getFont(ttf, FONT_STYLES[Typeface.ITALIC]);
185 style[0] = Typeface.ITALIC;
187 f = getFont(ttf, FONT_STYLES[Typeface.NORMAL]);
188 style[0] = Typeface.NORMAL;
196 styleMap.put(style[0], f);
203 private Font getFont(String ttf, String[] fontFileSuffix) {
204 for (String suffix : fontFileSuffix) {
205 String name = ttf + suffix + FONT_EXT;
207 File f = new File(name);
210 Font font = Font.createFont(Font.TRUETYPE_FONT, f);
214 } catch (FontFormatException e) {
215 // skip this font name
216 } catch (IOException e) {
217 // skip this font name
225 private final static class FontInfo {
227 final Set<String> families;
230 families = new HashSet<String>();
234 private final static class FontDefinitionParser extends DefaultHandler {
235 private final String mOsFontsLocation;
237 private FontInfo mFontInfo = null;
238 private final StringBuilder mBuilder = new StringBuilder();
239 private List<FontInfo> mFontList;
240 private List<String> mFallBackList;
242 private FontDefinitionParser(String osFontsLocation) {
244 mOsFontsLocation = osFontsLocation;
247 FontLoader getFontLoader() {
248 return new FontLoader(mFontList, mFallBackList);
252 * @see org.xml.sax.helpers.DefaultHandler#startElement(java.lang.String, java.lang.String, java.lang.String, org.xml.sax.Attributes)
255 public void startElement(String uri, String localName, String name, Attributes attributes)
256 throws SAXException {
257 if (NODE_FONTS.equals(localName)) {
258 mFontList = new ArrayList<FontInfo>();
259 mFallBackList = new ArrayList<String>();
260 } else if (NODE_FONT.equals(localName)) {
261 if (mFontList != null) {
262 String ttf = attributes.getValue(ATTR_TTF);
264 mFontInfo = new FontInfo();
265 mFontInfo.ttf = mOsFontsLocation + ttf;
266 mFontList.add(mFontInfo);
269 } else if (NODE_NAME.equals(localName)) {
270 // do nothing, we'll handle the name in the endElement
271 } else if (NODE_FALLBACK.equals(localName)) {
272 if (mFallBackList != null) {
273 String ttf = attributes.getValue(ATTR_TTF);
275 mFallBackList.add(mOsFontsLocation + ttf);
280 mBuilder.setLength(0);
282 super.startElement(uri, localName, name, attributes);
286 * @see org.xml.sax.helpers.DefaultHandler#characters(char[], int, int)
289 public void characters(char[] ch, int start, int length) throws SAXException {
290 mBuilder.append(ch, start, length);
294 * @see org.xml.sax.helpers.DefaultHandler#endElement(java.lang.String, java.lang.String, java.lang.String)
297 public void endElement(String uri, String localName, String name) throws SAXException {
298 if (NODE_FONTS.equals(localName)) {
299 // top level, do nothing
300 } else if (NODE_FONT.equals(localName)) {
302 } else if (NODE_NAME.equals(localName)) {
303 // handle a new name for an existing Font Info
304 if (mFontInfo != null) {
305 String family = trimXmlWhitespaces(mBuilder.toString());
306 mFontInfo.families.add(family);
308 } else if (NODE_FALLBACK.equals(localName)) {
309 // nothing to do here.
313 private String trimXmlWhitespaces(String value) {
318 // look for carriage return and replace all whitespace around it by just 1 space.
321 while ((index = value.indexOf('\n')) != -1) {
322 // look for whitespace on each side
323 int left = index - 1;
325 if (Character.isWhitespace(value.charAt(left))) {
332 int right = index + 1;
333 int count = value.length();
334 while (right < count) {
335 if (Character.isWhitespace(value.charAt(right))) {
342 // remove all between left and right (non inclusive) and replace by a single space.
343 String leftString = null;
345 leftString = value.substring(0, left + 1);
347 String rightString = null;
349 rightString = value.substring(right);
352 if (leftString != null) {
354 if (rightString != null) {
355 value += " " + rightString;
358 value = rightString != null ? rightString : "";
362 // now we un-escape the string
363 int length = value.length();
364 char[] buffer = value.toCharArray();
366 for (int i = 0 ; i < length ; i++) {
367 if (buffer[i] == '\\') {
368 if (buffer[i+1] == 'n') {
369 // replace the char with \n
373 // offset the rest of the buffer since we go from 2 to 1 char
374 System.arraycopy(buffer, i+1, buffer, i, length - i - 1);
379 return new String(buffer, 0, length);