1 /* 2 * Copyright (C) 2008 The Android Open Source Project 3 * 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 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 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. 15 */ 16 17 package com.android.layoutlib.bridge.impl; 18 19 import org.xml.sax.Attributes; 20 import org.xml.sax.SAXException; 21 import org.xml.sax.helpers.DefaultHandler; 22 23 import android.graphics.Typeface; 24 25 import java.awt.Font; 26 import java.io.File; 27 import java.io.FileInputStream; 28 import java.io.FileNotFoundException; 29 import java.io.IOException; 30 import java.util.ArrayList; 31 import java.util.HashSet; 32 import java.util.List; 33 import java.util.Set; 34 35 import javax.xml.parsers.ParserConfigurationException; 36 import javax.xml.parsers.SAXParser; 37 import javax.xml.parsers.SAXParserFactory; 38 39 /** 40 * Provides {@link Font} object to the layout lib. 41 * <p/> 42 * The fonts are loaded from the SDK directory. Family/style mapping is done by parsing the 43 * fonts.xml file located alongside the ttf files. 44 */ 45 public final class FontLoader { 46 private static final String FONTS_SYSTEM = "system_fonts.xml"; 47 private static final String FONTS_VENDOR = "vendor_fonts.xml"; 48 private static final String FONTS_FALLBACK = "fallback_fonts.xml"; 49 50 private static final String NODE_FAMILYSET = "familyset"; 51 private static final String NODE_FAMILY = "family"; 52 private static final String NODE_NAME = "name"; 53 private static final String NODE_FILE = "file"; 54 55 private static final String FONT_SUFFIX_NONE = ".ttf"; 56 private static final String FONT_SUFFIX_REGULAR = "-Regular.ttf"; 57 private static final String FONT_SUFFIX_BOLD = "-Bold.ttf"; 58 private static final String FONT_SUFFIX_ITALIC = "-Italic.ttf"; 59 private static final String FONT_SUFFIX_BOLDITALIC = "-BoldItalic.ttf"; 60 61 // This must match the values of Typeface styles so that we can use them for indices in this 62 // array. 63 private static final int[] AWT_STYLES = new int[] { 64 Font.PLAIN, 65 Font.BOLD, 66 Font.ITALIC, 67 Font.BOLD | Font.ITALIC 68 }; 69 private static int[] DERIVE_BOLD_ITALIC = new int[] { 70 Typeface.ITALIC, Typeface.BOLD, Typeface.NORMAL 71 }; 72 private static int[] DERIVE_ITALIC = new int[] { Typeface.NORMAL }; 73 private static int[] DERIVE_BOLD = new int[] { Typeface.NORMAL }; 74 75 private static final List<FontInfo> mMainFonts = new ArrayList<FontInfo>(); 76 private static final List<FontInfo> mFallbackFonts = new ArrayList<FontInfo>(); 77 78 private final String mOsFontsLocation; 79 80 public static FontLoader create(String fontOsLocation) { 81 try { 82 SAXParserFactory parserFactory = SAXParserFactory.newInstance(); 83 parserFactory.setNamespaceAware(true); 84 85 // parse the system fonts 86 FontHandler handler = parseFontFile(parserFactory, fontOsLocation, FONTS_SYSTEM); 87 List<FontInfo> systemFonts = handler.getFontList(); 88 89 90 // parse the fallback fonts 91 handler = parseFontFile(parserFactory, fontOsLocation, FONTS_FALLBACK); 92 List<FontInfo> fallbackFonts = handler.getFontList(); 93 94 return new FontLoader(fontOsLocation, systemFonts, fallbackFonts); 95 } catch (ParserConfigurationException e) { 96 // return null below 97 } catch (SAXException e) { 98 // return null below 99 } catch (FileNotFoundException e) { 100 // return null below 101 } catch (IOException e) { 102 // return null below 103 } 104 105 return null; 106 } 107 108 private static FontHandler parseFontFile(SAXParserFactory parserFactory, 109 String fontOsLocation, String fontFileName) 110 throws ParserConfigurationException, SAXException, IOException, FileNotFoundException { 111 112 SAXParser parser = parserFactory.newSAXParser(); 113 File f = new File(fontOsLocation, fontFileName); 114 115 FontHandler definitionParser = new FontHandler( 116 fontOsLocation + File.separator); 117 parser.parse(new FileInputStream(f), definitionParser); 118 return definitionParser; 119 } 120 121 private FontLoader(String fontOsLocation, 122 List<FontInfo> fontList, List<FontInfo> fallBackList) { 123 mOsFontsLocation = fontOsLocation; 124 mMainFonts.addAll(fontList); 125 mFallbackFonts.addAll(fallBackList); 126 } 127 128 129 public String getOsFontsLocation() { 130 return mOsFontsLocation; 131 } 132 133 /** 134 * Returns a {@link Font} object given a family name and a style value (constant in 135 * {@link Typeface}). 136 * @param family the family name 137 * @param style a 1-item array containing the requested style. Based on the font being read 138 * the actual style may be different. The array contains the actual style after 139 * the method returns. 140 * @return the font object or null if no match could be found. 141 */ 142 public synchronized List<Font> getFont(String family, int style) { 143 List<Font> result = new ArrayList<Font>(); 144 145 if (family == null) { 146 return result; 147 } 148 149 150 // get the font objects from the main list based on family. 151 for (FontInfo info : mMainFonts) { 152 if (info.families.contains(family)) { 153 result.add(info.font[style]); 154 break; 155 } 156 } 157 158 // add all the fallback fonts for the given style 159 for (FontInfo info : mFallbackFonts) { 160 result.add(info.font[style]); 161 } 162 163 return result; 164 } 165 166 167 public synchronized List<Font> getFallbackFonts(int style) { 168 List<Font> result = new ArrayList<Font>(); 169 // add all the fallback fonts 170 for (FontInfo info : mFallbackFonts) { 171 result.add(info.font[style]); 172 } 173 return result; 174 } 175 176 177 private final static class FontInfo { 178 final Font[] font = new Font[4]; // Matches the 4 type-face styles. 179 final Set<String> families; 180 181 FontInfo() { 182 families = new HashSet<String>(); 183 } 184 } 185 186 private final static class FontHandler extends DefaultHandler { 187 private final String mOsFontsLocation; 188 189 private FontInfo mFontInfo = null; 190 private final StringBuilder mBuilder = new StringBuilder(); 191 private List<FontInfo> mFontList = new ArrayList<FontInfo>(); 192 193 private FontHandler(String osFontsLocation) { 194 super(); 195 mOsFontsLocation = osFontsLocation; 196 } 197 198 public List<FontInfo> getFontList() { 199 return mFontList; 200 } 201 202 /* (non-Javadoc) 203 * @see org.xml.sax.helpers.DefaultHandler#startElement(java.lang.String, java.lang.String, java.lang.String, org.xml.sax.Attributes) 204 */ 205 @Override 206 public void startElement(String uri, String localName, String name, Attributes attributes) 207 throws SAXException { 208 if (NODE_FAMILYSET.equals(localName)) { 209 mFontList = new ArrayList<FontInfo>(); 210 } else if (NODE_FAMILY.equals(localName)) { 211 if (mFontList != null) { 212 mFontInfo = new FontInfo(); 213 } 214 } 215 216 mBuilder.setLength(0); 217 218 super.startElement(uri, localName, name, attributes); 219 } 220 221 /* (non-Javadoc) 222 * @see org.xml.sax.helpers.DefaultHandler#characters(char[], int, int) 223 */ 224 @Override 225 public void characters(char[] ch, int start, int length) throws SAXException { 226 mBuilder.append(ch, start, length); 227 } 228 229 /* (non-Javadoc) 230 * @see org.xml.sax.helpers.DefaultHandler#endElement(java.lang.String, java.lang.String, java.lang.String) 231 */ 232 @Override 233 public void endElement(String uri, String localName, String name) throws SAXException { 234 if (NODE_FAMILY.equals(localName)) { 235 if (mFontInfo != null) { 236 // if has a normal font file, add to the list 237 if (mFontInfo.font[Typeface.NORMAL] != null) { 238 mFontList.add(mFontInfo); 239 240 // create missing font styles, order is important. 241 if (mFontInfo.font[Typeface.BOLD_ITALIC] == null) { 242 computeDerivedFont(Typeface.BOLD_ITALIC, DERIVE_BOLD_ITALIC); 243 } 244 if (mFontInfo.font[Typeface.ITALIC] == null) { 245 computeDerivedFont(Typeface.ITALIC, DERIVE_ITALIC); 246 } 247 if (mFontInfo.font[Typeface.BOLD] == null) { 248 computeDerivedFont(Typeface.BOLD, DERIVE_BOLD); 249 } 250 } 251 252 mFontInfo = null; 253 } 254 } else if (NODE_NAME.equals(localName)) { 255 // handle a new name for an existing Font Info 256 if (mFontInfo != null) { 257 String family = trimXmlWhitespaces(mBuilder.toString()); 258 mFontInfo.families.add(family); 259 } 260 } else if (NODE_FILE.equals(localName)) { 261 // handle a new file for an existing Font Info 262 if (mFontInfo != null) { 263 String fileName = trimXmlWhitespaces(mBuilder.toString()); 264 Font font = getFont(fileName); 265 if (font != null) { 266 if (fileName.endsWith(FONT_SUFFIX_REGULAR)) { 267 mFontInfo.font[Typeface.NORMAL] = font; 268 } else if (fileName.endsWith(FONT_SUFFIX_BOLD)) { 269 mFontInfo.font[Typeface.BOLD] = font; 270 } else if (fileName.endsWith(FONT_SUFFIX_ITALIC)) { 271 mFontInfo.font[Typeface.ITALIC] = font; 272 } else if (fileName.endsWith(FONT_SUFFIX_BOLDITALIC)) { 273 mFontInfo.font[Typeface.BOLD_ITALIC] = font; 274 } else if (fileName.endsWith(FONT_SUFFIX_NONE)) { 275 mFontInfo.font[Typeface.NORMAL] = font; 276 } 277 } 278 } 279 } 280 } 281 282 private Font getFont(String fileName) { 283 try { 284 File file = new File(mOsFontsLocation, fileName); 285 if (file.exists()) { 286 return Font.createFont(Font.TRUETYPE_FONT, file); 287 } 288 } catch (Exception e) { 289 290 } 291 292 return null; 293 } 294 295 private void computeDerivedFont( int toCompute, int[] basedOnList) { 296 for (int basedOn : basedOnList) { 297 if (mFontInfo.font[basedOn] != null) { 298 mFontInfo.font[toCompute] = 299 mFontInfo.font[basedOn].deriveFont(AWT_STYLES[toCompute]); 300 return; 301 } 302 } 303 304 // we really shouldn't stop there. This means we don't have a NORMAL font... 305 assert false; 306 } 307 308 private String trimXmlWhitespaces(String value) { 309 if (value == null) { 310 return null; 311 } 312 313 // look for carriage return and replace all whitespace around it by just 1 space. 314 int index; 315 316 while ((index = value.indexOf('\n')) != -1) { 317 // look for whitespace on each side 318 int left = index - 1; 319 while (left >= 0) { 320 if (Character.isWhitespace(value.charAt(left))) { 321 left--; 322 } else { 323 break; 324 } 325 } 326 327 int right = index + 1; 328 int count = value.length(); 329 while (right < count) { 330 if (Character.isWhitespace(value.charAt(right))) { 331 right++; 332 } else { 333 break; 334 } 335 } 336 337 // remove all between left and right (non inclusive) and replace by a single space. 338 String leftString = null; 339 if (left >= 0) { 340 leftString = value.substring(0, left + 1); 341 } 342 String rightString = null; 343 if (right < count) { 344 rightString = value.substring(right); 345 } 346 347 if (leftString != null) { 348 value = leftString; 349 if (rightString != null) { 350 value += " " + rightString; 351 } 352 } else { 353 value = rightString != null ? rightString : ""; 354 } 355 } 356 357 // now we un-escape the string 358 int length = value.length(); 359 char[] buffer = value.toCharArray(); 360 361 for (int i = 0 ; i < length ; i++) { 362 if (buffer[i] == '\\') { 363 if (buffer[i+1] == 'n') { 364 // replace the char with \n 365 buffer[i+1] = '\n'; 366 } 367 368 // offset the rest of the buffer since we go from 2 to 1 char 369 System.arraycopy(buffer, i+1, buffer, i, length - i - 1); 370 length--; 371 } 372 } 373 374 return new String(buffer, 0, length); 375 } 376 377 } 378 } 379