Home | History | Annotate | Download | only in impl
      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