Merge "Refactor font parser for making SystemApi."

This commit is contained in:
TreeHugger Robot
2021-01-20 04:18:20 +00:00
committed by Android (Google) Code Review
10 changed files with 718 additions and 399 deletions

View File

@@ -19,171 +19,268 @@ package android.text;
import static java.lang.annotation.RetentionPolicy.SOURCE;
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
import android.graphics.fonts.FontStyle;
import android.graphics.fonts.FontVariationAxis;
import android.net.Uri;
import android.os.Build;
import android.os.LocaleList;
import java.io.File;
import java.lang.annotation.Retention;
import java.util.List;
/**
* Font configuration descriptions for System fonts.
* @hide
* @hide // TODO Make this SystemApi.
*/
public final class FontConfig {
private final @NonNull Family[] mFamilies;
private final @NonNull Alias[] mAliases;
private final @NonNull List<Family> mFamilies;
private final @NonNull List<Alias> mAliases;
public FontConfig(@NonNull Family[] families, @NonNull Alias[] aliases) {
/**
* Construct a SystemFontConfig instance.
*
* @param families a list of font families.
* @param aliases a list of aliases.
*
* @hide Only system server can create this instance and passed via IPC.
*/
public FontConfig(@NonNull List<Family> families, @NonNull List<Alias> aliases) {
mFamilies = families;
mAliases = aliases;
}
/**
* Returns the ordered list of families included in the system fonts.
*
* @return a list of font families.
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public @NonNull Family[] getFamilies() {
public @NonNull List<Family> getFontFamilies() {
return mFamilies;
}
/**
* Returns the list of aliases defined for the font families in the system fonts.
*
* @return a list of font families.
*/
public @NonNull Alias[] getAliases() {
public @NonNull List<Alias> getAliases() {
return mAliases;
}
/**
* Class that holds information about a Font.
* Returns the ordered list of families included in the system fonts.
* @deprecated Use getFontFamilies instead.
*/
@Deprecated
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public @NonNull Family[] getFamilies() {
return mFamilies.toArray(new Family[0]);
}
/**
* A class represents single font entry in system font configuration.
*/
public static final class Font {
private final @NonNull String mFontName;
private final int mTtcIndex;
private final @NonNull FontVariationAxis[] mAxes;
private final int mWeight;
private final boolean mIsItalic;
private Uri mUri;
private final String mFallbackFor;
private final @NonNull File mFilePath;
private final @Nullable File mOriginalPath;
private final @NonNull FontStyle mStyle;
private final @IntRange(from = 0) int mIndex;
private final @NonNull String mFontVariationSettings;
private final @Nullable String mFallback;
/**
* @hide
* Construct a Font instance.
*
* @hide Only system server can create this instance and passed via IPC.
*/
public Font(@NonNull String fontName, int ttcIndex, @NonNull FontVariationAxis[] axes,
int weight, boolean isItalic, String fallbackFor) {
mFontName = fontName;
mTtcIndex = ttcIndex;
mAxes = axes;
mWeight = weight;
mIsItalic = isItalic;
mFallbackFor = fallbackFor;
public Font(@NonNull File filePath, @Nullable File originalPath, @NonNull FontStyle style,
@IntRange(from = 0) int index, @NonNull String fontVariationSettings,
@Nullable String fallback) {
mFilePath = filePath;
mOriginalPath = originalPath;
mStyle = style;
mIndex = index;
mFontVariationSettings = fontVariationSettings;
mFallback = fallback;
}
/**
* Returns the name associated by the system to this font.
* Returns a file to the font file.
*
* @return a font file.
*/
public @NonNull String getFontName() {
return mFontName;
public @NonNull File getFilePath() {
return mFilePath;
}
/**
* Returns an original font file in the system directory.
*
* If the font file is not updated, returns null.
*
* @return returns the original font file in the system if the font file is updated. Returns
* null if the font file is not updated.
*/
public @Nullable File getOriginalPath() {
return mOriginalPath;
}
/**
* Returns a font style.
*
* @return a font style.
*/
public @NonNull FontStyle getStyle() {
return mStyle;
}
/**
* Returns a font index.
*
* @return a font index.
*/
public @IntRange(from = 0) int getIndex() {
return mIndex;
}
/**
* Return a font variation settings.
*
* @return a font variation settings.
*/
public @NonNull String getFontVariationSettings() {
return mFontVariationSettings;
}
/**
* Returns font family name that uses this font as a fallback.
*
* If this font is a fallback for the default font family, this is null.
*
* @return a font family name.
*/
public @Nullable String getFallback() {
return mFallback;
}
/**
* Returns the index to be used to access this font when accessing a TTC file.
* @deprecated Use getIndex instead.
* @hide
*/
@Deprecated
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public int getTtcIndex() {
return mTtcIndex;
return mIndex;
}
/**
* Returns the list of axes associated to this font.
* @deprecated Use getFontVariationSettings
* @hide
*/
@Deprecated
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public @NonNull FontVariationAxis[] getAxes() {
return mAxes;
return FontVariationAxis.fromFontVariationSettings(mFontVariationSettings);
}
/**
* Returns the weight value for this font.
* @deprecated Use getStyle instead.
* @hide
*/
@Deprecated
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public int getWeight() {
return mWeight;
return getStyle().getWeight();
}
/**
* Returns whether this font is italic.
* @deprecated Use getStyle instead.
* @hide
*/
@Deprecated
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public boolean isItalic() {
return mIsItalic;
}
/**
* Returns the content uri associated to this font.
*
* You can reach to the font contents by calling {@link
* android.content.ContentResolver#openInputStream}.
*/
public @Nullable Uri getUri() {
return mUri;
}
public void setUri(@NonNull Uri uri) {
mUri = uri;
}
public String getFallbackFor() {
return mFallbackFor;
return getStyle().getSlant() == FontStyle.FONT_SLANT_ITALIC;
}
}
/**
* Class that holds information about a Font alias.
* A class represents alias between named font families.
*
* In the system font configuration, an font family can be an alias of another font family with
* different font weight. For example, "sans-serif-medium" can be a medium weight of
* sans-serif font family.
*/
public static final class Alias {
private final @NonNull String mName;
private final @NonNull String mToName;
private final int mWeight;
private final @NonNull String mAliasName;
private final @NonNull String mReferName;
private final @IntRange(from = 0, to = 1000) int mWeight;
public Alias(@NonNull String name, @NonNull String toName, int weight) {
mName = name;
mToName = toName;
/**
* Construct an alias instance.
*
* @param aliasName an alias of the named font family.
* @param referName a referring font family name.
* @param weight a font weight of the referring font family.
* @hide Only system server can create this instance and passed via IPC.
*/
public Alias(@NonNull String aliasName, @NonNull String referName,
@IntRange(from = 0, to = 1000) int weight) {
mAliasName = aliasName;
mReferName = referName;
mWeight = weight;
}
/**
* Returns the new name for the alias.
* An alias of the named font family.
*
* @return an alias of the named font family.
*/
public @NonNull String getName() {
return mName;
public @NonNull String getAliasName() {
return mAliasName;
}
/**
* Returns the existing name to which this alias points to.
* A name of font family referring from {@link #getAliasName()}
*
* @return a referring font family name.
*/
public @NonNull String getToName() {
return mToName;
public @NonNull String getReferName() {
return mReferName;
}
/**
* Returns the weight associated with this alias.
* A font weight of the referring font family.
*
* @return a font weight of the referring font family.
*/
public int getWeight() {
public @IntRange(from = 0, to = 1000) int getWeight() {
return mWeight;
}
}
/**
* Class that holds information about a Font family.
* A class represents single font family entry in system font configuration.
*
* <p>
* A font family is a bundle of fonts for drawing text in various styles.
* For example, regular style font and bold style font can be bundled into a single font family,
* then system will select the correct style font from family for drawing.
*/
public static final class Family {
private final @NonNull String mName;
private final @NonNull Font[] mFonts;
// Comma separated BCP47 complient locale strings
private final @NonNull String mLanguages;
private final @NonNull List<Font> mFonts;
private final @Nullable String mName;
private final @Nullable LocaleList mLocaleList;
private final @Variant int mVariant;
/** @hide */
@Retention(SOURCE)
@@ -212,26 +309,81 @@ public final class FontConfig {
/**
* Value for font variant.
*
* Indiates the font is for elegant variant.
* Indicates the font is for elegant variant.
* @see android.graphics.Paint#setElegantTextHeight
*/
public static final int VARIANT_ELEGANT = 2;
// Must be same with Minikin's variant values.
// See frameworks/minikin/include/minikin/FontFamily.h
private final @Variant int mVariant;
public Family(@NonNull String name, @NonNull Font[] fonts, @NonNull String languages,
@Variant int variant) {
mName = name;
/**
* Construct a family instance.
*
* @hide Only system server can create this instance and passed via IPC.
*/
public Family(@NonNull List<Font> fonts, @Nullable String name,
@Nullable LocaleList localeList, @Variant int variant) {
mFonts = fonts;
mLanguages = languages;
mName = name;
mLocaleList = localeList;
mVariant = variant;
}
/**
* Returns the name given by the system to this font family.
* Returns a list of font files in this family.
*
* @return a list of font files.
*/
public @NonNull List<Font> getFontList() {
return mFonts;
}
/**
* Returns a family name if this family defines a new fallback.
*
* @return non-null if a family name is associated. Otherwise null.
*/
public @Nullable String getFallbackName() {
return mName;
}
/**
* Returns a locale list if associated.
*
* @return non-null if a locale list is associated. Otherwise null.
*/
public @NonNull LocaleList getLocaleList() {
return mLocaleList;
}
/**
* Returns a text height variant.
*
* @return text height variant.
*/
public @Variant int getTextHeightVariant() {
return mVariant;
}
/**
* Returns a family variant associated.
*
* @return a family variant.
* @deprecated Use getTextHeightVariant instead.
* @hide
*/
@Deprecated
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public @Variant int getVariant() {
return mVariant;
}
/**
* Returns a family name if associated.
*
* @return non-null if a family name is associated. Otherwise null.
* @deprecated Use getFallbackName instead.
* @hide
*/
@Deprecated
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public @Nullable String getName() {
return mName;
@@ -239,25 +391,23 @@ public final class FontConfig {
/**
* Returns the list of fonts included in this family.
* @deprecated Use getFontFiles instead
* @hide
*/
@Deprecated
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public @Nullable Font[] getFonts() {
return mFonts;
return mFonts.toArray(new Font[0]);
}
/**
* Returns the comma separated BCP47 complient languages for this family. May be null.
* Returns the comma separated BCP47 compliant languages for this family. May be null.
* @deprecated Use getLocaleList instead
* @hide
*/
@Deprecated
public @NonNull String getLanguages() {
return mLanguages;
}
/**
* Returns the font variant for this family, e.g. "elegant" or "compact". May be null.
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public @Variant int getVariant() {
return mVariant;
return mLocaleList.toLanguageTags();
}
}
}

View File

@@ -21,6 +21,8 @@ import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.content.res.AssetManager;
import android.graphics.fonts.FontCustomizationParser;
@@ -44,7 +46,6 @@ import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
@@ -139,27 +140,55 @@ public class TypefaceSystemFallbackTest {
}
}
private static void buildSystemFallback(String xml,
FontCustomizationParser.Result oemCustomization, ArrayMap<String, Typeface> fontMap,
ArrayMap<String, FontFamily[]> fallbackMap) {
private static void buildSystemFallback(
@NonNull String xml,
@Nullable String oemXml,
@NonNull ArrayMap<String, Typeface> outFontMap,
@NonNull ArrayMap<String, FontFamily[]> outFallbackMap) {
try (FileOutputStream fos = new FileOutputStream(TEST_FONTS_XML)) {
fos.write(xml.getBytes(Charset.forName("UTF-8")));
fos.write(xml.getBytes(StandardCharsets.UTF_8));
} catch (IOException e) {
throw new RuntimeException(e);
}
String oemXmlPath;
if (oemXml != null) {
try (FileOutputStream fos = new FileOutputStream(TEST_OEM_XML)) {
fos.write(oemXml.getBytes(StandardCharsets.UTF_8));
} catch (IOException e) {
throw new RuntimeException(e);
}
oemXmlPath = TEST_OEM_XML;
} else {
oemXmlPath = null;
}
Map<String, File> updatableFontMap = new HashMap<>();
for (File file : new File(TEST_UPDATABLE_FONT_DIR).listFiles()) {
updatableFontMap.put(file.getName(), file);
}
final FontConfig.Alias[] aliases = SystemFonts.buildSystemFallback(TEST_FONTS_XML,
TEST_FONT_DIR, updatableFontMap, oemCustomization, fallbackMap);
Typeface.initSystemDefaultTypefaces(fontMap, fallbackMap, aliases);
FontConfig fontConfig;
try {
fontConfig = FontListParser.parse(
TEST_FONTS_XML, TEST_FONT_DIR, oemXmlPath, TEST_OEM_DIR, updatableFontMap);
} catch (IOException | XmlPullParserException e) {
throw new RuntimeException(e);
}
Map<String, FontFamily[]> fallbackMap = SystemFonts.buildSystemFallback(fontConfig);
Map<String, Typeface> typefaceMap = SystemFonts.buildSystemTypefaces(
fontConfig, fallbackMap);
outFontMap.clear();
outFontMap.putAll(typefaceMap);
outFallbackMap.clear();
outFallbackMap.putAll(fallbackMap);
}
private static FontCustomizationParser.Result readFontCustomization(String oemXml) {
try (InputStream is = new ByteArrayInputStream(oemXml.getBytes(StandardCharsets.UTF_8))) {
return FontCustomizationParser.parse(is, TEST_OEM_DIR);
return FontCustomizationParser.parse(is, TEST_OEM_DIR, null);
} catch (IOException | XmlPullParserException e) {
throw new RuntimeException(e);
}
@@ -167,19 +196,22 @@ public class TypefaceSystemFallbackTest {
@Test
public void testBuildSystemFallback() {
final ArrayMap<String, Typeface> fontMap = new ArrayMap<>();
final ArrayMap<String, FontFamily[]> fallbackMap = new ArrayMap<>();
final FontCustomizationParser.Result oemCustomization =
new FontCustomizationParser.Result();
FontConfig fontConfig;
try {
fontConfig = FontListParser.parse(
SYSTEM_FONTS_XML, SYSTEM_FONT_DIR, null, TEST_OEM_DIR, null);
} catch (IOException | XmlPullParserException e) {
throw new RuntimeException(e);
}
assertFalse(fontConfig.getAliases().isEmpty());
assertFalse(fontConfig.getFontFamilies().isEmpty());
final FontConfig.Alias[] aliases = SystemFonts.buildSystemFallback(SYSTEM_FONTS_XML,
SYSTEM_FONT_DIR, oemCustomization, fallbackMap);
assertNotNull(aliases);
Map<String, FontFamily[]> fallbackMap = SystemFonts.buildSystemFallback(fontConfig);
assertFalse(fallbackMap.isEmpty());
Typeface.initSystemDefaultTypefaces(fontMap, fallbackMap, aliases);
assertFalse(fontMap.isEmpty());
Map<String, Typeface> typefaceMap = SystemFonts.buildSystemTypefaces(
fontConfig, fallbackMap);
assertFalse(typefaceMap.isEmpty());
}
@Test
@@ -199,10 +231,8 @@ public class TypefaceSystemFallbackTest {
+ "</familyset>";
final ArrayMap<String, Typeface> fontMap = new ArrayMap<>();
final ArrayMap<String, FontFamily[]> fallbackMap = new ArrayMap<>();
final FontCustomizationParser.Result oemCustomization =
new FontCustomizationParser.Result();
buildSystemFallback(xml, oemCustomization, fontMap, fallbackMap);
buildSystemFallback(xml, null, fontMap, fallbackMap);
assertEquals(1, fontMap.size());
assertTrue(fontMap.containsKey("sans-serif"));
@@ -229,10 +259,8 @@ public class TypefaceSystemFallbackTest {
+ "</familyset>";
final ArrayMap<String, Typeface> fontMap = new ArrayMap<>();
final ArrayMap<String, FontFamily[]> fallbackMap = new ArrayMap<>();
final FontCustomizationParser.Result oemCustomization =
new FontCustomizationParser.Result();
buildSystemFallback(xml, oemCustomization, fontMap, fallbackMap);
buildSystemFallback(xml, null, fontMap, fallbackMap);
final Paint paint = new Paint();
@@ -277,10 +305,8 @@ public class TypefaceSystemFallbackTest {
+ "</familyset>";
final ArrayMap<String, Typeface> fontMap = new ArrayMap<>();
final ArrayMap<String, FontFamily[]> fallbackMap = new ArrayMap<>();
final FontCustomizationParser.Result oemCustomization =
new FontCustomizationParser.Result();
buildSystemFallback(xml, oemCustomization, fontMap, fallbackMap);
buildSystemFallback(xml, null, fontMap, fallbackMap);
final Paint paint = new Paint();
@@ -324,10 +350,8 @@ public class TypefaceSystemFallbackTest {
+ "</familyset>";
final ArrayMap<String, Typeface> fontMap = new ArrayMap<>();
final ArrayMap<String, FontFamily[]> fallbackMap = new ArrayMap<>();
final FontCustomizationParser.Result oemCustomization =
new FontCustomizationParser.Result();
buildSystemFallback(xml, oemCustomization, fontMap, fallbackMap);
buildSystemFallback(xml, null, fontMap, fallbackMap);
final Paint paint = new Paint();
@@ -376,10 +400,8 @@ public class TypefaceSystemFallbackTest {
+ "</familyset>";
final ArrayMap<String, Typeface> fontMap = new ArrayMap<>();
final ArrayMap<String, FontFamily[]> fallbackMap = new ArrayMap<>();
final FontCustomizationParser.Result oemCustomization =
new FontCustomizationParser.Result();
buildSystemFallback(xml, oemCustomization, fontMap, fallbackMap);
buildSystemFallback(xml, null, fontMap, fallbackMap);
final Paint paint = new Paint();
@@ -424,10 +446,8 @@ public class TypefaceSystemFallbackTest {
+ "</familyset>";
final ArrayMap<String, Typeface> fontMap = new ArrayMap<>();
final ArrayMap<String, FontFamily[]> fallbackMap = new ArrayMap<>();
final FontCustomizationParser.Result oemCustomization =
new FontCustomizationParser.Result();
buildSystemFallback(xml, oemCustomization, fontMap, fallbackMap);
buildSystemFallback(xml, null, fontMap, fallbackMap);
final Paint paint = new Paint();
@@ -465,10 +485,8 @@ public class TypefaceSystemFallbackTest {
+ "</familyset>";
final ArrayMap<String, Typeface> fontMap = new ArrayMap<>();
final ArrayMap<String, FontFamily[]> fallbackMap = new ArrayMap<>();
final FontCustomizationParser.Result oemCustomization =
new FontCustomizationParser.Result();
buildSystemFallback(xml, oemCustomization, fontMap, fallbackMap);
buildSystemFallback(xml, null, fontMap, fallbackMap);
final Paint paint = new Paint();
@@ -506,10 +524,8 @@ public class TypefaceSystemFallbackTest {
+ "</familyset>";
final ArrayMap<String, Typeface> fontMap = new ArrayMap<>();
final ArrayMap<String, FontFamily[]> fallbackMap = new ArrayMap<>();
final FontCustomizationParser.Result oemCustomization =
new FontCustomizationParser.Result();
buildSystemFallback(xml, oemCustomization, fontMap, fallbackMap);
buildSystemFallback(xml, null, fontMap, fallbackMap);
final Paint paint = new Paint();
@@ -556,10 +572,8 @@ public class TypefaceSystemFallbackTest {
+ "</familyset>";
final ArrayMap<String, Typeface> fontMap = new ArrayMap<>();
final ArrayMap<String, FontFamily[]> fallbackMap = new ArrayMap<>();
final FontCustomizationParser.Result oemCustomization =
new FontCustomizationParser.Result();
buildSystemFallback(xml, oemCustomization, fontMap, fallbackMap);
buildSystemFallback(xml, null, fontMap, fallbackMap);
final Paint paint = new Paint();
paint.setTypeface(fontMap.get("sans-serif"));
@@ -600,10 +614,8 @@ public class TypefaceSystemFallbackTest {
+ "</familyset>";
final ArrayMap<String, Typeface> fontMap = new ArrayMap<>();
final ArrayMap<String, FontFamily[]> fallbackMap = new ArrayMap<>();
final FontCustomizationParser.Result oemCustomization =
new FontCustomizationParser.Result();
buildSystemFallback(xml, oemCustomization, fontMap, fallbackMap);
buildSystemFallback(xml, null, fontMap, fallbackMap);
final Paint paint = new Paint();
@@ -641,10 +653,8 @@ public class TypefaceSystemFallbackTest {
+ "</familyset>";
final ArrayMap<String, Typeface> fontMap = new ArrayMap<>();
final ArrayMap<String, FontFamily[]> fallbackMap = new ArrayMap<>();
final FontCustomizationParser.Result oemCustomization =
new FontCustomizationParser.Result();
buildSystemFallback(xml, oemCustomization, fontMap, fallbackMap);
buildSystemFallback(xml, null, fontMap, fallbackMap);
final Paint paint = new Paint();
@@ -679,10 +689,8 @@ public class TypefaceSystemFallbackTest {
+ "</fonts-modification>";
final ArrayMap<String, Typeface> fontMap = new ArrayMap<>();
final ArrayMap<String, FontFamily[]> fallbackMap = new ArrayMap<>();
final FontCustomizationParser.Result oemCustomization =
readFontCustomization(oemXml);
buildSystemFallback(xml, oemCustomization, fontMap, fallbackMap);
buildSystemFallback(xml, oemXml, fontMap, fallbackMap);
final Paint paint = new Paint();
@@ -717,10 +725,8 @@ public class TypefaceSystemFallbackTest {
+ "</fonts-modification>";
final ArrayMap<String, Typeface> fontMap = new ArrayMap<>();
final ArrayMap<String, FontFamily[]> fallbackMap = new ArrayMap<>();
final FontCustomizationParser.Result oemCustomization =
readFontCustomization(oemXml);
buildSystemFallback(xml, oemCustomization, fontMap, fallbackMap);
buildSystemFallback(xml, oemXml, fontMap, fallbackMap);
final Paint paint = new Paint();
@@ -751,10 +757,8 @@ public class TypefaceSystemFallbackTest {
+ "</fonts-modification>";
final ArrayMap<String, Typeface> fontMap = new ArrayMap<>();
final ArrayMap<String, FontFamily[]> fallbackMap = new ArrayMap<>();
final FontCustomizationParser.Result oemCustomization =
readFontCustomization(oemXml);
buildSystemFallback(xml, oemCustomization, fontMap, fallbackMap);
buildSystemFallback(xml, oemXml, fontMap, fallbackMap);
final Paint paint = new Paint();
@@ -804,10 +808,8 @@ public class TypefaceSystemFallbackTest {
+ "</fonts-modification>";
final ArrayMap<String, Typeface> fontMap = new ArrayMap<>();
final ArrayMap<String, FontFamily[]> fallbackMap = new ArrayMap<>();
final FontCustomizationParser.Result oemCustomization =
readFontCustomization(oemXml);
buildSystemFallback(xml, oemCustomization, fontMap, fallbackMap);
buildSystemFallback(xml, oemXml, fontMap, fallbackMap);
final Paint paint = new Paint();
@@ -862,12 +864,10 @@ public class TypefaceSystemFallbackTest {
+ "</familyset>";
final ArrayMap<String, Typeface> fontMap = new ArrayMap<>();
final ArrayMap<String, FontFamily[]> fallbackMap = new ArrayMap<>();
final FontCustomizationParser.Result oemCustomization =
new FontCustomizationParser.Result();
// Install all2em.ttf as a3em.ttf
copyAssetToFile("fonts/all2em.ttf", new File(TEST_UPDATABLE_FONT_DIR, "a3em.ttf"));
buildSystemFallback(xml, oemCustomization, fontMap, fallbackMap);
buildSystemFallback(xml, null, fontMap, fallbackMap);
final Paint paint = new Paint();

View File

@@ -27,7 +27,6 @@ import android.graphics.fonts.FontFamily;
import android.graphics.fonts.SystemFonts;
import android.os.SharedMemory;
import android.text.FontConfig;
import android.util.Pair;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.LargeTest;
@@ -41,7 +40,6 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import java.nio.ByteOrder;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
@@ -197,10 +195,10 @@ public class TypefaceTest {
@SmallTest
@Test
public void testSerialize() throws Exception {
HashMap<String, Typeface> systemFontMap = new HashMap<>();
Pair<FontConfig.Alias[], Map<String, FontFamily[]>> res =
SystemFonts.initializePreinstalledFonts();
Typeface.initSystemDefaultTypefaces(systemFontMap, res.second, res.first);
FontConfig fontConfig = SystemFonts.getSystemPreinstalledFontConfig();
Map<String, FontFamily[]> fallbackMap = SystemFonts.buildSystemFallback(fontConfig);
Map<String, Typeface> systemFontMap = SystemFonts.buildSystemTypefaces(fontConfig,
fallbackMap);
SharedMemory sharedMemory = Typeface.serializeFontMap(systemFontMap);
Map<String, Typeface> copiedFontMap =
Typeface.deserializeFontMap(sharedMemory.mapReadOnly().order(ByteOrder.BIG_ENDIAN));

View File

@@ -19,14 +19,15 @@ package android.text;
import android.annotation.NonNull;
import android.content.Context;
import android.content.res.AssetManager;
import android.graphics.FontListParser;
import android.graphics.Typeface;
import android.graphics.fonts.FontCustomizationParser;
import android.graphics.fonts.FontFamily;
import android.graphics.fonts.SystemFonts;
import android.util.ArrayMap;
import androidx.test.InstrumentationRegistry;
import org.xmlpull.v1.XmlPullParserException;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
@@ -34,12 +35,13 @@ import java.io.InputStream;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.util.Map;
public class FontFallbackSetup implements AutoCloseable {
private final String[] mTestFontFiles;
private final String mXml;
private final String mTestFontsDir;
final ArrayMap<String, Typeface> mFontMap = new ArrayMap<>();
private final Map<String, Typeface> mFontMap;
public FontFallbackSetup(@NonNull String testSubDir, @NonNull String[] testFontFiles,
@NonNull String xml) {
@@ -75,12 +77,15 @@ public class FontFallbackSetup implements AutoCloseable {
throw new RuntimeException(e);
}
final ArrayMap<String, FontFamily[]> fallbackMap = new ArrayMap<>();
final FontCustomizationParser.Result oemCustomization =
new FontCustomizationParser.Result();
final FontConfig.Alias[] aliases = SystemFonts.buildSystemFallback(testFontsXml,
mTestFontsDir, oemCustomization, fallbackMap);
Typeface.initSystemDefaultTypefaces(mFontMap, fallbackMap, aliases);
FontConfig fontConfig;
try {
fontConfig = FontListParser.parse(testFontsXml, mTestFontsDir, null, null, null);
} catch (IOException | XmlPullParserException e) {
throw new RuntimeException(e);
}
Map<String, FontFamily[]> fallbackMap = SystemFonts.buildSystemFallback(fontConfig);
mFontMap = SystemFonts.buildSystemTypefaces(fontConfig, fallbackMap);
}
@NonNull

View File

@@ -16,10 +16,14 @@
package android.graphics;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
import android.graphics.fonts.FontCustomizationParser;
import android.graphics.fonts.FontStyle;
import android.graphics.fonts.FontVariationAxis;
import android.os.Build;
import android.os.LocaleList;
import android.text.FontConfig;
import android.util.Xml;
@@ -27,6 +31,7 @@ import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
@@ -36,7 +41,6 @@ import java.util.regex.Pattern;
/**
* Parser for font config files.
*
* @hide
*/
public class FontListParser {
@@ -44,49 +48,89 @@ public class FontListParser {
/* Parse fallback list (no names) */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public static FontConfig parse(InputStream in) throws XmlPullParserException, IOException {
return parse(in, "/system/fonts", null);
XmlPullParser parser = Xml.newPullParser();
parser.setInput(in, null);
parser.nextTag();
return readFamilies(parser, "/system/fonts/", new FontCustomizationParser.Result(), null);
}
/**
* Parse the fonts.xml
* Parses system font config XMLs
*
* @param fontsXmlPath location of fonts.xml
* @param systemFontDir location of system font directory
* @param oemCustomizationXmlPath location of oem_customization.xml
* @param productFontDir location of oem customized font directory
* @param updatableFontMap map of updated font files.
* @return font configuration
* @throws IOException
* @throws XmlPullParserException
*/
public static FontConfig parse(InputStream in, String fontDir,
@Nullable Map<String, File> updatableFontMap)
throws XmlPullParserException, IOException {
try {
public static FontConfig parse(
@NonNull String fontsXmlPath,
@NonNull String systemFontDir,
@Nullable String oemCustomizationXmlPath,
@Nullable String productFontDir,
@Nullable Map<String, File> updatableFontMap
) throws IOException, XmlPullParserException {
FontCustomizationParser.Result oemCustomization;
if (oemCustomizationXmlPath != null) {
try (InputStream is = new FileInputStream(oemCustomizationXmlPath)) {
oemCustomization = FontCustomizationParser.parse(is, productFontDir,
updatableFontMap);
} catch (IOException e) {
// OEM customization may not exists. Ignoring
oemCustomization = new FontCustomizationParser.Result();
}
} else {
oemCustomization = new FontCustomizationParser.Result();
}
try (InputStream is = new FileInputStream(fontsXmlPath)) {
XmlPullParser parser = Xml.newPullParser();
parser.setInput(in, null);
parser.setInput(is, null);
parser.nextTag();
return readFamilies(parser, fontDir, updatableFontMap);
} finally {
in.close();
return readFamilies(parser, systemFontDir, oemCustomization, updatableFontMap);
}
}
private static FontConfig readFamilies(XmlPullParser parser, String fontDir,
private static FontConfig readFamilies(
@NonNull XmlPullParser parser,
@NonNull String fontDir,
@NonNull FontCustomizationParser.Result customization,
@Nullable Map<String, File> updatableFontMap)
throws XmlPullParserException, IOException {
List<FontConfig.Family> families = new ArrayList<>();
List<FontConfig.Alias> aliases = new ArrayList<>();
List<FontConfig.Alias> aliases = new ArrayList<>(customization.getAdditionalAliases());
Map<String, FontConfig.Family> oemNamedFamilies =
customization.getAdditionalNamedFamilies();
parser.require(XmlPullParser.START_TAG, null, "familyset");
while (parser.next() != XmlPullParser.END_TAG) {
if (parser.getEventType() != XmlPullParser.START_TAG) continue;
String tag = parser.getName();
if (tag.equals("family")) {
families.add(readFamily(parser, fontDir, updatableFontMap));
FontConfig.Family family = readFamily(parser, fontDir, updatableFontMap);
String name = family.getFallbackName();
if (name == null || !oemNamedFamilies.containsKey(name)) {
// The OEM customization overrides system named family. Skip if OEM
// customization XML defines the same named family.
families.add(family);
}
} else if (tag.equals("alias")) {
aliases.add(readAlias(parser));
} else {
skip(parser);
}
}
return new FontConfig(families.toArray(new FontConfig.Family[families.size()]),
aliases.toArray(new FontConfig.Alias[aliases.size()]));
families.addAll(oemNamedFamilies.values());
return new FontConfig(families, aliases);
}
/**
* Reads a family element
* Read family tag in fonts.xml or oem_customization.xml
*/
public static FontConfig.Family readFamily(XmlPullParser parser, String fontDir,
@Nullable Map<String, File> updatableFontMap)
@@ -94,7 +138,7 @@ public class FontListParser {
final String name = parser.getAttributeValue(null, "name");
final String lang = parser.getAttributeValue("", "lang");
final String variant = parser.getAttributeValue(null, "variant");
final List<FontConfig.Font> fonts = new ArrayList<FontConfig.Font>();
final List<FontConfig.Font> fonts = new ArrayList<>();
while (parser.next() != XmlPullParser.END_TAG) {
if (parser.getEventType() != XmlPullParser.START_TAG) continue;
final String tag = parser.getName();
@@ -112,20 +156,22 @@ public class FontListParser {
intVariant = FontConfig.Family.VARIANT_ELEGANT;
}
}
return new FontConfig.Family(name, fonts.toArray(new FontConfig.Font[fonts.size()]), lang,
intVariant);
return new FontConfig.Family(fonts, name, LocaleList.forLanguageTags(lang), intVariant);
}
/** Matches leading and trailing XML whitespace. */
private static final Pattern FILENAME_WHITESPACE_PATTERN =
Pattern.compile("^[ \\n\\r\\t]+|[ \\n\\r\\t]+$");
private static FontConfig.Font readFont(XmlPullParser parser, String fontDir,
private static FontConfig.Font readFont(
@NonNull XmlPullParser parser,
@NonNull String fontDir,
@Nullable Map<String, File> updatableFontMap)
throws XmlPullParserException, IOException {
String indexStr = parser.getAttributeValue(null, "index");
int index = indexStr == null ? 0 : Integer.parseInt(indexStr);
List<FontVariationAxis> axes = new ArrayList<FontVariationAxis>();
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"));
@@ -144,12 +190,37 @@ public class FontListParser {
}
}
String sanitizedName = FILENAME_WHITESPACE_PATTERN.matcher(filename).replaceAll("");
String fontName = findFontFile(sanitizedName, fontDir, updatableFontMap);
return new FontConfig.Font(fontName, index, axes.toArray(
new FontVariationAxis[axes.size()]), weight, isItalic, fallbackFor);
String updatedName = findUpdatedFontFile(sanitizedName, updatableFontMap);
String filePath;
String originalPath;
if (updatedName != null) {
filePath = updatedName;
originalPath = fontDir + sanitizedName;
} else {
filePath = fontDir + sanitizedName;
originalPath = null;
}
String varSettings;
if (axes.isEmpty()) {
varSettings = "";
} else {
varSettings = FontVariationAxis.toFontVariationSettings(
axes.toArray(new FontVariationAxis[0]));
}
return new FontConfig.Font(new File(filePath),
originalPath == null ? null : new File(originalPath),
new FontStyle(
weight,
isItalic ? FontStyle.FONT_SLANT_ITALIC : FontStyle.FONT_SLANT_UPRIGHT
),
index,
varSettings,
fallbackFor);
}
private static String findFontFile(String name, String fontDir,
private static String findUpdatedFontFile(String name,
@Nullable Map<String, File> updatableFontMap) {
if (updatableFontMap != null) {
File updatedFile = updatableFontMap.get(name);
@@ -157,7 +228,7 @@ public class FontListParser {
return updatedFile.getAbsolutePath();
}
}
return fontDir + name;
return null;
}
private static FontVariationAxis readAxis(XmlPullParser parser)
@@ -193,12 +264,12 @@ public class FontListParser {
int depth = 1;
while (depth > 0) {
switch (parser.next()) {
case XmlPullParser.START_TAG:
depth++;
break;
case XmlPullParser.END_TAG:
depth--;
break;
case XmlPullParser.START_TAG:
depth++;
break;
case XmlPullParser.END_TAG:
depth--;
break;
}
}
}

View File

@@ -45,7 +45,6 @@ import android.text.FontConfig;
import android.util.Base64;
import android.util.LongSparseArray;
import android.util.LruCache;
import android.util.Pair;
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
@@ -197,7 +196,11 @@ public class Typeface {
// Must be the same as the C++ constant in core/jni/android/graphics/FontFamily.cpp
/** @hide */
public static final int RESOLVE_BY_FONT_TABLE = -1;
private static final String DEFAULT_FAMILY = "sans-serif";
/**
* The key of the default font family.
* @hide
*/
public static final String DEFAULT_FAMILY = "sans-serif";
// Style value for building typeface.
private static final int STYLE_NORMAL = 0;
@@ -1139,18 +1142,19 @@ public class Typeface {
/** @hide */
@VisibleForTesting
public static void initSystemDefaultTypefaces(Map<String, Typeface> systemFontMap,
Map<String, FontFamily[]> fallbacks,
FontConfig.Alias[] aliases) {
public static void initSystemDefaultTypefaces(Map<String, FontFamily[]> fallbacks,
List<FontConfig.Alias> aliases,
Map<String, Typeface> outSystemFontMap) {
for (Map.Entry<String, FontFamily[]> entry : fallbacks.entrySet()) {
systemFontMap.put(entry.getKey(), createFromFamilies(entry.getValue()));
outSystemFontMap.put(entry.getKey(), createFromFamilies(entry.getValue()));
}
for (FontConfig.Alias alias : aliases) {
if (systemFontMap.containsKey(alias.getName())) {
for (int i = 0; i < aliases.size(); ++i) {
final FontConfig.Alias alias = aliases.get(i);
if (outSystemFontMap.containsKey(alias.getAliasName())) {
continue; // If alias and named family are conflict, use named family.
}
final Typeface base = systemFontMap.get(alias.getToName());
final Typeface base = outSystemFontMap.get(alias.getReferName());
if (base == null) {
// The missing target is a valid thing, some configuration don't have font files,
// e.g. wear devices. Just skip this alias.
@@ -1159,7 +1163,7 @@ public class Typeface {
final int weight = alias.getWeight();
final Typeface newFace = weight == 400 ? base :
new Typeface(nativeCreateWeightAlias(base.native_instance, weight));
systemFontMap.put(alias.getName(), newFace);
outSystemFontMap.put(alias.getAliasName(), newFace);
}
}
@@ -1339,11 +1343,11 @@ public class Typeface {
/** @hide */
public static void loadPreinstalledSystemFontMap() {
final HashMap<String, Typeface> systemFontMap = new HashMap<>();
Pair<FontConfig.Alias[], Map<String, FontFamily[]>> pair =
SystemFonts.initializePreinstalledFonts();
initSystemDefaultTypefaces(systemFontMap, pair.second, pair.first);
setSystemFontMap(systemFontMap);
final FontConfig fontConfig = SystemFonts.getSystemPreinstalledFontConfig();
final Map<String, FontFamily[]> fallback = SystemFonts.buildSystemFallback(fontConfig);
final Map<String, Typeface> typefaceMap =
SystemFonts.buildSystemTypefaces(fontConfig, fallback);
setSystemFontMap(typefaceMap);
}
static {

View File

@@ -16,18 +16,25 @@
package android.graphics.fonts;
import static android.text.FontConfig.Alias;
import static android.text.FontConfig.Family;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.graphics.FontListParser;
import android.text.FontConfig;
import android.util.Xml;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Parser for font customization
@@ -39,8 +46,27 @@ public class FontCustomizationParser {
* Represents a customization XML
*/
public static class Result {
ArrayList<FontConfig.Family> mAdditionalNamedFamilies = new ArrayList<>();
ArrayList<FontConfig.Alias> mAdditionalAliases = new ArrayList<>();
private final Map<String, Family> mAdditionalNamedFamilies;
private final List<Alias> mAdditionalAliases;
public Result() {
mAdditionalNamedFamilies = Collections.emptyMap();
mAdditionalAliases = Collections.emptyList();
}
public Result(Map<String, Family> additionalNamedFamilies,
List<Alias> additionalAliases) {
mAdditionalNamedFamilies = additionalNamedFamilies;
mAdditionalAliases = additionalAliases;
}
public Map<String, Family> getAdditionalNamedFamilies() {
return mAdditionalNamedFamilies;
}
public List<Alias> getAdditionalAliases() {
return mAdditionalAliases;
}
}
/**
@@ -48,56 +74,67 @@ public class FontCustomizationParser {
*
* Caller must close the input stream
*/
public static Result parse(@NonNull InputStream in, @NonNull String fontDir)
throws XmlPullParserException, IOException {
public static Result parse(
@NonNull InputStream in,
@NonNull String fontDir,
@Nullable Map<String, File> updatableFontMap
) throws XmlPullParserException, IOException {
XmlPullParser parser = Xml.newPullParser();
parser.setInput(in, null);
parser.nextTag();
return readFamilies(parser, fontDir);
return readFamilies(parser, fontDir, updatableFontMap);
}
private static void validate(Result result) {
HashSet<String> familyNames = new HashSet<>();
for (int i = 0; i < result.mAdditionalNamedFamilies.size(); ++i) {
final FontConfig.Family family = result.mAdditionalNamedFamilies.get(i);
final String name = family.getName();
private static Map<String, Family> validateAndTransformToMap(List<Family> families) {
HashMap<String, Family> namedFamily = new HashMap<>();
for (int i = 0; i < families.size(); ++i) {
final Family family = families.get(i);
final String name = family.getFallbackName();
if (name == null) {
throw new IllegalArgumentException("new-named-family requires name attribute");
}
if (!familyNames.add(name)) {
if (namedFamily.put(name, family) != null) {
throw new IllegalArgumentException(
"new-named-family requires unique name attribute");
}
}
return namedFamily;
}
private static Result readFamilies(XmlPullParser parser, String fontDir)
throws XmlPullParserException, IOException {
Result out = new Result();
private static Result readFamilies(
@NonNull XmlPullParser parser,
@NonNull String fontDir,
@Nullable Map<String, File> updatableFontMap
) throws XmlPullParserException, IOException {
List<Family> families = new ArrayList<>();
List<Alias> aliases = new ArrayList<>();
parser.require(XmlPullParser.START_TAG, null, "fonts-modification");
while (parser.next() != XmlPullParser.END_TAG) {
if (parser.getEventType() != XmlPullParser.START_TAG) continue;
String tag = parser.getName();
if (tag.equals("family")) {
readFamily(parser, fontDir, out);
readFamily(parser, fontDir, families, updatableFontMap);
} else if (tag.equals("alias")) {
out.mAdditionalAliases.add(FontListParser.readAlias(parser));
aliases.add(FontListParser.readAlias(parser));
} else {
FontListParser.skip(parser);
}
}
validate(out);
return out;
return new Result(validateAndTransformToMap(families), aliases);
}
private static void readFamily(XmlPullParser parser, String fontDir, Result out)
private static void readFamily(
@NonNull XmlPullParser parser,
@NonNull String fontDir,
@NonNull List<Family> out,
@Nullable Map<String, File> updatableFontMap)
throws XmlPullParserException, IOException {
final String customizationType = parser.getAttributeValue(null, "customizationType");
if (customizationType == null) {
throw new IllegalArgumentException("customizationType must be specified");
}
if (customizationType.equals("new-named-family")) {
out.mAdditionalNamedFamilies.add(FontListParser.readFamily(parser, fontDir, null));
out.add(FontListParser.readFamily(parser, fontDir, updatableFontMap));
} else {
throw new IllegalArgumentException("Unknown customizationType=" + customizationType);
}

View File

@@ -18,6 +18,7 @@ package android.graphics.fonts;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.text.FontConfig;
import com.android.internal.util.Preconditions;
@@ -119,7 +120,7 @@ public final class FontFamily {
nAddFont(builderPtr, mFonts.get(i).getNativePtr());
}
final long ptr = nBuild(builderPtr, langTags, variant, isCustomFallback);
final FontFamily family = new FontFamily(mFonts, ptr);
final FontFamily family = new FontFamily(mFonts, langTags, variant, ptr);
sFamilyRegistory.registerNativeAllocation(family, ptr);
return family;
}
@@ -138,14 +139,35 @@ public final class FontFamily {
}
private final ArrayList<Font> mFonts;
private final String mLangTags;
private final int mVariant;
private final long mNativePtr;
// Use Builder instead.
private FontFamily(@NonNull ArrayList<Font> fonts, long ptr) {
private FontFamily(@NonNull ArrayList<Font> fonts, String langTags, int variant, long ptr) {
mFonts = fonts;
mLangTags = langTags;
mVariant = variant;
mNativePtr = ptr;
}
/**
* Returns a BCP-47 compliant language tags associated with this font family.
* @hide
* @return a BCP-47 compliant language tag.
*/
public @Nullable String getLangTags() {
return mLangTags;
}
/**
* @hide
* @return a family variant
*/
public int getVariant() {
return mVariant;
}
/**
* Returns a font
*

View File

@@ -23,11 +23,9 @@ import android.graphics.Typeface;
import android.text.FontConfig;
import android.util.ArrayMap;
import android.util.Log;
import android.util.Pair;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import org.xmlpull.v1.XmlPullParserException;
@@ -37,7 +35,6 @@ import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
@@ -52,6 +49,11 @@ public final class SystemFonts {
private static final String TAG = "SystemFonts";
private static final String DEFAULT_FAMILY = "sans-serif";
private static final String FONTS_XML = "/system/etc/fonts.xml";
private static final String SYSTEM_FONT_DIR = "/system/fonts/";
private static final String OEM_XML = "/product/etc/fonts_customization.xml";
private static final String OEM_FONT_DIR = "/product/fonts/";
private SystemFonts() {} // Do not instansiate.
private static final Object LOCK = new Object();
@@ -88,12 +90,10 @@ public final class SystemFonts {
}
private static @NonNull Set<Font> collectAllFonts() {
final FontCustomizationParser.Result oemCustomization =
readFontCustomization("/product/etc/fonts_customization.xml", "/product/fonts/");
Map<String, FontFamily[]> map = new ArrayMap<>();
// TODO: use updated fonts
buildSystemFallback("/system/etc/fonts.xml", "/system/fonts/", null /* updatableFontMap */,
oemCustomization, map);
FontConfig fontConfig = getSystemPreinstalledFontConfig();
Map<String, FontFamily[]> map = buildSystemFallback(fontConfig);
Set<Font> res = new HashSet<>();
for (FontFamily[] families : map.values()) {
for (FontFamily family : families) {
@@ -119,15 +119,16 @@ public final class SystemFonts {
@NonNull ArrayMap<String, ArrayList<FontFamily>> fallbackMap,
@NonNull Map<String, ByteBuffer> cache) {
final String languageTags = xmlFamily.getLanguages();
final int variant = xmlFamily.getVariant();
final String languageTags = xmlFamily.getLocaleList().toLanguageTags();
final int variant = xmlFamily.getTextHeightVariant();
final ArrayList<FontConfig.Font> defaultFonts = new ArrayList<>();
final ArrayMap<String, ArrayList<FontConfig.Font>> specificFallbackFonts = new ArrayMap<>();
final ArrayMap<String, ArrayList<FontConfig.Font>> specificFallbackFonts =
new ArrayMap<>();
// Collect default fallback and specific fallback fonts.
for (final FontConfig.Font font : xmlFamily.getFonts()) {
final String fallbackName = font.getFallbackFor();
final String fallbackName = font.getFallback();
if (fallbackName == null) {
defaultFonts.add(font);
} else {
@@ -141,19 +142,22 @@ public final class SystemFonts {
}
final FontFamily defaultFamily = defaultFonts.isEmpty() ? null : createFontFamily(
xmlFamily.getName(), defaultFonts, languageTags, variant, cache);
xmlFamily.getFallbackName(), defaultFonts, languageTags, variant, cache);
// Insert family into fallback map.
for (int i = 0; i < fallbackMap.size(); i++) {
final ArrayList<FontConfig.Font> fallback =
specificFallbackFonts.get(fallbackMap.keyAt(i));
String name = fallbackMap.keyAt(i);
final ArrayList<FontConfig.Font> fallback = specificFallbackFonts.get(name);
if (fallback == null) {
if (defaultFamily != null) {
String familyName = xmlFamily.getFallbackName();
if (defaultFamily != null
// do not add myself to the fallback chain.
&& (familyName == null || !familyName.equals(name))) {
fallbackMap.valueAt(i).add(defaultFamily);
}
} else {
final FontFamily family = createFontFamily(
xmlFamily.getName(), fallback, languageTags, variant, cache);
xmlFamily.getFallbackName(), fallback, languageTags, variant, cache);
if (family != null) {
fallbackMap.valueAt(i).add(family);
} else if (defaultFamily != null) {
@@ -177,7 +181,7 @@ public final class SystemFonts {
FontFamily.Builder b = null;
for (int i = 0; i < fonts.size(); i++) {
final FontConfig.Font fontConfig = fonts.get(i);
final String fullPath = fontConfig.getFontName();
final String fullPath = fontConfig.getFilePath().getAbsolutePath();
ByteBuffer buffer = cache.get(fullPath);
if (buffer == null) {
if (cache.containsKey(fullPath)) {
@@ -193,11 +197,10 @@ public final class SystemFonts {
final Font font;
try {
font = new Font.Builder(buffer, new File(fullPath), languageTags)
.setWeight(fontConfig.getWeight())
.setSlant(fontConfig.isItalic() ? FontStyle.FONT_SLANT_ITALIC
: FontStyle.FONT_SLANT_UPRIGHT)
.setTtcIndex(fontConfig.getTtcIndex())
.setFontVariationSettings(fontConfig.getAxes())
.setWeight(fontConfig.getStyle().getWeight())
.setSlant(fontConfig.getStyle().getSlant())
.setTtcIndex(fontConfig.getIndex())
.setFontVariationSettings(fontConfig.getFontVariationSettings())
.build();
} catch (IOException e) {
throw new RuntimeException(e); // Never reaches here
@@ -215,10 +218,11 @@ public final class SystemFonts {
private static void appendNamedFamily(@NonNull FontConfig.Family xmlFamily,
@NonNull HashMap<String, ByteBuffer> bufferCache,
@NonNull ArrayMap<String, ArrayList<FontFamily>> fallbackListMap) {
final String familyName = xmlFamily.getName();
final String familyName = xmlFamily.getFallbackName();
final FontFamily family = createFontFamily(
familyName, Arrays.asList(xmlFamily.getFonts()),
xmlFamily.getLanguages(), xmlFamily.getVariant(), bufferCache);
familyName, xmlFamily.getFontList(),
xmlFamily.getLocaleList().toLanguageTags(), xmlFamily.getTextHeightVariant(),
bufferCache);
if (family == null) {
return;
}
@@ -228,115 +232,104 @@ public final class SystemFonts {
}
/**
* @see #buildSystemFallback(String, String, Map, FontCustomizationParser.Result, Map)
* Get the updated FontConfig.
*
* @param updatableFontMap a font mapping of updated font files.
* @hide
*/
@VisibleForTesting
public static FontConfig.Alias[] buildSystemFallback(@NonNull String xmlPath,
@NonNull String fontDir,
@NonNull FontCustomizationParser.Result oemCustomization,
@NonNull Map<String, FontFamily[]> fallbackMap) {
return buildSystemFallback(xmlPath, fontDir, null /* updatableFontMap */,
oemCustomization, fallbackMap);
public static @NonNull FontConfig getSystemFontConfig(
@Nullable Map<String, File> updatableFontMap
) {
return getSystemFontConfigInternal(FONTS_XML, SYSTEM_FONT_DIR, OEM_XML, OEM_FONT_DIR,
updatableFontMap);
}
/**
* Build the system fallback from xml file.
*
* @param xmlPath A full path string to the fonts.xml file.
* @param fontDir A full path string to the system font directory. This must end with
* slash('/').
* @param updatableFontMap A map from font file name to updated font file path.
* @param fallbackMap An output system fallback map. Caller must pass empty map.
* @return a list of aliases
* Get the system preinstalled FontConfig.
* @hide
*/
public static @NonNull FontConfig getSystemPreinstalledFontConfig() {
return getSystemFontConfigInternal(FONTS_XML, SYSTEM_FONT_DIR, OEM_XML, OEM_FONT_DIR, null);
}
/* package */ static @NonNull FontConfig getSystemFontConfigInternal(
@NonNull String fontsXml,
@NonNull String systemFontDir,
@Nullable String oemXml,
@Nullable String productFontDir,
@Nullable Map<String, File> updatableFontMap
) {
try {
return FontListParser.parse(fontsXml, systemFontDir, oemXml, productFontDir,
updatableFontMap);
} catch (IOException e) {
Log.e(TAG, "Failed to open/read system font configurations.", e);
return new FontConfig(Collections.emptyList(), Collections.emptyList());
} catch (XmlPullParserException e) {
Log.e(TAG, "Failed to parse the system font configuration.", e);
return new FontConfig(Collections.emptyList(), Collections.emptyList());
}
}
/**
* Build the system fallback from FontConfig.
* @hide
*/
@VisibleForTesting
public static FontConfig.Alias[] buildSystemFallback(@NonNull String xmlPath,
@NonNull String fontDir,
@Nullable Map<String, File> updatableFontMap,
@NonNull FontCustomizationParser.Result oemCustomization,
@NonNull Map<String, FontFamily[]> fallbackMap) {
try {
final FileInputStream fontsIn = new FileInputStream(xmlPath);
final FontConfig fontConfig = FontListParser.parse(fontsIn, fontDir, updatableFontMap);
public static Map<String, FontFamily[]> buildSystemFallback(FontConfig fontConfig) {
final Map<String, FontFamily[]> fallbackMap = new HashMap<>();
final HashMap<String, ByteBuffer> bufferCache = new HashMap<>();
final List<FontConfig.Family> xmlFamilies = fontConfig.getFontFamilies();
final HashMap<String, ByteBuffer> bufferCache = new HashMap<String, ByteBuffer>();
final FontConfig.Family[] xmlFamilies = fontConfig.getFamilies();
final ArrayMap<String, ArrayList<FontFamily>> fallbackListMap = new ArrayMap<>();
// First traverse families which have a 'name' attribute to create fallback map.
for (final FontConfig.Family xmlFamily : xmlFamilies) {
final String familyName = xmlFamily.getName();
if (familyName == null) {
continue;
}
appendNamedFamily(xmlFamily, bufferCache, fallbackListMap);
final ArrayMap<String, ArrayList<FontFamily>> fallbackListMap = new ArrayMap<>();
// First traverse families which have a 'name' attribute to create fallback map.
for (final FontConfig.Family xmlFamily : xmlFamilies) {
final String familyName = xmlFamily.getFallbackName();
if (familyName == null) {
continue;
}
for (int i = 0; i < oemCustomization.mAdditionalNamedFamilies.size(); ++i) {
appendNamedFamily(oemCustomization.mAdditionalNamedFamilies.get(i),
bufferCache, fallbackListMap);
}
// Then, add fallback fonts to the each fallback map.
for (int i = 0; i < xmlFamilies.length; i++) {
final FontConfig.Family xmlFamily = xmlFamilies[i];
// The first family (usually the sans-serif family) is always placed immediately
// after the primary family in the fallback.
if (i == 0 || xmlFamily.getName() == null) {
pushFamilyToFallback(xmlFamily, fallbackListMap, bufferCache);
}
}
// Build the font map and fallback map.
for (int i = 0; i < fallbackListMap.size(); i++) {
final String fallbackName = fallbackListMap.keyAt(i);
final List<FontFamily> familyList = fallbackListMap.valueAt(i);
final FontFamily[] families = familyList.toArray(new FontFamily[familyList.size()]);
fallbackMap.put(fallbackName, families);
}
final ArrayList<FontConfig.Alias> list = new ArrayList<>();
list.addAll(Arrays.asList(fontConfig.getAliases()));
list.addAll(oemCustomization.mAdditionalAliases);
return list.toArray(new FontConfig.Alias[list.size()]);
} catch (IOException | XmlPullParserException e) {
Log.e(TAG, "Failed initialize system fallbacks.", e);
return ArrayUtils.emptyArray(FontConfig.Alias.class);
appendNamedFamily(xmlFamily, bufferCache, fallbackListMap);
}
}
private static FontCustomizationParser.Result readFontCustomization(
@NonNull String customizeXml, @NonNull String customFontsDir) {
try (FileInputStream f = new FileInputStream(customizeXml)) {
return FontCustomizationParser.parse(f, customFontsDir);
} catch (IOException e) {
return new FontCustomizationParser.Result();
} catch (XmlPullParserException e) {
Log.e(TAG, "Failed to parse font customization XML", e);
return new FontCustomizationParser.Result();
// Then, add fallback fonts to the each fallback map.
for (int i = 0; i < xmlFamilies.size(); i++) {
final FontConfig.Family xmlFamily = xmlFamilies.get(i);
// The first family (usually the sans-serif family) is always placed immediately
// after the primary family in the fallback.
if (i == 0 || xmlFamily.getFallbackName() == null) {
pushFamilyToFallback(xmlFamily, fallbackListMap, bufferCache);
}
}
// Build the font map and fallback map.
for (int i = 0; i < fallbackListMap.size(); i++) {
final String fallbackName = fallbackListMap.keyAt(i);
final List<FontFamily> familyList = fallbackListMap.valueAt(i);
fallbackMap.put(fallbackName, familyList.toArray(new FontFamily[0]));
}
return fallbackMap;
}
/** @hide */
public static @NonNull Pair<FontConfig.Alias[], Map<String, FontFamily[]>>
initializePreinstalledFonts() {
return initializeSystemFonts(null);
/**
* Build the system Typeface mappings from FontConfig and FallbackMap.
* @hide
*/
@VisibleForTesting
public static Map<String, Typeface> buildSystemTypefaces(
FontConfig fontConfig,
Map<String, FontFamily[]> fallbackMap) {
final HashMap<String, Typeface> result = new HashMap<>();
Typeface.initSystemDefaultTypefaces(fallbackMap, fontConfig.getAliases(), result);
return result;
}
/** @hide */
public static Pair<FontConfig.Alias[], Map<String, FontFamily[]>>
initializeSystemFonts(@Nullable Map<String, File> updatableFontMap) {
final FontCustomizationParser.Result oemCustomization =
readFontCustomization("/product/etc/fonts_customization.xml", "/product/fonts/");
Map<String, FontFamily[]> map = new ArrayMap<>();
FontConfig.Alias[] aliases = buildSystemFallback("/system/etc/fonts.xml", "/system/fonts/",
updatableFontMap, oemCustomization, map);
/**
* @hide
*/
public void resetFallbackMapping(Map<String, FontFamily[]> fallbackMap) {
synchronized (LOCK) {
sFamilyMap = map;
sFamilyMap = fallbackMap;
}
return new Pair(aliases, map);
}
}

View File

@@ -26,7 +26,6 @@ import android.graphics.fonts.SystemFonts;
import android.os.SharedMemory;
import android.system.ErrnoException;
import android.text.FontConfig;
import android.util.Pair;
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
@@ -40,7 +39,6 @@ import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.NioUtils;
import java.nio.channels.FileChannel;
import java.util.HashMap;
import java.util.Map;
/** A service for managing system fonts. */
@@ -69,7 +67,10 @@ public final class FontManagerService {
@Override
@Nullable
public SharedMemory getSerializedSystemFontMap() {
return mService.getSerializedSystemFontMap();
if (!Typeface.ENABLE_LAZY_TYPEFACE_INITIALIZATION) {
return null;
}
return mService.getCurrentFontSettings().getSerializedSystemFontMap();
}
});
}
@@ -98,50 +99,22 @@ public final class FontManagerService {
private final UpdatableFontDir mUpdatableFontDir;
@GuardedBy("FontManagerService.this")
@Nullable
private SharedMemory mSerializedSystemFontMap = null;
@Nullable SystemFontSettings mCurrentFontSettings = null;
private FontManagerService() {
mUpdatableFontDir = ENABLE_FONT_UPDATES
? new UpdatableFontDir(new File(FONT_FILES_DIR), new OtfFontFileParser()) : null;
}
@Nullable
private SharedMemory getSerializedSystemFontMap() {
if (!Typeface.ENABLE_LAZY_TYPEFACE_INITIALIZATION) {
return null;
}
@NonNull private SystemFontSettings getCurrentFontSettings() {
synchronized (FontManagerService.this) {
if (mSerializedSystemFontMap == null) {
mSerializedSystemFontMap = createSerializedSystemFontMapLocked();
if (mCurrentFontSettings == null) {
mCurrentFontSettings = SystemFontSettings.create(mUpdatableFontDir);
}
return mSerializedSystemFontMap;
return mCurrentFontSettings;
}
}
@Nullable
private SharedMemory createSerializedSystemFontMapLocked() {
if (mUpdatableFontDir != null) {
HashMap<String, Typeface> systemFontMap = new HashMap<>();
Map<String, File> fontFileMap = mUpdatableFontDir.getFontFileMap();
Pair<FontConfig.Alias[], Map<String, FontFamily[]>> pair =
SystemFonts.initializeSystemFonts(fontFileMap);
Typeface.initSystemDefaultTypefaces(systemFontMap, pair.second, pair.first);
try {
return Typeface.serializeFontMap(systemFontMap);
} catch (IOException | ErrnoException e) {
Slog.w(TAG, "Failed to serialize updatable font map. "
+ "Retrying with system image fonts.", e);
}
}
try {
return Typeface.serializeFontMap(Typeface.getSystemFontMap());
} catch (IOException | ErrnoException e) {
Slog.e(TAG, "Failed to serialize SystemServer system font map", e);
}
return null;
}
private boolean installFontFile(String name, FileDescriptor fd) {
if (mUpdatableFontDir == null) return false;
synchronized (FontManagerService.this) {
@@ -152,8 +125,74 @@ public final class FontManagerService {
return false;
}
// Create updated font map in the next getSerializedSystemFontMap() call.
mSerializedSystemFontMap = null;
mCurrentFontSettings = null;
return true;
}
}
private static class SystemFontSettings {
private final @NonNull SharedMemory mSerializedSystemFontMap;
private final @NonNull FontConfig mSystemFontConfig;
private final @NonNull Map<String, FontFamily[]> mSystemFallbackMap;
private final @NonNull Map<String, Typeface> mSystemTypefaceMap;
SystemFontSettings(
@NonNull SharedMemory serializedSystemFontMap,
@NonNull FontConfig systemFontConfig,
@NonNull Map<String, FontFamily[]> systemFallbackMap,
@NonNull Map<String, Typeface> systemTypefaceMap) {
mSerializedSystemFontMap = serializedSystemFontMap;
mSystemFontConfig = systemFontConfig;
mSystemFallbackMap = systemFallbackMap;
mSystemTypefaceMap = systemTypefaceMap;
}
public @NonNull SharedMemory getSerializedSystemFontMap() {
return mSerializedSystemFontMap;
}
public @NonNull FontConfig getSystemFontConfig() {
return mSystemFontConfig;
}
public @NonNull Map<String, FontFamily[]> getSystemFallbackMap() {
return mSystemFallbackMap;
}
public @NonNull Map<String, Typeface> getSystemTypefaceMap() {
return mSystemTypefaceMap;
}
public static @Nullable SystemFontSettings create(
@Nullable UpdatableFontDir updatableFontDir) {
if (updatableFontDir != null) {
final FontConfig fontConfig = SystemFonts.getSystemFontConfig(
updatableFontDir.getFontFileMap());
final Map<String, FontFamily[]> fallback =
SystemFonts.buildSystemFallback(fontConfig);
final Map<String, Typeface> typefaceMap =
SystemFonts.buildSystemTypefaces(fontConfig, fallback);
try {
final SharedMemory shm = Typeface.serializeFontMap(typefaceMap);
return new SystemFontSettings(shm, fontConfig, fallback, typefaceMap);
} catch (IOException | ErrnoException e) {
Slog.w(TAG, "Failed to serialize updatable font map. "
+ "Retrying with system image fonts.", e);
}
}
final FontConfig fontConfig = SystemFonts.getSystemPreinstalledFontConfig();
final Map<String, FontFamily[]> fallback = SystemFonts.buildSystemFallback(fontConfig);
final Map<String, Typeface> typefaceMap =
SystemFonts.buildSystemTypefaces(fontConfig, fallback);
try {
final SharedMemory shm = Typeface.serializeFontMap(typefaceMap);
return new SystemFontSettings(shm, fontConfig, fallback, typefaceMap);
} catch (IOException | ErrnoException e) {
Slog.e(TAG, "Failed to serialize SystemServer system font map", e);
}
return null;
}
};
}