/* * 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.impl; 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.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.util.ArrayList; import java.util.HashSet; import java.util.List; 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. *

* 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_SYSTEM = "system_fonts.xml"; private static final String FONTS_VENDOR = "vendor_fonts.xml"; private static final String FONTS_FALLBACK = "fallback_fonts.xml"; private static final String NODE_FAMILYSET = "familyset"; private static final String NODE_FAMILY = "family"; private static final String NODE_NAME = "name"; private static final String NODE_FILE = "file"; private static final String FONT_SUFFIX_NONE = ".ttf"; private static final String FONT_SUFFIX_REGULAR = "-Regular.ttf"; private static final String FONT_SUFFIX_BOLD = "-Bold.ttf"; private static final String FONT_SUFFIX_ITALIC = "-Italic.ttf"; private static final String FONT_SUFFIX_BOLDITALIC = "-BoldItalic.ttf"; // This must match the values of Typeface styles so that we can use them for indices in this // array. private static final int[] AWT_STYLES = new int[] { Font.PLAIN, Font.BOLD, Font.ITALIC, Font.BOLD | Font.ITALIC }; private static int[] DERIVE_BOLD_ITALIC = new int[] { Typeface.ITALIC, Typeface.BOLD, Typeface.NORMAL }; private static int[] DERIVE_ITALIC = new int[] { Typeface.NORMAL }; private static int[] DERIVE_BOLD = new int[] { Typeface.NORMAL }; private static final List mMainFonts = new ArrayList(); private static final List mFallbackFonts = new ArrayList(); private final String mOsFontsLocation; public static FontLoader create(String fontOsLocation) { try { SAXParserFactory parserFactory = SAXParserFactory.newInstance(); parserFactory.setNamespaceAware(true); // parse the system fonts FontHandler handler = parseFontFile(parserFactory, fontOsLocation, FONTS_SYSTEM); List systemFonts = handler.getFontList(); // parse the fallback fonts handler = parseFontFile(parserFactory, fontOsLocation, FONTS_FALLBACK); List fallbackFonts = handler.getFontList(); return new FontLoader(fontOsLocation, systemFonts, fallbackFonts); } 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 static FontHandler parseFontFile(SAXParserFactory parserFactory, String fontOsLocation, String fontFileName) throws ParserConfigurationException, SAXException, IOException, FileNotFoundException { SAXParser parser = parserFactory.newSAXParser(); File f = new File(fontOsLocation, fontFileName); FontHandler definitionParser = new FontHandler( fontOsLocation + File.separator); parser.parse(new FileInputStream(f), definitionParser); return definitionParser; } private FontLoader(String fontOsLocation, List fontList, List fallBackList) { mOsFontsLocation = fontOsLocation; mMainFonts.addAll(fontList); mFallbackFonts.addAll(fallBackList); } public String getOsFontsLocation() { return mOsFontsLocation; } /** * Returns a {@link Font} object given a family name and a style value (constant in * {@link Typeface}). * @param family the family name * @param style a 1-item array containing the requested style. Based on the font being read * the actual style may be different. The array contains the actual style after * the method returns. * @return the font object or null if no match could be found. */ public synchronized List getFont(String family, int style) { List result = new ArrayList(); if (family == null) { return result; } // get the font objects from the main list based on family. for (FontInfo info : mMainFonts) { if (info.families.contains(family)) { result.add(info.font[style]); break; } } // add all the fallback fonts for the given style for (FontInfo info : mFallbackFonts) { result.add(info.font[style]); } return result; } public synchronized List getFallbackFonts(int style) { List result = new ArrayList(); // add all the fallback fonts for (FontInfo info : mFallbackFonts) { result.add(info.font[style]); } return result; } private final static class FontInfo { final Font[] font = new Font[4]; // Matches the 4 type-face styles. final Set families; FontInfo() { families = new HashSet(); } } private final static class FontHandler extends DefaultHandler { private final String mOsFontsLocation; private FontInfo mFontInfo = null; private final StringBuilder mBuilder = new StringBuilder(); private List mFontList = new ArrayList(); private FontHandler(String osFontsLocation) { super(); mOsFontsLocation = osFontsLocation; } public List getFontList() { return 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 (NODE_FAMILYSET.equals(localName)) { mFontList = new ArrayList(); } else if (NODE_FAMILY.equals(localName)) { if (mFontList != null) { mFontInfo = new FontInfo(); } } mBuilder.setLength(0); super.startElement(uri, localName, name, attributes); } /* (non-Javadoc) * @see org.xml.sax.helpers.DefaultHandler#characters(char[], int, int) */ @Override public void characters(char[] ch, int start, int length) throws SAXException { mBuilder.append(ch, start, length); } /* (non-Javadoc) * @see org.xml.sax.helpers.DefaultHandler#endElement(java.lang.String, java.lang.String, java.lang.String) */ @Override public void endElement(String uri, String localName, String name) throws SAXException { if (NODE_FAMILY.equals(localName)) { if (mFontInfo != null) { // if has a normal font file, add to the list if (mFontInfo.font[Typeface.NORMAL] != null) { mFontList.add(mFontInfo); // create missing font styles, order is important. if (mFontInfo.font[Typeface.BOLD_ITALIC] == null) { computeDerivedFont(Typeface.BOLD_ITALIC, DERIVE_BOLD_ITALIC); } if (mFontInfo.font[Typeface.ITALIC] == null) { computeDerivedFont(Typeface.ITALIC, DERIVE_ITALIC); } if (mFontInfo.font[Typeface.BOLD] == null) { computeDerivedFont(Typeface.BOLD, DERIVE_BOLD); } } mFontInfo = null; } } else if (NODE_NAME.equals(localName)) { // handle a new name for an existing Font Info if (mFontInfo != null) { String family = trimXmlWhitespaces(mBuilder.toString()); mFontInfo.families.add(family); } } else if (NODE_FILE.equals(localName)) { // handle a new file for an existing Font Info if (mFontInfo != null) { String fileName = trimXmlWhitespaces(mBuilder.toString()); Font font = getFont(fileName); if (font != null) { if (fileName.endsWith(FONT_SUFFIX_REGULAR)) { mFontInfo.font[Typeface.NORMAL] = font; } else if (fileName.endsWith(FONT_SUFFIX_BOLD)) { mFontInfo.font[Typeface.BOLD] = font; } else if (fileName.endsWith(FONT_SUFFIX_ITALIC)) { mFontInfo.font[Typeface.ITALIC] = font; } else if (fileName.endsWith(FONT_SUFFIX_BOLDITALIC)) { mFontInfo.font[Typeface.BOLD_ITALIC] = font; } else if (fileName.endsWith(FONT_SUFFIX_NONE)) { mFontInfo.font[Typeface.NORMAL] = font; } } } } } private Font getFont(String fileName) { try { File file = new File(mOsFontsLocation, fileName); if (file.exists()) { return Font.createFont(Font.TRUETYPE_FONT, file); } } catch (Exception e) { } return null; } private void computeDerivedFont( int toCompute, int[] basedOnList) { for (int basedOn : basedOnList) { if (mFontInfo.font[basedOn] != null) { mFontInfo.font[toCompute] = mFontInfo.font[basedOn].deriveFont(AWT_STYLES[toCompute]); return; } } // we really shouldn't stop there. This means we don't have a NORMAL font... assert false; } 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); } } }