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