New weight-aware font config

Parse new fonts.xml config file, and resolve weight selection based on
the base weight of the font (as defined by a weight alias specified in
the config file) and the requested bold flag. This change improves the
appearance of bold spans for alternate weights of Roboto.

In addition, this patch enables weight selection for fallback fonts.
For example, if an additional font with a weight of 100 is added to the
Hebrew font family in the fallback list, then requesting
"sans-serif-thin" would select that font for Hebrew text.

Bug: 14538154
Change-Id: I99a04fad4f7bf01c75726e760d42735dd9003496
This commit is contained in:
Raph Levien
2014-08-25 13:47:16 -07:00
parent 0db4f35e0b
commit 117cbebe81
9 changed files with 204 additions and 112 deletions

View File

@@ -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<FontFamily*>(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<FontFamily*>(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<FontFamily*>(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 },
};

View File

@@ -41,6 +41,12 @@ static jlong Typeface_createFromTypeface(JNIEnv* env, jobject, jlong familyHandl
return reinterpret_cast<jlong>(face);
}
static jlong Typeface_createWeightAlias(JNIEnv* env, jobject, jlong familyHandle, jint weight) {
TypefaceImpl* family = reinterpret_cast<TypefaceImpl*>(familyHandle);
TypefaceImpl* face = TypefaceImpl_createWeightAlias(family, weight);
return reinterpret_cast<jlong>(face);
}
static void Typeface_unref(JNIEnv* env, jobject obj, jlong faceHandle) {
TypefaceImpl* face = reinterpret_cast<TypefaceImpl*>(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",

View File

@@ -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<FontFamily *> 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<FontFamily*>(families[0]);
@@ -150,11 +153,13 @@ TypefaceImpl* TypefaceImpl_createFromFamilies(const jlong* families, size_t size
SkTypeface* skTypeface = reinterpret_cast<MinikinFontSkia*>(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) {

View File

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

View File

@@ -1,5 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
NOTE: this file is the legacy format, for compatibility with apps. The new,
more flexible format is fonts.xml. Please keep the two in sync until the legacy
format can be fully removed.
Fallback Fonts
This file specifies the fonts, and the priority order, that will be searched for any

View File

@@ -1,4 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
NOTE: this is the newer (L) version of the system font configuration,
supporting richer weight selection. Some apps will expect the older
version, so please keep system_fonts.xml and fallback_fonts.xml in sync
with any changes, even though framework will only read this file.
All fonts withohut names are added to the default list. Fonts are chosen
based on a match: full BCP-47 language tag including script, then just
language, and finally order (the first font containing the glyph).
Order of appearance is also the tiebreaker for weight matching. This is
the reason why the 900 weights of Roboto precede the 700 weights - we
prefer the former when an 800 weight is requested. Since bold spans
effectively add 300 to the weight, this ensures that 900 is the bold
paired with the 500 weight, ensuring adequate contrast.
-->
<familyset version="22">
<!-- first font is default -->
<family name="sans-serif">
@@ -10,15 +26,16 @@
<font weight="400" style="italic">Roboto-Italic.ttf</font>
<font weight="500" style="normal">Roboto-Medium.ttf</font>
<font weight="500" style="italic">Roboto-MediumItalic.ttf</font>
<font weight="700" style="normal">Roboto-Bold.ttf</font>
<font weight="700" style="italic">Roboto-BoldItalic.ttf</font>
<font weight="900" style="normal">Roboto-Black.ttf</font>
<font weight="900" style="italic">Roboto-BlackItalic.ttf</font>
<font weight="700" style="normal">Roboto-Bold.ttf</font>
<font weight="700" style="italic">Roboto-BoldItalic.ttf</font>
</family>
<!-- Note that aliases must come after the fonts they reference. -->
<alias name="sans-serif-thin" to="sans-serif" weight="100" />
<alias name="sans-serif-light" to="sans-serif" weight="300" />
<alias name="sans-serif-medium" to="sans-serif" weight="500" />
<alias name="sans-serif-black" to="sans-serif" weight="900" />
<alias name="arial" to="sans-serif" />
<alias name="helvetica" to="sans-serif" />
@@ -227,6 +244,9 @@
<family>
<font weight="400" style="normal">NotoColorEmoji.ttf</font>
</family>
<family>
<font weight="400" style="normal">DroidSansFallback.ttf</font>
</family>
<family lang="ja">
<font weight="400" style="normal">MTLmr3m.ttf</font>
</family>

View File

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

View File

@@ -33,23 +33,48 @@ import java.util.List;
*/
public class FontListParser {
public static class Config {
Config() {
families = new ArrayList<Family>();
aliases = new ArrayList<Alias>();
}
public List<Family> families;
public List<Alias> 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<String> names, List<String> fontFiles, String lang, String variant) {
this.names = names;
this.fontFiles = fontFiles;
public Family(String name, List<Font> fonts, String lang, String variant) {
this.name = name;
this.fonts = fonts;
this.lang = lang;
this.variant = variant;
}
public List<String> names;
// todo: need attributes for font files
public List<String> fontFiles;
public String name;
public List<Font> fonts;
public String lang;
public String variant;
}
/* Parse fallback list (no names) */
public static List<Family> 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<Family> readFamilies(XmlPullParser parser)
private static Config readFamilies(XmlPullParser parser)
throws XmlPullParserException, IOException {
List<Family> families = new ArrayList<Family>();
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<String> names = null;
List<String> fontFiles = new ArrayList<String>();
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<Font> fonts = new ArrayList<Font>();
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<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);
}
}
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 {

View File

@@ -66,8 +66,7 @@ public class Typeface {
static Map<String, Typeface> 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<FontListParser.Family> systemFontConfig = FontListParser.parse(systemIn);
FileInputStream fallbackIn = new FileInputStream(configFilename);
List<FontFamily> familyList = new ArrayList<FontFamily>();
// 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<String, Typeface> systemFonts = new HashMap<String, Typeface>();
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);