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

Commit eec33b7d authored by Seigo Nonaka's avatar Seigo Nonaka Committed by Android (Google) Code Review
Browse files

Merge "Rewrite system fallback construction with new FontFamily"

parents 64d9dcbe eaa917e6
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