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

* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *

* * @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. *
     * 
     *   // 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();
     * 
     * 
* * Here is an example of caching font object that has *
     * 
     *   private LongSparseArray> 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 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;
     *   }
     * 
     * 
* @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 getAvailableFonts() { // The font uniqueness is already calculated in the native code. So use IdentityHashMap // for avoiding hash/equals calculation. IdentityHashMap 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(); }
ValueNameAndroid Definition
100Thin{@link FontStyle#FONT_WEIGHT_THIN}
200Extra Light (Ultra Light){@link FontStyle#FONT_WEIGHT_EXTRA_LIGHT}
300Light{@link FontStyle#FONT_WEIGHT_LIGHT}
400Normal (Regular){@link FontStyle#FONT_WEIGHT_NORMAL}
500Medium{@link FontStyle#FONT_WEIGHT_MEDIUM}
600Semi Bold (Demi Bold){@link FontStyle#FONT_WEIGHT_SEMI_BOLD}
700Bold{@link FontStyle#FONT_WEIGHT_BOLD}
800Extra Bold (Ultra Bold){@link FontStyle#FONT_WEIGHT_EXTRA_BOLD}
900Black (Heavy){@link FontStyle#FONT_WEIGHT_BLACK}