Loading core/jni/android/graphics/fonts/FontFamily.cpp +14 −4 Original line number Diff line number Diff line Loading @@ -17,11 +17,13 @@ #define LOG_TAG "Minikin" #include <nativehelper/JNIHelp.h> #include <nativehelper/ScopedUtfChars.h> #include <core_jni_helpers.h> #include "FontUtils.h" #include <minikin/FontFamily.h> #include <minikin/LocaleList.h> #include <memory> Loading Loading @@ -54,10 +56,18 @@ static void FontFamily_Builder_addFont(jlong builderPtr, jlong fontPtr) { } // Regular JNI static jlong FontFamily_Builder_build(JNIEnv* env, jobject clazz, jlong builderPtr) { static jlong FontFamily_Builder_build(JNIEnv* env, jobject clazz, jlong builderPtr, jstring langTags, jint variant) { std::unique_ptr<NativeFamilyBuilder> builder(toBuilder(builderPtr)); std::shared_ptr<minikin::FontFamily> family = std::make_shared<minikin::FontFamily>(std::move(builder->fonts)); uint32_t localeId; if (langTags == nullptr) { localeId = minikin::registerLocaleList(""); } else { ScopedUtfChars str(env, langTags); localeId = minikin::registerLocaleList(str.c_str()); } std::shared_ptr<minikin::FontFamily> family = std::make_shared<minikin::FontFamily>( localeId, static_cast<minikin::FamilyVariant>(variant), std::move(builder->fonts)); if (family->getCoverage().length() == 0) { // No coverage means minikin rejected given font for some reasons. jniThrowException(env, "java/lang/IllegalArgumentException", Loading @@ -77,7 +87,7 @@ static jlong FontFamily_Builder_GetReleaseFunc() { static const JNINativeMethod gFontFamilyBuilderMethods[] = { { "nInitBuilder", "()J", (void*) FontFamily_Builder_initBuilder }, { "nAddFont", "(JJ)V", (void*) FontFamily_Builder_addFont }, { "nBuild", "(J)J", (void*) FontFamily_Builder_build }, { "nBuild", "(JLjava/lang/String;I)J", (void*) FontFamily_Builder_build }, { "nGetReleaseNativeFamily", "()J", (void*) FontFamily_Builder_GetReleaseFunc }, }; Loading core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java +12 −3 Original line number Diff line number Diff line Loading @@ -23,9 +23,12 @@ import static org.junit.Assert.assertTrue; import android.content.Context; import android.content.res.AssetManager; import android.graphics.fonts.FontFamily; import android.graphics.fonts.SystemFonts; import android.support.test.InstrumentationRegistry; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; import android.text.FontConfig; import android.util.ArrayMap; import org.junit.After; Loading Loading @@ -112,7 +115,9 @@ public class TypefaceSystemFallbackTest { } catch (IOException e) { throw new RuntimeException(e); } Typeface.buildSystemFallback(TEST_FONTS_XML, TEST_FONT_DIR, fontMap, fallbackMap); final FontConfig.Alias[] aliases = SystemFonts.buildSystemFallback(TEST_FONTS_XML, TEST_FONT_DIR, fallbackMap); Typeface.initSystemDefaultTypefaces(fontMap, fallbackMap, aliases); } @Test Loading @@ -120,10 +125,14 @@ public class TypefaceSystemFallbackTest { final ArrayMap<String, Typeface> fontMap = new ArrayMap<>(); final ArrayMap<String, FontFamily[]> fallbackMap = new ArrayMap<>(); Typeface.buildSystemFallback(SYSTEM_FONTS_XML, SYSTEM_FONT_DIR, fontMap, fallbackMap); final FontConfig.Alias[] aliases = SystemFonts.buildSystemFallback(SYSTEM_FONTS_XML, SYSTEM_FONT_DIR, fallbackMap); assertFalse(fontMap.isEmpty()); assertNotNull(aliases); assertFalse(fallbackMap.isEmpty()); Typeface.initSystemDefaultTypefaces(fontMap, fallbackMap, aliases); assertFalse(fontMap.isEmpty()); } @Test Loading core/tests/coretests/src/android/text/FontFallbackSetup.java +5 −2 Original line number Diff line number Diff line Loading @@ -19,8 +19,9 @@ package android.text; import android.annotation.NonNull; import android.content.Context; import android.content.res.AssetManager; import android.graphics.FontFamily; import android.graphics.Typeface; import android.graphics.fonts.FontFamily; import android.graphics.fonts.SystemFonts; import android.support.test.InstrumentationRegistry; import android.util.ArrayMap; Loading Loading @@ -73,7 +74,9 @@ public class FontFallbackSetup implements AutoCloseable { } final ArrayMap<String, FontFamily[]> fallbackMap = new ArrayMap<>(); Typeface.buildSystemFallback(testFontsXml, mTestFontsDir, mFontMap, fallbackMap); final FontConfig.Alias[] aliases = SystemFonts.buildSystemFallback(testFontsXml, mTestFontsDir, fallbackMap); Typeface.initSystemDefaultTypefaces(mFontMap, fallbackMap, aliases); } @NonNull Loading graphics/java/android/graphics/Typeface.java +49 −188 Original line number Diff line number Diff line Loading @@ -28,13 +28,13 @@ import android.annotation.Nullable; import android.annotation.UnsupportedAppUsage; import android.content.res.AssetManager; import android.graphics.fonts.FontVariationAxis; import android.graphics.fonts.SystemFonts; import android.net.Uri; import android.provider.FontRequest; import android.provider.FontsContract; import android.text.FontConfig; import android.util.ArrayMap; import android.util.Base64; import android.util.Log; import android.util.LongSparseArray; import android.util.LruCache; import android.util.SparseArray; Loading @@ -47,12 +47,9 @@ import dalvik.annotation.optimization.CriticalNative; import libcore.util.NativeAllocationRegistry; import org.xmlpull.v1.XmlPullParserException; import java.io.File; import java.io.FileDescriptor; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.lang.annotation.Retention; Loading Loading @@ -121,10 +118,14 @@ public class Typeface { private static final Object sDynamicCacheLock = new Object(); static Typeface sDefaultTypeface; // Following two fields are not used but left for hiddenapi private list @UnsupportedAppUsage static final Map<String, Typeface> sSystemFontMap; // We cannot support sSystemFallbackMap since we will migrate to public FontFamily API. @UnsupportedAppUsage static final Map<String, FontFamily[]> sSystemFallbackMap; static final Map<String, FontFamily[]> sSystemFallbackMap = Collections.emptyMap(); /** * @hide Loading Loading @@ -566,11 +567,7 @@ public class Typeface { return null; } Typeface base = sSystemFontMap.get(mFallbackFamilyName); if (base == null) { base = sDefaultTypeface; } final Typeface base = getSystemDefaultTypeface(mFallbackFamilyName); if (mWeight == RESOLVE_BY_FONT_TABLE && mItalic == RESOLVE_BY_FONT_TABLE) { return base; } Loading Loading @@ -687,7 +684,7 @@ public class Typeface { * @return The best matching typeface. */ public static Typeface create(String familyName, @Style int style) { return create(sSystemFontMap.get(familyName), style); return create(getSystemDefaultTypeface(familyName), style); } /** Loading Loading @@ -897,7 +894,9 @@ public class Typeface { * Create a new typeface from an array of font families. * * @param families array of font families * @deprecated */ @Deprecated @UnsupportedAppUsage private static Typeface createFromFamilies(FontFamily[] families) { long[] ptrArray = new long[families.length]; Loading @@ -908,6 +907,21 @@ public class Typeface { ptrArray, RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE)); } /** * Create a new typeface from an array of android.graphics.fonts.FontFamily. * * @param families array of font families */ private static Typeface createFromFamilies( @Nullable android.graphics.fonts.FontFamily[] families) { final long[] ptrArray = new long[families.length]; for (int i = 0; i < families.length; ++i) { ptrArray[i] = families[i].getNativePtr(); } return new Typeface(nativeCreateFromArray(ptrArray, RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE)); } /** * This method is used by supportlib-v27. * TODO: Remove private API use in supportlib: http://b/72665240 Loading @@ -934,16 +948,13 @@ public class Typeface { @UnsupportedAppUsage private static Typeface createFromFamiliesWithDefault(FontFamily[] families, String fallbackName, int weight, int italic) { FontFamily[] fallback = sSystemFallbackMap.get(fallbackName); if (fallback == null) { fallback = sSystemFallbackMap.get(DEFAULT_FAMILY); } android.graphics.fonts.FontFamily[] fallback = SystemFonts.getSystemFallback(fallbackName); long[] ptrArray = new long[families.length + fallback.length]; for (int i = 0; i < families.length; i++) { ptrArray[i] = families[i].mNativePtr; } for (int i = 0; i < fallback.length; i++) { ptrArray[i + families.length] = fallback[i].mNativePtr; ptrArray[i + families.length] = fallback[i].getNativePtr(); } return new Typeface(nativeCreateFromArray(ptrArray, weight, italic)); } Loading @@ -961,191 +972,41 @@ public class Typeface { mWeight = nativeGetWeight(ni); } private static @Nullable ByteBuffer mmap(String fullPath) { try (FileInputStream file = new FileInputStream(fullPath)) { final FileChannel fileChannel = file.getChannel(); final long fontSize = fileChannel.size(); return fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, fontSize); } catch (IOException e) { Log.e(TAG, "Error mapping font file " + fullPath); return null; } } private static @Nullable FontFamily createFontFamily( String familyName, List<FontConfig.Font> fonts, String[] languageTags, int variant, Map<String, ByteBuffer> cache, String fontDir) { final FontFamily family = new FontFamily(languageTags, variant); for (int i = 0; i < fonts.size(); i++) { final FontConfig.Font font = fonts.get(i); final String fullPath = fontDir + font.getFontName(); ByteBuffer buffer = cache.get(fullPath); if (buffer == null) { if (cache.containsKey(fullPath)) { continue; // Already failed to mmap. Skip it. } buffer = mmap(fullPath); cache.put(fullPath, buffer); if (buffer == null) { continue; } } if (!family.addFontFromBuffer(buffer, font.getTtcIndex(), font.getAxes(), font.getWeight(), font.isItalic() ? STYLE_ITALIC : STYLE_NORMAL)) { Log.e(TAG, "Error creating font " + fullPath + "#" + font.getTtcIndex()); } } if (!family.freeze()) { Log.e(TAG, "Unable to load Family: " + familyName + " : " + Arrays.toString(languageTags)); return null; } return family; private static Typeface getSystemDefaultTypeface(@NonNull String familyName) { Typeface tf = sSystemFontMap.get(familyName); return tf == null ? Typeface.DEFAULT : tf; } private static void pushFamilyToFallback(FontConfig.Family xmlFamily, ArrayMap<String, ArrayList<FontFamily>> fallbackMap, Map<String, ByteBuffer> cache, String fontDir) { final String[] languageTags = xmlFamily.getLanguages(); final int variant = xmlFamily.getVariant(); final ArrayList<FontConfig.Font> defaultFonts = new ArrayList<>(); 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(); if (fallbackName == null) { defaultFonts.add(font); } else { ArrayList<FontConfig.Font> fallback = specificFallbackFonts.get(fallbackName); if (fallback == null) { fallback = new ArrayList<>(); specificFallbackFonts.put(fallbackName, fallback); } fallback.add(font); } /** @hide */ @VisibleForTesting public static void initSystemDefaultTypefaces(Map<String, Typeface> systemFontMap, Map<String, android.graphics.fonts.FontFamily[]> fallbacks, FontConfig.Alias[] aliases) { for (Map.Entry<String, android.graphics.fonts.FontFamily[]> entry : fallbacks.entrySet()) { systemFontMap.put(entry.getKey(), createFromFamilies(entry.getValue())); } final FontFamily defaultFamily = defaultFonts.isEmpty() ? null : createFontFamily( xmlFamily.getName(), defaultFonts, languageTags, variant, cache, fontDir); // Insert family into fallback map. for (int i = 0; i < fallbackMap.size(); i++) { final ArrayList<FontConfig.Font> fallback = specificFallbackFonts.get(fallbackMap.keyAt(i)); if (fallback == null) { if (defaultFamily != null) { fallbackMap.valueAt(i).add(defaultFamily); } } else { final FontFamily family = createFontFamily( xmlFamily.getName(), fallback, languageTags, variant, cache, fontDir); if (family != null) { fallbackMap.valueAt(i).add(family); } else if (defaultFamily != null) { fallbackMap.valueAt(i).add(defaultFamily); } else { // There is no valid for for default fallback. Ignore. } } for (FontConfig.Alias alias : aliases) { final Typeface base = systemFontMap.get(alias.getToName()); final int weight = alias.getWeight(); final Typeface newFace = weight == 400 ? base : new Typeface(nativeCreateWeightAlias(base.native_instance, weight)); systemFontMap.put(alias.getName(), newFace); } } /** * 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 fontMap An output system font map. Caller must pass empty map. * @param fallbackMap An output system fallback map. Caller must pass empty map. * @hide */ @VisibleForTesting // Following methods are left for layoutlib // TODO: Remove once layoutlib stop calling buildSystemFallback /** @hide */ public static void buildSystemFallback(String xmlPath, String fontDir, ArrayMap<String, Typeface> fontMap, ArrayMap<String, FontFamily[]> fallbackMap) { try { final FileInputStream fontsIn = new FileInputStream(xmlPath); final FontConfig fontConfig = FontListParser.parse(fontsIn); 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; } final FontFamily family = createFontFamily( xmlFamily.getName(), Arrays.asList(xmlFamily.getFonts()), xmlFamily.getLanguages(), xmlFamily.getVariant(), bufferCache, fontDir); if (family == null) { continue; } final ArrayList<FontFamily> fallback = new ArrayList<>(); fallback.add(family); fallbackListMap.put(familyName, fallback); } // 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, fontDir); } } // 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 long[] ptrArray = new long[families.length]; for (int j = 0; j < families.length; j++) { ptrArray[j] = families[j].mNativePtr; } fontMap.put(fallbackName, new Typeface(nativeCreateFromArray( ptrArray, RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE))); } // Insert alias to font maps. for (final FontConfig.Alias alias : fontConfig.getAliases()) { Typeface base = fontMap.get(alias.getToName()); Typeface newFace = base; int weight = alias.getWeight(); if (weight != 400) { newFace = new Typeface(nativeCreateWeightAlias(base.native_instance, weight)); } fontMap.put(alias.getName(), newFace); } } catch (RuntimeException e) { Log.w(TAG, "Didn't create default family (most likely, non-Minikin build)", e); // TODO: normal in non-Minikin case, remove or make error when Minikin-only } catch (FileNotFoundException e) { Log.e(TAG, "Error opening " + xmlPath, e); } catch (IOException e) { Log.e(TAG, "Error reading " + xmlPath, e); } catch (XmlPullParserException e) { Log.e(TAG, "XML parse exception for " + xmlPath, e); } } static { final ArrayMap<String, Typeface> systemFontMap = new ArrayMap<>(); final ArrayMap<String, FontFamily[]> systemFallbackMap = new ArrayMap<>(); buildSystemFallback("/system/etc/fonts.xml", "/system/fonts/", systemFontMap, systemFallbackMap); final HashMap<String, Typeface> systemFontMap = new HashMap<>(); initSystemDefaultTypefaces(systemFontMap, SystemFonts.getRawSystemFallbackMap(), SystemFonts.getAliases()); sSystemFontMap = Collections.unmodifiableMap(systemFontMap); sSystemFallbackMap = Collections.unmodifiableMap(systemFallbackMap); setDefault(sSystemFontMap.get(DEFAULT_FAMILY)); Loading graphics/java/android/graphics/fonts/FontFamily.java +19 −2 Original line number Diff line number Diff line Loading @@ -18,6 +18,9 @@ package android.graphics.fonts; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.text.FontConfig; import android.text.TextUtils; import com.android.internal.util.Preconditions; Loading Loading @@ -107,11 +110,25 @@ public class FontFamily { * @return a font family */ public @NonNull FontFamily build() { return build(null, FontConfig.Family.VARIANT_DEFAULT); } /** @hide */ public @NonNull FontFamily build(@Nullable String[] langTags, int variant) { final long builderPtr = nInitBuilder(); for (int i = 0; i < mFonts.size(); ++i) { nAddFont(builderPtr, mFonts.get(i).getNativePtr()); } final long ptr = nBuild(builderPtr); final String langString; if (langTags == null || langTags.length == 0) { langString = null; } else if (langTags.length == 1) { langString = langTags[0]; } else { langString = TextUtils.join(",", langTags); } final long ptr = nBuild(builderPtr, langString, variant); final FontFamily family = new FontFamily(mFonts, ptr); sFamilyRegistory.registerNativeAllocation(family, ptr); return family; Loading @@ -124,7 +141,7 @@ public class FontFamily { private static native long nInitBuilder(); @CriticalNative private static native void nAddFont(long builderPtr, long fontPtr); private static native long nBuild(long builderPtr); private static native long nBuild(long builderPtr, String langTags, int variant); @CriticalNative private static native long nGetReleaseNativeFamily(); } Loading Loading
core/jni/android/graphics/fonts/FontFamily.cpp +14 −4 Original line number Diff line number Diff line Loading @@ -17,11 +17,13 @@ #define LOG_TAG "Minikin" #include <nativehelper/JNIHelp.h> #include <nativehelper/ScopedUtfChars.h> #include <core_jni_helpers.h> #include "FontUtils.h" #include <minikin/FontFamily.h> #include <minikin/LocaleList.h> #include <memory> Loading Loading @@ -54,10 +56,18 @@ static void FontFamily_Builder_addFont(jlong builderPtr, jlong fontPtr) { } // Regular JNI static jlong FontFamily_Builder_build(JNIEnv* env, jobject clazz, jlong builderPtr) { static jlong FontFamily_Builder_build(JNIEnv* env, jobject clazz, jlong builderPtr, jstring langTags, jint variant) { std::unique_ptr<NativeFamilyBuilder> builder(toBuilder(builderPtr)); std::shared_ptr<minikin::FontFamily> family = std::make_shared<minikin::FontFamily>(std::move(builder->fonts)); uint32_t localeId; if (langTags == nullptr) { localeId = minikin::registerLocaleList(""); } else { ScopedUtfChars str(env, langTags); localeId = minikin::registerLocaleList(str.c_str()); } std::shared_ptr<minikin::FontFamily> family = std::make_shared<minikin::FontFamily>( localeId, static_cast<minikin::FamilyVariant>(variant), std::move(builder->fonts)); if (family->getCoverage().length() == 0) { // No coverage means minikin rejected given font for some reasons. jniThrowException(env, "java/lang/IllegalArgumentException", Loading @@ -77,7 +87,7 @@ static jlong FontFamily_Builder_GetReleaseFunc() { static const JNINativeMethod gFontFamilyBuilderMethods[] = { { "nInitBuilder", "()J", (void*) FontFamily_Builder_initBuilder }, { "nAddFont", "(JJ)V", (void*) FontFamily_Builder_addFont }, { "nBuild", "(J)J", (void*) FontFamily_Builder_build }, { "nBuild", "(JLjava/lang/String;I)J", (void*) FontFamily_Builder_build }, { "nGetReleaseNativeFamily", "()J", (void*) FontFamily_Builder_GetReleaseFunc }, }; Loading
core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java +12 −3 Original line number Diff line number Diff line Loading @@ -23,9 +23,12 @@ import static org.junit.Assert.assertTrue; import android.content.Context; import android.content.res.AssetManager; import android.graphics.fonts.FontFamily; import android.graphics.fonts.SystemFonts; import android.support.test.InstrumentationRegistry; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; import android.text.FontConfig; import android.util.ArrayMap; import org.junit.After; Loading Loading @@ -112,7 +115,9 @@ public class TypefaceSystemFallbackTest { } catch (IOException e) { throw new RuntimeException(e); } Typeface.buildSystemFallback(TEST_FONTS_XML, TEST_FONT_DIR, fontMap, fallbackMap); final FontConfig.Alias[] aliases = SystemFonts.buildSystemFallback(TEST_FONTS_XML, TEST_FONT_DIR, fallbackMap); Typeface.initSystemDefaultTypefaces(fontMap, fallbackMap, aliases); } @Test Loading @@ -120,10 +125,14 @@ public class TypefaceSystemFallbackTest { final ArrayMap<String, Typeface> fontMap = new ArrayMap<>(); final ArrayMap<String, FontFamily[]> fallbackMap = new ArrayMap<>(); Typeface.buildSystemFallback(SYSTEM_FONTS_XML, SYSTEM_FONT_DIR, fontMap, fallbackMap); final FontConfig.Alias[] aliases = SystemFonts.buildSystemFallback(SYSTEM_FONTS_XML, SYSTEM_FONT_DIR, fallbackMap); assertFalse(fontMap.isEmpty()); assertNotNull(aliases); assertFalse(fallbackMap.isEmpty()); Typeface.initSystemDefaultTypefaces(fontMap, fallbackMap, aliases); assertFalse(fontMap.isEmpty()); } @Test Loading
core/tests/coretests/src/android/text/FontFallbackSetup.java +5 −2 Original line number Diff line number Diff line Loading @@ -19,8 +19,9 @@ package android.text; import android.annotation.NonNull; import android.content.Context; import android.content.res.AssetManager; import android.graphics.FontFamily; import android.graphics.Typeface; import android.graphics.fonts.FontFamily; import android.graphics.fonts.SystemFonts; import android.support.test.InstrumentationRegistry; import android.util.ArrayMap; Loading Loading @@ -73,7 +74,9 @@ public class FontFallbackSetup implements AutoCloseable { } final ArrayMap<String, FontFamily[]> fallbackMap = new ArrayMap<>(); Typeface.buildSystemFallback(testFontsXml, mTestFontsDir, mFontMap, fallbackMap); final FontConfig.Alias[] aliases = SystemFonts.buildSystemFallback(testFontsXml, mTestFontsDir, fallbackMap); Typeface.initSystemDefaultTypefaces(mFontMap, fallbackMap, aliases); } @NonNull Loading
graphics/java/android/graphics/Typeface.java +49 −188 Original line number Diff line number Diff line Loading @@ -28,13 +28,13 @@ import android.annotation.Nullable; import android.annotation.UnsupportedAppUsage; import android.content.res.AssetManager; import android.graphics.fonts.FontVariationAxis; import android.graphics.fonts.SystemFonts; import android.net.Uri; import android.provider.FontRequest; import android.provider.FontsContract; import android.text.FontConfig; import android.util.ArrayMap; import android.util.Base64; import android.util.Log; import android.util.LongSparseArray; import android.util.LruCache; import android.util.SparseArray; Loading @@ -47,12 +47,9 @@ import dalvik.annotation.optimization.CriticalNative; import libcore.util.NativeAllocationRegistry; import org.xmlpull.v1.XmlPullParserException; import java.io.File; import java.io.FileDescriptor; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.lang.annotation.Retention; Loading Loading @@ -121,10 +118,14 @@ public class Typeface { private static final Object sDynamicCacheLock = new Object(); static Typeface sDefaultTypeface; // Following two fields are not used but left for hiddenapi private list @UnsupportedAppUsage static final Map<String, Typeface> sSystemFontMap; // We cannot support sSystemFallbackMap since we will migrate to public FontFamily API. @UnsupportedAppUsage static final Map<String, FontFamily[]> sSystemFallbackMap; static final Map<String, FontFamily[]> sSystemFallbackMap = Collections.emptyMap(); /** * @hide Loading Loading @@ -566,11 +567,7 @@ public class Typeface { return null; } Typeface base = sSystemFontMap.get(mFallbackFamilyName); if (base == null) { base = sDefaultTypeface; } final Typeface base = getSystemDefaultTypeface(mFallbackFamilyName); if (mWeight == RESOLVE_BY_FONT_TABLE && mItalic == RESOLVE_BY_FONT_TABLE) { return base; } Loading Loading @@ -687,7 +684,7 @@ public class Typeface { * @return The best matching typeface. */ public static Typeface create(String familyName, @Style int style) { return create(sSystemFontMap.get(familyName), style); return create(getSystemDefaultTypeface(familyName), style); } /** Loading Loading @@ -897,7 +894,9 @@ public class Typeface { * Create a new typeface from an array of font families. * * @param families array of font families * @deprecated */ @Deprecated @UnsupportedAppUsage private static Typeface createFromFamilies(FontFamily[] families) { long[] ptrArray = new long[families.length]; Loading @@ -908,6 +907,21 @@ public class Typeface { ptrArray, RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE)); } /** * Create a new typeface from an array of android.graphics.fonts.FontFamily. * * @param families array of font families */ private static Typeface createFromFamilies( @Nullable android.graphics.fonts.FontFamily[] families) { final long[] ptrArray = new long[families.length]; for (int i = 0; i < families.length; ++i) { ptrArray[i] = families[i].getNativePtr(); } return new Typeface(nativeCreateFromArray(ptrArray, RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE)); } /** * This method is used by supportlib-v27. * TODO: Remove private API use in supportlib: http://b/72665240 Loading @@ -934,16 +948,13 @@ public class Typeface { @UnsupportedAppUsage private static Typeface createFromFamiliesWithDefault(FontFamily[] families, String fallbackName, int weight, int italic) { FontFamily[] fallback = sSystemFallbackMap.get(fallbackName); if (fallback == null) { fallback = sSystemFallbackMap.get(DEFAULT_FAMILY); } android.graphics.fonts.FontFamily[] fallback = SystemFonts.getSystemFallback(fallbackName); long[] ptrArray = new long[families.length + fallback.length]; for (int i = 0; i < families.length; i++) { ptrArray[i] = families[i].mNativePtr; } for (int i = 0; i < fallback.length; i++) { ptrArray[i + families.length] = fallback[i].mNativePtr; ptrArray[i + families.length] = fallback[i].getNativePtr(); } return new Typeface(nativeCreateFromArray(ptrArray, weight, italic)); } Loading @@ -961,191 +972,41 @@ public class Typeface { mWeight = nativeGetWeight(ni); } private static @Nullable ByteBuffer mmap(String fullPath) { try (FileInputStream file = new FileInputStream(fullPath)) { final FileChannel fileChannel = file.getChannel(); final long fontSize = fileChannel.size(); return fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, fontSize); } catch (IOException e) { Log.e(TAG, "Error mapping font file " + fullPath); return null; } } private static @Nullable FontFamily createFontFamily( String familyName, List<FontConfig.Font> fonts, String[] languageTags, int variant, Map<String, ByteBuffer> cache, String fontDir) { final FontFamily family = new FontFamily(languageTags, variant); for (int i = 0; i < fonts.size(); i++) { final FontConfig.Font font = fonts.get(i); final String fullPath = fontDir + font.getFontName(); ByteBuffer buffer = cache.get(fullPath); if (buffer == null) { if (cache.containsKey(fullPath)) { continue; // Already failed to mmap. Skip it. } buffer = mmap(fullPath); cache.put(fullPath, buffer); if (buffer == null) { continue; } } if (!family.addFontFromBuffer(buffer, font.getTtcIndex(), font.getAxes(), font.getWeight(), font.isItalic() ? STYLE_ITALIC : STYLE_NORMAL)) { Log.e(TAG, "Error creating font " + fullPath + "#" + font.getTtcIndex()); } } if (!family.freeze()) { Log.e(TAG, "Unable to load Family: " + familyName + " : " + Arrays.toString(languageTags)); return null; } return family; private static Typeface getSystemDefaultTypeface(@NonNull String familyName) { Typeface tf = sSystemFontMap.get(familyName); return tf == null ? Typeface.DEFAULT : tf; } private static void pushFamilyToFallback(FontConfig.Family xmlFamily, ArrayMap<String, ArrayList<FontFamily>> fallbackMap, Map<String, ByteBuffer> cache, String fontDir) { final String[] languageTags = xmlFamily.getLanguages(); final int variant = xmlFamily.getVariant(); final ArrayList<FontConfig.Font> defaultFonts = new ArrayList<>(); 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(); if (fallbackName == null) { defaultFonts.add(font); } else { ArrayList<FontConfig.Font> fallback = specificFallbackFonts.get(fallbackName); if (fallback == null) { fallback = new ArrayList<>(); specificFallbackFonts.put(fallbackName, fallback); } fallback.add(font); } /** @hide */ @VisibleForTesting public static void initSystemDefaultTypefaces(Map<String, Typeface> systemFontMap, Map<String, android.graphics.fonts.FontFamily[]> fallbacks, FontConfig.Alias[] aliases) { for (Map.Entry<String, android.graphics.fonts.FontFamily[]> entry : fallbacks.entrySet()) { systemFontMap.put(entry.getKey(), createFromFamilies(entry.getValue())); } final FontFamily defaultFamily = defaultFonts.isEmpty() ? null : createFontFamily( xmlFamily.getName(), defaultFonts, languageTags, variant, cache, fontDir); // Insert family into fallback map. for (int i = 0; i < fallbackMap.size(); i++) { final ArrayList<FontConfig.Font> fallback = specificFallbackFonts.get(fallbackMap.keyAt(i)); if (fallback == null) { if (defaultFamily != null) { fallbackMap.valueAt(i).add(defaultFamily); } } else { final FontFamily family = createFontFamily( xmlFamily.getName(), fallback, languageTags, variant, cache, fontDir); if (family != null) { fallbackMap.valueAt(i).add(family); } else if (defaultFamily != null) { fallbackMap.valueAt(i).add(defaultFamily); } else { // There is no valid for for default fallback. Ignore. } } for (FontConfig.Alias alias : aliases) { final Typeface base = systemFontMap.get(alias.getToName()); final int weight = alias.getWeight(); final Typeface newFace = weight == 400 ? base : new Typeface(nativeCreateWeightAlias(base.native_instance, weight)); systemFontMap.put(alias.getName(), newFace); } } /** * 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 fontMap An output system font map. Caller must pass empty map. * @param fallbackMap An output system fallback map. Caller must pass empty map. * @hide */ @VisibleForTesting // Following methods are left for layoutlib // TODO: Remove once layoutlib stop calling buildSystemFallback /** @hide */ public static void buildSystemFallback(String xmlPath, String fontDir, ArrayMap<String, Typeface> fontMap, ArrayMap<String, FontFamily[]> fallbackMap) { try { final FileInputStream fontsIn = new FileInputStream(xmlPath); final FontConfig fontConfig = FontListParser.parse(fontsIn); 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; } final FontFamily family = createFontFamily( xmlFamily.getName(), Arrays.asList(xmlFamily.getFonts()), xmlFamily.getLanguages(), xmlFamily.getVariant(), bufferCache, fontDir); if (family == null) { continue; } final ArrayList<FontFamily> fallback = new ArrayList<>(); fallback.add(family); fallbackListMap.put(familyName, fallback); } // 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, fontDir); } } // 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 long[] ptrArray = new long[families.length]; for (int j = 0; j < families.length; j++) { ptrArray[j] = families[j].mNativePtr; } fontMap.put(fallbackName, new Typeface(nativeCreateFromArray( ptrArray, RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE))); } // Insert alias to font maps. for (final FontConfig.Alias alias : fontConfig.getAliases()) { Typeface base = fontMap.get(alias.getToName()); Typeface newFace = base; int weight = alias.getWeight(); if (weight != 400) { newFace = new Typeface(nativeCreateWeightAlias(base.native_instance, weight)); } fontMap.put(alias.getName(), newFace); } } catch (RuntimeException e) { Log.w(TAG, "Didn't create default family (most likely, non-Minikin build)", e); // TODO: normal in non-Minikin case, remove or make error when Minikin-only } catch (FileNotFoundException e) { Log.e(TAG, "Error opening " + xmlPath, e); } catch (IOException e) { Log.e(TAG, "Error reading " + xmlPath, e); } catch (XmlPullParserException e) { Log.e(TAG, "XML parse exception for " + xmlPath, e); } } static { final ArrayMap<String, Typeface> systemFontMap = new ArrayMap<>(); final ArrayMap<String, FontFamily[]> systemFallbackMap = new ArrayMap<>(); buildSystemFallback("/system/etc/fonts.xml", "/system/fonts/", systemFontMap, systemFallbackMap); final HashMap<String, Typeface> systemFontMap = new HashMap<>(); initSystemDefaultTypefaces(systemFontMap, SystemFonts.getRawSystemFallbackMap(), SystemFonts.getAliases()); sSystemFontMap = Collections.unmodifiableMap(systemFontMap); sSystemFallbackMap = Collections.unmodifiableMap(systemFallbackMap); setDefault(sSystemFontMap.get(DEFAULT_FAMILY)); Loading
graphics/java/android/graphics/fonts/FontFamily.java +19 −2 Original line number Diff line number Diff line Loading @@ -18,6 +18,9 @@ package android.graphics.fonts; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.text.FontConfig; import android.text.TextUtils; import com.android.internal.util.Preconditions; Loading Loading @@ -107,11 +110,25 @@ public class FontFamily { * @return a font family */ public @NonNull FontFamily build() { return build(null, FontConfig.Family.VARIANT_DEFAULT); } /** @hide */ public @NonNull FontFamily build(@Nullable String[] langTags, int variant) { final long builderPtr = nInitBuilder(); for (int i = 0; i < mFonts.size(); ++i) { nAddFont(builderPtr, mFonts.get(i).getNativePtr()); } final long ptr = nBuild(builderPtr); final String langString; if (langTags == null || langTags.length == 0) { langString = null; } else if (langTags.length == 1) { langString = langTags[0]; } else { langString = TextUtils.join(",", langTags); } final long ptr = nBuild(builderPtr, langString, variant); final FontFamily family = new FontFamily(mFonts, ptr); sFamilyRegistory.registerNativeAllocation(family, ptr); return family; Loading @@ -124,7 +141,7 @@ public class FontFamily { private static native long nInitBuilder(); @CriticalNative private static native void nAddFont(long builderPtr, long fontPtr); private static native long nBuild(long builderPtr); private static native long nBuild(long builderPtr, String langTags, int variant); @CriticalNative private static native long nGetReleaseNativeFamily(); } Loading