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

Commit 0579097d authored by Seigo Nonaka's avatar Seigo Nonaka
Browse files

Support variable font family

A variable font family sets the requested weight and italic style
axes before passing down to the rendering pipeline.
Also, add more accessors to the PositionedGlyph APIs for knowing
fake bold/italic information as well as wght/ital overrides.

Bug: 281769620
Test: minikin_tests
Test: atest CtsTextTestCases:android.text.cts.VariableFamilyTest

Change-Id: I4a4770bf185a1c21113a293fe3d831573411ec26
parent 99a0d419
Loading
Loading
Loading
Loading
+6 −0
Original line number Diff line number Diff line
@@ -17433,6 +17433,7 @@ package android.graphics.fonts {
    ctor public FontFamily.Builder(@NonNull android.graphics.fonts.Font);
    method @NonNull public android.graphics.fonts.FontFamily.Builder addFont(@NonNull android.graphics.fonts.Font);
    method @NonNull public android.graphics.fonts.FontFamily build();
    method @Nullable public android.graphics.fonts.FontFamily buildVariableFamily();
  }
  public final class FontStyle {
@@ -17609,13 +17610,18 @@ package android.graphics.text {
    method public float getAdvance();
    method public float getAscent();
    method public float getDescent();
    method public boolean getFakeBold(@IntRange(from=0) int);
    method public boolean getFakeItalic(@IntRange(from=0) int);
    method @NonNull public android.graphics.fonts.Font getFont(@IntRange(from=0) int);
    method @IntRange(from=0) public int getGlyphId(@IntRange(from=0) int);
    method public float getGlyphX(@IntRange(from=0) int);
    method public float getGlyphY(@IntRange(from=0) int);
    method public float getItalicOverride(@IntRange(from=0) int);
    method public float getOffsetX();
    method public float getOffsetY();
    method public float getWeightOverride(@IntRange(from=0) int);
    method @IntRange(from=0) public int glyphCount();
    field public static final float NO_OVERRIDE = 1.4E-45f;
  }
  public class TextRunShaper {
+131 −5
Original line number Diff line number Diff line
@@ -30,6 +30,7 @@ import dalvik.annotation.optimization.FastNative;
import libcore.util.NativeAllocationRegistry;

import java.util.ArrayList;
import java.util.Set;

/**
 * A font family class can be used for creating Typeface.
@@ -58,6 +59,7 @@ import java.util.ArrayList;
 *
 */
public final class FontFamily {

    private static final String TAG = "FontFamily";

    /**
@@ -73,6 +75,7 @@ public final class FontFamily {
        // initial capacity.
        private final SparseIntArray mStyles = new SparseIntArray(4);


        /**
         * Constructs a builder.
         *
@@ -109,24 +112,64 @@ public final class FontFamily {
            return this;
        }

        /**
         * Build a variable font family that automatically adjust the `wght` and `ital` axes value
         * for the requested weight/italic style values.
         *
         * To build a variable font family, added fonts must meet one of following conditions.
         *
         * If two font files are added, both font files must support `wght` axis and one font must
         * support {@link FontStyle#FONT_SLANT_UPRIGHT} and another font must support
         * {@link FontStyle#FONT_SLANT_ITALIC}. If the requested weight value is lower than minimum
         * value of the supported `wght` axis, the minimum supported `wght` value is used. If the
         * requested weight value is larger than maximum value of the supported `wght` axis, the
         * maximum supported `wght` value is used. The weight values of the fonts are ignored.
         *
         * If one font file is added, that font must support the `wght` axis. If that font support
         * `ital` axis, that `ital` value is set to 1 when the italic style is requested. If that
         * font doesn't support `ital` axis, synthetic italic may be used. If the requested
         * weight value is lower than minimum value of the supported `wght` axis, the minimum
         * supported `wght` value is used. If the requested weight value is larger than maximum
         * value of the supported `wght`axis, the maximum supported `wght` value is used. The weight
         * value of the font is ignored.
         *
         * If none of the above conditions are met, this function return {@code null}.
         *
         * @return A variable font family. null if a variable font cannot be built from the given
         *         fonts.
         */
        public @Nullable FontFamily buildVariableFamily() {
            int variableFamilyType = analyzeAndResolveVariableType(mFonts);
            if (variableFamilyType == VARIABLE_FONT_FAMILY_TYPE_UNKNOWN) {
                return null;
            }
            return build("", FontConfig.FontFamily.VARIANT_DEFAULT,
                    true /* isCustomFallback */,
                    false /* isDefaultFallback */,
                    variableFamilyType);
        }

        /**
         * Build the font family
         * @return a font family
         */
        public @NonNull FontFamily build() {
            return build("", FontConfig.FontFamily.VARIANT_DEFAULT, true /* isCustomFallback */,
                    false /* isDefaultFallback */);
            return build("", FontConfig.FontFamily.VARIANT_DEFAULT,
                    true /* isCustomFallback */,
                    false /* isDefaultFallback */,
                    VARIABLE_FONT_FAMILY_TYPE_NONE);
        }

        /** @hide */
        public @NonNull FontFamily build(@NonNull String langTags, int variant,
                boolean isCustomFallback, boolean isDefaultFallback) {
                boolean isCustomFallback, boolean isDefaultFallback, int variableFamilyType) {

            final long builderPtr = nInitBuilder();
            for (int i = 0; i < mFonts.size(); ++i) {
                nAddFont(builderPtr, mFonts.get(i).getNativePtr());
            }
            final long ptr = nBuild(builderPtr, langTags, variant, isCustomFallback,
                    isDefaultFallback);
                    isDefaultFallback, variableFamilyType);
            final FontFamily family = new FontFamily(ptr);
            sFamilyRegistory.registerNativeAllocation(family, ptr);
            return family;
@@ -136,11 +179,94 @@ public final class FontFamily {
            return font.getStyle().getWeight() | (font.getStyle().getSlant()  << 16);
        }

        /**
         * @see #buildVariableFamily()
         * @hide
         */
        public static final int VARIABLE_FONT_FAMILY_TYPE_UNKNOWN = -1;

        /**
         * @see #buildVariableFamily()
         * @hide
         */
        public static final int VARIABLE_FONT_FAMILY_TYPE_NONE = 0;
        /**
         * @see #buildVariableFamily()
         * @hide
         */
        public static final int VARIABLE_FONT_FAMILY_TYPE_SINGLE_FONT_WGHT_ONLY = 1;
        /**
         * @see #buildVariableFamily()
         * @hide
         */
        public static final int VARIABLE_FONT_FAMILY_TYPE_SINGLE_FONT_WGHT_ITAL = 2;
        /**
         * @see #buildVariableFamily()
         * @hide
         */
        public static final int VARIABLE_FONT_FAMILY_TYPE_TWO_FONTS_WGHT = 3;

        /**
         * The registered italic axis used for adjusting requested style.
         * https://learn.microsoft.com/en-us/typography/opentype/spec/dvaraxistag_ital
         */
        private static final int TAG_ital = 0x6974616C;  // i(0x69), t(0x74), a(0x61), l(0x6c)

        /**
         * The registered weight axis used for adjusting requested style.
         * https://learn.microsoft.com/en-us/typography/opentype/spec/dvaraxistag_wght
         */
        private static final int TAG_wght = 0x77676874;  // w(0x77), g(0x67), h(0x68), t(0x74)

        private static int analyzeAndResolveVariableType(ArrayList<Font> fonts) {
            if (fonts.size() > 2) {
                return VARIABLE_FONT_FAMILY_TYPE_UNKNOWN;
            }

            if (fonts.size() == 1) {
                Font font = fonts.get(0);
                Set<Integer> supportedAxes =
                        FontFileUtil.getSupportedAxes(font.getBuffer(), font.getTtcIndex());
                if (supportedAxes.contains(TAG_wght)) {
                    if (supportedAxes.contains(TAG_ital)) {
                        return VARIABLE_FONT_FAMILY_TYPE_SINGLE_FONT_WGHT_ITAL;
                    } else {
                        return VARIABLE_FONT_FAMILY_TYPE_SINGLE_FONT_WGHT_ONLY;
                    }
                } else {
                    return VARIABLE_FONT_FAMILY_TYPE_UNKNOWN;
                }
            } else {
                for (int i = 0; i < fonts.size(); ++i) {
                    Font font = fonts.get(i);
                    Set<Integer> supportedAxes =
                            FontFileUtil.getSupportedAxes(font.getBuffer(), font.getTtcIndex());
                    if (!supportedAxes.contains(TAG_wght)) {
                        return VARIABLE_FONT_FAMILY_TYPE_UNKNOWN;
                    }
                }
                boolean italic1 = fonts.get(0).getStyle().getSlant() == FontStyle.FONT_SLANT_ITALIC;
                boolean italic2 = fonts.get(1).getStyle().getSlant() == FontStyle.FONT_SLANT_ITALIC;

                if (italic1 == italic2) {
                    return VARIABLE_FONT_FAMILY_TYPE_UNKNOWN;
                } else {
                    if (italic1) {
                        // Swap fonts to make the first font upright, second font italic.
                        Font firstFont = fonts.get(0);
                        fonts.set(0, fonts.get(1));
                        fonts.set(1, firstFont);
                    }
                    return VARIABLE_FONT_FAMILY_TYPE_TWO_FONTS_WGHT;
                }
            }
        }

        private static native long nInitBuilder();
        @CriticalNative
        private static native void nAddFont(long builderPtr, long fontPtr);
        private static native long nBuild(long builderPtr, String langTags, int variant,
                boolean isCustomFallback, boolean isDefaultFallback);
                boolean isCustomFallback, boolean isDefaultFallback, int variableFamilyType);
        @CriticalNative
        private static native long nGetReleaseNativeFamily();
    }
+71 −0
Original line number Diff line number Diff line
@@ -19,11 +19,14 @@ package android.graphics.fonts;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.util.ArraySet;

import dalvik.annotation.optimization.FastNative;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Collections;
import java.util.Set;

/**
 * Provides a utility for font file operations.
@@ -62,6 +65,7 @@ public class FontFileUtil {
    private static final int SFNT_VERSION_OTTO = 0x4F54544F;
    private static final int TTC_TAG = 0x74746366;
    private static final int OS2_TABLE_TAG = 0x4F532F32;
    private static final int FVAR_TABLE_TAG = 0x66766172;

    private static final int ANALYZE_ERROR = 0xFFFFFFFF;

@@ -200,6 +204,73 @@ public class FontFileUtil {
        }
    }

    private static int getUInt16(ByteBuffer buffer, int offset) {
        return ((int) buffer.getShort(offset)) & 0xFFFF;
    }

    /**
     * Returns supported axes of font
     *
     * @param buffer A buffer of the entire font file.
     * @param index A font index in case of font collection. Must be 0 otherwise.
     * @return set of supported axes tag. Returns empty set on error.
     */
    public static Set<Integer> getSupportedAxes(@NonNull ByteBuffer buffer, int index) {
        ByteOrder originalOrder = buffer.order();
        buffer.order(ByteOrder.BIG_ENDIAN);
        try {
            int fontFileOffset = 0;
            int magicNumber = buffer.getInt(0);
            if (magicNumber == TTC_TAG) {
                // TTC file.
                if (index >= buffer.getInt(8 /* offset to number of fonts in TTC */)) {
                    return Collections.EMPTY_SET;
                }
                fontFileOffset = buffer.getInt(
                        12 /* offset to array of offsets of font files */ + 4 * index);
            }
            int sfntVersion = buffer.getInt(fontFileOffset);

            if (sfntVersion != SFNT_VERSION_1 && sfntVersion != SFNT_VERSION_OTTO) {
                return Collections.EMPTY_SET;
            }

            int numTables = buffer.getShort(fontFileOffset + 4 /* offset to number of tables */);
            int fvarTableOffset = -1;
            for (int i = 0; i < numTables; ++i) {
                int tableOffset = fontFileOffset + 12 /* size of offset table */
                        + i * 16 /* size of table record */;
                if (buffer.getInt(tableOffset) == FVAR_TABLE_TAG) {
                    fvarTableOffset = buffer.getInt(tableOffset + 8 /* offset to the table */);
                    break;
                }
            }

            if (fvarTableOffset == -1) {
                // Couldn't find OS/2 table. use regular style
                return Collections.EMPTY_SET;
            }

            if (buffer.getShort(fvarTableOffset) != 1
                    || buffer.getShort(fvarTableOffset + 2) != 0) {
                return Collections.EMPTY_SET;
            }

            int axesArrayOffset = getUInt16(buffer, fvarTableOffset + 4);
            int axisCount = getUInt16(buffer, fvarTableOffset + 8);
            int axisSize = getUInt16(buffer, fvarTableOffset + 10);

            ArraySet<Integer> axes = new ArraySet<>();
            for (int i = 0; i < axisCount; ++i) {
                axes.add(buffer.getInt(fvarTableOffset + axesArrayOffset + axisSize * i));
            }

            return axes;
        } finally {
            buffer.order(originalOrder);
        }
    }

    @FastNative
    private static native long nGetFontRevision(@NonNull ByteBuffer buffer,
            @IntRange(from = 0) int index);
+1 −1
Original line number Diff line number Diff line
@@ -194,7 +194,7 @@ public final class SystemFonts {
            }
        }
        return b == null ? null : b.build(languageTags, variant, false /* isCustomFallback */,
                isDefaultFallback);
                isDefaultFallback, FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_NONE);
    }

    private static void appendNamedFamilyList(@NonNull FontConfig.NamedFamilyList namedFamilyList,
+70 −0
Original line number Diff line number Diff line
@@ -164,6 +164,68 @@ public final class PositionedGlyphs {
        return nGetY(mLayoutPtr, index) + mYOffset;
    }

    /**
     * Returns true if the fake bold option used for drawing, otherwise false.
     *
     * @param index the glyph index
     * @return true if the fake bold option is on, otherwise off.
     */
    public boolean getFakeBold(@IntRange(from = 0) int index) {
        Preconditions.checkArgumentInRange(index, 0, glyphCount() - 1, "index");
        return nGetFakeBold(mLayoutPtr, index);
    }

    /**
     * Returns true if the fake italic option used for drawing, otherwise false.
     *
     * @param index the glyph index
     * @return true if the fake italic option is on, otherwise off.
     */
    public boolean getFakeItalic(@IntRange(from = 0) int index) {
        Preconditions.checkArgumentInRange(index, 0, glyphCount() - 1, "index");
        return nGetFakeItalic(mLayoutPtr, index);
    }

    /**
     * A special value returned by {@link #getWeightOverride(int)} and
     * {@link #getItalicOverride(int)} that indicates no font variation setting is overridden.
     */
    public static final float NO_OVERRIDE = Float.MIN_VALUE;

    /**
     * Returns overridden weight value if the font is variable font and `wght` value is overridden
     * for drawing. Otherwise returns {@link #NO_OVERRIDE}.
     *
     * @param index the glyph index
     * @return overridden weight value or {@link #NO_OVERRIDE}.
     */
    public float getWeightOverride(@IntRange(from = 0) int index) {
        Preconditions.checkArgumentInRange(index, 0, glyphCount() - 1, "index");
        float value = nGetWeightOverride(mLayoutPtr, index);
        if (value == -1) {
            return NO_OVERRIDE;
        } else {
            return value;
        }
    }

    /**
     * Returns overridden italic value if the font is variable font and `ital` value is overridden
     * for drawing. Otherwise returns {@link #NO_OVERRIDE}.
     *
     * @param index the glyph index
     * @return overridden weight value or {@link #NO_OVERRIDE}.
     */
    public float getItalicOverride(@IntRange(from = 0) int index) {
        Preconditions.checkArgumentInRange(index, 0, glyphCount() - 1, "index");
        float value = nGetItalicOverride(mLayoutPtr, index);
        if (value == -1) {
            return NO_OVERRIDE;
        } else {
            return value;
        }
    }

    /**
     * Create single style layout from native result.
     *
@@ -210,6 +272,14 @@ public final class PositionedGlyphs {
    private static native long nGetFont(long minikinLayout, int i);
    @CriticalNative
    private static native long nReleaseFunc();
    @CriticalNative
    private static native boolean nGetFakeBold(long minikinLayout, int i);
    @CriticalNative
    private static native boolean nGetFakeItalic(long minikinLayout, int i);
    @CriticalNative
    private static native float nGetWeightOverride(long minikinLayout, int i);
    @CriticalNative
    private static native float nGetItalicOverride(long minikinLayout, int i);

    @Override
    public boolean equals(Object o) {
Loading