Add FontListParser#writeFamily().

Bug: 179103383
Test: atest FrameworksCoreTests:FontListParserTest
Change-Id: I992acd13dc5dc084a00ee9f3832c4d7111ecd4e4
This commit is contained in:
Kohsuke Yatoh
2021-02-08 16:11:39 -08:00
parent 5a939299b0
commit f91ea1b201
3 changed files with 450 additions and 16 deletions

View File

@@ -36,6 +36,7 @@ import java.io.File;
import java.lang.annotation.Retention;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
@@ -155,6 +156,32 @@ public final class FontConfig implements Parcelable {
}
};
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
FontConfig that = (FontConfig) o;
return mLastModifiedTimeMillis == that.mLastModifiedTimeMillis
&& mConfigVersion == that.mConfigVersion
&& Objects.equals(mFamilies, that.mFamilies)
&& Objects.equals(mAliases, that.mAliases);
}
@Override
public int hashCode() {
return Objects.hash(mFamilies, mAliases, mLastModifiedTimeMillis, mConfigVersion);
}
@Override
public String toString() {
return "FontConfig{"
+ "mFamilies=" + mFamilies
+ ", mAliases=" + mAliases
+ ", mLastModifiedTimeMillis=" + mLastModifiedTimeMillis
+ ", mConfigVersion=" + mConfigVersion
+ '}';
}
/**
* Represents single font entry in system font configuration.
*
@@ -317,6 +344,37 @@ public final class FontConfig implements Parcelable {
public boolean isItalic() {
return getStyle().getSlant() == FontStyle.FONT_SLANT_ITALIC;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Font font = (Font) o;
return mIndex == font.mIndex
&& Objects.equals(mFile, font.mFile)
&& Objects.equals(mOriginalFile, font.mOriginalFile)
&& Objects.equals(mStyle, font.mStyle)
&& Objects.equals(mFontVariationSettings, font.mFontVariationSettings)
&& Objects.equals(mFontFamilyName, font.mFontFamilyName);
}
@Override
public int hashCode() {
return Objects.hash(mFile, mOriginalFile, mStyle, mIndex, mFontVariationSettings,
mFontFamilyName);
}
@Override
public String toString() {
return "Font{"
+ "mFile=" + mFile
+ ", mOriginalFile=" + mOriginalFile
+ ", mStyle=" + mStyle
+ ", mIndex=" + mIndex
+ ", mFontVariationSettings='" + mFontVariationSettings + '\''
+ ", mFontFamilyName='" + mFontFamilyName + '\''
+ '}';
}
}
/**
@@ -398,6 +456,30 @@ public final class FontConfig implements Parcelable {
return new Alias[size];
}
};
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Alias alias = (Alias) o;
return mWeight == alias.mWeight
&& Objects.equals(mName, alias.mName)
&& Objects.equals(mOriginal, alias.mOriginal);
}
@Override
public int hashCode() {
return Objects.hash(mName, mOriginal, mWeight);
}
@Override
public String toString() {
return "Alias{"
+ "mName='" + mName + '\''
+ ", mOriginal='" + mOriginal + '\''
+ ", mWeight=" + mWeight
+ '}';
}
}
/**
@@ -413,7 +495,7 @@ public final class FontConfig implements Parcelable {
public static final class FontFamily implements Parcelable {
private final @NonNull List<Font> mFonts;
private final @Nullable String mName;
private final @Nullable LocaleList mLocaleList;
private final @NonNull LocaleList mLocaleList;
private final @Variant int mVariant;
/** @hide */
@@ -454,7 +536,7 @@ public final class FontConfig implements Parcelable {
* @hide Only system server can create this instance and passed via IPC.
*/
public FontFamily(@NonNull List<Font> fonts, @Nullable String name,
@Nullable LocaleList localeList, @Variant int variant) {
@NonNull LocaleList localeList, @Variant int variant) {
mFonts = fonts;
mName = name;
mLocaleList = localeList;
@@ -493,8 +575,6 @@ public final class FontConfig implements Parcelable {
*
* The locale list will be used for deciding which font family should be used in fallback
* list.
*
* @return non-null if a locale list is available. Otherwise null.
*/
public @NonNull LocaleList getLocaleList() {
return mLocaleList;
@@ -559,5 +639,31 @@ public final class FontConfig implements Parcelable {
public @NonNull String getLanguages() {
return mLocaleList.toLanguageTags();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
FontFamily that = (FontFamily) o;
return mVariant == that.mVariant
&& Objects.equals(mFonts, that.mFonts)
&& Objects.equals(mName, that.mName)
&& Objects.equals(mLocaleList, that.mLocaleList);
}
@Override
public int hashCode() {
return Objects.hash(mFonts, mName, mLocaleList, mVariant);
}
@Override
public String toString() {
return "FontFamily{"
+ "mFonts=" + mFonts
+ ", mName='" + mName + '\''
+ ", mLocaleList=" + mLocaleList
+ ", mVariant=" + mVariant
+ '}';
}
}
}

View File

@@ -0,0 +1,245 @@
/*
* Copyright (C) 2021 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 static android.graphics.fonts.FontStyle.FONT_SLANT_ITALIC;
import static android.graphics.fonts.FontStyle.FONT_SLANT_UPRIGHT;
import static android.graphics.fonts.FontStyle.FONT_WEIGHT_NORMAL;
import static android.text.FontConfig.FontFamily.VARIANT_COMPACT;
import static android.text.FontConfig.FontFamily.VARIANT_DEFAULT;
import static android.text.FontConfig.FontFamily.VARIANT_ELEGANT;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import android.graphics.fonts.FontStyle;
import android.os.LocaleList;
import android.text.FontConfig;
import android.util.TypedXmlSerializer;
import android.util.Xml;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
@SmallTest
@RunWith(AndroidJUnit4.class)
public final class FontListParserTest {
@Test
public void named() throws Exception {
String xml = "<?xml version='1.0' encoding='UTF-8'?>"
+ "<family name='sans-serif'>"
+ " <font>test.ttf</font>"
+ "</family>";
FontConfig.FontFamily expected = new FontConfig.FontFamily(
Arrays.asList(
new FontConfig.Font(new File("test.ttf"), null,
new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT),
0, "", null)),
"sans-serif", LocaleList.getEmptyLocaleList(), VARIANT_DEFAULT);
FontConfig.FontFamily family = readFamily(xml);
assertThat(family).isEqualTo(expected);
String serialized = writeFamily(family);
assertWithMessage("serialized = " + serialized)
.that(readFamily(serialized)).isEqualTo(expected);
}
@Test
public void fallback() throws Exception {
String xml = "<?xml version='1.0' encoding='UTF-8'?>"
+ "<family lang='en'>"
+ " <font>test.ttf</font>"
+ " <font fallbackFor='serif'>test.ttf</font>"
+ "</family>";
FontConfig.FontFamily expected = new FontConfig.FontFamily(
Arrays.asList(
new FontConfig.Font(new File("test.ttf"), null,
new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT),
0, "", null),
new FontConfig.Font(new File("test.ttf"), null,
new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT),
0, "", "serif")),
null, LocaleList.forLanguageTags("en"), VARIANT_DEFAULT);
FontConfig.FontFamily family = readFamily(xml);
assertThat(family).isEqualTo(expected);
String serialized = writeFamily(family);
assertWithMessage("serialized = " + serialized)
.that(readFamily(serialized)).isEqualTo(expected);
}
@Test
public void compact() throws Exception {
String xml = "<?xml version='1.0' encoding='UTF-8'?>"
+ "<family lang='en' variant='compact'>"
+ " <font>test.ttf</font>"
+ "</family>";
FontConfig.FontFamily expected = new FontConfig.FontFamily(
Arrays.asList(
new FontConfig.Font(new File("test.ttf"), null,
new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT),
0, "", null)),
null, LocaleList.forLanguageTags("en"), VARIANT_COMPACT);
FontConfig.FontFamily family = readFamily(xml);
assertThat(family).isEqualTo(expected);
String serialized = writeFamily(family);
assertWithMessage("serialized = " + serialized)
.that(readFamily(serialized)).isEqualTo(expected);
}
@Test
public void elegant() throws Exception {
String xml = "<?xml version='1.0' encoding='UTF-8'?>"
+ "<family lang='en' variant='elegant'>"
+ " <font>test.ttf</font>"
+ "</family>";
FontConfig.FontFamily expected = new FontConfig.FontFamily(
Arrays.asList(
new FontConfig.Font(new File("test.ttf"), null,
new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT),
0, "", null)),
null, LocaleList.forLanguageTags("en"), VARIANT_ELEGANT);
FontConfig.FontFamily family = readFamily(xml);
assertThat(family).isEqualTo(expected);
String serialized = writeFamily(family);
assertWithMessage("serialized = " + serialized)
.that(readFamily(serialized)).isEqualTo(expected);
}
@Test
public void styles() throws Exception {
String xml = "<?xml version='1.0' encoding='UTF-8'?>"
+ "<family name='sans-serif'>"
+ " <font style='normal'>normal.ttf</font>"
+ " <font weight='100'>weight.ttf</font>"
+ " <font style='italic'>italic.ttf</font>"
+ "</family>";
FontConfig.FontFamily expected = new FontConfig.FontFamily(
Arrays.asList(
new FontConfig.Font(new File("normal.ttf"), null,
new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT),
0, "", null),
new FontConfig.Font(new File("weight.ttf"), null,
new FontStyle(100, FONT_SLANT_UPRIGHT),
0, "", null),
new FontConfig.Font(new File("italic.ttf"), null,
new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_ITALIC),
0, "", null)),
"sans-serif", LocaleList.getEmptyLocaleList(), VARIANT_DEFAULT);
FontConfig.FontFamily family = readFamily(xml);
assertThat(family).isEqualTo(expected);
String serialized = writeFamily(family);
assertWithMessage("serialized = " + serialized)
.that(readFamily(serialized)).isEqualTo(expected);
}
@Test
public void variable() throws Exception {
String xml = "<?xml version='1.0' encoding='UTF-8'?>"
+ "<family name='sans-serif'>"
+ " <font>test-VF.ttf"
+ " <axis tag='wdth' stylevalue='100' />"
+ " <axis tag='wght' stylevalue='200' />"
+ " </font>"
+ " <font>test-VF.ttf"
+ " <axis tag='wdth' stylevalue='400' />"
+ " <axis tag='wght' stylevalue='700' />"
+ " </font>"
+ "</family>";
FontConfig.FontFamily expected = new FontConfig.FontFamily(
Arrays.asList(
new FontConfig.Font(new File("test-VF.ttf"), null,
new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT),
0, "'wdth' 100.0,'wght' 200.0", null),
new FontConfig.Font(new File("test-VF.ttf"), null,
new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT),
0, "'wdth' 400.0,'wght' 700.0", null)),
"sans-serif", LocaleList.getEmptyLocaleList(), VARIANT_DEFAULT);
FontConfig.FontFamily family = readFamily(xml);
assertThat(family).isEqualTo(expected);
String serialized = writeFamily(family);
assertWithMessage("serialized = " + serialized)
.that(readFamily(serialized)).isEqualTo(expected);
}
@Test
public void ttc() throws Exception {
String xml = "<?xml version='1.0' encoding='UTF-8'?>"
+ "<family name='sans-serif'>"
+ " <font index='0'>test.ttc</font>"
+ " <font index='1'>test.ttc</font>"
+ "</family>";
FontConfig.FontFamily expected = new FontConfig.FontFamily(
Arrays.asList(
new FontConfig.Font(new File("test.ttc"), null,
new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT),
0, "", null),
new FontConfig.Font(new File("test.ttc"), null,
new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT),
1, "", null)),
"sans-serif", LocaleList.getEmptyLocaleList(), VARIANT_DEFAULT);
FontConfig.FontFamily family = readFamily(xml);
assertThat(family).isEqualTo(expected);
String serialized = writeFamily(family);
assertWithMessage("serialized = " + serialized)
.that(readFamily(serialized)).isEqualTo(expected);
}
private FontConfig.FontFamily readFamily(String xml)
throws IOException, XmlPullParserException {
StandardCharsets.UTF_8.name();
ByteArrayInputStream buffer = new ByteArrayInputStream(
xml.getBytes(StandardCharsets.UTF_8));
XmlPullParser parser = Xml.newPullParser();
parser.setInput(buffer, "UTF-8");
parser.nextTag();
return FontListParser.readFamily(parser, "", null);
}
private String writeFamily(FontConfig.FontFamily family) throws IOException {
TypedXmlSerializer out = Xml.newFastSerializer();
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
out.setOutput(buffer, "UTF-8");
out.startTag(null, "family");
FontListParser.writeFamily(out, family);
out.endTag(null, "family");
out.endDocument();
return buffer.toString("UTF-8");
}
}

View File

@@ -25,6 +25,8 @@ import android.graphics.fonts.FontVariationAxis;
import android.os.Build;
import android.os.LocaleList;
import android.text.FontConfig;
import android.text.TextUtils;
import android.util.TypedXmlSerializer;
import android.util.Xml;
import org.xmlpull.v1.XmlPullParser;
@@ -45,6 +47,27 @@ import java.util.regex.Pattern;
*/
public class FontListParser {
// XML constants for FontFamily.
private static final String ATTR_NAME = "name";
private static final String ATTR_LANG = "lang";
private static final String ATTR_VARIANT = "variant";
private static final String TAG_FONT = "font";
private static final String VARIANT_COMPACT = "compact";
private static final String VARIANT_ELEGANT = "elegant";
// XML constants for Font.
public static final String ATTR_INDEX = "index";
public static final String ATTR_WEIGHT = "weight";
public static final String ATTR_STYLE = "style";
public static final String ATTR_FALLBACK_FOR = "fallbackFor";
public static final String STYLE_ITALIC = "italic";
public static final String STYLE_NORMAL = "normal";
public static final String TAG_AXIS = "axis";
// XML constants for FontVariationAxis.
public static final String ATTR_TAG = "tag";
public static final String ATTR_STYLEVALUE = "stylevalue";
/* Parse fallback list (no names) */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public static FontConfig parse(InputStream in) throws XmlPullParserException, IOException {
@@ -148,7 +171,7 @@ public class FontListParser {
while (parser.next() != XmlPullParser.END_TAG) {
if (parser.getEventType() != XmlPullParser.START_TAG) continue;
final String tag = parser.getName();
if (tag.equals("font")) {
if (tag.equals(TAG_FONT)) {
fonts.add(readFont(parser, fontDir, updatableFontMap));
} else {
skip(parser);
@@ -156,15 +179,41 @@ public class FontListParser {
}
int intVariant = FontConfig.FontFamily.VARIANT_DEFAULT;
if (variant != null) {
if (variant.equals("compact")) {
if (variant.equals(VARIANT_COMPACT)) {
intVariant = FontConfig.FontFamily.VARIANT_COMPACT;
} else if (variant.equals("elegant")) {
} else if (variant.equals(VARIANT_ELEGANT)) {
intVariant = FontConfig.FontFamily.VARIANT_ELEGANT;
}
}
return new FontConfig.FontFamily(fonts, name, LocaleList.forLanguageTags(lang), intVariant);
}
/**
* Write a family tag representing {@code fontFamily}. The tag should be started by the caller.
*/
public static void writeFamily(TypedXmlSerializer out, FontConfig.FontFamily fontFamily)
throws IOException {
if (!TextUtils.isEmpty(fontFamily.getName())) {
out.attribute(null, ATTR_NAME, fontFamily.getName());
}
if (!fontFamily.getLocaleList().isEmpty()) {
out.attribute(null, ATTR_LANG, fontFamily.getLocaleList().toLanguageTags());
}
switch (fontFamily.getVariant()) {
case FontConfig.FontFamily.VARIANT_COMPACT:
out.attribute(null, ATTR_VARIANT, VARIANT_COMPACT);
break;
case FontConfig.FontFamily.VARIANT_ELEGANT:
out.attribute(null, ATTR_VARIANT, VARIANT_ELEGANT);
break;
}
for (FontConfig.Font font : fontFamily.getFontList()) {
out.startTag(null, TAG_FONT);
writeFont(out, font);
out.endTag(null, TAG_FONT);
}
}
/** Matches leading and trailing XML whitespace. */
private static final Pattern FILENAME_WHITESPACE_PATTERN =
Pattern.compile("^[ \\n\\r\\t]+|[ \\n\\r\\t]+$");
@@ -175,13 +224,13 @@ public class FontListParser {
@Nullable Map<String, File> updatableFontMap)
throws XmlPullParserException, IOException {
String indexStr = parser.getAttributeValue(null, "index");
String indexStr = parser.getAttributeValue(null, ATTR_INDEX);
int index = indexStr == null ? 0 : Integer.parseInt(indexStr);
List<FontVariationAxis> axes = new ArrayList<>();
String weightStr = parser.getAttributeValue(null, "weight");
int weight = weightStr == null ? 400 : Integer.parseInt(weightStr);
boolean isItalic = "italic".equals(parser.getAttributeValue(null, "style"));
String fallbackFor = parser.getAttributeValue(null, "fallbackFor");
String weightStr = parser.getAttributeValue(null, ATTR_WEIGHT);
int weight = weightStr == null ? FontStyle.FONT_WEIGHT_NORMAL : Integer.parseInt(weightStr);
boolean isItalic = STYLE_ITALIC.equals(parser.getAttributeValue(null, ATTR_STYLE));
String fallbackFor = parser.getAttributeValue(null, ATTR_FALLBACK_FOR);
StringBuilder filename = new StringBuilder();
while (parser.next() != XmlPullParser.END_TAG) {
if (parser.getEventType() == XmlPullParser.TEXT) {
@@ -189,7 +238,7 @@ public class FontListParser {
}
if (parser.getEventType() != XmlPullParser.START_TAG) continue;
String tag = parser.getName();
if (tag.equals("axis")) {
if (tag.equals(TAG_AXIS)) {
axes.add(readAxis(parser));
} else {
skip(parser);
@@ -237,14 +286,48 @@ public class FontListParser {
return null;
}
private static void writeFont(TypedXmlSerializer out, FontConfig.Font font)
throws IOException {
if (font.getTtcIndex() != 0) {
out.attributeInt(null, ATTR_INDEX, font.getTtcIndex());
}
if (font.getStyle().getWeight() != FontStyle.FONT_WEIGHT_NORMAL) {
out.attributeInt(null, ATTR_WEIGHT, font.getStyle().getWeight());
}
if (font.getStyle().getSlant() == FontStyle.FONT_SLANT_ITALIC) {
out.attribute(null, ATTR_STYLE, STYLE_ITALIC);
} else {
out.attribute(null, ATTR_STYLE, STYLE_NORMAL);
}
if (!TextUtils.isEmpty(font.getFontFamilyName())) {
out.attribute(null, ATTR_FALLBACK_FOR, font.getFontFamilyName());
}
out.text(font.getFile().getName());
FontVariationAxis[] axes =
FontVariationAxis.fromFontVariationSettings(font.getFontVariationSettings());
if (axes != null) {
for (FontVariationAxis axis : axes) {
out.startTag(null, TAG_AXIS);
writeAxis(out, axis);
out.endTag(null, TAG_AXIS);
}
}
}
private static FontVariationAxis readAxis(XmlPullParser parser)
throws XmlPullParserException, IOException {
String tagStr = parser.getAttributeValue(null, "tag");
String styleValueStr = parser.getAttributeValue(null, "stylevalue");
String tagStr = parser.getAttributeValue(null, ATTR_TAG);
String styleValueStr = parser.getAttributeValue(null, ATTR_STYLEVALUE);
skip(parser); // axis tag is empty, ignore any contents and consume end tag
return new FontVariationAxis(tagStr, Float.parseFloat(styleValueStr));
}
private static void writeAxis(TypedXmlSerializer out, FontVariationAxis axis)
throws IOException {
out.attribute(null, ATTR_TAG, axis.getTag());
out.attributeFloat(null, ATTR_STYLEVALUE, axis.getStyleValue());
}
/**
* Reads alias elements
*/
@@ -255,7 +338,7 @@ public class FontListParser {
String weightStr = parser.getAttributeValue(null, "weight");
int weight;
if (weightStr == null) {
weight = 400;
weight = FontStyle.FONT_WEIGHT_NORMAL;
} else {
weight = Integer.parseInt(weightStr);
}