/* * 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