diff --git a/core/jni/android/graphics/FontFamily.cpp b/core/jni/android/graphics/FontFamily.cpp index 1d465b3041e3e..bfb30b7efc245 100644 --- a/core/jni/android/graphics/FontFamily.cpp +++ b/core/jni/android/graphics/FontFamily.cpp @@ -62,10 +62,26 @@ static jboolean FontFamily_addFont(JNIEnv* env, jobject clazz, jlong familyPtr, ALOGE("addFont failed to create font %s", str.c_str()); return false; } - FontFamily* fontFamily = (FontFamily*)familyPtr; + FontFamily* fontFamily = reinterpret_cast(familyPtr); return addSkTypeface(fontFamily, face); } +static jboolean FontFamily_addFontWeightStyle(JNIEnv* env, jobject clazz, jlong familyPtr, + jstring path, jint weight, jboolean isItalic) { + NPE_CHECK_RETURN_ZERO(env, path); + ScopedUtfChars str(env, path); + SkTypeface* face = SkTypeface::CreateFromFile(str.c_str()); + if (face == NULL) { + ALOGE("addFont failed to create font %s", str.c_str()); + return false; + } + FontFamily* fontFamily = reinterpret_cast(familyPtr); + MinikinFont* minikinFont = new MinikinFontSkia(face); + fontFamily->addFont(minikinFont, FontStyle(weight / 100, isItalic)); + minikinFont->Unref(); + return true; +} + static jboolean FontFamily_addFontFromAsset(JNIEnv* env, jobject, jlong familyPtr, jobject jassetMgr, jstring jpath) { NPE_CHECK_RETURN_ZERO(env, jassetMgr); @@ -92,17 +108,18 @@ static jboolean FontFamily_addFontFromAsset(JNIEnv* env, jobject, jlong familyPt ALOGE("addFontFromAsset failed to create font %s", str.c_str()); return false; } - FontFamily* fontFamily = (FontFamily*)familyPtr; + FontFamily* fontFamily = reinterpret_cast(familyPtr); return addSkTypeface(fontFamily, face); } /////////////////////////////////////////////////////////////////////////////// static JNINativeMethod gFontFamilyMethods[] = { - { "nCreateFamily", "(Ljava/lang/String;I)J", (void*)FontFamily_create }, - { "nUnrefFamily", "(J)V", (void*)FontFamily_unref }, - { "nAddFont", "(JLjava/lang/String;)Z", (void*)FontFamily_addFont }, - { "nAddFontFromAsset", "(JLandroid/content/res/AssetManager;Ljava/lang/String;)Z", + { "nCreateFamily", "(Ljava/lang/String;I)J", (void*)FontFamily_create }, + { "nUnrefFamily", "(J)V", (void*)FontFamily_unref }, + { "nAddFont", "(JLjava/lang/String;)Z", (void*)FontFamily_addFont }, + { "nAddFontWeightStyle", "(JLjava/lang/String;IZ)Z", (void*)FontFamily_addFontWeightStyle }, + { "nAddFontFromAsset", "(JLandroid/content/res/AssetManager;Ljava/lang/String;)Z", (void*)FontFamily_addFontFromAsset }, }; diff --git a/core/jni/android/graphics/Typeface.cpp b/core/jni/android/graphics/Typeface.cpp index cf4e8386615fd..2029658e033d9 100644 --- a/core/jni/android/graphics/Typeface.cpp +++ b/core/jni/android/graphics/Typeface.cpp @@ -41,6 +41,12 @@ static jlong Typeface_createFromTypeface(JNIEnv* env, jobject, jlong familyHandl return reinterpret_cast(face); } +static jlong Typeface_createWeightAlias(JNIEnv* env, jobject, jlong familyHandle, jint weight) { + TypefaceImpl* family = reinterpret_cast(familyHandle); + TypefaceImpl* face = TypefaceImpl_createWeightAlias(family, weight); + return reinterpret_cast(face); +} + static void Typeface_unref(JNIEnv* env, jobject obj, jlong faceHandle) { TypefaceImpl* face = reinterpret_cast(faceHandle); TypefaceImpl_unref(face); @@ -65,6 +71,7 @@ static void Typeface_setDefault(JNIEnv *env, jobject, jlong faceHandle) { static JNINativeMethod gTypefaceMethods[] = { { "nativeCreateFromTypeface", "(JI)J", (void*)Typeface_createFromTypeface }, + { "nativeCreateWeightAlias", "(JI)J", (void*)Typeface_createWeightAlias }, { "nativeUnref", "(J)V", (void*)Typeface_unref }, { "nativeGetStyle", "(J)I", (void*)Typeface_getStyle }, { "nativeCreateFromArray", "([J)J", diff --git a/core/jni/android/graphics/TypefaceImpl.cpp b/core/jni/android/graphics/TypefaceImpl.cpp index 9ce6de118794d..7afbeb2aba5a7 100644 --- a/core/jni/android/graphics/TypefaceImpl.cpp +++ b/core/jni/android/graphics/TypefaceImpl.cpp @@ -39,14 +39,17 @@ namespace android { -// Any weight greater than or equal to this is considered "bold" for -// legacy API. -static const int kBoldThreshold = 6; - -static FontStyle styleFromSkiaStyle(SkTypeface::Style skiaStyle) { - int weight = (skiaStyle & SkTypeface::kBold) != 0 ? 7 : 4; - bool italic = (skiaStyle & SkTypeface::kItalic) != 0; - return FontStyle(weight, italic); +// Resolve the 1..9 weight based on base weight and bold flag +static void resolveStyle(TypefaceImpl* typeface) { + int weight = typeface->fBaseWeight / 100; + if (typeface->fSkiaStyle & SkTypeface::kBold) { + weight += 3; + } + if (weight > 9) { + weight = 9; + } + bool italic = (typeface->fSkiaStyle & SkTypeface::kItalic) != 0; + typeface->fStyle = FontStyle(weight, italic); } TypefaceImpl* gDefaultTypeface = NULL; @@ -90,7 +93,9 @@ static void getDefaultTypefaceOnce() { // default so we can make progress before that happens. gDefaultTypeface = new TypefaceImpl; gDefaultTypeface->fFontCollection = makeFontCollection(); - gDefaultTypeface->fStyle = FontStyle(); + gDefaultTypeface->fSkiaStyle = SkTypeface::kNormal; + gDefaultTypeface->fBaseWeight = 400; + resolveStyle(gDefaultTypeface); } } @@ -109,25 +114,23 @@ TypefaceImpl* TypefaceImpl_createFromTypeface(TypefaceImpl* src, SkTypeface::Sty if (result != 0) { result->fFontCollection = resolvedFace->fFontCollection; result->fFontCollection->Ref(); - result->fStyle = styleFromSkiaStyle(style); + result->fSkiaStyle = style; + result->fBaseWeight = resolvedFace->fBaseWeight; + resolveStyle(result); } return result; } -static TypefaceImpl* createFromSkTypeface(SkTypeface* typeface) { - if (typeface == NULL) { - return NULL; - } - MinikinFont* minikinFont = new MinikinFontSkia(typeface); - std::vector typefaces; - FontFamily* family = new FontFamily(); - family->addFont(minikinFont); - minikinFont->Unref(); - typefaces.push_back(family); +TypefaceImpl* TypefaceImpl_createWeightAlias(TypefaceImpl* src, int weight) { + TypefaceImpl* resolvedFace = TypefaceImpl_resolveDefault(src); TypefaceImpl* result = new TypefaceImpl; - result->fFontCollection = new FontCollection(typefaces); - family->Unref(); - result->fStyle = FontStyle(); // TODO: improve + if (result != 0) { + result->fFontCollection = resolvedFace->fFontCollection; + result->fFontCollection->Ref(); + result->fSkiaStyle = resolvedFace->fSkiaStyle; + result->fBaseWeight = weight; + resolveStyle(result); + } return result; } @@ -141,7 +144,7 @@ TypefaceImpl* TypefaceImpl_createFromFamilies(const jlong* families, size_t size result->fFontCollection = new FontCollection(familyVec); if (size == 0) { ALOGW("createFromFamilies creating empty collection"); - result->fStyle = FontStyle(); + result->fSkiaStyle = SkTypeface::kNormal; } else { const FontStyle defaultStyle; FontFamily* firstFamily = reinterpret_cast(families[0]); @@ -150,11 +153,13 @@ TypefaceImpl* TypefaceImpl_createFromFamilies(const jlong* families, size_t size SkTypeface* skTypeface = reinterpret_cast(mf)->GetSkTypeface(); // TODO: probably better to query more precise style from family, will be important // when we open up API to access 100..900 weights - result->fStyle = styleFromSkiaStyle(skTypeface->style()); + result->fSkiaStyle = skTypeface->style(); } else { - result->fStyle = defaultStyle; + result->fSkiaStyle = SkTypeface::kNormal; } } + result->fBaseWeight = 400; + resolveStyle(result); return result; } @@ -166,12 +171,7 @@ void TypefaceImpl_unref(TypefaceImpl* face) { } int TypefaceImpl_getStyle(TypefaceImpl* face) { - FontStyle style = face->fStyle; - int result = style.getItalic() ? SkTypeface::kItalic : 0; - if (style.getWeight() >= kBoldThreshold) { - result |= SkTypeface::kBold; - } - return result; + return face->fSkiaStyle; } void TypefaceImpl_setDefault(TypefaceImpl* face) { diff --git a/core/jni/android/graphics/TypefaceImpl.h b/core/jni/android/graphics/TypefaceImpl.h index 12b3403a2d954..d129f6217b62c 100644 --- a/core/jni/android/graphics/TypefaceImpl.h +++ b/core/jni/android/graphics/TypefaceImpl.h @@ -28,6 +28,13 @@ namespace android { struct TypefaceImpl { FontCollection *fFontCollection; + + // style used for constructing and querying Typeface objects + SkTypeface::Style fSkiaStyle; + // base weight in CSS-style units, 100..900 + int fBaseWeight; + + // resolved style actually used for rendering FontStyle fStyle; }; @@ -41,6 +48,8 @@ TypefaceImpl* TypefaceImpl_resolveDefault(TypefaceImpl* src); TypefaceImpl* TypefaceImpl_createFromTypeface(TypefaceImpl* src, SkTypeface::Style style); +TypefaceImpl* TypefaceImpl_createWeightAlias(TypefaceImpl* src, int baseweight); + // When we remove the USE_MINIKIN ifdef, probably a good idea to move the casting // (from jlong to FontFamily*) to the caller in Typeface.cpp. TypefaceImpl* TypefaceImpl_createFromFamilies(const jlong* families, size_t size); diff --git a/data/fonts/fallback_fonts.xml b/data/fonts/fallback_fonts.xml index 0f0281bfd96c1..c4a949feca29e 100644 --- a/data/fonts/fallback_fonts.xml +++ b/data/fonts/fallback_fonts.xml @@ -1,5 +1,9 @@ @@ -10,15 +26,16 @@ Roboto-Italic.ttf Roboto-Medium.ttf Roboto-MediumItalic.ttf - Roboto-Bold.ttf - Roboto-BoldItalic.ttf Roboto-Black.ttf Roboto-BlackItalic.ttf + Roboto-Bold.ttf + Roboto-BoldItalic.ttf + @@ -227,6 +244,9 @@ NotoColorEmoji.ttf + + DroidSansFallback.ttf + MTLmr3m.ttf diff --git a/graphics/java/android/graphics/FontFamily.java b/graphics/java/android/graphics/FontFamily.java index 11d3165fdc298..b8b7e76e3b207 100644 --- a/graphics/java/android/graphics/FontFamily.java +++ b/graphics/java/android/graphics/FontFamily.java @@ -18,8 +18,6 @@ package android.graphics; import android.content.res.AssetManager; -import java.io.File; - /** * A family of typefaces with different styles. * @@ -64,6 +62,10 @@ public class FontFamily { return nAddFont(mNativePtr, path); } + public boolean addFontWeightStyle(String path, int weight, boolean style) { + return nAddFontWeightStyle(mNativePtr, path, weight, style); + } + public boolean addFontFromAsset(AssetManager mgr, String path) { return nAddFontFromAsset(mNativePtr, mgr, path); } @@ -71,6 +73,8 @@ public class FontFamily { private static native long nCreateFamily(String lang, int variant); private static native void nUnrefFamily(long nativePtr); private static native boolean nAddFont(long nativeFamily, String path); + private static native boolean nAddFontWeightStyle(long nativeFamily, String path, + int weight, boolean isItalic); private static native boolean nAddFontFromAsset(long nativeFamily, AssetManager mgr, String path); } diff --git a/graphics/java/android/graphics/FontListParser.java b/graphics/java/android/graphics/FontListParser.java index a863a06121fc6..97081f9c77fd3 100644 --- a/graphics/java/android/graphics/FontListParser.java +++ b/graphics/java/android/graphics/FontListParser.java @@ -33,23 +33,48 @@ import java.util.List; */ public class FontListParser { + public static class Config { + Config() { + families = new ArrayList(); + aliases = new ArrayList(); + } + public List families; + public List aliases; + } + + public static class Font { + Font(String fontName, int weight, boolean isItalic) { + this.fontName = fontName; + this.weight = weight; + this.isItalic = isItalic; + } + public String fontName; + public int weight; + public boolean isItalic; + } + + public static class Alias { + public String name; + public String toName; + public int weight; + } + public static class Family { - public Family(List names, List fontFiles, String lang, String variant) { - this.names = names; - this.fontFiles = fontFiles; + public Family(String name, List fonts, String lang, String variant) { + this.name = name; + this.fonts = fonts; this.lang = lang; this.variant = variant; } - public List names; - // todo: need attributes for font files - public List fontFiles; + public String name; + public List fonts; public String lang; public String variant; } /* Parse fallback list (no names) */ - public static List parse(InputStream in) throws XmlPullParserException, IOException { + public static Config parse(InputStream in) throws XmlPullParserException, IOException { try { XmlPullParser parser = Xml.newPullParser(); parser.setInput(in, null); @@ -60,57 +85,59 @@ public class FontListParser { } } - private static List readFamilies(XmlPullParser parser) + private static Config readFamilies(XmlPullParser parser) throws XmlPullParserException, IOException { - List families = new ArrayList(); + Config config = new Config(); 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)); + config.families.add(readFamily(parser)); + } else if (parser.getName().equals("alias")) { + config.aliases.add(readAlias(parser)); } else { skip(parser); } } - return families; + return config; } private static Family readFamily(XmlPullParser parser) throws XmlPullParserException, IOException { - List names = null; - List fontFiles = new ArrayList(); - String lang = null; - String variant = null; + String name = parser.getAttributeValue(null, "name"); + String lang = parser.getAttributeValue(null, "lang"); + String variant = parser.getAttributeValue(null, "variant"); + List fonts = new ArrayList(); 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")) { - if (lang == null) { - lang = parser.getAttributeValue(null, "lang"); - } - if (variant == null) { - variant = parser.getAttributeValue(null, "variant"); - } - String filename = parser.nextText(); - String fullFilename = "/system/fonts/" + filename; - fontFiles.add(fullFilename); - } - } - } else if (tag.equals("nameset")) { - names = new ArrayList(); - 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); - } - } + if (tag.equals("font")) { + String weightStr = parser.getAttributeValue(null, "weight"); + int weight = weightStr == null ? 400 : Integer.parseInt(weightStr); + boolean isItalic = "italic".equals(parser.getAttributeValue(null, "style")); + String filename = parser.nextText(); + String fullFilename = "/system/fonts/" + filename; + fonts.add(new Font(fullFilename, weight, isItalic)); + } else { + skip(parser); } } - return new Family(names, fontFiles, lang, variant); + return new Family(name, fonts, lang, variant); + } + + private static Alias readAlias(XmlPullParser parser) + throws XmlPullParserException, IOException { + Alias alias = new Alias(); + alias.name = parser.getAttributeValue(null, "name"); + alias.toName = parser.getAttributeValue(null, "to"); + String weightStr = parser.getAttributeValue(null, "weight"); + if (weightStr == null) { + alias.weight = 400; + } else { + alias.weight = Integer.parseInt(weightStr); + } + skip(parser); // alias tag is empty, ignore any contents and consume end tag + return alias; } private static void skip(XmlPullParser parser) throws XmlPullParserException, IOException { diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java index 568d3f2aad4ea..db42314447ffa 100644 --- a/graphics/java/android/graphics/Typeface.java +++ b/graphics/java/android/graphics/Typeface.java @@ -66,8 +66,7 @@ public class Typeface { static Map sSystemFontMap; static FontFamily[] sFallbackFonts; - static final String SYSTEM_FONTS_CONFIG = "system_fonts.xml"; - static final String FALLBACK_FONTS_CONFIG = "fallback_fonts.xml"; + static final String FONTS_CONFIG = "fonts.xml"; /** * @hide @@ -261,10 +260,9 @@ public class Typeface { } private static FontFamily makeFamilyFromParsed(FontListParser.Family family) { - // TODO: expand to handle attributes like lang and variant FontFamily fontFamily = new FontFamily(family.lang, family.variant); - for (String fontFile : family.fontFiles) { - fontFamily.addFont(fontFile); + for (FontListParser.Font font : family.fonts) { + fontFamily.addFontWeightStyle(font.fontName, font.weight, font.isItalic); } return fontFamily; } @@ -277,48 +275,53 @@ public class Typeface { private static void init() { // Load font config and initialize Minikin state File systemFontConfigLocation = getSystemFontConfigLocation(); - File systemConfigFilename = new File(systemFontConfigLocation, SYSTEM_FONTS_CONFIG); - File configFilename = new File(systemFontConfigLocation, FALLBACK_FONTS_CONFIG); + File configFilename = new File(systemFontConfigLocation, FONTS_CONFIG); try { - // TODO: throws an exception non-Minikin builds, to fail early; - // remove when Minikin-only - new FontFamily(); + FileInputStream fontsIn = new FileInputStream(configFilename); + FontListParser.Config fontConfig = FontListParser.parse(fontsIn); - FileInputStream systemIn = new FileInputStream(systemConfigFilename); - List systemFontConfig = FontListParser.parse(systemIn); - - FileInputStream fallbackIn = new FileInputStream(configFilename); List familyList = new ArrayList(); // Note that the default typeface is always present in the fallback list; // this is an enhancement from pre-Minikin behavior. - familyList.add(makeFamilyFromParsed(systemFontConfig.get(0))); - for (Family f : FontListParser.parse(fallbackIn)) { - familyList.add(makeFamilyFromParsed(f)); + for (int i = 0; i < fontConfig.families.size(); i++) { + Family f = fontConfig.families.get(i); + if (i == 0 || f.name == null) { + familyList.add(makeFamilyFromParsed(f)); + } } sFallbackFonts = familyList.toArray(new FontFamily[familyList.size()]); setDefault(Typeface.createFromFamilies(sFallbackFonts)); Map systemFonts = new HashMap(); - for (int i = 0; i < systemFontConfig.size(); i++) { + for (int i = 0; i < fontConfig.families.size(); i++) { Typeface typeface; - Family f = systemFontConfig.get(i); - if (i == 0) { - // The first entry is the default typeface; no sense in duplicating - // the corresponding FontFamily. - typeface = sDefaultTypeface; - } else { - FontFamily fontFamily = makeFamilyFromParsed(f); - FontFamily[] families = { fontFamily }; - typeface = Typeface.createFromFamiliesWithDefault(families); + Family f = fontConfig.families.get(i); + if (f.name != null) { + if (i == 0) { + // The first entry is the default typeface; no sense in + // duplicating the corresponding FontFamily. + typeface = sDefaultTypeface; + } else { + FontFamily fontFamily = makeFamilyFromParsed(f); + FontFamily[] families = { fontFamily }; + typeface = Typeface.createFromFamiliesWithDefault(families); + } + systemFonts.put(f.name, typeface); } - for (String name : f.names) { - systemFonts.put(name, typeface); + } + for (FontListParser.Alias alias : fontConfig.aliases) { + Typeface base = systemFonts.get(alias.toName); + Typeface newFace = base; + int weight = alias.weight; + if (weight != 400) { + newFace = new Typeface(nativeCreateWeightAlias(base.native_instance, weight)); } + systemFonts.put(alias.name, newFace); } sSystemFontMap = systemFonts; } catch (RuntimeException e) { - Log.w(TAG, "Didn't create default family (most likely, non-Minikin build)"); + Log.w(TAG, "Didn't create default family (most likely, non-Minikin build)", e); // TODO: normal in non-Minikin case, remove or make error when Minikin-only } catch (FileNotFoundException e) { Log.e(TAG, "Error opening " + configFilename); @@ -383,6 +386,7 @@ public class Typeface { } private static native long nativeCreateFromTypeface(long native_instance, int style); + private static native long nativeCreateWeightAlias(long native_instance, int weight); private static native void nativeUnref(long native_instance); private static native int nativeGetStyle(long native_instance); private static native long nativeCreateFromArray(long[] familyArray);