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);
}