• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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