Support for fallback fonts in layoutlib.

BUG 2041229

Change-Id: Ib12bcb7f6d8f0e4c2b51871f8129ecf51fa938ee
diff --git a/data/fonts/fonts.xml b/data/fonts/fonts.xml
index 2bce846..6bcaaaf 100644
--- a/data/fonts/fonts.xml
+++ b/data/fonts/fonts.xml
@@ -13,6 +13,10 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
+<!--
+	This is only used by the layoutlib to display
+	layouts in ADT.
+-->
 <fonts>
     <font ttf="DroidSans">
         <name>sans-serif</name>
@@ -39,5 +43,6 @@
         <name>courier new</name>
         <name>monaco</name>
     </font>
-    <font ttf="DroidSansFallback" />
+    <fallback ttf="DroidSansFallback" />
+    <fallback ttf="DroidSansJapanese" />
 </fonts>
\ No newline at end of file
diff --git a/tools/layoutlib/bridge/src/android/graphics/Canvas.java b/tools/layoutlib/bridge/src/android/graphics/Canvas.java
index 3fa1d1d..4986c77 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Canvas.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Canvas.java
@@ -26,6 +26,7 @@
 import android.graphics.Region;
 import android.graphics.Xfermode;
 import android.graphics.Paint.Align;
+import android.graphics.Paint.FontInfo;
 import android.graphics.Paint.Style;
 import android.graphics.Region.Op;
 
@@ -37,6 +38,7 @@
 import java.awt.RenderingHints;
 import java.awt.geom.AffineTransform;
 import java.awt.image.BufferedImage;
+import java.util.List;
 import java.util.Stack;
 
 import javax.microedition.khronos.opengles.GL;
@@ -620,19 +622,21 @@
      */
     @Override
     public void drawText(char[] text, int index, int count, float x, float y, Paint paint) {
+        // WARNING: the logic in this method is similar to Paint.measureText.
+        // Any change to this method should be reflected in Paint.measureText
         Graphics2D g = getGraphics2d();
 
         g = (Graphics2D)g.create();
         g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
 
-        g.setFont(paint.getFont());
-
-        // set the color. because this only handles RGB we have to handle the alpha separately
+        // set the color. because this only handles RGB, the alpha channel is handled
+        // as a composite.
         g.setColor(new Color(paint.getColor()));
         int alpha = paint.getAlpha();
         float falpha = alpha / 255.f;
         g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, falpha));
 
+
         // Paint.TextAlign indicates how the text is positioned relative to X.
         // LEFT is the default and there's nothing to do.
         if (paint.getTextAlign() != Align.LEFT) {
@@ -644,9 +648,83 @@
             }
         }
 
-        g.drawChars(text, index, count, (int)x, (int)y);
+        List<FontInfo> fonts = paint.getFonts();
+        try {
+            if (fonts.size() > 0) {
+                FontInfo mainFont = fonts.get(0);
+                int i = index;
+                int lastIndex = index + count;
+                while (i < lastIndex) {
+                    // always start with the main font.
+                    int upTo = mainFont.mFont.canDisplayUpTo(text, i, lastIndex);
+                    if (upTo == -1) {
+                        // draw all the rest and exit.
+                        g.setFont(mainFont.mFont);
+                        g.drawChars(text, i, lastIndex - i, (int)x, (int)y);
+                        return;
+                    } else if (upTo > 0) {
+                        // draw what's possible
+                        g.setFont(mainFont.mFont);
+                        g.drawChars(text, i, upTo - i, (int)x, (int)y);
 
-        g.dispose();
+                        // compute the width that was drawn to increase x
+                        x += mainFont.mMetrics.charsWidth(text, i, upTo - i);
+
+                        // move index to the first non displayed char.
+                        i = upTo;
+
+                        // don't call continue at this point. Since it is certain the main font
+                        // cannot display the font a index upTo (now ==i), we move on to the
+                        // fallback fonts directly.
+                    }
+
+                    // no char supported, attempt to read the next char(s) with the
+                    // fallback font. In this case we only test the first character
+                    // and then go back to test with the main font.
+                    // Special test for 2-char characters.
+                    boolean foundFont = false;
+                    for (int f = 1 ; f < fonts.size() ; f++) {
+                        FontInfo fontInfo = fonts.get(f);
+
+                        // need to check that the font can display the character. We test
+                        // differently if the char is a high surrogate.
+                        int charCount = Character.isHighSurrogate(text[i]) ? 2 : 1;
+                        upTo = fontInfo.mFont.canDisplayUpTo(text, i, i + charCount);
+                        if (upTo == -1) {
+                            // draw that char
+                            g.setFont(fontInfo.mFont);
+                            g.drawChars(text, i, charCount, (int)x, (int)y);
+
+                            // update x
+                            x += fontInfo.mMetrics.charsWidth(text, i, charCount);
+
+                            // update the index in the text, and move on
+                            i += charCount;
+                            foundFont = true;
+                            break;
+
+                        }
+                    }
+
+                    // in case no font can display the char, display it with the main font.
+                    // (it'll put a square probably)
+                    if (foundFont == false) {
+                        int charCount = Character.isHighSurrogate(text[i]) ? 2 : 1;
+
+                        g.setFont(mainFont.mFont);
+                        g.drawChars(text, i, charCount, (int)x, (int)y);
+
+                        // measure it to advance x
+                        x += mainFont.mMetrics.charsWidth(text, i, charCount);
+
+                        // and move to the next chars.
+                        i += charCount;
+                    }
+                }
+            }
+        } finally {
+            g.dispose();
+        }
     }
 
     /* (non-Javadoc)
diff --git a/tools/layoutlib/bridge/src/android/graphics/Paint.java b/tools/layoutlib/bridge/src/android/graphics/Paint.java
index 2972ff3..86de56b 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Paint.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Paint.java
@@ -26,6 +26,9 @@
 import java.awt.font.FontRenderContext;
 import java.awt.geom.AffineTransform;
 import java.awt.geom.Rectangle2D;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
 
 /**
  * A paint implementation overridden by the LayoutLib bridge.
@@ -44,10 +47,17 @@
     private Join mJoin = Join.MITER;
     private int mFlags = 0;
 
-    private Font mFont;
+    /**
+     * Class associating a {@link Font} and it's {@link java.awt.FontMetrics}.
+     */
+    public static final class FontInfo {
+        Font mFont;
+        java.awt.FontMetrics mMetrics;
+    }
+
+    private List<FontInfo> mFonts;
     private final FontRenderContext mFontContext = new FontRenderContext(
             new AffineTransform(), true, true);
-    private java.awt.FontMetrics mMetrics;
 
     @SuppressWarnings("hiding")
     public static final int ANTI_ALIAS_FLAG       = _Original_Paint.ANTI_ALIAS_FLAG;
@@ -201,10 +211,11 @@
     }
 
     /**
-     * Returns the {@link Font} object.
+     * Returns the list of {@link Font} objects. The first item is the main font, the rest
+     * are fall backs for characters not present in the main font.
      */
-    public Font getFont() {
-        return mFont;
+    public List<FontInfo> getFonts() {
+        return mFonts;
     }
 
     private void initFont() {
@@ -215,17 +226,29 @@
     /**
      * Update the {@link Font} object from the typeface, text size and scaling
      */
+    @SuppressWarnings("deprecation")
     private void updateFontObject() {
         if (mTypeface != null) {
-            // get the typeface font object, and get our font object from it, based on the current size
-            mFont = mTypeface.getFont().deriveFont(mTextSize);
-            if (mScaleX != 1.0 || mSkewX != 0) {
-                // TODO: support skew
-                mFont = mFont.deriveFont(new AffineTransform(
-                        mScaleX, mSkewX, 0, 0, 1, 0));
+            // Get the fonts from the TypeFace object.
+            List<Font> fonts = mTypeface.getFonts();
+
+            // create new font objects as well as FontMetrics, based on the current text size
+            // and skew info.
+            ArrayList<FontInfo> infoList = new ArrayList<FontInfo>(fonts.size());
+            for (Font font : fonts) {
+                FontInfo info = new FontInfo();
+                info.mFont = font.deriveFont(mTextSize);
+                if (mScaleX != 1.0 || mSkewX != 0) {
+                    // TODO: support skew
+                    info.mFont = info.mFont.deriveFont(new AffineTransform(
+                            mScaleX, mSkewX, 0, 0, 1, 0));
+                }
+                info.mMetrics = Toolkit.getDefaultToolkit().getFontMetrics(info.mFont);
+
+                infoList.add(info);
             }
 
-            mMetrics = Toolkit.getDefaultToolkit().getFontMetrics(mFont);
+            mFonts = Collections.unmodifiableList(infoList);
         }
     }
 
@@ -310,34 +333,36 @@
      * @return the font's recommended interline spacing.
      */
     public float getFontMetrics(FontMetrics metrics) {
-        if (mMetrics != null) {
+        if (mFonts.size() > 0) {
+            java.awt.FontMetrics javaMetrics = mFonts.get(0).mMetrics;
             if (metrics != null) {
-                // ascent stuff should be negatif, but awt returns them as positive.
-                metrics.top = - mMetrics.getMaxAscent();
-                metrics.ascent = - mMetrics.getAscent();
-                metrics.descent = mMetrics.getDescent();
-                metrics.bottom = mMetrics.getMaxDescent();
-                metrics.leading = mMetrics.getLeading();
+                // Android expects negative ascent so we invert the value from Java.
+                metrics.top = - javaMetrics.getMaxAscent();
+                metrics.ascent = - javaMetrics.getAscent();
+                metrics.descent = javaMetrics.getDescent();
+                metrics.bottom = javaMetrics.getMaxDescent();
+                metrics.leading = javaMetrics.getLeading();
             }
 
-            return mMetrics.getHeight();
+            return javaMetrics.getHeight();
         }
 
         return 0;
     }
 
     public int getFontMetricsInt(FontMetricsInt metrics) {
-        if (mMetrics != null) {
+        if (mFonts.size() > 0) {
+            java.awt.FontMetrics javaMetrics = mFonts.get(0).mMetrics;
             if (metrics != null) {
-                // ascent stuff should be negatif, but awt returns them as positive.
-                metrics.top = - mMetrics.getMaxAscent();
-                metrics.ascent = - mMetrics.getAscent();
-                metrics.descent = mMetrics.getDescent();
-                metrics.bottom = mMetrics.getMaxDescent();
-                metrics.leading = mMetrics.getLeading();
+                // Android expects negative ascent so we invert the value from Java.
+                metrics.top = - javaMetrics.getMaxAscent();
+                metrics.ascent = - javaMetrics.getAscent();
+                metrics.descent = javaMetrics.getDescent();
+                metrics.bottom = javaMetrics.getMaxDescent();
+                metrics.leading = javaMetrics.getLeading();
             }
 
-            return mMetrics.getHeight();
+            return javaMetrics.getHeight();
         }
 
         return 0;
@@ -716,9 +741,10 @@
      */
     @Override
     public float ascent() {
-        if (mMetrics != null) {
-            // ascent stuff should be negatif, but awt returns them as positive.
-            return - mMetrics.getAscent();
+        if (mFonts.size() > 0) {
+            java.awt.FontMetrics javaMetrics = mFonts.get(0).mMetrics;
+            // Android expects negative ascent so we invert the value from Java.
+            return - javaMetrics.getAscent();
         }
 
         return 0;
@@ -733,8 +759,9 @@
      */
     @Override
     public float descent() {
-        if (mMetrics != null) {
-            return mMetrics.getDescent();
+        if (mFonts.size() > 0) {
+            java.awt.FontMetrics javaMetrics = mFonts.get(0).mMetrics;
+            return javaMetrics.getDescent();
         }
 
         return 0;
@@ -750,10 +777,55 @@
      */
     @Override
     public float measureText(char[] text, int index, int count) {
-        if (mFont != null && text != null && text.length > 0) {
-            Rectangle2D bounds = mFont.getStringBounds(text, index, index + count, mFontContext);
+        // WARNING: the logic in this method is similar to Canvas.drawText.
+        // Any change to this method should be reflected in Canvas.drawText
+        if (mFonts.size() > 0) {
+            FontInfo mainFont = mFonts.get(0);
+            int i = index;
+            int lastIndex = index + count;
+            float total = 0f;
+            while (i < lastIndex) {
+                // always start with the main font.
+                int upTo = mainFont.mFont.canDisplayUpTo(text, i, lastIndex);
+                if (upTo == -1) {
+                    // shortcut to exit
+                    return total + mainFont.mMetrics.charsWidth(text, i, lastIndex - i);
+                } else if (upTo > 0) {
+                    total += mainFont.mMetrics.charsWidth(text, i, upTo - i);
+                    i = upTo;
+                    // don't call continue at this point. Since it is certain the main font
+                    // cannot display the font a index upTo (now ==i), we move on to the
+                    // fallback fonts directly.
+                }
 
-            return (float)bounds.getWidth();
+                // no char supported, attempt to read the next char(s) with the
+                // fallback font. In this case we only test the first character
+                // and then go back to test with the main font.
+                // Special test for 2-char characters.
+                boolean foundFont = false;
+                for (int f = 1 ; f < mFonts.size() ; f++) {
+                    FontInfo fontInfo = mFonts.get(f);
+
+                    // need to check that the font can display the character. We test
+                    // differently if the char is a high surrogate.
+                    int charCount = Character.isHighSurrogate(text[i]) ? 2 : 1;
+                    upTo = fontInfo.mFont.canDisplayUpTo(text, i, i + charCount);
+                    if (upTo == -1) {
+                        total += fontInfo.mMetrics.charsWidth(text, i, charCount);
+                        i += charCount;
+                        foundFont = true;
+                        break;
+
+                    }
+                }
+
+                // in case no font can display the char, measure it with the main font.
+                if (foundFont == false) {
+                    int size = Character.isHighSurrogate(text[i]) ? 2 : 1;
+                    total += mainFont.mMetrics.charsWidth(text, i, size);
+                    i += size;
+                }
+            }
         }
 
         return 0;
@@ -919,14 +991,30 @@
     @Override
     public int getTextWidths(char[] text, int index, int count,
                              float[] widths) {
-        if (mMetrics != null) {
+        if (mFonts.size() > 0) {
             if ((index | count) < 0 || index + count > text.length
                     || count > widths.length) {
                 throw new ArrayIndexOutOfBoundsException();
             }
 
+            // FIXME: handle multi-char characters.
+            // Need to figure out if the lengths of the width array takes into account
+            // multi-char characters.
             for (int i = 0; i < count; i++) {
-                widths[i] = mMetrics.charWidth(text[i + index]);
+                char c = text[i + index];
+                boolean found = false;
+                for (FontInfo info : mFonts) {
+                    if (info.mFont.canDisplay(c)) {
+                        widths[i] = info.mMetrics.charWidth(c);
+                        found = true;
+                        break;
+                    }
+                }
+
+                if (found == false) {
+                    // we stop there.
+                    return i;
+                }
             }
 
             return count;
@@ -1070,7 +1158,8 @@
      */
     @Override
     public void getTextBounds(char[] text, int index, int count, Rect bounds) {
-        if (mFont != null) {
+        // FIXME
+        if (mFonts.size() > 0) {
             if ((index | count) < 0 || index + count > text.length) {
                 throw new ArrayIndexOutOfBoundsException();
             }
@@ -1078,7 +1167,9 @@
                 throw new NullPointerException("need bounds Rect");
             }
 
-            Rectangle2D rect = mFont.getStringBounds(text, index, index + count, mFontContext);
+            FontInfo mainInfo = mFonts.get(0);
+
+            Rectangle2D rect = mainInfo.mFont.getStringBounds(text, index, index + count, mFontContext);
             bounds.set(0, 0, (int)rect.getWidth(), (int)rect.getHeight());
         }
     }
diff --git a/tools/layoutlib/bridge/src/android/graphics/Typeface.java b/tools/layoutlib/bridge/src/android/graphics/Typeface.java
index e878b04..af3adb5 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Typeface.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Typeface.java
@@ -21,9 +21,12 @@
 import android.content.res.AssetManager;
 
 import java.awt.Font;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
 
 /**
- * Re-implementation of Typeface over java.awt 
+ * Re-implementation of Typeface over java.awt
  */
 public class Typeface {
     private static final String DEFAULT_FAMILY = "sans-serif";
@@ -46,11 +49,11 @@
 
     private static Typeface[] sDefaults;
     private static FontLoader mFontLoader;
-    
+
     private final int mStyle;
-    private final Font mFont;
+    private final List<Font> mFonts;
     private final String mFamily;
-    
+
     // Style
     public static final int NORMAL = _Original_Typeface.NORMAL;
     public static final int BOLD = _Original_Typeface.BOLD;
@@ -58,12 +61,13 @@
     public static final int BOLD_ITALIC = _Original_Typeface.BOLD_ITALIC;
 
     /**
-     * Returns the underlying {@link Font} object.
+     * Returns the underlying {@link Font} objects. The first item in the list is the real
+     * font. Any other items are fallback fonts for characters not found in the first one.
      */
-    public Font getFont() {
-        return mFont;
+    public List<Font> getFonts() {
+        return mFonts;
     }
-    
+
     /** Returns the typeface's intrinsic style attributes */
     public int getStyle() {
         return mStyle;
@@ -94,9 +98,12 @@
         styleBuffer[0] = style;
         Font font = mFontLoader.getFont(familyName, styleBuffer);
         if (font != null) {
-            return new Typeface(familyName, styleBuffer[0], font);
+            ArrayList<Font> list = new ArrayList<Font>();
+            list.add(font);
+            list.addAll(mFontLoader.getFallBackFonts());
+            return new Typeface(familyName, styleBuffer[0], list);
         }
-        
+
         return null;
     }
 
@@ -115,7 +122,10 @@
         styleBuffer[0] = style;
         Font font = mFontLoader.getFont(family.mFamily, styleBuffer);
         if (font != null) {
-            return new Typeface(family.mFamily, styleBuffer[0], font);
+            ArrayList<Font> list = new ArrayList<Font>();
+            list.add(font);
+            list.addAll(mFontLoader.getFallBackFonts());
+            return new Typeface(family.mFamily, styleBuffer[0], list);
         }
 
         return null;
@@ -129,7 +139,7 @@
     public static Typeface defaultFromStyle(int style) {
         return sDefaults[style];
     }
-    
+
     /**
      * Create a new typeface from the specified font data.
      * @param mgr The application's asset manager
@@ -140,17 +150,17 @@
         return null;
         //return new Typeface(nativeCreateFromAsset(mgr, path));
     }
-    
+
     // don't allow clients to call this directly
-    private Typeface(String family, int style, Font f) {
+    private Typeface(String family, int style, List<Font> fonts) {
         mFamily = family;
-        mFont = f;
+        mFonts = Collections.unmodifiableList(fonts);
         mStyle = style;
     }
-    
+
     public static void init(FontLoader fontLoader) {
         mFontLoader = fontLoader;
-        
+
         DEFAULT = create(DEFAULT_FAMILY, NORMAL);
         DEFAULT_BOLD = create(DEFAULT_FAMILY, BOLD);
         SANS_SERIF = create("sans-serif", NORMAL);
@@ -162,14 +172,14 @@
                 create(DEFAULT_FAMILY, ITALIC),
                 create(DEFAULT_FAMILY, BOLD_ITALIC),
         };
-        
+
         /*
         DEFAULT         = create((String)null, 0);
         DEFAULT_BOLD    = create((String)null, Typeface.BOLD);
         SANS_SERIF      = create("sans-serif", 0);
         SERIF           = create("serif", 0);
         MONOSPACE       = create("monospace", 0);
-        
+
         sDefaults = new Typeface[] {
             DEFAULT,
             DEFAULT_BOLD,
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/FontLoader.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/FontLoader.java
index 1bdd1cc..801503b 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/FontLoader.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/FontLoader.java
@@ -29,6 +29,7 @@
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -47,14 +48,13 @@
  */
 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_FALLBACK = "fallback";
 
-    private static final String[] NODE_LEVEL = { NODE_FONTS, NODE_FONT, NODE_NAME };
+    private static final String ATTR_TTF = "ttf";
 
     private static final String FONT_EXT = ".ttf";
 
@@ -62,7 +62,7 @@
     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,
@@ -70,23 +70,25 @@
         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>>();
-    
+
+    private List<Font> mFallBackFonts = null;
+
     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
@@ -101,12 +103,35 @@
         return null;
     }
 
-    private FontLoader(List<FontInfo> fontList) {
+    private FontLoader(List<FontInfo> fontList, List<String> fallBackList) {
         for (FontInfo info : fontList) {
             for (String family : info.families) {
                 mFamilyToTtf.put(family, info.ttf);
             }
         }
+
+        ArrayList<Font> list = new ArrayList<Font>();
+        for (String path : fallBackList) {
+            File f = new File(path + FONT_EXT);
+            if (f.isFile()) {
+                try {
+                    Font font = Font.createFont(Font.TRUETYPE_FONT, f);
+                    if (font != null) {
+                        list.add(font);
+                    }
+                } catch (FontFormatException e) {
+                    // skip this font name
+                } catch (IOException e) {
+                    // skip this font name
+                }
+            }
+        }
+
+        mFallBackFonts = Collections.unmodifiableList(list);
+    }
+
+    public List<Font> getFallBackFonts() {
+        return mFallBackFonts;
     }
 
     public synchronized Font getFont(String family, int[] style) {
@@ -116,25 +141,25 @@
 
         // 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:
@@ -178,7 +203,7 @@
     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 {
@@ -193,14 +218,14 @@
                 }
             }
         }
-        
+
         return null;
     }
 
     private final static class FontInfo {
         String ttf;
         final Set<String> families;
-        
+
         FontInfo() {
             families = new HashSet<String>();
         }
@@ -208,19 +233,19 @@
 
     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 List<FontInfo> mFontList;
+        private List<String> mFallBackList;
+
         private FontDefinitionParser(String osFontsLocation) {
             super();
             mOsFontsLocation = osFontsLocation;
         }
-        
+
         FontLoader getFontLoader() {
-            return new FontLoader(mFontList);
+            return new FontLoader(mFontList, mFallBackList);
         }
 
         /* (non-Javadoc)
@@ -229,10 +254,11 @@
         @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.
+            if (NODE_FONTS.equals(localName)) {
+                mFontList = new ArrayList<FontInfo>();
+                mFallBackList = new ArrayList<String>();
+            } else if (NODE_FONT.equals(localName)) {
+                if (mFontList != null) {
                     String ttf = attributes.getValue(ATTR_TTF);
                     if (ttf != null) {
                         mFontInfo = new FontInfo();
@@ -240,42 +266,50 @@
                         mFontList.add(mFontInfo);
                     }
                 }
+            } else if (NODE_NAME.equals(localName)) {
+                // do nothing, we'll handle the name in the endElement
+            } else if (NODE_FALLBACK.equals(localName)) {
+                if (mFallBackList != null) {
+                    String ttf = attributes.getValue(ATTR_TTF);
+                    if (ttf != null) {
+                        mFallBackList.add(mOsFontsLocation + ttf);
+                    }
+                }
             }
 
+            mBuilder.setLength(0);
+
             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);
-            }
+            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;
+            if (NODE_FONTS.equals(localName)) {
+                // top level, do nothing
+            } else if (NODE_FONT.equals(localName)) {
+                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_FALLBACK.equals(localName)) {
+                // nothing to do here.
             }
         }
-        
+
         private String trimXmlWhitespaces(String value) {
             if (value == null) {
                 return null;
@@ -283,7 +317,7 @@
 
             // 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;
@@ -294,7 +328,7 @@
                         break;
                     }
                 }
-                
+
                 int right = index + 1;
                 int count = value.length();
                 while (right < count) {
@@ -304,7 +338,7 @@
                         break;
                     }
                 }
-                
+
                 // remove all between left and right (non inclusive) and replace by a single space.
                 String leftString = null;
                 if (left >= 0) {
@@ -314,7 +348,7 @@
                 if (right < count) {
                     rightString = value.substring(right);
                 }
-                
+
                 if (leftString != null) {
                     value = leftString;
                     if (rightString != null) {
@@ -324,24 +358,24 @@
                     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);
         }