blob: 6d03414b089d67e47b6c2f42702dfeed1610ca66 [file] [log] [blame]
reed@google.comdd335ae2012-12-13 19:24:05 +00001/*
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 */
djsollen@google.com58629292011-11-03 13:08:29 +00007
8#include "FontHostConfiguration_android.h"
djsollen@google.come63793a2012-03-21 15:39:03 +00009#include "SkString.h"
djsollen@google.com58629292011-11-03 13:08:29 +000010#include "SkTDArray.h"
djsollen@google.come63793a2012-03-21 15:39:03 +000011#include <expat.h>
djsollen@google.comfc9054d2012-05-10 16:13:38 +000012#include <sys/system_properties.h>
djsollen@google.com58629292011-11-03 13:08:29 +000013
14#define SYSTEM_FONTS_FILE "/system/etc/system_fonts.xml"
15#define FALLBACK_FONTS_FILE "/system/etc/fallback_fonts.xml"
16#define VENDOR_FONTS_FILE "/vendor/etc/fallback_fonts.xml"
17
18
19// These defines are used to determine the kind of tag that we're currently
20// populating with data. We only care about the sibling tags nameset and fileset
21// for now.
22#define NO_TAG 0
23#define NAMESET_TAG 1
24#define FILESET_TAG 2
25
26/**
27 * The FamilyData structure is passed around by the parser so that each handler
28 * can read these variables that are relevant to the current parsing.
29 */
30struct FamilyData {
djsollen@google.combd084732013-01-29 15:39:35 +000031 FamilyData(XML_Parser *parserRef, SkTDArray<FontFamily*> &familiesRef, const AndroidLocale &localeRef) :
32 parser(parserRef), families(familiesRef), currentTag(NO_TAG),
33 locale(localeRef), currentFamilyLangMatch(false), familyLangMatchCount(0) {}
djsollen@google.com58629292011-11-03 13:08:29 +000034
35 XML_Parser *parser; // The expat parser doing the work
36 SkTDArray<FontFamily*> &families; // The array that each family is put into as it is parsed
37 FontFamily *currentFamily; // The current family being created
38 int currentTag; // A flag to indicate whether we're in nameset/fileset tags
djsollen@google.combd084732013-01-29 15:39:35 +000039 const AndroidLocale &locale; // The locale to which we compare the "lang" attribute of File.
40 bool currentFamilyLangMatch; // If currentFamily's File has a "lang" attribute and matches locale.
41 int familyLangMatchCount; // Number of families containing File which has a "lang" attribute and matches locale.
djsollen@google.com58629292011-11-03 13:08:29 +000042};
43
44/**
45 * Handler for arbitrary text. This is used to parse the text inside each name
46 * or file tag. The resulting strings are put into the fNames or fFileNames arrays.
47 */
48void textHandler(void *data, const char *s, int len) {
49 FamilyData *familyData = (FamilyData*) data;
50 // Make sure we're in the right state to store this name information
51 if (familyData->currentFamily &&
52 (familyData->currentTag == NAMESET_TAG || familyData->currentTag == FILESET_TAG)) {
53 // Malloc new buffer to store the string
54 char *buff;
55 buff = (char*) malloc((len + 1) * sizeof(char));
56 strncpy(buff, s, len);
57 buff[len] = '\0';
58 switch (familyData->currentTag) {
59 case NAMESET_TAG:
60 *(familyData->currentFamily->fNames.append()) = buff;
61 break;
62 case FILESET_TAG:
63 *(familyData->currentFamily->fFileNames.append()) = buff;
64 break;
65 default:
66 // Noop - don't care about any text that's not in the Fonts or Names list
67 break;
68 }
69 }
70}
71
72/**
73 * Handler for the start of a tag. The only tags we expect are family, nameset,
74 * fileset, name, and file.
75 */
76void startElementHandler(void *data, const char *tag, const char **atts) {
77 FamilyData *familyData = (FamilyData*) data;
78 int len = strlen(tag);
79 if (strncmp(tag, "family", len)== 0) {
80 familyData->currentFamily = new FontFamily();
81 familyData->currentFamily->order = -1;
82 // The Family tag has an optional "order" attribute with an integer value >= 0
83 // If this attribute does not exist, the default value is -1
84 for (int i = 0; atts[i] != NULL; i += 2) {
85 const char* attribute = atts[i];
86 const char* valueString = atts[i+1];
87 int value;
88 int len = sscanf(valueString, "%d", &value);
89 if (len > 0) {
90 familyData->currentFamily->order = value;
91 }
92 }
93 } else if (len == 7 && strncmp(tag, "nameset", len)== 0) {
94 familyData->currentTag = NAMESET_TAG;
95 } else if (len == 7 && strncmp(tag, "fileset", len) == 0) {
96 familyData->currentTag = FILESET_TAG;
djsollen@google.combd084732013-01-29 15:39:35 +000097 } else if (strncmp(tag, "name", len) == 0 && familyData->currentTag == NAMESET_TAG) {
djsollen@google.com58629292011-11-03 13:08:29 +000098 XML_SetCharacterDataHandler(*familyData->parser, textHandler);
djsollen@google.combd084732013-01-29 15:39:35 +000099 } else if (strncmp(tag, "file", len) == 0 && familyData->currentTag == FILESET_TAG) {
100 // From JB MR1, the File tag has a "lang" attribute to specify a language specific font file
101 // and the family entry has higher priority than the others without "lang" attribute.
102 bool includeTheEntry = true;
103 for (int i = 0; atts[i] != NULL; i += 2) {
104 const char* attribute = atts[i];
105 const char* value = atts[i+1];
106 if (strncmp(attribute, "lang", 4) == 0) {
107 if (strcmp(value, familyData->locale.language) == 0) {
108 // Found matching "lang" attribute. The current Family will have higher priority in the family list.
109 familyData->currentFamilyLangMatch = true;
110 } else {
111 // Don't include the entry if "lang" is specified but not matching.
112 includeTheEntry = false;
113 }
114 }
115 }
116 if (includeTheEntry) {
117 XML_SetCharacterDataHandler(*familyData->parser, textHandler);
118 }
djsollen@google.com58629292011-11-03 13:08:29 +0000119 }
120}
121
122/**
123 * Handler for the end of tags. We only care about family, nameset, fileset,
124 * name, and file.
125 */
126void endElementHandler(void *data, const char *tag) {
127 FamilyData *familyData = (FamilyData*) data;
128 int len = strlen(tag);
129 if (strncmp(tag, "family", len)== 0) {
130 // Done parsing a Family - store the created currentFamily in the families array
djsollen@google.combd084732013-01-29 15:39:35 +0000131 if (familyData->currentFamilyLangMatch) {
132 *familyData->families.insert(familyData->familyLangMatchCount++) = familyData->currentFamily;
133 familyData->currentFamilyLangMatch = false;
134 } else {
135 *familyData->families.append() = familyData->currentFamily;
136 }
djsollen@google.com58629292011-11-03 13:08:29 +0000137 familyData->currentFamily = NULL;
138 } else if (len == 7 && strncmp(tag, "nameset", len)== 0) {
139 familyData->currentTag = NO_TAG;
140 } else if (len == 7 && strncmp(tag, "fileset", len)== 0) {
141 familyData->currentTag = NO_TAG;
142 } else if ((strncmp(tag, "name", len) == 0 && familyData->currentTag == NAMESET_TAG) ||
143 (strncmp(tag, "file", len) == 0 && familyData->currentTag == FILESET_TAG)) {
144 // Disable the arbitrary text handler installed to load Name data
145 XML_SetCharacterDataHandler(*familyData->parser, NULL);
146 }
147}
148
djsollen@google.come63793a2012-03-21 15:39:03 +0000149/**
150 * Read the persistent locale.
151 */
djsollen@google.comfc9054d2012-05-10 16:13:38 +0000152void getLocale(AndroidLocale &locale)
djsollen@google.come63793a2012-03-21 15:39:03 +0000153{
djsollen@google.comfc9054d2012-05-10 16:13:38 +0000154 char propLang[PROP_VALUE_MAX], propRegn[PROP_VALUE_MAX];
155 __system_property_get("persist.sys.language", propLang);
156 __system_property_get("persist.sys.country", propRegn);
djsollen@google.come63793a2012-03-21 15:39:03 +0000157
djsollen@google.come63793a2012-03-21 15:39:03 +0000158 if (*propLang == 0 && *propRegn == 0) {
159 /* Set to ro properties, default is en_US */
djsollen@google.comfc9054d2012-05-10 16:13:38 +0000160 __system_property_get("ro.product.locale.language", propLang);
161 __system_property_get("ro.product.locale.region", propRegn);
162 if (*propLang == 0 && *propRegn == 0) {
163 strcpy(propLang, "en");
164 strcpy(propRegn, "US");
165 }
djsollen@google.come63793a2012-03-21 15:39:03 +0000166 }
djsollen@google.comfc9054d2012-05-10 16:13:38 +0000167 strncpy(locale.language, propLang, 2);
168 locale.language[2] = '\0';
169 strncpy(locale.region, propRegn, 2);
170 locale.region[2] = '\0';
djsollen@google.come63793a2012-03-21 15:39:03 +0000171}
djsollen@google.come63793a2012-03-21 15:39:03 +0000172
173/**
174 * Use the current system locale (language and region) to open the best matching
175 * customization. For example, when the language is Japanese, the sequence might be:
176 * /system/etc/fallback_fonts-ja-JP.xml
177 * /system/etc/fallback_fonts-ja.xml
178 * /system/etc/fallback_fonts.xml
179 */
djsollen@google.combd084732013-01-29 15:39:35 +0000180FILE* openLocalizedFile(const char* origname, const AndroidLocale& locale) {
djsollen@google.come63793a2012-03-21 15:39:03 +0000181 FILE* file = 0;
djsollen@google.come63793a2012-03-21 15:39:03 +0000182 SkString basename;
183 SkString filename;
djsollen@google.come63793a2012-03-21 15:39:03 +0000184
185 basename.set(origname);
186 // Remove the .xml suffix. We'll add it back in a moment.
187 if (basename.endsWith(".xml")) {
188 basename.resize(basename.size()-4);
189 }
djsollen@google.come63793a2012-03-21 15:39:03 +0000190 // Try first with language and region
djsollen@google.comfc9054d2012-05-10 16:13:38 +0000191 filename.printf("%s-%s-%s.xml", basename.c_str(), locale.language, locale.region);
djsollen@google.come63793a2012-03-21 15:39:03 +0000192 file = fopen(filename.c_str(), "r");
193 if (!file) {
194 // If not found, try next with just language
djsollen@google.comfc9054d2012-05-10 16:13:38 +0000195 filename.printf("%s-%s.xml", basename.c_str(), locale.language);
djsollen@google.come63793a2012-03-21 15:39:03 +0000196 file = fopen(filename.c_str(), "r");
djsollen@google.come63793a2012-03-21 15:39:03 +0000197
djsollen@google.comfc9054d2012-05-10 16:13:38 +0000198 if (!file) {
199 // If still not found, try just the original name
200 file = fopen(origname, "r");
201 }
djsollen@google.come63793a2012-03-21 15:39:03 +0000202 }
203 return file;
204}
205
djsollen@google.com58629292011-11-03 13:08:29 +0000206/**
207 * This function parses the given filename and stores the results in the given
208 * families array.
209 */
210void parseConfigFile(const char *filename, SkTDArray<FontFamily*> &families) {
djsollen@google.combd084732013-01-29 15:39:35 +0000211 AndroidLocale locale;
212 getLocale(locale);
djsollen@google.com58629292011-11-03 13:08:29 +0000213 XML_Parser parser = XML_ParserCreate(NULL);
djsollen@google.combd084732013-01-29 15:39:35 +0000214 FamilyData familyData(&parser, families, locale);
215 XML_SetUserData(parser, &familyData);
djsollen@google.com58629292011-11-03 13:08:29 +0000216 XML_SetElementHandler(parser, startElementHandler, endElementHandler);
djsollen@google.combd084732013-01-29 15:39:35 +0000217 FILE *file = openLocalizedFile(filename, locale);
djsollen@google.com58629292011-11-03 13:08:29 +0000218 // Some of the files we attempt to parse (in particular, /vendor/etc/fallback_fonts.xml)
219 // are optional - failure here is okay because one of these optional files may not exist.
220 if (file == NULL) {
221 return;
222 }
223 char buffer[512];
224 bool done = false;
225 while (!done) {
226 fgets(buffer, sizeof(buffer), file);
227 int len = strlen(buffer);
228 if (feof(file) != 0) {
229 done = true;
230 }
231 XML_Parse(parser, buffer, len, done);
232 }
djsollen@google.combd084732013-01-29 15:39:35 +0000233 fclose(file);
234 XML_ParserFree(parser);
djsollen@google.com58629292011-11-03 13:08:29 +0000235}
236
djsollen@google.come63793a2012-03-21 15:39:03 +0000237void getSystemFontFamilies(SkTDArray<FontFamily*> &fontFamilies) {
djsollen@google.com58629292011-11-03 13:08:29 +0000238 parseConfigFile(SYSTEM_FONTS_FILE, fontFamilies);
djsollen@google.come63793a2012-03-21 15:39:03 +0000239}
240
241void getFallbackFontFamilies(SkTDArray<FontFamily*> &fallbackFonts) {
242 SkTDArray<FontFamily*> vendorFonts;
djsollen@google.com58629292011-11-03 13:08:29 +0000243 parseConfigFile(FALLBACK_FONTS_FILE, fallbackFonts);
244 parseConfigFile(VENDOR_FONTS_FILE, vendorFonts);
245
246 // This loop inserts the vendor fallback fonts in the correct order in the
247 // overall fallbacks list.
248 int currentOrder = -1;
249 for (int i = 0; i < vendorFonts.count(); ++i) {
250 FontFamily* family = vendorFonts[i];
251 int order = family->order;
252 if (order < 0) {
253 if (currentOrder < 0) {
254 // Default case - just add it to the end of the fallback list
255 *fallbackFonts.append() = family;
256 } else {
257 // no order specified on this font, but we're incrementing the order
258 // based on an earlier order insertion request
259 *fallbackFonts.insert(currentOrder++) = family;
260 }
261 } else {
262 // Add the font into the fallback list in the specified order. Set
263 // currentOrder for correct placement of other fonts in the vendor list.
264 *fallbackFonts.insert(order) = family;
265 currentOrder = order + 1;
266 }
267 }
djsollen@google.come63793a2012-03-21 15:39:03 +0000268}
269
270/**
271 * Loads data on font families from various expected configuration files. The
272 * resulting data is returned in the given fontFamilies array.
273 */
274void getFontFamilies(SkTDArray<FontFamily*> &fontFamilies) {
275 SkTDArray<FontFamily*> fallbackFonts;
276
277 getSystemFontFamilies(fontFamilies);
278 getFallbackFontFamilies(fallbackFonts);
279
djsollen@google.com58629292011-11-03 13:08:29 +0000280 // Append all fallback fonts to system fonts
281 for (int i = 0; i < fallbackFonts.count(); ++i) {
282 *fontFamilies.append() = fallbackFonts[i];
283 }
284}
djsollen@google.com5df2a992012-06-25 13:58:22 +0000285
286void getTestFontFamilies(SkTDArray<FontFamily*> &fontFamilies,
287 const char* testMainConfigFile,
288 const char* testFallbackConfigFile) {
289 parseConfigFile(testMainConfigFile, fontFamilies);
290
291 SkTDArray<FontFamily*> fallbackFonts;
292 parseConfigFile(testFallbackConfigFile, fallbackFonts);
293
294 // Append all fallback fonts to system fonts
295 for (int i = 0; i < fallbackFonts.count(); ++i) {
296 *fontFamilies.append() = fallbackFonts[i];
297 }
298}