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