Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit eaa917e6 authored by Seigo Nonaka's avatar Seigo Nonaka
Browse files

Rewrite system fallback construction with new FontFamily

Bug: 111133573
Bug: 112196940
Test: atest CtsWidgetTestCases:EditTextTest
    CtsWidgetTestCases:TextViewFadingEdgeTest
    FrameworksCoreTests:TextViewFallbackLineSpacingTest
    FrameworksCoreTests:TextViewTest FrameworksCoreTests:TypefaceTest
    CtsGraphicsTestCases:TypefaceTest CtsWidgetTestCases:TextViewTest
    CtsTextTestCases FrameworksCoreTests:android.text
    CtsWidgetTestCases:TextViewPrecomputedTextTest
    CtsGraphicsTestCases:android.graphics.fonts

Change-Id: I69e21940eaec4427f62abe59ae003b4f60855f22
parent 9359a6a2
Loading
Loading
Loading
Loading
+14 −4
Original line number Diff line number Diff line
@@ -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>

@@ -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",
@@ -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 },
};
+12 −3
Original line number Diff line number Diff line
@@ -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;
@@ -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
@@ -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
+5 −2
Original line number Diff line number Diff line
@@ -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;

@@ -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
+49 −188
Original line number Diff line number Diff line
@@ -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;
@@ -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;
@@ -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
@@ -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;
            }
@@ -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);
    }

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

+19 −2
Original line number Diff line number Diff line
@@ -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;

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