922 lines
34 KiB
Java
922 lines
34 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 android.annotation.IntRange;
|
|
import android.annotation.NonNull;
|
|
import android.annotation.Nullable;
|
|
import android.content.res.AssetFileDescriptor;
|
|
import android.content.res.AssetManager;
|
|
import android.content.res.Resources;
|
|
import android.graphics.Paint;
|
|
import android.graphics.RectF;
|
|
import android.os.LocaleList;
|
|
import android.os.ParcelFileDescriptor;
|
|
import android.text.TextUtils;
|
|
import android.util.TypedValue;
|
|
|
|
import com.android.internal.annotations.GuardedBy;
|
|
import com.android.internal.util.Preconditions;
|
|
|
|
import dalvik.annotation.optimization.CriticalNative;
|
|
import dalvik.annotation.optimization.FastNative;
|
|
|
|
import libcore.util.NativeAllocationRegistry;
|
|
|
|
import java.io.File;
|
|
import java.io.FileInputStream;
|
|
import java.io.FileNotFoundException;
|
|
import java.io.IOException;
|
|
import java.io.InputStream;
|
|
import java.nio.ByteBuffer;
|
|
import java.nio.ByteOrder;
|
|
import java.nio.channels.FileChannel;
|
|
import java.util.Arrays;
|
|
import java.util.Collections;
|
|
import java.util.IdentityHashMap;
|
|
import java.util.Objects;
|
|
import java.util.Set;
|
|
|
|
/**
|
|
* A font class can be used for creating FontFamily.
|
|
*/
|
|
public final class Font {
|
|
private static final String TAG = "Font";
|
|
|
|
private static final int NOT_SPECIFIED = -1;
|
|
private static final int STYLE_ITALIC = 1;
|
|
private static final int STYLE_NORMAL = 0;
|
|
|
|
private static class NoImagePreloadHolder {
|
|
private static final NativeAllocationRegistry BUFFER_REGISTRY =
|
|
NativeAllocationRegistry.createMalloced(
|
|
ByteBuffer.class.getClassLoader(), nGetReleaseNativeFont());
|
|
|
|
private static final NativeAllocationRegistry FONT_REGISTRY =
|
|
NativeAllocationRegistry.createMalloced(Font.class.getClassLoader(),
|
|
nGetReleaseNativeFont());
|
|
}
|
|
|
|
/**
|
|
* A builder class for creating new Font.
|
|
*/
|
|
public static final class Builder {
|
|
|
|
|
|
private @Nullable ByteBuffer mBuffer;
|
|
private @Nullable File mFile;
|
|
private @Nullable Font mFont;
|
|
private @NonNull String mLocaleList = "";
|
|
private @IntRange(from = -1, to = 1000) int mWeight = NOT_SPECIFIED;
|
|
private @IntRange(from = -1, to = 1) int mItalic = NOT_SPECIFIED;
|
|
private @IntRange(from = 0) int mTtcIndex = 0;
|
|
private @Nullable FontVariationAxis[] mAxes = null;
|
|
private @Nullable IOException mException;
|
|
|
|
/**
|
|
* Constructs a builder with a byte buffer.
|
|
*
|
|
* Note that only direct buffer can be used as the source of font data.
|
|
*
|
|
* @see ByteBuffer#allocateDirect(int)
|
|
* @param buffer a byte buffer of a font data
|
|
*/
|
|
public Builder(@NonNull ByteBuffer buffer) {
|
|
Preconditions.checkNotNull(buffer, "buffer can not be null");
|
|
if (!buffer.isDirect()) {
|
|
throw new IllegalArgumentException(
|
|
"Only direct buffer can be used as the source of font data.");
|
|
}
|
|
mBuffer = buffer;
|
|
}
|
|
|
|
/**
|
|
* Construct a builder with a byte buffer and file path.
|
|
*
|
|
* This method is intended to be called only from SystemFonts.
|
|
* @hide
|
|
*/
|
|
public Builder(@NonNull ByteBuffer buffer, @NonNull File path,
|
|
@NonNull String localeList) {
|
|
this(buffer);
|
|
mFile = path;
|
|
mLocaleList = localeList;
|
|
}
|
|
|
|
/**
|
|
* Construct a builder with a byte buffer and file path.
|
|
*
|
|
* This method is intended to be called only from SystemFonts.
|
|
* @param path font file path
|
|
* @param localeList comma concatenated BCP47 compliant language tag.
|
|
* @hide
|
|
*/
|
|
public Builder(@NonNull File path, @NonNull String localeList) {
|
|
this(path);
|
|
mLocaleList = localeList;
|
|
}
|
|
|
|
/**
|
|
* Constructs a builder with a file path.
|
|
*
|
|
* @param path a file path to the font file
|
|
*/
|
|
public Builder(@NonNull File path) {
|
|
Preconditions.checkNotNull(path, "path can not be null");
|
|
try (FileInputStream fis = new FileInputStream(path)) {
|
|
final FileChannel fc = fis.getChannel();
|
|
mBuffer = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size());
|
|
} catch (IOException e) {
|
|
mException = e;
|
|
}
|
|
mFile = path;
|
|
}
|
|
|
|
/**
|
|
* Constructs a builder with a file descriptor.
|
|
*
|
|
* @param fd a file descriptor
|
|
*/
|
|
public Builder(@NonNull ParcelFileDescriptor fd) {
|
|
this(fd, 0, -1);
|
|
}
|
|
|
|
/**
|
|
* Constructs a builder with a file descriptor.
|
|
*
|
|
* @param fd a file descriptor
|
|
* @param offset an offset to of the font data in the file
|
|
* @param size a size of the font data. If -1 is passed, use until end of the file.
|
|
*/
|
|
public Builder(@NonNull ParcelFileDescriptor fd, @IntRange(from = 0) long offset,
|
|
@IntRange(from = -1) long size) {
|
|
try (FileInputStream fis = new FileInputStream(fd.getFileDescriptor())) {
|
|
final FileChannel fc = fis.getChannel();
|
|
size = (size == -1) ? fc.size() - offset : size;
|
|
mBuffer = fc.map(FileChannel.MapMode.READ_ONLY, offset, size);
|
|
} catch (IOException e) {
|
|
mException = e;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Constructs a builder from an asset manager and a file path in an asset directory.
|
|
*
|
|
* @param am the application's asset manager
|
|
* @param path the file name of the font data in the asset directory
|
|
*/
|
|
public Builder(@NonNull AssetManager am, @NonNull String path) {
|
|
try {
|
|
mBuffer = createBuffer(am, path, true /* is asset */, AssetManager.COOKIE_UNKNOWN);
|
|
} catch (IOException e) {
|
|
mException = e;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Constructs a builder from an asset manager and a file path in an asset directory.
|
|
*
|
|
* @param am the application's asset manager
|
|
* @param path the file name of the font data in the asset directory
|
|
* @param isAsset true if the undelying data is in asset
|
|
* @param cookie set asset cookie
|
|
* @hide
|
|
*/
|
|
public Builder(@NonNull AssetManager am, @NonNull String path, boolean isAsset,
|
|
int cookie) {
|
|
try {
|
|
mBuffer = createBuffer(am, path, isAsset, cookie);
|
|
} catch (IOException e) {
|
|
mException = e;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Constructs a builder from resources.
|
|
*
|
|
* Resource ID must points the font file. XML font can not be used here.
|
|
*
|
|
* @param res the resource of this application.
|
|
* @param resId the resource ID of font file.
|
|
*/
|
|
public Builder(@NonNull Resources res, int resId) {
|
|
final TypedValue value = new TypedValue();
|
|
res.getValue(resId, value, true);
|
|
if (value.string == null) {
|
|
mException = new FileNotFoundException(resId + " not found");
|
|
return;
|
|
}
|
|
final String str = value.string.toString();
|
|
if (str.toLowerCase().endsWith(".xml")) {
|
|
mException = new FileNotFoundException(resId + " must be font file.");
|
|
return;
|
|
}
|
|
|
|
try {
|
|
mBuffer = createBuffer(res.getAssets(), str, false, value.assetCookie);
|
|
} catch (IOException e) {
|
|
mException = e;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Constructs a builder from existing Font instance.
|
|
*
|
|
* @param font the font instance.
|
|
*/
|
|
public Builder(@NonNull Font font) {
|
|
mFont = font;
|
|
// Copies all parameters as a default value.
|
|
mBuffer = font.getBuffer();
|
|
mWeight = font.getStyle().getWeight();
|
|
mItalic = font.getStyle().getSlant();
|
|
mAxes = font.getAxes();
|
|
mFile = font.getFile();
|
|
mTtcIndex = font.getTtcIndex();
|
|
}
|
|
|
|
/**
|
|
* Creates a buffer containing font data using the assetManager and other
|
|
* provided inputs.
|
|
*
|
|
* @param am the application's asset manager
|
|
* @param path the file name of the font data in the asset directory
|
|
* @param isAsset true if the undelying data is in asset
|
|
* @param cookie set asset cookie
|
|
* @return buffer containing the contents of the file
|
|
*
|
|
* @hide
|
|
*/
|
|
public static ByteBuffer createBuffer(@NonNull AssetManager am, @NonNull String path,
|
|
boolean isAsset, int cookie) throws IOException {
|
|
Preconditions.checkNotNull(am, "assetManager can not be null");
|
|
Preconditions.checkNotNull(path, "path can not be null");
|
|
|
|
// Attempt to open as FD, which should work unless the asset is compressed
|
|
AssetFileDescriptor assetFD;
|
|
try {
|
|
if (isAsset) {
|
|
assetFD = am.openFd(path);
|
|
} else if (cookie > 0) {
|
|
assetFD = am.openNonAssetFd(cookie, path);
|
|
} else {
|
|
assetFD = am.openNonAssetFd(path);
|
|
}
|
|
|
|
try (FileInputStream fis = assetFD.createInputStream()) {
|
|
final FileChannel fc = fis.getChannel();
|
|
long startOffset = assetFD.getStartOffset();
|
|
long declaredLength = assetFD.getDeclaredLength();
|
|
return fc.map(FileChannel.MapMode.READ_ONLY, startOffset, declaredLength);
|
|
}
|
|
} catch (IOException e) {
|
|
// failed to open as FD so now we will attempt to open as an input stream
|
|
}
|
|
|
|
try (InputStream assetStream = isAsset ? am.open(path, AssetManager.ACCESS_BUFFER)
|
|
: am.openNonAsset(cookie, path, AssetManager.ACCESS_BUFFER)) {
|
|
|
|
int capacity = assetStream.available();
|
|
ByteBuffer buffer = ByteBuffer.allocateDirect(capacity);
|
|
buffer.order(ByteOrder.nativeOrder());
|
|
assetStream.read(buffer.array(), buffer.arrayOffset(), assetStream.available());
|
|
|
|
if (assetStream.read() != -1) {
|
|
throw new IOException("Unable to access full contents of " + path);
|
|
}
|
|
|
|
return buffer;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sets weight of the font.
|
|
*
|
|
* Tells the system the weight of the given font. If this function is not called, the system
|
|
* will resolve the weight value by reading font tables.
|
|
*
|
|
* Here are pairs of the common names and their values.
|
|
* <p>
|
|
* <table>
|
|
* <thead>
|
|
* <tr>
|
|
* <th align="center">Value</th>
|
|
* <th align="center">Name</th>
|
|
* <th align="center">Android Definition</th>
|
|
* </tr>
|
|
* </thead>
|
|
* <tbody>
|
|
* <tr>
|
|
* <td align="center">100</td>
|
|
* <td align="center">Thin</td>
|
|
* <td align="center">{@link FontStyle#FONT_WEIGHT_THIN}</td>
|
|
* </tr>
|
|
* <tr>
|
|
* <td align="center">200</td>
|
|
* <td align="center">Extra Light (Ultra Light)</td>
|
|
* <td align="center">{@link FontStyle#FONT_WEIGHT_EXTRA_LIGHT}</td>
|
|
* </tr>
|
|
* <tr>
|
|
* <td align="center">300</td>
|
|
* <td align="center">Light</td>
|
|
* <td align="center">{@link FontStyle#FONT_WEIGHT_LIGHT}</td>
|
|
* </tr>
|
|
* <tr>
|
|
* <td align="center">400</td>
|
|
* <td align="center">Normal (Regular)</td>
|
|
* <td align="center">{@link FontStyle#FONT_WEIGHT_NORMAL}</td>
|
|
* </tr>
|
|
* <tr>
|
|
* <td align="center">500</td>
|
|
* <td align="center">Medium</td>
|
|
* <td align="center">{@link FontStyle#FONT_WEIGHT_MEDIUM}</td>
|
|
* </tr>
|
|
* <tr>
|
|
* <td align="center">600</td>
|
|
* <td align="center">Semi Bold (Demi Bold)</td>
|
|
* <td align="center">{@link FontStyle#FONT_WEIGHT_SEMI_BOLD}</td>
|
|
* </tr>
|
|
* <tr>
|
|
* <td align="center">700</td>
|
|
* <td align="center">Bold</td>
|
|
* <td align="center">{@link FontStyle#FONT_WEIGHT_BOLD}</td>
|
|
* </tr>
|
|
* <tr>
|
|
* <td align="center">800</td>
|
|
* <td align="center">Extra Bold (Ultra Bold)</td>
|
|
* <td align="center">{@link FontStyle#FONT_WEIGHT_EXTRA_BOLD}</td>
|
|
* </tr>
|
|
* <tr>
|
|
* <td align="center">900</td>
|
|
* <td align="center">Black (Heavy)</td>
|
|
* <td align="center">{@link FontStyle#FONT_WEIGHT_BLACK}</td>
|
|
* </tr>
|
|
* </tbody>
|
|
* </p>
|
|
*
|
|
* @see FontStyle#FONT_WEIGHT_THIN
|
|
* @see FontStyle#FONT_WEIGHT_EXTRA_LIGHT
|
|
* @see FontStyle#FONT_WEIGHT_LIGHT
|
|
* @see FontStyle#FONT_WEIGHT_NORMAL
|
|
* @see FontStyle#FONT_WEIGHT_MEDIUM
|
|
* @see FontStyle#FONT_WEIGHT_SEMI_BOLD
|
|
* @see FontStyle#FONT_WEIGHT_BOLD
|
|
* @see FontStyle#FONT_WEIGHT_EXTRA_BOLD
|
|
* @see FontStyle#FONT_WEIGHT_BLACK
|
|
* @param weight a weight value
|
|
* @return this builder
|
|
*/
|
|
public @NonNull Builder setWeight(
|
|
@IntRange(from = FontStyle.FONT_WEIGHT_MIN, to = FontStyle.FONT_WEIGHT_MAX)
|
|
int weight) {
|
|
Preconditions.checkArgument(
|
|
FontStyle.FONT_WEIGHT_MIN <= weight && weight <= FontStyle.FONT_WEIGHT_MAX);
|
|
mWeight = weight;
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Sets italic information of the font.
|
|
*
|
|
* Tells the system the style of the given font. If this function is not called, the system
|
|
* will resolve the style by reading font tables.
|
|
*
|
|
* For example, if you want to use italic font as upright font, call {@code
|
|
* setSlant(FontStyle.FONT_SLANT_UPRIGHT)} explicitly.
|
|
*
|
|
* @return this builder
|
|
*/
|
|
public @NonNull Builder setSlant(@FontStyle.FontSlant int slant) {
|
|
mItalic = slant == FontStyle.FONT_SLANT_UPRIGHT ? STYLE_NORMAL : STYLE_ITALIC;
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Sets an index of the font collection. See {@link android.R.attr#ttcIndex}.
|
|
*
|
|
* @param ttcIndex An index of the font collection. If the font source is not font
|
|
* collection, do not call this method or specify 0.
|
|
* @return this builder
|
|
*/
|
|
public @NonNull Builder setTtcIndex(@IntRange(from = 0) int ttcIndex) {
|
|
mTtcIndex = ttcIndex;
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Sets the font variation settings.
|
|
*
|
|
* @param variationSettings see {@link FontVariationAxis#fromFontVariationSettings(String)}
|
|
* @return this builder
|
|
* @throws IllegalArgumentException If given string is not a valid font variation settings
|
|
* format.
|
|
*/
|
|
public @NonNull Builder setFontVariationSettings(@Nullable String variationSettings) {
|
|
mAxes = FontVariationAxis.fromFontVariationSettings(variationSettings);
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Sets the font variation settings.
|
|
*
|
|
* @param axes an array of font variation axis tag-value pairs
|
|
* @return this builder
|
|
*/
|
|
public @NonNull Builder setFontVariationSettings(@Nullable FontVariationAxis[] axes) {
|
|
mAxes = axes == null ? null : axes.clone();
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Creates the font based on the configured values.
|
|
* @return the Font object
|
|
*/
|
|
public @NonNull Font build() throws IOException {
|
|
if (mException != null) {
|
|
throw new IOException("Failed to read font contents", mException);
|
|
}
|
|
if (mWeight == NOT_SPECIFIED || mItalic == NOT_SPECIFIED) {
|
|
final int packed = FontFileUtil.analyzeStyle(mBuffer, mTtcIndex, mAxes);
|
|
if (FontFileUtil.isSuccess(packed)) {
|
|
if (mWeight == NOT_SPECIFIED) {
|
|
mWeight = FontFileUtil.unpackWeight(packed);
|
|
}
|
|
if (mItalic == NOT_SPECIFIED) {
|
|
mItalic = FontFileUtil.unpackItalic(packed) ? STYLE_ITALIC : STYLE_NORMAL;
|
|
}
|
|
} else {
|
|
mWeight = 400;
|
|
mItalic = STYLE_NORMAL;
|
|
}
|
|
}
|
|
mWeight = Math.max(FontStyle.FONT_WEIGHT_MIN,
|
|
Math.min(FontStyle.FONT_WEIGHT_MAX, mWeight));
|
|
final boolean italic = (mItalic == STYLE_ITALIC);
|
|
final int slant = (mItalic == STYLE_ITALIC)
|
|
? FontStyle.FONT_SLANT_ITALIC : FontStyle.FONT_SLANT_UPRIGHT;
|
|
final long builderPtr = nInitBuilder();
|
|
if (mAxes != null) {
|
|
for (FontVariationAxis axis : mAxes) {
|
|
nAddAxis(builderPtr, axis.getOpenTypeTagValue(), axis.getStyleValue());
|
|
}
|
|
}
|
|
final ByteBuffer readonlyBuffer = mBuffer.asReadOnlyBuffer();
|
|
final String filePath = mFile == null ? "" : mFile.getAbsolutePath();
|
|
|
|
long ptr;
|
|
final Font font;
|
|
if (mFont == null) {
|
|
ptr = nBuild(builderPtr, readonlyBuffer, filePath, mLocaleList, mWeight, italic,
|
|
mTtcIndex);
|
|
font = new Font(ptr);
|
|
} else {
|
|
ptr = nClone(mFont.getNativePtr(), builderPtr, mWeight, italic, mTtcIndex);
|
|
font = new Font(ptr);
|
|
}
|
|
return font;
|
|
}
|
|
|
|
/**
|
|
* Native methods for creating Font
|
|
*/
|
|
private static native long nInitBuilder();
|
|
@CriticalNative
|
|
private static native void nAddAxis(long builderPtr, int tag, float value);
|
|
private static native long nBuild(
|
|
long builderPtr, @NonNull ByteBuffer buffer, @NonNull String filePath,
|
|
@NonNull String localeList, int weight, boolean italic, int ttcIndex);
|
|
|
|
@FastNative
|
|
private static native long nClone(long fontPtr, long builderPtr, int weight,
|
|
boolean italic, int ttcIndex);
|
|
}
|
|
|
|
private final long mNativePtr; // address of the shared ptr of minikin::Font
|
|
private final Object mLock = new Object();
|
|
|
|
@GuardedBy("mLock")
|
|
private @NonNull ByteBuffer mBuffer = null;
|
|
@GuardedBy("mLock")
|
|
private boolean mIsFileInitialized = false;
|
|
@GuardedBy("mLock")
|
|
private @Nullable File mFile = null;
|
|
@GuardedBy("mLock")
|
|
private FontStyle mFontStyle = null;
|
|
@GuardedBy("mLock")
|
|
private @Nullable FontVariationAxis[] mAxes = null;
|
|
@GuardedBy("mLock")
|
|
private @NonNull LocaleList mLocaleList = null;
|
|
|
|
/**
|
|
* Use Builder instead
|
|
*
|
|
* Caller must increment underlying minikin::Font ref count.
|
|
* This class takes the ownership of the passing native objects.
|
|
*
|
|
* @hide
|
|
*/
|
|
public Font(long nativePtr) {
|
|
mNativePtr = nativePtr;
|
|
|
|
NoImagePreloadHolder.FONT_REGISTRY.registerNativeAllocation(this, mNativePtr);
|
|
}
|
|
|
|
/**
|
|
* Returns a font file buffer.
|
|
*
|
|
* Duplicate before reading values by {@link ByteBuffer#duplicate()} for avoiding unexpected
|
|
* reading position sharing.
|
|
*
|
|
* @return a font buffer
|
|
*/
|
|
public @NonNull ByteBuffer getBuffer() {
|
|
synchronized (mLock) {
|
|
if (mBuffer == null) {
|
|
// Create new instance of native FontWrapper, i.e. incrementing ref count of
|
|
// minikin Font instance for keeping buffer fo ByteBuffer reference which may live
|
|
// longer than this object.
|
|
long ref = nCloneFont(mNativePtr);
|
|
ByteBuffer fromNative = nNewByteBuffer(mNativePtr);
|
|
|
|
// Bind ByteBuffer's lifecycle with underlying font object.
|
|
NoImagePreloadHolder.BUFFER_REGISTRY.registerNativeAllocation(fromNative, ref);
|
|
|
|
// JNI NewDirectBuffer creates writable ByteBuffer even if it is mmaped readonly.
|
|
mBuffer = fromNative.asReadOnlyBuffer();
|
|
}
|
|
return mBuffer;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns a file path of this font.
|
|
*
|
|
* This returns null if this font is not created from regular file.
|
|
*
|
|
* @return a file path of the font
|
|
*/
|
|
public @Nullable File getFile() {
|
|
synchronized (mLock) {
|
|
if (!mIsFileInitialized) {
|
|
String path = nGetFontPath(mNativePtr);
|
|
if (!TextUtils.isEmpty(path)) {
|
|
mFile = new File(path);
|
|
}
|
|
mIsFileInitialized = true;
|
|
}
|
|
return mFile;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get a style associated with this font.
|
|
*
|
|
* @see Builder#setWeight(int)
|
|
* @see Builder#setSlant(int)
|
|
* @return a font style
|
|
*/
|
|
public @NonNull FontStyle getStyle() {
|
|
synchronized (mLock) {
|
|
if (mFontStyle == null) {
|
|
int packedStyle = nGetPackedStyle(mNativePtr);
|
|
mFontStyle = new FontStyle(
|
|
FontFileUtil.unpackWeight(packedStyle),
|
|
FontFileUtil.unpackItalic(packedStyle)
|
|
? FontStyle.FONT_SLANT_ITALIC : FontStyle.FONT_SLANT_UPRIGHT);
|
|
}
|
|
return mFontStyle;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get a TTC index value associated with this font.
|
|
*
|
|
* If TTF/OTF file is provided, this value is always 0.
|
|
*
|
|
* @see Builder#setTtcIndex(int)
|
|
* @return a TTC index value
|
|
*/
|
|
public @IntRange(from = 0) int getTtcIndex() {
|
|
return nGetIndex(mNativePtr);
|
|
}
|
|
|
|
/**
|
|
* Get a font variation settings associated with this font
|
|
*
|
|
* @see Builder#setFontVariationSettings(String)
|
|
* @see Builder#setFontVariationSettings(FontVariationAxis[])
|
|
* @return font variation settings
|
|
*/
|
|
public @Nullable FontVariationAxis[] getAxes() {
|
|
synchronized (mLock) {
|
|
if (mAxes == null) {
|
|
int axisCount = nGetAxisCount(mNativePtr);
|
|
mAxes = new FontVariationAxis[axisCount];
|
|
char[] charBuffer = new char[4];
|
|
for (int i = 0; i < axisCount; ++i) {
|
|
long packedAxis = nGetAxisInfo(mNativePtr, i);
|
|
float value = Float.intBitsToFloat((int) (packedAxis & 0x0000_0000_FFFF_FFFFL));
|
|
charBuffer[0] = (char) ((packedAxis & 0xFF00_0000_0000_0000L) >>> 56);
|
|
charBuffer[1] = (char) ((packedAxis & 0x00FF_0000_0000_0000L) >>> 48);
|
|
charBuffer[2] = (char) ((packedAxis & 0x0000_FF00_0000_0000L) >>> 40);
|
|
charBuffer[3] = (char) ((packedAxis & 0x0000_00FF_0000_0000L) >>> 32);
|
|
mAxes[i] = new FontVariationAxis(new String(charBuffer), value);
|
|
}
|
|
}
|
|
}
|
|
return mAxes;
|
|
}
|
|
|
|
/**
|
|
* Get a locale list of this font.
|
|
*
|
|
* This is always empty if this font is not a system font.
|
|
* @return a locale list
|
|
*/
|
|
public @NonNull LocaleList getLocaleList() {
|
|
synchronized (mLock) {
|
|
if (mLocaleList == null) {
|
|
String langTags = nGetLocaleList(mNativePtr);
|
|
if (TextUtils.isEmpty(langTags)) {
|
|
mLocaleList = LocaleList.getEmptyLocaleList();
|
|
} else {
|
|
mLocaleList = LocaleList.forLanguageTags(langTags);
|
|
}
|
|
}
|
|
return mLocaleList;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Retrieve the glyph horizontal advance and bounding box.
|
|
*
|
|
* Note that {@link android.graphics.Typeface} in {@link android.graphics.Paint} is ignored.
|
|
*
|
|
* @param glyphId a glyph ID
|
|
* @param paint a paint object used for resolving glyph style
|
|
* @param outBoundingBox a nullable destination object. If null is passed, this function just
|
|
* return the horizontal advance. If non-null is passed, this function
|
|
* fills bounding box information to this object.
|
|
* @return the amount of horizontal advance in pixels
|
|
*/
|
|
public float getGlyphBounds(@IntRange(from = 0) int glyphId, @NonNull Paint paint,
|
|
@Nullable RectF outBoundingBox) {
|
|
return nGetGlyphBounds(mNativePtr, glyphId, paint.getNativeInstance(), outBoundingBox);
|
|
}
|
|
|
|
/**
|
|
* Retrieve the font metrics information.
|
|
*
|
|
* Note that {@link android.graphics.Typeface} in {@link android.graphics.Paint} is ignored.
|
|
*
|
|
* @param paint a paint object used for retrieving font metrics.
|
|
* @param outMetrics a nullable destination object. If null is passed, this function only
|
|
* retrieve recommended interline spacing. If non-null is passed, this function
|
|
* fills to font metrics to it.
|
|
*
|
|
* @see Paint#getFontMetrics()
|
|
* @see Paint#getFontMetricsInt()
|
|
*/
|
|
public void getMetrics(@NonNull Paint paint, @Nullable Paint.FontMetrics outMetrics) {
|
|
nGetFontMetrics(mNativePtr, paint.getNativeInstance(), outMetrics);
|
|
}
|
|
|
|
/** @hide */
|
|
public long getNativePtr() {
|
|
return mNativePtr;
|
|
}
|
|
|
|
/**
|
|
* Returns the unique ID of the source font data.
|
|
*
|
|
* You can use this identifier as a key of the cache or checking if two fonts can be
|
|
* interpolated with font variation settings.
|
|
* <pre>
|
|
* <code>
|
|
* // Following three Fonts, fontA, fontB, fontC have the same identifier.
|
|
* Font fontA = new Font.Builder("/path/to/font").build();
|
|
* Font fontB = new Font.Builder(fontA).setTtcIndex(1).build();
|
|
* Font fontC = new Font.Builder(fontB).setFontVariationSettings("'wght' 700).build();
|
|
*
|
|
* // Following fontD has the different identifier from above three.
|
|
* Font fontD = new Font.Builder("/path/to/another/font").build();
|
|
*
|
|
* // Following fontE has different identifier from above four even the font path is the same.
|
|
* // To get the same identifier, please create new Font instance from existing fonts.
|
|
* Font fontE = new Font.Builder("/path/to/font").build();
|
|
* </code>
|
|
* </pre>
|
|
*
|
|
* Here is an example of caching font object that has
|
|
* <pre>
|
|
* <code>
|
|
* private LongSparseArray<SparseArray<Font>> mCache = new LongSparseArray<>();
|
|
*
|
|
* private Font getFontWeightVariation(Font font, int weight) {
|
|
* // Different collection index is treated as different font.
|
|
* long key = ((long) font.getSourceIdentifier()) << 32 | (long) font.getTtcIndex();
|
|
*
|
|
* SparseArray<Font> weightCache = mCache.get(key);
|
|
* if (weightCache == null) {
|
|
* weightCache = new SparseArray<>();
|
|
* mCache.put(key, weightCache);
|
|
* }
|
|
*
|
|
* Font cachedFont = weightCache.get(weight);
|
|
* if (cachedFont != null) {
|
|
* return cachedFont;
|
|
* }
|
|
*
|
|
* Font newFont = new Font.Builder(cachedFont)
|
|
* .setFontVariationSettings("'wght' " + weight);
|
|
* .build();
|
|
*
|
|
* weightCache.put(weight, newFont);
|
|
* return newFont;
|
|
* }
|
|
* </code>
|
|
* </pre>
|
|
* @return an unique identifier for the font source data.
|
|
*/
|
|
public int getSourceIdentifier() {
|
|
return nGetSourceId(mNativePtr);
|
|
}
|
|
|
|
/**
|
|
* Returns true if the given font is created from the same source data from this font.
|
|
*
|
|
* This method essentially compares {@link ByteBuffer} inside Font, but has some optimization
|
|
* for faster comparing. This method compares the internal object before going to one-by-one
|
|
* byte compare with {@link ByteBuffer}. This typically works efficiently if you compares the
|
|
* font that is created from {@link Builder#Builder(Font)}.
|
|
*
|
|
* This API is typically useful for checking if two fonts can be interpolated by font variation
|
|
* axes. For example, when you call {@link android.text.TextShaper} for the same
|
|
* string but different style, you may get two font objects which is created from the same
|
|
* source but have different parameters. You may want to animate between them by interpolating
|
|
* font variation settings if these fonts are created from the same source.
|
|
*
|
|
* @param other a font object to be compared.
|
|
* @return true if given font is created from the same source from this font. Otherwise false.
|
|
*/
|
|
private boolean isSameSource(@NonNull Font other) {
|
|
Objects.requireNonNull(other);
|
|
|
|
ByteBuffer myBuffer = getBuffer();
|
|
ByteBuffer otherBuffer = other.getBuffer();
|
|
|
|
// Shortcut for the same instance.
|
|
if (myBuffer == otherBuffer) {
|
|
return true;
|
|
}
|
|
|
|
// Shortcut for different font buffer check by comparing size.
|
|
if (myBuffer.capacity() != otherBuffer.capacity()) {
|
|
return false;
|
|
}
|
|
|
|
// ByteBuffer#equals compares all bytes which is not performant for e.g. HashMap. Since
|
|
// underlying native font object holds buffer address, check if this buffer points exactly
|
|
// the same address as a shortcut of equality. For being compatible with of API30 or before,
|
|
// check buffer position even if the buffer points the same address.
|
|
if (getSourceIdentifier() == other.getSourceIdentifier()
|
|
&& myBuffer.position() == otherBuffer.position()) {
|
|
return true;
|
|
}
|
|
|
|
// Unfortunately, need to compare bytes one-by-one since the buffer may be different font
|
|
// file but has the same file size, or two font has same content but they are allocated
|
|
// differently. For being compatible with API30 ore before, compare with ByteBuffer#equals.
|
|
return myBuffer.equals(otherBuffer);
|
|
}
|
|
|
|
/** @hide */
|
|
public boolean paramEquals(@NonNull Font f) {
|
|
return f.getStyle().equals(getStyle())
|
|
&& f.getTtcIndex() == getTtcIndex()
|
|
&& Arrays.equals(f.getAxes(), getAxes())
|
|
&& Objects.equals(f.getLocaleList(), getLocaleList())
|
|
&& Objects.equals(getFile(), f.getFile());
|
|
}
|
|
|
|
@Override
|
|
public boolean equals(@Nullable Object o) {
|
|
if (o == this) {
|
|
return true;
|
|
}
|
|
if (!(o instanceof Font)) {
|
|
return false;
|
|
}
|
|
|
|
Font f = (Font) o;
|
|
|
|
// The underlying minikin::Font object is the source of the truth of font information. Thus,
|
|
// Pointer equality is the object equality.
|
|
if (nGetMinikinFontPtr(mNativePtr) == nGetMinikinFontPtr(f.mNativePtr)) {
|
|
return true;
|
|
}
|
|
|
|
if (!paramEquals(f)) {
|
|
return false;
|
|
}
|
|
|
|
return isSameSource(f);
|
|
}
|
|
|
|
@Override
|
|
public int hashCode() {
|
|
return Objects.hash(
|
|
getStyle(),
|
|
getTtcIndex(),
|
|
Arrays.hashCode(getAxes()),
|
|
// Use Buffer size instead of ByteBuffer#hashCode since ByteBuffer#hashCode traverse
|
|
// data which is not performant e.g. for HashMap. The hash collision are less likely
|
|
// happens because it is unlikely happens the different font files has exactly the
|
|
// same size.
|
|
getLocaleList());
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
return "Font {"
|
|
+ "path=" + getFile()
|
|
+ ", style=" + getStyle()
|
|
+ ", ttcIndex=" + getTtcIndex()
|
|
+ ", axes=" + FontVariationAxis.toFontVariationSettings(getAxes())
|
|
+ ", localeList=" + getLocaleList()
|
|
+ ", buffer=" + getBuffer()
|
|
+ "}";
|
|
}
|
|
|
|
/** @hide */
|
|
public static Set<Font> getAvailableFonts() {
|
|
// The font uniqueness is already calculated in the native code. So use IdentityHashMap
|
|
// for avoiding hash/equals calculation.
|
|
IdentityHashMap<Font, Font> map = new IdentityHashMap<>();
|
|
for (long nativePtr : nGetAvailableFontSet()) {
|
|
Font font = new Font(nativePtr);
|
|
map.put(font, font);
|
|
}
|
|
return Collections.unmodifiableSet(map.keySet());
|
|
}
|
|
|
|
@CriticalNative
|
|
private static native long nGetMinikinFontPtr(long font);
|
|
|
|
@CriticalNative
|
|
private static native long nCloneFont(long font);
|
|
|
|
@FastNative
|
|
private static native ByteBuffer nNewByteBuffer(long font);
|
|
|
|
@CriticalNative
|
|
private static native long nGetBufferAddress(long font);
|
|
|
|
@CriticalNative
|
|
private static native int nGetSourceId(long font);
|
|
|
|
@CriticalNative
|
|
private static native long nGetReleaseNativeFont();
|
|
|
|
@FastNative
|
|
private static native float nGetGlyphBounds(long font, int glyphId, long paint, RectF rect);
|
|
|
|
@FastNative
|
|
private static native float nGetFontMetrics(long font, long paint, Paint.FontMetrics metrics);
|
|
|
|
@FastNative
|
|
private static native String nGetFontPath(long fontPtr);
|
|
|
|
@FastNative
|
|
private static native String nGetLocaleList(long familyPtr);
|
|
|
|
@CriticalNative
|
|
private static native int nGetPackedStyle(long fontPtr);
|
|
|
|
@CriticalNative
|
|
private static native int nGetIndex(long fontPtr);
|
|
|
|
@CriticalNative
|
|
private static native int nGetAxisCount(long fontPtr);
|
|
|
|
@CriticalNative
|
|
private static native long nGetAxisInfo(long fontPtr, int i);
|
|
|
|
@FastNative
|
|
private static native long[] nGetAvailableFontSet();
|
|
}
|