|
1 /* |
|
2 * Copyright 2011 The Android Open Source Project |
|
3 * |
|
4 * Use of this source code is governed by a BSD-style license that can be |
|
5 * found in the LICENSE file. |
|
6 */ |
|
7 |
|
8 #include "SkFontConfigParser_android.h" |
|
9 #include "SkTDArray.h" |
|
10 #include "SkTypeface.h" |
|
11 |
|
12 #include <expat.h> |
|
13 #include <stdio.h> |
|
14 #include <sys/system_properties.h> |
|
15 |
|
16 #define SYSTEM_FONTS_FILE "/system/etc/system_fonts.xml" |
|
17 #define FALLBACK_FONTS_FILE "/system/etc/fallback_fonts.xml" |
|
18 #define VENDOR_FONTS_FILE "/vendor/etc/fallback_fonts.xml" |
|
19 |
|
20 // These defines are used to determine the kind of tag that we're currently |
|
21 // populating with data. We only care about the sibling tags nameset and fileset |
|
22 // for now. |
|
23 #define NO_TAG 0 |
|
24 #define NAMESET_TAG 1 |
|
25 #define FILESET_TAG 2 |
|
26 |
|
27 /** |
|
28 * The FamilyData structure is passed around by the parser so that each handler |
|
29 * can read these variables that are relevant to the current parsing. |
|
30 */ |
|
31 struct FamilyData { |
|
32 FamilyData(XML_Parser *parserRef, SkTDArray<FontFamily*> &familiesRef) : |
|
33 parser(parserRef), |
|
34 families(familiesRef), |
|
35 currentFamily(NULL), |
|
36 currentFontInfo(NULL), |
|
37 currentTag(NO_TAG) {}; |
|
38 |
|
39 XML_Parser *parser; // The expat parser doing the work |
|
40 SkTDArray<FontFamily*> &families; // The array that each family is put into as it is parsed |
|
41 FontFamily *currentFamily; // The current family being created |
|
42 FontFileInfo *currentFontInfo; // The current fontInfo being created |
|
43 int currentTag; // A flag to indicate whether we're in nameset/fileset tags |
|
44 }; |
|
45 |
|
46 /** |
|
47 * Handler for arbitrary text. This is used to parse the text inside each name |
|
48 * or file tag. The resulting strings are put into the fNames or FontFileInfo arrays. |
|
49 */ |
|
50 static void textHandler(void *data, const char *s, int len) { |
|
51 FamilyData *familyData = (FamilyData*) data; |
|
52 // Make sure we're in the right state to store this name information |
|
53 if (familyData->currentFamily && |
|
54 (familyData->currentTag == NAMESET_TAG || familyData->currentTag == FILESET_TAG)) { |
|
55 // Malloc new buffer to store the string |
|
56 char *buff; |
|
57 buff = (char*) malloc((len + 1) * sizeof(char)); |
|
58 strncpy(buff, s, len); |
|
59 buff[len] = '\0'; |
|
60 switch (familyData->currentTag) { |
|
61 case NAMESET_TAG: |
|
62 *(familyData->currentFamily->fNames.append()) = buff; |
|
63 break; |
|
64 case FILESET_TAG: |
|
65 if (familyData->currentFontInfo) { |
|
66 familyData->currentFontInfo->fFileName = buff; |
|
67 } |
|
68 break; |
|
69 default: |
|
70 // Noop - don't care about any text that's not in the Fonts or Names list |
|
71 break; |
|
72 } |
|
73 } |
|
74 } |
|
75 |
|
76 /** |
|
77 * Handler for font files. This processes the attributes for language and |
|
78 * variants then lets textHandler handle the actual file name |
|
79 */ |
|
80 static void fontFileElementHandler(FamilyData *familyData, const char **attributes) { |
|
81 FontFileInfo* newFileInfo = new FontFileInfo(); |
|
82 if (attributes) { |
|
83 int currentAttributeIndex = 0; |
|
84 while (attributes[currentAttributeIndex]) { |
|
85 const char* attributeName = attributes[currentAttributeIndex]; |
|
86 const char* attributeValue = attributes[currentAttributeIndex+1]; |
|
87 int nameLength = strlen(attributeName); |
|
88 int valueLength = strlen(attributeValue); |
|
89 if (strncmp(attributeName, "variant", nameLength) == 0) { |
|
90 if (strncmp(attributeValue, "elegant", valueLength) == 0) { |
|
91 newFileInfo->fPaintOptions.setFontVariant(SkPaintOptionsAndroid::kElegant_Variant); |
|
92 } else if (strncmp(attributeValue, "compact", valueLength) == 0) { |
|
93 newFileInfo->fPaintOptions.setFontVariant(SkPaintOptionsAndroid::kCompact_Variant); |
|
94 } |
|
95 } else if (strncmp(attributeName, "lang", nameLength) == 0) { |
|
96 newFileInfo->fPaintOptions.setLanguage(attributeValue); |
|
97 } |
|
98 //each element is a pair of attributeName/attributeValue string pairs |
|
99 currentAttributeIndex += 2; |
|
100 } |
|
101 } |
|
102 *(familyData->currentFamily->fFontFiles.append()) = newFileInfo; |
|
103 familyData->currentFontInfo = newFileInfo; |
|
104 XML_SetCharacterDataHandler(*familyData->parser, textHandler); |
|
105 } |
|
106 |
|
107 /** |
|
108 * Handler for the start of a tag. The only tags we expect are family, nameset, |
|
109 * fileset, name, and file. |
|
110 */ |
|
111 static void startElementHandler(void *data, const char *tag, const char **atts) { |
|
112 FamilyData *familyData = (FamilyData*) data; |
|
113 int len = strlen(tag); |
|
114 if (strncmp(tag, "family", len)== 0) { |
|
115 familyData->currentFamily = new FontFamily(); |
|
116 familyData->currentFamily->order = -1; |
|
117 // The Family tag has an optional "order" attribute with an integer value >= 0 |
|
118 // If this attribute does not exist, the default value is -1 |
|
119 for (int i = 0; atts[i] != NULL; i += 2) { |
|
120 const char* valueString = atts[i+1]; |
|
121 int value; |
|
122 int len = sscanf(valueString, "%d", &value); |
|
123 if (len > 0) { |
|
124 familyData->currentFamily->order = value; |
|
125 } |
|
126 } |
|
127 } else if (len == 7 && strncmp(tag, "nameset", len) == 0) { |
|
128 familyData->currentTag = NAMESET_TAG; |
|
129 } else if (len == 7 && strncmp(tag, "fileset", len) == 0) { |
|
130 familyData->currentTag = FILESET_TAG; |
|
131 } else if (strncmp(tag, "name", len) == 0 && familyData->currentTag == NAMESET_TAG) { |
|
132 // If it's a Name, parse the text inside |
|
133 XML_SetCharacterDataHandler(*familyData->parser, textHandler); |
|
134 } else if (strncmp(tag, "file", len) == 0 && familyData->currentTag == FILESET_TAG) { |
|
135 // If it's a file, parse the attributes, then parse the text inside |
|
136 fontFileElementHandler(familyData, atts); |
|
137 } |
|
138 } |
|
139 |
|
140 /** |
|
141 * Handler for the end of tags. We only care about family, nameset, fileset, |
|
142 * name, and file. |
|
143 */ |
|
144 static void endElementHandler(void *data, const char *tag) { |
|
145 FamilyData *familyData = (FamilyData*) data; |
|
146 int len = strlen(tag); |
|
147 if (strncmp(tag, "family", len)== 0) { |
|
148 // Done parsing a Family - store the created currentFamily in the families array |
|
149 *familyData->families.append() = familyData->currentFamily; |
|
150 familyData->currentFamily = NULL; |
|
151 } else if (len == 7 && strncmp(tag, "nameset", len) == 0) { |
|
152 familyData->currentTag = NO_TAG; |
|
153 } else if (len == 7 && strncmp(tag, "fileset", len) == 0) { |
|
154 familyData->currentTag = NO_TAG; |
|
155 } else if ((strncmp(tag, "name", len) == 0 && familyData->currentTag == NAMESET_TAG) || |
|
156 (strncmp(tag, "file", len) == 0 && familyData->currentTag == FILESET_TAG)) { |
|
157 // Disable the arbitrary text handler installed to load Name data |
|
158 XML_SetCharacterDataHandler(*familyData->parser, NULL); |
|
159 } |
|
160 } |
|
161 |
|
162 /** |
|
163 * This function parses the given filename and stores the results in the given |
|
164 * families array. |
|
165 */ |
|
166 static void parseConfigFile(const char *filename, SkTDArray<FontFamily*> &families) { |
|
167 |
|
168 FILE* file = NULL; |
|
169 |
|
170 #if !defined(SK_BUILD_FOR_ANDROID_FRAMEWORK) |
|
171 // if we are using a version of Android prior to Android 4.2 (JellyBean MR1 |
|
172 // at API Level 17) then we need to look for files with a different suffix. |
|
173 char sdkVersion[PROP_VALUE_MAX]; |
|
174 __system_property_get("ro.build.version.sdk", sdkVersion); |
|
175 const int sdkVersionInt = atoi(sdkVersion); |
|
176 |
|
177 if (0 != *sdkVersion && sdkVersionInt < 17) { |
|
178 SkString basename; |
|
179 SkString updatedFilename; |
|
180 SkString locale = SkFontConfigParser::GetLocale(); |
|
181 |
|
182 basename.set(filename); |
|
183 // Remove the .xml suffix. We'll add it back in a moment. |
|
184 if (basename.endsWith(".xml")) { |
|
185 basename.resize(basename.size()-4); |
|
186 } |
|
187 // Try first with language and region |
|
188 updatedFilename.printf("%s-%s.xml", basename.c_str(), locale.c_str()); |
|
189 file = fopen(updatedFilename.c_str(), "r"); |
|
190 if (!file) { |
|
191 // If not found, try next with just language |
|
192 updatedFilename.printf("%s-%.2s.xml", basename.c_str(), locale.c_str()); |
|
193 file = fopen(updatedFilename.c_str(), "r"); |
|
194 } |
|
195 } |
|
196 #endif |
|
197 |
|
198 if (NULL == file) { |
|
199 file = fopen(filename, "r"); |
|
200 } |
|
201 |
|
202 // Some of the files we attempt to parse (in particular, /vendor/etc/fallback_fonts.xml) |
|
203 // are optional - failure here is okay because one of these optional files may not exist. |
|
204 if (NULL == file) { |
|
205 return; |
|
206 } |
|
207 |
|
208 XML_Parser parser = XML_ParserCreate(NULL); |
|
209 FamilyData *familyData = new FamilyData(&parser, families); |
|
210 XML_SetUserData(parser, familyData); |
|
211 XML_SetElementHandler(parser, startElementHandler, endElementHandler); |
|
212 |
|
213 char buffer[512]; |
|
214 bool done = false; |
|
215 while (!done) { |
|
216 fgets(buffer, sizeof(buffer), file); |
|
217 int len = strlen(buffer); |
|
218 if (feof(file) != 0) { |
|
219 done = true; |
|
220 } |
|
221 XML_Parse(parser, buffer, len, done); |
|
222 } |
|
223 XML_ParserFree(parser); |
|
224 fclose(file); |
|
225 } |
|
226 |
|
227 static void getSystemFontFamilies(SkTDArray<FontFamily*> &fontFamilies) { |
|
228 parseConfigFile(SYSTEM_FONTS_FILE, fontFamilies); |
|
229 } |
|
230 |
|
231 static void getFallbackFontFamilies(SkTDArray<FontFamily*> &fallbackFonts) { |
|
232 SkTDArray<FontFamily*> vendorFonts; |
|
233 parseConfigFile(FALLBACK_FONTS_FILE, fallbackFonts); |
|
234 parseConfigFile(VENDOR_FONTS_FILE, vendorFonts); |
|
235 |
|
236 // This loop inserts the vendor fallback fonts in the correct order in the |
|
237 // overall fallbacks list. |
|
238 int currentOrder = -1; |
|
239 for (int i = 0; i < vendorFonts.count(); ++i) { |
|
240 FontFamily* family = vendorFonts[i]; |
|
241 int order = family->order; |
|
242 if (order < 0) { |
|
243 if (currentOrder < 0) { |
|
244 // Default case - just add it to the end of the fallback list |
|
245 *fallbackFonts.append() = family; |
|
246 } else { |
|
247 // no order specified on this font, but we're incrementing the order |
|
248 // based on an earlier order insertion request |
|
249 *fallbackFonts.insert(currentOrder++) = family; |
|
250 } |
|
251 } else { |
|
252 // Add the font into the fallback list in the specified order. Set |
|
253 // currentOrder for correct placement of other fonts in the vendor list. |
|
254 *fallbackFonts.insert(order) = family; |
|
255 currentOrder = order + 1; |
|
256 } |
|
257 } |
|
258 } |
|
259 |
|
260 /** |
|
261 * Loads data on font families from various expected configuration files. The |
|
262 * resulting data is returned in the given fontFamilies array. |
|
263 */ |
|
264 void SkFontConfigParser::GetFontFamilies(SkTDArray<FontFamily*> &fontFamilies) { |
|
265 |
|
266 getSystemFontFamilies(fontFamilies); |
|
267 |
|
268 // Append all the fallback fonts to system fonts |
|
269 SkTDArray<FontFamily*> fallbackFonts; |
|
270 getFallbackFontFamilies(fallbackFonts); |
|
271 for (int i = 0; i < fallbackFonts.count(); ++i) { |
|
272 fallbackFonts[i]->fIsFallbackFont = true; |
|
273 *fontFamilies.append() = fallbackFonts[i]; |
|
274 } |
|
275 } |
|
276 |
|
277 void SkFontConfigParser::GetTestFontFamilies(SkTDArray<FontFamily*> &fontFamilies, |
|
278 const char* testMainConfigFile, |
|
279 const char* testFallbackConfigFile) { |
|
280 parseConfigFile(testMainConfigFile, fontFamilies); |
|
281 |
|
282 SkTDArray<FontFamily*> fallbackFonts; |
|
283 parseConfigFile(testFallbackConfigFile, fallbackFonts); |
|
284 |
|
285 // Append all fallback fonts to system fonts |
|
286 for (int i = 0; i < fallbackFonts.count(); ++i) { |
|
287 fallbackFonts[i]->fIsFallbackFont = true; |
|
288 *fontFamilies.append() = fallbackFonts[i]; |
|
289 } |
|
290 } |
|
291 |
|
292 /** |
|
293 * Read the persistent locale. |
|
294 */ |
|
295 SkString SkFontConfigParser::GetLocale() |
|
296 { |
|
297 char propLang[PROP_VALUE_MAX], propRegn[PROP_VALUE_MAX]; |
|
298 __system_property_get("persist.sys.language", propLang); |
|
299 __system_property_get("persist.sys.country", propRegn); |
|
300 |
|
301 if (*propLang == 0 && *propRegn == 0) { |
|
302 /* Set to ro properties, default is en_US */ |
|
303 __system_property_get("ro.product.locale.language", propLang); |
|
304 __system_property_get("ro.product.locale.region", propRegn); |
|
305 if (*propLang == 0 && *propRegn == 0) { |
|
306 strcpy(propLang, "en"); |
|
307 strcpy(propRegn, "US"); |
|
308 } |
|
309 } |
|
310 |
|
311 SkString locale(6); |
|
312 char* localeCStr = locale.writable_str(); |
|
313 |
|
314 strncpy(localeCStr, propLang, 2); |
|
315 localeCStr[2] = '-'; |
|
316 strncpy(&localeCStr[3], propRegn, 2); |
|
317 localeCStr[5] = '\0'; |
|
318 |
|
319 return locale; |
|
320 } |