Merge "Parsing of XML font configuration files for Minikin"
diff --git a/core/jni/android/graphics/FontFamily.cpp b/core/jni/android/graphics/FontFamily.cpp
index 5782312..05154d9 100644
--- a/core/jni/android/graphics/FontFamily.cpp
+++ b/core/jni/android/graphics/FontFamily.cpp
@@ -49,6 +49,10 @@
     ScopedUtfChars str(env, path);
     ALOGD("addFont %s", str.c_str());
     SkTypeface* face = SkTypeface::CreateFromFile(str.c_str());
+    if (face == NULL) {
+        ALOGE("addFont failed to create font %s", str.c_str());
+        return false;
+    }
     MinikinFont* minikinFont = new MinikinFontSkia(face);
     FontFamily* fontFamily = (FontFamily*)familyPtr;
     return fontFamily->addFont(minikinFont);
diff --git a/core/jni/android/graphics/Typeface.cpp b/core/jni/android/graphics/Typeface.cpp
index 02b04de..b20c246 100644
--- a/core/jni/android/graphics/Typeface.cpp
+++ b/core/jni/android/graphics/Typeface.cpp
@@ -120,6 +120,11 @@
     return reinterpret_cast<jlong>(TypefaceImpl_createFromFamilies(families.get(), families.size()));
 }
 
+static void Typeface_setDefault(JNIEnv *env, jobject, jlong faceHandle) {
+    TypefaceImpl* face = reinterpret_cast<TypefaceImpl*>(faceHandle);
+    return TypefaceImpl_setDefault(face);
+}
+
 ///////////////////////////////////////////////////////////////////////////////
 
 static JNINativeMethod gTypefaceMethods[] = {
@@ -133,6 +138,7 @@
                                            (void*)Typeface_createFromFile },
     { "nativeCreateFromArray",    "([J)J",
                                            (void*)Typeface_createFromArray },
+    { "nativeSetDefault",         "(J)V",   (void*)Typeface_setDefault },
 };
 
 int register_android_graphics_Typeface(JNIEnv* env)
diff --git a/core/jni/android/graphics/TypefaceImpl.cpp b/core/jni/android/graphics/TypefaceImpl.cpp
index a60dd7e..fa5acb8 100644
--- a/core/jni/android/graphics/TypefaceImpl.cpp
+++ b/core/jni/android/graphics/TypefaceImpl.cpp
@@ -52,48 +52,46 @@
     return FontStyle(weight, italic);
 }
 
-TypefaceImpl* gDefaultTypeface;
+TypefaceImpl* gDefaultTypeface = NULL;
 pthread_once_t gDefaultTypefaceOnce = PTHREAD_ONCE_INIT;
 
-// TODO: this currently builds a font collection from hardcoded paths.
-// It will get replaced by an implementation that parses the XML files.
+// This installs a default typeface (from a hardcoded path) that allows
+// layouts to work (not crash on null pointer) before the default
+// typeface is set.
+// TODO: investigate why layouts are being created before Typeface.java
+// class initialization.
 static FontCollection *makeFontCollection() {
     std::vector<FontFamily *>typefaces;
     const char *fns[] = {
         "/system/fonts/Roboto-Regular.ttf",
-        "/system/fonts/Roboto-Italic.ttf",
-        "/system/fonts/Roboto-BoldItalic.ttf",
-        "/system/fonts/Roboto-Light.ttf",
-        "/system/fonts/Roboto-Thin.ttf",
-        "/system/fonts/Roboto-Bold.ttf",
-        "/system/fonts/Roboto-ThinItalic.ttf",
-        "/system/fonts/Roboto-LightItalic.ttf"
     };
 
     FontFamily *family = new FontFamily();
     for (size_t i = 0; i < sizeof(fns)/sizeof(fns[0]); i++) {
         const char *fn = fns[i];
+        ALOGD("makeFontCollection adding %s", fn);
         SkTypeface *skFace = SkTypeface::CreateFromFile(fn);
-        MinikinFont *font = new MinikinFontSkia(skFace);
-        family->addFont(font);
+        if (skFace != NULL) {
+            MinikinFont *font = new MinikinFontSkia(skFace);
+            family->addFont(font);
+        } else {
+            ALOGE("failed to create font %s", fn);
+        }
     }
     typefaces.push_back(family);
 
-    family = new FontFamily();
-    const char *fn = "/system/fonts/NotoSansDevanagari-Regular.ttf";
-    SkTypeface *skFace = SkTypeface::CreateFromFile(fn);
-    MinikinFont *font = new MinikinFontSkia(skFace);
-    family->addFont(font);
-    typefaces.push_back(family);
-
     return new FontCollection(typefaces);
 }
 
 static void getDefaultTypefaceOnce() {
     Layout::init();
-    gDefaultTypeface = new TypefaceImpl;
-    gDefaultTypeface->fFontCollection = makeFontCollection();
-    gDefaultTypeface->fStyle = FontStyle();
+    if (gDefaultTypeface == NULL) {
+        // We expect the client to set a default typeface, but provide a
+        // default so we can make progress before that happens.
+        gDefaultTypeface = new TypefaceImpl;
+        gDefaultTypeface->fFontCollection = makeFontCollection();
+        gDefaultTypeface->fStyle = FontStyle();
+    }
 }
 
 TypefaceImpl* TypefaceImpl_resolveDefault(TypefaceImpl* src) {
@@ -116,6 +114,9 @@
 }
 
 static TypefaceImpl* createFromSkTypeface(SkTypeface* typeface) {
+    if (typeface == NULL) {
+        return NULL;
+    }
     MinikinFont* minikinFont = new MinikinFontSkia(typeface);
     std::vector<FontFamily *> typefaces;
     FontFamily* family = new FontFamily();
@@ -176,6 +177,10 @@
     return result;
 }
 
+void TypefaceImpl_setDefault(TypefaceImpl* face) {
+    gDefaultTypeface = face;
+}
+
 #else  // USE_MINIKIN
 
 /* Just use SkTypeface instead. */
@@ -219,6 +224,9 @@
     return face->style();
 }
 
+void TypefaceImpl_setDefault(TypefaceImpl* face) {
+}
+
 #endif  // USE_MINIKIN
 
 }
diff --git a/core/jni/android/graphics/TypefaceImpl.h b/core/jni/android/graphics/TypefaceImpl.h
index 4e021cd..4e92bce 100644
--- a/core/jni/android/graphics/TypefaceImpl.h
+++ b/core/jni/android/graphics/TypefaceImpl.h
@@ -61,6 +61,8 @@
 
 int TypefaceImpl_getStyle(TypefaceImpl* face);
 
+void TypefaceImpl_setDefault(TypefaceImpl* face);
+
 }
 
 #endif  // ANDROID_TYPEFACE_IMPL_H
\ No newline at end of file
diff --git a/graphics/java/android/graphics/FontFamily.java b/graphics/java/android/graphics/FontFamily.java
index afa8706..7c55ae8 100644
--- a/graphics/java/android/graphics/FontFamily.java
+++ b/graphics/java/android/graphics/FontFamily.java
@@ -24,9 +24,17 @@
  * @hide
  */
 public class FontFamily {
+    /**
+     * @hide
+     */
     public long mNativePtr;
+
     public FontFamily() {
         mNativePtr = nCreateFamily();
+        mNativePtr = nCreateFamily();
+        if (mNativePtr == 0) {
+            throw new RuntimeException();
+        }
     }
     // TODO: finalization
 
diff --git a/graphics/java/android/graphics/FontListParser.java b/graphics/java/android/graphics/FontListParser.java
new file mode 100644
index 0000000..f304f4e
--- /dev/null
+++ b/graphics/java/android/graphics/FontListParser.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2014 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 android.graphics;
+
+import android.util.Xml;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Parser for font config files.
+ *
+ * @hide
+ */
+public class FontListParser {
+
+    public static class Family {
+        public Family(List<String> names, List<String> fontFiles) {
+            this.names = names;
+            this.fontFiles = fontFiles;
+        }
+
+        public List<String> names;
+        // todo: need attributes for font files
+        public List<String> fontFiles;
+    }
+
+    /* Parse fallback list (no names) */
+    public static List<Family> parse(InputStream in) throws XmlPullParserException, IOException {
+        try {
+            XmlPullParser parser = Xml.newPullParser();
+            parser.setInput(in, null);
+            parser.nextTag();
+            return readFamilies(parser);
+        } finally {
+            in.close();
+        }
+    }
+
+    private static List<Family> readFamilies(XmlPullParser parser)
+            throws XmlPullParserException, IOException {
+        List<Family> families = new ArrayList<Family>();
+        parser.require(XmlPullParser.START_TAG, null, "familyset");
+        while (parser.next() != XmlPullParser.END_TAG) {
+            if (parser.getEventType() != XmlPullParser.START_TAG) continue;
+            if (parser.getName().equals("family")) {
+                families.add(readFamily(parser));
+            } else {
+                skip(parser);
+            }
+        }
+        return families;
+    }
+
+    private static Family readFamily(XmlPullParser parser)
+            throws XmlPullParserException, IOException {
+        List<String> names = null;
+        List<String> fontFiles = new ArrayList<String>();
+        while (parser.next() != XmlPullParser.END_TAG) {
+            if (parser.getEventType() != XmlPullParser.START_TAG) continue;
+            String tag = parser.getName();
+            if (tag.equals("fileset")) {
+                while (parser.next() != XmlPullParser.END_TAG) {
+                    if (parser.getEventType() != XmlPullParser.START_TAG) continue;
+                    if (parser.getName().equals("file")) {
+                        String filename = parser.nextText();
+                        String fullFilename = "/system/fonts/" + filename;
+                        fontFiles.add(fullFilename);
+                    }
+                }
+            } else if (tag.equals("nameset")) {
+                names = new ArrayList<String>();
+                while (parser.next() != XmlPullParser.END_TAG) {
+                    if (parser.getEventType() != XmlPullParser.START_TAG) continue;
+                    if (parser.getName().equals("name")) {
+                        String name = parser.nextText();
+                        names.add(name);
+                    }
+                }
+            }
+        }
+        return new Family(names, fontFiles);
+    }
+
+    private static void skip(XmlPullParser parser) throws XmlPullParserException, IOException {
+        int depth = 1;
+        while (depth > 0) {
+            switch (parser.next()) {
+            case XmlPullParser.START_TAG:
+                depth++;
+                break;
+            case XmlPullParser.END_TAG:
+                depth--;
+                break;
+            }
+        }
+    }
+}
diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java
index 4b95fcd..7322948 100644
--- a/graphics/java/android/graphics/Typeface.java
+++ b/graphics/java/android/graphics/Typeface.java
@@ -17,11 +17,21 @@
 package android.graphics;
 
 import android.content.res.AssetManager;
+import android.graphics.FontListParser.Family;
 import android.util.Log;
-import android.util.SparseArray;
 import android.util.LongSparseArray;
+import android.util.SparseArray;
+
+import org.xmlpull.v1.XmlPullParserException;
 
 import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
 
 /**
  * The Typeface class specifies the typeface and intrinsic style of a font.
@@ -31,6 +41,8 @@
  */
 public class Typeface {
 
+    private static String TAG = "Typeface";
+
     /** The default NORMAL typeface object */
     public static final Typeface DEFAULT;
     /**
@@ -50,6 +62,9 @@
     private static final LongSparseArray<SparseArray<Typeface>> sTypefaceCache =
             new LongSparseArray<SparseArray<Typeface>>(3);
 
+    static Typeface sDefaultTypeface;
+    static Map<String, Typeface> sSystemFontMap;
+
     /**
      * @hide
      */
@@ -63,6 +78,11 @@
 
     private int mStyle = 0;
 
+    private static void setDefault(Typeface t) {
+        sDefaultTypeface = t;
+        nativeSetDefault(t.native_instance);
+    }
+
     /** Returns the typeface's intrinsic style attributes */
     public int getStyle() {
         return mStyle;
@@ -90,6 +110,9 @@
      * @return The best matching typeface.
      */
     public static Typeface create(String familyName, int style) {
+        if (sSystemFontMap != null) {
+            return create(sSystemFontMap.get(familyName), style);
+        }
         return new Typeface(nativeCreate(familyName, style));
     }
 
@@ -143,7 +166,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
@@ -157,7 +180,7 @@
     /**
      * Create a new typeface from the specified font file.
      *
-     * @param path The path to the font data. 
+     * @param path The path to the font data.
      * @return The new typeface.
      */
     public static Typeface createFromFile(File path) {
@@ -167,7 +190,7 @@
     /**
      * Create a new typeface from the specified font file.
      *
-     * @param path The full path to the font data. 
+     * @param path The full path to the font data.
      * @return The new typeface.
      */
     public static Typeface createFromFile(String path) {
@@ -199,14 +222,64 @@
         native_instance = ni;
         mStyle = nativeGetStyle(ni);
     }
-    
+
+    private static FontFamily makeFamilyFromParsed(FontListParser.Family family) {
+        // TODO: expand to handle attributes like lang and variant
+        FontFamily fontFamily = new FontFamily();
+        for (String fontFile : family.fontFiles) {
+            fontFamily.addFont(new File(fontFile));
+        }
+        return fontFamily;
+    }
+
     static {
+        // Load font config and initialize Minikin state
+        String systemConfigFilename = "/system/etc/system_fonts.xml";
+        String configFilename = "/system/etc/fallback_fonts.xml";
+        try {
+            // TODO: throws an exception non-Minikin builds, to fail early;
+            // remove when Minikin-only
+            new FontFamily();
+
+            FileInputStream systemIn = new FileInputStream(systemConfigFilename);
+            List<FontListParser.Family> systemFontConfig = FontListParser.parse(systemIn);
+            Map<String, Typeface> systemFonts = new HashMap<String, Typeface>();
+            for (Family f : systemFontConfig) {
+                FontFamily fontFamily = makeFamilyFromParsed(f);
+                FontFamily[] families = { fontFamily };
+                Typeface typeface = Typeface.createFromFamilies(families);
+                for (String name : f.names) {
+                    systemFonts.put(name, typeface);
+                }
+            }
+            sSystemFontMap = systemFonts;
+
+            FileInputStream fallbackIn = new FileInputStream(configFilename);
+            List<FontFamily> families = new ArrayList<FontFamily>();
+            families.add(makeFamilyFromParsed(systemFontConfig.get(0)));
+            for (Family f : FontListParser.parse(fallbackIn)) {
+                families.add(makeFamilyFromParsed(f));
+            }
+            FontFamily[] familyArray = families.toArray(new FontFamily[families.size()]);
+            setDefault(Typeface.createFromFamilies(familyArray));
+        } catch (RuntimeException e) {
+            Log.w(TAG, "Didn't create default family (most likely, non-Minikin build)");
+            // TODO: normal in non-Minikin case, remove or make error when Minikin-only
+        } catch (FileNotFoundException e) {
+            Log.e(TAG, "Error opening " + configFilename);
+        } catch (IOException e) {
+            Log.e(TAG, "Error reading " + configFilename);
+        } catch (XmlPullParserException e) {
+            Log.e(TAG, "XML parse exception for " + configFilename);
+        }
+
+        // Set up defaults and typefaces exposed in public API
         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,
@@ -215,6 +288,7 @@
         };
     }
 
+    @Override
     protected void finalize() throws Throwable {
         try {
             nativeUnref(native_instance);
@@ -252,4 +326,5 @@
     private static native long nativeCreateFromAsset(AssetManager mgr, String path);
     private static native long nativeCreateFromFile(String path);
     private static native long nativeCreateFromArray(long[] familyArray);
+    private static native void nativeSetDefault(long native_instance);
 }