diff options
| -rw-r--r-- | core/api/current.txt | 6 | ||||
| -rw-r--r-- | graphics/java/android/graphics/fonts/FontFamily.java | 136 | ||||
| -rw-r--r-- | graphics/java/android/graphics/fonts/FontFileUtil.java | 71 | ||||
| -rw-r--r-- | graphics/java/android/graphics/fonts/SystemFonts.java | 2 | ||||
| -rw-r--r-- | graphics/java/android/graphics/text/PositionedGlyphs.java | 70 | ||||
| -rw-r--r-- | libs/hwui/jni/FontFamily.cpp | 3 | ||||
| -rw-r--r-- | libs/hwui/jni/fonts/FontFamily.cpp | 7 | ||||
| -rw-r--r-- | libs/hwui/jni/text/TextShaper.cpp | 46 |
8 files changed, 322 insertions, 19 deletions
diff --git a/core/api/current.txt b/core/api/current.txt index ce192020d788..db6dc5d5dc38 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -17442,6 +17442,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 { @@ -17622,13 +17623,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 { diff --git a/graphics/java/android/graphics/fonts/FontFamily.java b/graphics/java/android/graphics/fonts/FontFamily.java index bf79b1bedd8e..7cca7f19da7d 100644 --- a/graphics/java/android/graphics/fonts/FontFamily.java +++ b/graphics/java/android/graphics/fonts/FontFamily.java @@ -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. * @@ -110,23 +113,63 @@ public final class FontFamily { } /** + * 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(); } diff --git a/graphics/java/android/graphics/fonts/FontFileUtil.java b/graphics/java/android/graphics/fonts/FontFileUtil.java index 917eef2ffede..ff38282255f2 100644 --- a/graphics/java/android/graphics/fonts/FontFileUtil.java +++ b/graphics/java/android/graphics/fonts/FontFileUtil.java @@ -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); diff --git a/graphics/java/android/graphics/fonts/SystemFonts.java b/graphics/java/android/graphics/fonts/SystemFonts.java index 8fe28ae731b8..3fea65f42d4f 100644 --- a/graphics/java/android/graphics/fonts/SystemFonts.java +++ b/graphics/java/android/graphics/fonts/SystemFonts.java @@ -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, diff --git a/graphics/java/android/graphics/text/PositionedGlyphs.java b/graphics/java/android/graphics/text/PositionedGlyphs.java index 8d20e9cee7d7..49e9d0cf8372 100644 --- a/graphics/java/android/graphics/text/PositionedGlyphs.java +++ b/graphics/java/android/graphics/text/PositionedGlyphs.java @@ -165,6 +165,68 @@ public final class PositionedGlyphs { } /** + * 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. * * @hide @@ -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) { diff --git a/libs/hwui/jni/FontFamily.cpp b/libs/hwui/jni/FontFamily.cpp index 69774cc6e133..af1668fbd7dd 100644 --- a/libs/hwui/jni/FontFamily.cpp +++ b/libs/hwui/jni/FontFamily.cpp @@ -89,7 +89,8 @@ static jlong FontFamily_create(CRITICAL_JNI_PARAMS_COMMA jlong builderPtr) { } std::shared_ptr<minikin::FontFamily> family = minikin::FontFamily::create( builder->langId, builder->variant, std::move(builder->fonts), - true /* isCustomFallback */, false /* isDefaultFallback */); + true /* isCustomFallback */, false /* isDefaultFallback */, + minikin::VariationFamilyType::None); if (family->getCoverage().length() == 0) { return 0; } diff --git a/libs/hwui/jni/fonts/FontFamily.cpp b/libs/hwui/jni/fonts/FontFamily.cpp index ee158ee4e316..1e392b14728d 100644 --- a/libs/hwui/jni/fonts/FontFamily.cpp +++ b/libs/hwui/jni/fonts/FontFamily.cpp @@ -60,7 +60,7 @@ static void FontFamily_Builder_addFont(CRITICAL_JNI_PARAMS_COMMA jlong builderPt // Regular JNI static jlong FontFamily_Builder_build(JNIEnv* env, jobject clazz, jlong builderPtr, jstring langTags, jint variant, jboolean isCustomFallback, - jboolean isDefaultFallback) { + jboolean isDefaultFallback, jint variationFamilyType) { std::unique_ptr<NativeFamilyBuilder> builder(toBuilder(builderPtr)); uint32_t localeId; if (langTags == nullptr) { @@ -71,7 +71,8 @@ static jlong FontFamily_Builder_build(JNIEnv* env, jobject clazz, jlong builderP } std::shared_ptr<minikin::FontFamily> family = minikin::FontFamily::create( localeId, static_cast<minikin::FamilyVariant>(variant), std::move(builder->fonts), - isCustomFallback, isDefaultFallback); + isCustomFallback, isDefaultFallback, + static_cast<minikin::VariationFamilyType>(variationFamilyType)); if (family->getCoverage().length() == 0) { // No coverage means minikin rejected given font for some reasons. jniThrowException(env, "java/lang/IllegalArgumentException", @@ -121,7 +122,7 @@ static jlong FontFamily_getFont(CRITICAL_JNI_PARAMS_COMMA jlong familyPtr, jint static const JNINativeMethod gFontFamilyBuilderMethods[] = { {"nInitBuilder", "()J", (void*)FontFamily_Builder_initBuilder}, {"nAddFont", "(JJ)V", (void*)FontFamily_Builder_addFont}, - {"nBuild", "(JLjava/lang/String;IZZ)J", (void*)FontFamily_Builder_build}, + {"nBuild", "(JLjava/lang/String;IZZI)J", (void*)FontFamily_Builder_build}, {"nGetReleaseNativeFamily", "()J", (void*)FontFamily_Builder_GetReleaseFunc}, }; diff --git a/libs/hwui/jni/text/TextShaper.cpp b/libs/hwui/jni/text/TextShaper.cpp index d69a47c5b085..8c377b9f1829 100644 --- a/libs/hwui/jni/text/TextShaper.cpp +++ b/libs/hwui/jni/text/TextShaper.cpp @@ -148,6 +148,30 @@ static jfloat TextShaper_Result_getY(CRITICAL_JNI_PARAMS_COMMA jlong ptr, jint i } // CriticalNative +static jboolean TextShaper_Result_getFakeBold(CRITICAL_JNI_PARAMS_COMMA jlong ptr, jint i) { + const LayoutWrapper* layout = reinterpret_cast<LayoutWrapper*>(ptr); + return layout->layout.getFakery(i).isFakeBold(); +} + +// CriticalNative +static jboolean TextShaper_Result_getFakeItalic(CRITICAL_JNI_PARAMS_COMMA jlong ptr, jint i) { + const LayoutWrapper* layout = reinterpret_cast<LayoutWrapper*>(ptr); + return layout->layout.getFakery(i).isFakeItalic(); +} + +// CriticalNative +static jfloat TextShaper_Result_getWeightOverride(CRITICAL_JNI_PARAMS_COMMA jlong ptr, jint i) { + const LayoutWrapper* layout = reinterpret_cast<LayoutWrapper*>(ptr); + return layout->layout.getFakery(i).wghtAdjustment(); +} + +// CriticalNative +static jfloat TextShaper_Result_getItalicOverride(CRITICAL_JNI_PARAMS_COMMA jlong ptr, jint i) { + const LayoutWrapper* layout = reinterpret_cast<LayoutWrapper*>(ptr); + return layout->layout.getFakery(i).italAdjustment(); +} + +// CriticalNative static jlong TextShaper_Result_getFont(CRITICAL_JNI_PARAMS_COMMA jlong ptr, jint i) { const LayoutWrapper* layout = reinterpret_cast<LayoutWrapper*>(ptr); std::shared_ptr<minikin::Font> fontRef = layout->layout.getFontRef(i); @@ -185,15 +209,19 @@ static const JNINativeMethod gMethods[] = { }; static const JNINativeMethod gResultMethods[] = { - { "nGetGlyphCount", "(J)I", (void*)TextShaper_Result_getGlyphCount }, - { "nGetTotalAdvance", "(J)F", (void*)TextShaper_Result_getTotalAdvance }, - { "nGetAscent", "(J)F", (void*)TextShaper_Result_getAscent }, - { "nGetDescent", "(J)F", (void*)TextShaper_Result_getDescent }, - { "nGetGlyphId", "(JI)I", (void*)TextShaper_Result_getGlyphId }, - { "nGetX", "(JI)F", (void*)TextShaper_Result_getX }, - { "nGetY", "(JI)F", (void*)TextShaper_Result_getY }, - { "nGetFont", "(JI)J", (void*)TextShaper_Result_getFont }, - { "nReleaseFunc", "()J", (void*)TextShaper_Result_nReleaseFunc }, + {"nGetGlyphCount", "(J)I", (void*)TextShaper_Result_getGlyphCount}, + {"nGetTotalAdvance", "(J)F", (void*)TextShaper_Result_getTotalAdvance}, + {"nGetAscent", "(J)F", (void*)TextShaper_Result_getAscent}, + {"nGetDescent", "(J)F", (void*)TextShaper_Result_getDescent}, + {"nGetGlyphId", "(JI)I", (void*)TextShaper_Result_getGlyphId}, + {"nGetX", "(JI)F", (void*)TextShaper_Result_getX}, + {"nGetY", "(JI)F", (void*)TextShaper_Result_getY}, + {"nGetFont", "(JI)J", (void*)TextShaper_Result_getFont}, + {"nGetFakeBold", "(JI)Z", (void*)TextShaper_Result_getFakeBold}, + {"nGetFakeItalic", "(JI)Z", (void*)TextShaper_Result_getFakeItalic}, + {"nGetWeightOverride", "(JI)F", (void*)TextShaper_Result_getWeightOverride}, + {"nGetItalicOverride", "(JI)F", (void*)TextShaper_Result_getItalicOverride}, + {"nReleaseFunc", "()J", (void*)TextShaper_Result_nReleaseFunc}, }; int register_android_graphics_text_TextShaper(JNIEnv* env) { |