Loading core/java/android/text/FontConfig.java +110 −4 Original line number Diff line number Diff line Loading @@ -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; /** Loading Loading @@ -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. * Loading Loading @@ -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 + '\'' + '}'; } } /** Loading Loading @@ -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 + '}'; } } /** Loading @@ -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 */ Loading Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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 + '}'; } } } core/tests/coretests/src/android/graphics/FontListParserTest.java 0 → 100644 +245 −0 Original line number Diff line number Diff line /* * 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"); } } graphics/java/android/graphics/FontListParser.java +95 −12 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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 { Loading Loading @@ -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); Loading @@ -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]+$"); Loading @@ -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) { Loading @@ -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); Loading Loading @@ -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 */ Loading @@ -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); } Loading Loading
core/java/android/text/FontConfig.java +110 −4 Original line number Diff line number Diff line Loading @@ -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; /** Loading Loading @@ -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. * Loading Loading @@ -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 + '\'' + '}'; } } /** Loading Loading @@ -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 + '}'; } } /** Loading @@ -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 */ Loading Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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 + '}'; } } }
core/tests/coretests/src/android/graphics/FontListParserTest.java 0 → 100644 +245 −0 Original line number Diff line number Diff line /* * 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"); } }
graphics/java/android/graphics/FontListParser.java +95 −12 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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 { Loading Loading @@ -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); Loading @@ -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]+$"); Loading @@ -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) { Loading @@ -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); Loading Loading @@ -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 */ Loading @@ -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); } Loading