/* * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.graphics.fonts; import static com.android.text.flags.Flags.FLAG_NEW_FONTS_FALLBACK_XML; import static java.lang.annotation.RetentionPolicy.SOURCE; import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.text.FontConfig; import android.util.SparseIntArray; import com.android.internal.util.Preconditions; import dalvik.annotation.optimization.CriticalNative; import dalvik.annotation.optimization.FastNative; import libcore.util.NativeAllocationRegistry; import java.lang.annotation.Retention; import java.util.ArrayList; import java.util.Set; /** * A font family class can be used for creating Typeface. * *

* A font family is a bundle of fonts for drawing text in various styles. * For example, you can bundle regular style font and bold style font into a single font family, * then system will select the correct style font from family for drawing. * *

 *  FontFamily family = new FontFamily.Builder(new Font.Builder("regular.ttf").build())
 *      .addFont(new Font.Builder("bold.ttf").build()).build();
 *  Typeface typeface = new Typeface.Builder2(family).build();
 *
 *  SpannableStringBuilder ssb = new SpannableStringBuilder("Hello, World.");
 *  ssb.setSpan(new StyleSpan(Typeface.Bold), 6, 12, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
 *
 *  textView.setTypeface(typeface);
 *  textView.setText(ssb);
 * 
* * In this example, "Hello, " is drawn with "regular.ttf", and "World." is drawn with "bold.ttf". * * If there is no font exactly matches with the text style, the system will select the closest font. *

* */ public final class FontFamily { private static final String TAG = "FontFamily"; /** * A builder class for creating new FontFamily. */ public static final class Builder { private static class NoImagePreloadHolder { private static final NativeAllocationRegistry sFamilyRegistry = NativeAllocationRegistry.createMalloced(FontFamily.class.getClassLoader(), nGetReleaseNativeFamily()); } private final ArrayList mFonts = new ArrayList<>(); // Most FontFamily only has regular, bold, italic, bold-italic. Thus 4 should be good for // initial capacity. private final SparseIntArray mStyles = new SparseIntArray(4); /** * Constructs a builder. * * @param font a font */ public Builder(@NonNull Font font) { Preconditions.checkNotNull(font, "font can not be null"); mStyles.append(makeStyleIdentifier(font), 0); mFonts.add(font); } /** * Adds different style font to the builder. * * System will select the font if the text style is closest to the font. * If the same style font is already added to the builder, this method will fail with * {@link IllegalArgumentException}. * * Note that system assumes all fonts bundled in FontFamily have the same coverage for the * code points. For example, regular style font and bold style font must have the same code * point coverage, otherwise some character may be shown as tofu. * * @param font a font * @return this builder */ public @NonNull Builder addFont(@NonNull Font font) { Preconditions.checkNotNull(font, "font can not be null"); int key = makeStyleIdentifier(font); if (mStyles.indexOfKey(key) >= 0) { throw new IllegalArgumentException(font + " has already been added"); } mStyles.append(key, 0); mFonts.add(font); 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, the provided font files cannot be used for * variable font family and this function returns {@code null}. Even if this function * returns {@code null}, you can still use {@link #build()} method for creating FontFamily * instance with manually specifying variation settings by using * {@link Font.Builder#setFontVariationSettings(String)}. * * @return A variable font family. null if a variable font cannot be built from the given * fonts. */ @FlaggedApi(FLAG_NEW_FONTS_FALLBACK_XML) 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 */, VARIABLE_FONT_FAMILY_TYPE_NONE); } /** @hide */ public @NonNull FontFamily build(@NonNull String langTags, int variant, 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, variableFamilyType); final FontFamily family = new FontFamily(ptr); NoImagePreloadHolder.sFamilyRegistry.registerNativeAllocation(family, ptr); return family; } private static int makeStyleIdentifier(@NonNull Font font) { return font.getStyle().getWeight() | (font.getStyle().getSlant() << 16); } /** * A special variable font family type that indicates `analyzeAndResolveVariableType` could * not be identified the variable font family type. * * @see #buildVariableFamily() * @hide */ public static final int VARIABLE_FONT_FAMILY_TYPE_UNKNOWN = -1; /** * A variable font family type that indicates no variable font family can be used. * * The font family is used as bundle of static fonts. * @see #buildVariableFamily() * @hide */ public static final int VARIABLE_FONT_FAMILY_TYPE_NONE = 0; /** * A variable font family type that indicates single font file can be used for multiple * weight. For the italic style, fake italic may be applied. * * @see #buildVariableFamily() * @hide */ public static final int VARIABLE_FONT_FAMILY_TYPE_SINGLE_FONT_WGHT_ONLY = 1; /** * A variable font family type that indicates single font file can be used for multiple * weight and italic. * * @see #buildVariableFamily() * @hide */ public static final int VARIABLE_FONT_FAMILY_TYPE_SINGLE_FONT_WGHT_ITAL = 2; /** * A variable font family type that indicates two font files are included in the family: * one can be used for upright with various weights, the other one can be used for italic * with various weights. * * @see #buildVariableFamily() * @hide */ public static final int VARIABLE_FONT_FAMILY_TYPE_TWO_FONTS_WGHT = 3; /** @hide */ @Retention(SOURCE) @IntDef(prefix = { "VARIABLE_FONT_FAMILY_TYPE_" }, value = { VARIABLE_FONT_FAMILY_TYPE_UNKNOWN, VARIABLE_FONT_FAMILY_TYPE_NONE, VARIABLE_FONT_FAMILY_TYPE_SINGLE_FONT_WGHT_ONLY, VARIABLE_FONT_FAMILY_TYPE_SINGLE_FONT_WGHT_ITAL, VARIABLE_FONT_FAMILY_TYPE_TWO_FONTS_WGHT }) public @interface VariableFontFamilyType {} /** * 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) /** @hide */ public static @VariableFontFamilyType int analyzeAndResolveVariableType( ArrayList fonts) { if (fonts.size() > 2) { return VARIABLE_FONT_FAMILY_TYPE_UNKNOWN; } if (fonts.size() == 1) { Font font = fonts.get(0); Set 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 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, int variableFamilyType); @CriticalNative private static native long nGetReleaseNativeFamily(); } private final long mNativePtr; // Use Builder instead. /** @hide */ public FontFamily(long ptr) { mNativePtr = ptr; } /** * Returns a BCP-47 compliant language tags associated with this font family. * @hide * @return a BCP-47 compliant language tag. */ public @Nullable String getLangTags() { return nGetLangTags(mNativePtr); } /** * @hide * @return a family variant */ public int getVariant() { return nGetVariant(mNativePtr); } /** * Returns a font * * @param index an index of the font * @return a registered font */ public @NonNull Font getFont(@IntRange(from = 0) int index) { if (index < 0 || getSize() <= index) { throw new IndexOutOfBoundsException(); } return new Font(nGetFont(mNativePtr, index)); } /** * Returns the number of fonts in this FontFamily. * * @return the number of fonts registered in this family. */ public @IntRange(from = 1) int getSize() { return nGetFontSize(mNativePtr); } /** @hide */ public long getNativePtr() { return mNativePtr; } @CriticalNative private static native int nGetFontSize(long family); @CriticalNative private static native long nGetFont(long family, int i); @FastNative private static native String nGetLangTags(long family); @CriticalNative private static native int nGetVariant(long family); }