286 lines
10 KiB
Java
286 lines
10 KiB
Java
/*
|
|
* Copyright 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.util.ArraySet;
|
|
|
|
import dalvik.annotation.optimization.FastNative;
|
|
|
|
import java.nio.ByteBuffer;
|
|
import java.nio.ByteOrder;
|
|
import java.util.Collections;
|
|
import java.util.Set;
|
|
|
|
/**
|
|
* Provides a utility for font file operations.
|
|
* @hide
|
|
*/
|
|
public class FontFileUtil {
|
|
|
|
private FontFileUtil() {} // Do not instantiate
|
|
|
|
/**
|
|
* Unpack the weight value from packed integer.
|
|
*/
|
|
public static int unpackWeight(int packed) {
|
|
return packed & 0xFFFF;
|
|
}
|
|
|
|
/**
|
|
* Unpack the italic value from packed integer.
|
|
*/
|
|
public static boolean unpackItalic(int packed) {
|
|
return (packed & 0x10000) != 0;
|
|
}
|
|
|
|
/**
|
|
* Returns true if the analyzeStyle succeeded
|
|
*/
|
|
public static boolean isSuccess(int packed) {
|
|
return packed != ANALYZE_ERROR;
|
|
}
|
|
|
|
private static int pack(@IntRange(from = 0, to = 1000) int weight, boolean italic) {
|
|
return weight | (italic ? 0x10000 : 0);
|
|
}
|
|
|
|
private static final int SFNT_VERSION_1 = 0x00010000;
|
|
private static final int SFNT_VERSION_OTTO = 0x4F54544F;
|
|
private static final int TTC_TAG = 0x74746366;
|
|
private static final int OS2_TABLE_TAG = 0x4F532F32;
|
|
private static final int FVAR_TABLE_TAG = 0x66766172;
|
|
|
|
private static final int ANALYZE_ERROR = 0xFFFFFFFF;
|
|
|
|
/**
|
|
* Analyze the font file returns packed style info
|
|
*/
|
|
public static final int analyzeStyle(@NonNull ByteBuffer buffer,
|
|
@IntRange(from = 0) int ttcIndex, @Nullable FontVariationAxis[] varSettings) {
|
|
int weight = -1;
|
|
int italic = -1;
|
|
if (varSettings != null) {
|
|
for (FontVariationAxis axis :varSettings) {
|
|
if ("wght".equals(axis.getTag())) {
|
|
weight = (int) axis.getStyleValue();
|
|
} else if ("ital".equals(axis.getTag())) {
|
|
italic = (axis.getStyleValue() == 1.0f) ? 1 : 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (weight != -1 && italic != -1) {
|
|
// Both weight/italic style are specified by variation settings.
|
|
// No need to look into OS/2 table.
|
|
// TODO: Good to look HVAR table to check if this font supports wght/ital axes.
|
|
return pack(weight, italic == 1);
|
|
}
|
|
|
|
ByteOrder originalOrder = buffer.order();
|
|
buffer.order(ByteOrder.BIG_ENDIAN);
|
|
try {
|
|
int fontFileOffset = 0;
|
|
int magicNumber = buffer.getInt(0);
|
|
if (magicNumber == TTC_TAG) {
|
|
// TTC file.
|
|
if (ttcIndex >= buffer.getInt(8 /* offset to number of fonts in TTC */)) {
|
|
return ANALYZE_ERROR;
|
|
}
|
|
fontFileOffset = buffer.getInt(
|
|
12 /* offset to array of offsets of font files */ + 4 * ttcIndex);
|
|
}
|
|
int sfntVersion = buffer.getInt(fontFileOffset);
|
|
|
|
if (sfntVersion != SFNT_VERSION_1 && sfntVersion != SFNT_VERSION_OTTO) {
|
|
return ANALYZE_ERROR;
|
|
}
|
|
|
|
int numTables = buffer.getShort(fontFileOffset + 4 /* offset to number of tables */);
|
|
int os2TableOffset = -1;
|
|
for (int i = 0; i < numTables; ++i) {
|
|
int tableOffset = fontFileOffset + 12 /* size of offset table */
|
|
+ i * 16 /* size of table record */;
|
|
if (buffer.getInt(tableOffset) == OS2_TABLE_TAG) {
|
|
os2TableOffset = buffer.getInt(tableOffset + 8 /* offset to the table */);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (os2TableOffset == -1) {
|
|
// Couldn't find OS/2 table. use regular style
|
|
return pack(400, false);
|
|
}
|
|
|
|
int weightFromOS2 = buffer.getShort(os2TableOffset + 4 /* offset to weight class */);
|
|
boolean italicFromOS2 =
|
|
(buffer.getShort(os2TableOffset + 62 /* offset to fsSelection */) & 1) != 0;
|
|
return pack(weight == -1 ? weightFromOS2 : weight,
|
|
italic == -1 ? italicFromOS2 : italic == 1);
|
|
} finally {
|
|
buffer.order(originalOrder);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Analyze head OpenType table and return fontRevision value as 32bit integer.
|
|
*
|
|
* The font revision is stored in 16.16 bit fixed point value. This function returns this fixed
|
|
* point value as 32 bit integer, i.e. the value multiplied with 65536.
|
|
*
|
|
* IllegalArgumentException will be thrown for invalid font data.
|
|
* If the font file is invalid, returns -1L.
|
|
*
|
|
* @param buffer a buffer of OpenType font
|
|
* @param index a font index
|
|
* @return font revision that shifted 16 bits left.
|
|
*/
|
|
public static long getRevision(@NonNull ByteBuffer buffer, @IntRange(from = 0) int index) {
|
|
return nGetFontRevision(buffer, index);
|
|
}
|
|
|
|
/**
|
|
* Analyze name OpenType table and return PostScript name.
|
|
*
|
|
* IllegalArgumentException will be thrown for invalid font data.
|
|
* null will be returned if not found or the PostScript name is invalid.
|
|
*
|
|
* @param buffer a buffer of OpenType font
|
|
* @param index a font index
|
|
* @return a post script name or null if it is invalid or not found.
|
|
*/
|
|
public static String getPostScriptName(@NonNull ByteBuffer buffer,
|
|
@IntRange(from = 0) int index) {
|
|
return nGetFontPostScriptName(buffer, index);
|
|
}
|
|
|
|
/**
|
|
* Analyze name OpenType table and return true if the font has PostScript Type 1 glyphs.
|
|
*
|
|
* IllegalArgumentException will be thrown for invalid font data.
|
|
* -1 will be returned if the byte buffer is not a OpenType font data.
|
|
* 0 will be returned if the font file doesn't have PostScript Type 1 glyphs, i.e. ttf file.
|
|
* 1 will be returned if the font file has PostScript Type 1 glyphs, i.e. otf file.
|
|
*
|
|
* @param buffer a buffer of OpenType font
|
|
* @param index a font index
|
|
* @return a post script name or null if it is invalid or not found.
|
|
*/
|
|
public static int isPostScriptType1Font(@NonNull ByteBuffer buffer,
|
|
@IntRange(from = 0) int index) {
|
|
return nIsPostScriptType1Font(buffer, index);
|
|
}
|
|
|
|
/**
|
|
* Analyze the file content and returns 1 if the font file is an OpenType collection file, 0 if
|
|
* the font file is a OpenType font file, -1 otherwise.
|
|
*/
|
|
public static int isCollectionFont(@NonNull ByteBuffer buffer) {
|
|
ByteBuffer copied = buffer.slice();
|
|
copied.order(ByteOrder.BIG_ENDIAN);
|
|
int magicNumber = copied.getInt(0);
|
|
if (magicNumber == TTC_TAG) {
|
|
return 1;
|
|
} else if (magicNumber == SFNT_VERSION_1 || magicNumber == SFNT_VERSION_OTTO) {
|
|
return 0;
|
|
} else {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
private static int getUInt16(ByteBuffer buffer, int offset) {
|
|
return ((int) buffer.getShort(offset)) & 0xFFFF;
|
|
}
|
|
|
|
/**
|
|
* Returns supported axes of font
|
|
*
|
|
* @param buffer A buffer of the entire font file.
|
|
* @param index A font index in case of font collection. Must be 0 otherwise.
|
|
* @return set of supported axes tag. Returns empty set on error.
|
|
*/
|
|
public static Set<Integer> getSupportedAxes(@NonNull ByteBuffer buffer, int index) {
|
|
ByteOrder originalOrder = buffer.order();
|
|
buffer.order(ByteOrder.BIG_ENDIAN);
|
|
try {
|
|
int fontFileOffset = 0;
|
|
int magicNumber = buffer.getInt(0);
|
|
if (magicNumber == TTC_TAG) {
|
|
// TTC file.
|
|
if (index >= buffer.getInt(8 /* offset to number of fonts in TTC */)) {
|
|
return Collections.EMPTY_SET;
|
|
}
|
|
fontFileOffset = buffer.getInt(
|
|
12 /* offset to array of offsets of font files */ + 4 * index);
|
|
}
|
|
int sfntVersion = buffer.getInt(fontFileOffset);
|
|
|
|
if (sfntVersion != SFNT_VERSION_1 && sfntVersion != SFNT_VERSION_OTTO) {
|
|
return Collections.EMPTY_SET;
|
|
}
|
|
|
|
int numTables = buffer.getShort(fontFileOffset + 4 /* offset to number of tables */);
|
|
int fvarTableOffset = -1;
|
|
for (int i = 0; i < numTables; ++i) {
|
|
int tableOffset = fontFileOffset + 12 /* size of offset table */
|
|
+ i * 16 /* size of table record */;
|
|
if (buffer.getInt(tableOffset) == FVAR_TABLE_TAG) {
|
|
fvarTableOffset = buffer.getInt(tableOffset + 8 /* offset to the table */);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (fvarTableOffset == -1) {
|
|
// Couldn't find OS/2 table. use regular style
|
|
return Collections.EMPTY_SET;
|
|
}
|
|
|
|
if (buffer.getShort(fvarTableOffset) != 1
|
|
|| buffer.getShort(fvarTableOffset + 2) != 0) {
|
|
return Collections.EMPTY_SET;
|
|
}
|
|
|
|
int axesArrayOffset = getUInt16(buffer, fvarTableOffset + 4);
|
|
int axisCount = getUInt16(buffer, fvarTableOffset + 8);
|
|
int axisSize = getUInt16(buffer, fvarTableOffset + 10);
|
|
|
|
ArraySet<Integer> axes = new ArraySet<>();
|
|
for (int i = 0; i < axisCount; ++i) {
|
|
axes.add(buffer.getInt(fvarTableOffset + axesArrayOffset + axisSize * i));
|
|
}
|
|
|
|
return axes;
|
|
} finally {
|
|
buffer.order(originalOrder);
|
|
}
|
|
}
|
|
|
|
@FastNative
|
|
private static native long nGetFontRevision(@NonNull ByteBuffer buffer,
|
|
@IntRange(from = 0) int index);
|
|
|
|
@FastNative
|
|
private static native String nGetFontPostScriptName(@NonNull ByteBuffer buffer,
|
|
@IntRange(from = 0) int index);
|
|
|
|
@FastNative
|
|
private static native int nIsPostScriptType1Font(@NonNull ByteBuffer buffer,
|
|
@IntRange(from = 0) int index);
|
|
}
|