381 lines
15 KiB
Java
381 lines
15 KiB
Java
![]() |
/*
|
||
|
* 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.
|
||
|
*
|
||
|
* <p>
|
||
|
* 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.
|
||
|
*
|
||
|
* <pre>
|
||
|
* 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);
|
||
|
* </pre>
|
||
|
*
|
||
|
* 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.
|
||
|
* </p>
|
||
|
*
|
||
|
*/
|
||
|
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<Font> 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<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, 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);
|
||
|
}
|