1 /* 2 * Copyright 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.graphics.fonts; 18 19 import android.annotation.IntRange; 20 import android.annotation.NonNull; 21 import android.annotation.Nullable; 22 import android.util.ArraySet; 23 24 import dalvik.annotation.optimization.FastNative; 25 26 import java.nio.ByteBuffer; 27 import java.nio.ByteOrder; 28 import java.util.Collections; 29 import java.util.Set; 30 31 /** 32 * Provides a utility for font file operations. 33 * @hide 34 */ 35 public class FontFileUtil { 36 FontFileUtil()37 private FontFileUtil() {} // Do not instantiate 38 39 /** 40 * Unpack the weight value from packed integer. 41 */ unpackWeight(int packed)42 public static int unpackWeight(int packed) { 43 return packed & 0xFFFF; 44 } 45 46 /** 47 * Unpack the italic value from packed integer. 48 */ unpackItalic(int packed)49 public static boolean unpackItalic(int packed) { 50 return (packed & 0x10000) != 0; 51 } 52 53 /** 54 * Returns true if the analyzeStyle succeeded 55 */ isSuccess(int packed)56 public static boolean isSuccess(int packed) { 57 return packed != ANALYZE_ERROR; 58 } 59 pack(@ntRangefrom = 0, to = 1000) int weight, boolean italic)60 private static int pack(@IntRange(from = 0, to = 1000) int weight, boolean italic) { 61 return weight | (italic ? 0x10000 : 0); 62 } 63 64 private static final int SFNT_VERSION_1 = 0x00010000; 65 private static final int SFNT_VERSION_OTTO = 0x4F54544F; 66 private static final int TTC_TAG = 0x74746366; 67 private static final int OS2_TABLE_TAG = 0x4F532F32; 68 private static final int FVAR_TABLE_TAG = 0x66766172; 69 70 private static final int ANALYZE_ERROR = 0xFFFFFFFF; 71 72 /** 73 * Analyze the font file returns packed style info 74 */ analyzeStyle(@onNull ByteBuffer buffer, @IntRange(from = 0) int ttcIndex, @Nullable FontVariationAxis[] varSettings)75 public static final int analyzeStyle(@NonNull ByteBuffer buffer, 76 @IntRange(from = 0) int ttcIndex, @Nullable FontVariationAxis[] varSettings) { 77 int weight = -1; 78 int italic = -1; 79 if (varSettings != null) { 80 for (FontVariationAxis axis :varSettings) { 81 if ("wght".equals(axis.getTag())) { 82 weight = (int) axis.getStyleValue(); 83 } else if ("ital".equals(axis.getTag())) { 84 italic = (axis.getStyleValue() == 1.0f) ? 1 : 0; 85 } 86 } 87 } 88 89 if (weight != -1 && italic != -1) { 90 // Both weight/italic style are specified by variation settings. 91 // No need to look into OS/2 table. 92 // TODO: Good to look HVAR table to check if this font supports wght/ital axes. 93 return pack(weight, italic == 1); 94 } 95 96 ByteOrder originalOrder = buffer.order(); 97 buffer.order(ByteOrder.BIG_ENDIAN); 98 try { 99 int fontFileOffset = 0; 100 int magicNumber = buffer.getInt(0); 101 if (magicNumber == TTC_TAG) { 102 // TTC file. 103 if (ttcIndex >= buffer.getInt(8 /* offset to number of fonts in TTC */)) { 104 return ANALYZE_ERROR; 105 } 106 fontFileOffset = buffer.getInt( 107 12 /* offset to array of offsets of font files */ + 4 * ttcIndex); 108 } 109 int sfntVersion = buffer.getInt(fontFileOffset); 110 111 if (sfntVersion != SFNT_VERSION_1 && sfntVersion != SFNT_VERSION_OTTO) { 112 return ANALYZE_ERROR; 113 } 114 115 int numTables = buffer.getShort(fontFileOffset + 4 /* offset to number of tables */); 116 int os2TableOffset = -1; 117 for (int i = 0; i < numTables; ++i) { 118 int tableOffset = fontFileOffset + 12 /* size of offset table */ 119 + i * 16 /* size of table record */; 120 if (buffer.getInt(tableOffset) == OS2_TABLE_TAG) { 121 os2TableOffset = buffer.getInt(tableOffset + 8 /* offset to the table */); 122 break; 123 } 124 } 125 126 if (os2TableOffset == -1) { 127 // Couldn't find OS/2 table. use regular style 128 return pack(400, false); 129 } 130 131 int weightFromOS2 = buffer.getShort(os2TableOffset + 4 /* offset to weight class */); 132 boolean italicFromOS2 = 133 (buffer.getShort(os2TableOffset + 62 /* offset to fsSelection */) & 1) != 0; 134 return pack(weight == -1 ? weightFromOS2 : weight, 135 italic == -1 ? italicFromOS2 : italic == 1); 136 } finally { 137 buffer.order(originalOrder); 138 } 139 } 140 141 /** 142 * Analyze head OpenType table and return fontRevision value as 32bit integer. 143 * 144 * The font revision is stored in 16.16 bit fixed point value. This function returns this fixed 145 * point value as 32 bit integer, i.e. the value multiplied with 65536. 146 * 147 * IllegalArgumentException will be thrown for invalid font data. 148 * If the font file is invalid, returns -1L. 149 * 150 * @param buffer a buffer of OpenType font 151 * @param index a font index 152 * @return font revision that shifted 16 bits left. 153 */ getRevision(@onNull ByteBuffer buffer, @IntRange(from = 0) int index)154 public static long getRevision(@NonNull ByteBuffer buffer, @IntRange(from = 0) int index) { 155 return nGetFontRevision(buffer, index); 156 } 157 158 /** 159 * Analyze name OpenType table and return PostScript name. 160 * 161 * IllegalArgumentException will be thrown for invalid font data. 162 * null will be returned if not found or the PostScript name is invalid. 163 * 164 * @param buffer a buffer of OpenType font 165 * @param index a font index 166 * @return a post script name or null if it is invalid or not found. 167 */ getPostScriptName(@onNull ByteBuffer buffer, @IntRange(from = 0) int index)168 public static String getPostScriptName(@NonNull ByteBuffer buffer, 169 @IntRange(from = 0) int index) { 170 return nGetFontPostScriptName(buffer, index); 171 } 172 173 /** 174 * Analyze name OpenType table and return true if the font has PostScript Type 1 glyphs. 175 * 176 * IllegalArgumentException will be thrown for invalid font data. 177 * -1 will be returned if the byte buffer is not a OpenType font data. 178 * 0 will be returned if the font file doesn't have PostScript Type 1 glyphs, i.e. ttf file. 179 * 1 will be returned if the font file has PostScript Type 1 glyphs, i.e. otf file. 180 * 181 * @param buffer a buffer of OpenType font 182 * @param index a font index 183 * @return a post script name or null if it is invalid or not found. 184 */ isPostScriptType1Font(@onNull ByteBuffer buffer, @IntRange(from = 0) int index)185 public static int isPostScriptType1Font(@NonNull ByteBuffer buffer, 186 @IntRange(from = 0) int index) { 187 return nIsPostScriptType1Font(buffer, index); 188 } 189 190 /** 191 * Analyze the file content and returns 1 if the font file is an OpenType collection file, 0 if 192 * the font file is a OpenType font file, -1 otherwise. 193 */ isCollectionFont(@onNull ByteBuffer buffer)194 public static int isCollectionFont(@NonNull ByteBuffer buffer) { 195 ByteBuffer copied = buffer.slice(); 196 copied.order(ByteOrder.BIG_ENDIAN); 197 int magicNumber = copied.getInt(0); 198 if (magicNumber == TTC_TAG) { 199 return 1; 200 } else if (magicNumber == SFNT_VERSION_1 || magicNumber == SFNT_VERSION_OTTO) { 201 return 0; 202 } else { 203 return -1; 204 } 205 } 206 getUInt16(ByteBuffer buffer, int offset)207 private static int getUInt16(ByteBuffer buffer, int offset) { 208 return ((int) buffer.getShort(offset)) & 0xFFFF; 209 } 210 211 /** 212 * Returns supported axes of font 213 * 214 * @param buffer A buffer of the entire font file. 215 * @param index A font index in case of font collection. Must be 0 otherwise. 216 * @return set of supported axes tag. Returns empty set on error. 217 */ getSupportedAxes(@onNull ByteBuffer buffer, int index)218 public static Set<Integer> getSupportedAxes(@NonNull ByteBuffer buffer, int index) { 219 ByteOrder originalOrder = buffer.order(); 220 buffer.order(ByteOrder.BIG_ENDIAN); 221 try { 222 int fontFileOffset = 0; 223 int magicNumber = buffer.getInt(0); 224 if (magicNumber == TTC_TAG) { 225 // TTC file. 226 if (index >= buffer.getInt(8 /* offset to number of fonts in TTC */)) { 227 return Collections.EMPTY_SET; 228 } 229 fontFileOffset = buffer.getInt( 230 12 /* offset to array of offsets of font files */ + 4 * index); 231 } 232 int sfntVersion = buffer.getInt(fontFileOffset); 233 234 if (sfntVersion != SFNT_VERSION_1 && sfntVersion != SFNT_VERSION_OTTO) { 235 return Collections.EMPTY_SET; 236 } 237 238 int numTables = buffer.getShort(fontFileOffset + 4 /* offset to number of tables */); 239 int fvarTableOffset = -1; 240 for (int i = 0; i < numTables; ++i) { 241 int tableOffset = fontFileOffset + 12 /* size of offset table */ 242 + i * 16 /* size of table record */; 243 if (buffer.getInt(tableOffset) == FVAR_TABLE_TAG) { 244 fvarTableOffset = buffer.getInt(tableOffset + 8 /* offset to the table */); 245 break; 246 } 247 } 248 249 if (fvarTableOffset == -1) { 250 // Couldn't find OS/2 table. use regular style 251 return Collections.EMPTY_SET; 252 } 253 254 if (buffer.getShort(fvarTableOffset) != 1 255 || buffer.getShort(fvarTableOffset + 2) != 0) { 256 return Collections.EMPTY_SET; 257 } 258 259 int axesArrayOffset = getUInt16(buffer, fvarTableOffset + 4); 260 int axisCount = getUInt16(buffer, fvarTableOffset + 8); 261 int axisSize = getUInt16(buffer, fvarTableOffset + 10); 262 263 ArraySet<Integer> axes = new ArraySet<>(); 264 for (int i = 0; i < axisCount; ++i) { 265 axes.add(buffer.getInt(fvarTableOffset + axesArrayOffset + axisSize * i)); 266 } 267 268 return axes; 269 } finally { 270 buffer.order(originalOrder); 271 } 272 } 273 274 @FastNative nGetFontRevision(@onNull ByteBuffer buffer, @IntRange(from = 0) int index)275 private static native long nGetFontRevision(@NonNull ByteBuffer buffer, 276 @IntRange(from = 0) int index); 277 278 @FastNative nGetFontPostScriptName(@onNull ByteBuffer buffer, @IntRange(from = 0) int index)279 private static native String nGetFontPostScriptName(@NonNull ByteBuffer buffer, 280 @IntRange(from = 0) int index); 281 282 @FastNative nIsPostScriptType1Font(@onNull ByteBuffer buffer, @IntRange(from = 0) int index)283 private static native int nIsPostScriptType1Font(@NonNull ByteBuffer buffer, 284 @IntRange(from = 0) int index); 285 } 286