blob: aed7001bfe3f5bc88e85a55e7797cf9c3b81f296 [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 {
31 FamilyData(XML_Parser *parserRef, SkTDArray<FontFamily*> &familiesRef) :
32 parser(parserRef), families(familiesRef), currentTag(NO_TAG) {};
33
34 XML_Parser *parser; // The expat parser doing the work
35 SkTDArray<FontFamily*> &families; // The array that each family is put into as it is parsed
36 FontFamily *currentFamily; // The current family being created
37 int currentTag; // A flag to indicate whether we're in nameset/fileset tags
38};
39
40/**
41 * Handler for arbitrary text. This is used to parse the text inside each name
42 * or file tag. The resulting strings are put into the fNames or fFileNames arrays.
43 */
44void textHandler(void *data, const char *s, int len) {
45 FamilyData *familyData = (FamilyData*) data;
46 // Make sure we're in the right state to store this name information
47 if (familyData->currentFamily &&
48 (familyData->currentTag == NAMESET_TAG || familyData->currentTag == FILESET_TAG)) {
49 // Malloc new buffer to store the string
50 char *buff;
51 buff = (char*) malloc((len + 1) * sizeof(char));
52 strncpy(buff, s, len);
53 buff[len] = '\0';
54 switch (familyData->currentTag) {
55 case NAMESET_TAG:
56 *(familyData->currentFamily->fNames.append()) = buff;
57 break;
58 case FILESET_TAG:
59 *(familyData->currentFamily->fFileNames.append()) = buff;
60 break;
61 default:
62 // Noop - don't care about any text that's not in the Fonts or Names list
63 break;
64 }
65 }
66}
67
68/**
69 * Handler for the start of a tag. The only tags we expect are family, nameset,
70 * fileset, name, and file.
71 */
72void startElementHandler(void *data, const char *tag, const char **atts) {
73 FamilyData *familyData = (FamilyData*) data;
74 int len = strlen(tag);
75 if (strncmp(tag, "family", len)== 0) {
76 familyData->currentFamily = new FontFamily();
77 familyData->currentFamily->order = -1;
78 // The Family tag has an optional "order" attribute with an integer value >= 0
79 // If this attribute does not exist, the default value is -1
80 for (int i = 0; atts[i] != NULL; i += 2) {
81 const char* attribute = atts[i];
82 const char* valueString = atts[i+1];
83 int value;
84 int len = sscanf(valueString, "%d", &value);
85 if (len > 0) {
86 familyData->currentFamily->order = value;
87 }
88 }
89 } else if (len == 7 && strncmp(tag, "nameset", len)== 0) {
90 familyData->currentTag = NAMESET_TAG;
91 } else if (len == 7 && strncmp(tag, "fileset", len) == 0) {
92 familyData->currentTag = FILESET_TAG;
93 } else if ((strncmp(tag, "name", len) == 0 && familyData->currentTag == NAMESET_TAG) ||
94 (strncmp(tag, "file", len) == 0 && familyData->currentTag == FILESET_TAG)) {
95 // If it's a Name, parse the text inside
96 XML_SetCharacterDataHandler(*familyData->parser, textHandler);
97 }
98}
99
100/**
101 * Handler for the end of tags. We only care about family, nameset, fileset,
102 * name, and file.
103 */
104void endElementHandler(void *data, const char *tag) {
105 FamilyData *familyData = (FamilyData*) data;
106 int len = strlen(tag);
107 if (strncmp(tag, "family", len)== 0) {
108 // Done parsing a Family - store the created currentFamily in the families array
109 *familyData->families.append() = familyData->currentFamily;
110 familyData->currentFamily = NULL;
111 } else if (len == 7 && strncmp(tag, "nameset", len)== 0) {
112 familyData->currentTag = NO_TAG;
113 } else if (len == 7 && strncmp(tag, "fileset", len)== 0) {
114 familyData->currentTag = NO_TAG;
115 } else if ((strncmp(tag, "name", len) == 0 && familyData->currentTag == NAMESET_TAG) ||
116 (strncmp(tag, "file", len) == 0 && familyData->currentTag == FILESET_TAG)) {
117 // Disable the arbitrary text handler installed to load Name data
118 XML_SetCharacterDataHandler(*familyData->parser, NULL);
119 }
120}
121
djsollen@google.come63793a2012-03-21 15:39:03 +0000122/**
123 * Read the persistent locale.
124 */
djsollen@google.comfc9054d2012-05-10 16:13:38 +0000125void getLocale(AndroidLocale &locale)
djsollen@google.come63793a2012-03-21 15:39:03 +0000126{
djsollen@google.comfc9054d2012-05-10 16:13:38 +0000127 char propLang[PROP_VALUE_MAX], propRegn[PROP_VALUE_MAX];
128 __system_property_get("persist.sys.language", propLang);
129 __system_property_get("persist.sys.country", propRegn);
djsollen@google.come63793a2012-03-21 15:39:03 +0000130
djsollen@google.come63793a2012-03-21 15:39:03 +0000131 if (*propLang == 0 && *propRegn == 0) {
132 /* Set to ro properties, default is en_US */
djsollen@google.comfc9054d2012-05-10 16:13:38 +0000133 __system_property_get("ro.product.locale.language", propLang);
134 __system_property_get("ro.product.locale.region", propRegn);
135 if (*propLang == 0 && *propRegn == 0) {
136 strcpy(propLang, "en");
137 strcpy(propRegn, "US");
138 }
djsollen@google.come63793a2012-03-21 15:39:03 +0000139 }
djsollen@google.comfc9054d2012-05-10 16:13:38 +0000140 strncpy(locale.language, propLang, 2);
141 locale.language[2] = '\0';
142 strncpy(locale.region, propRegn, 2);
143 locale.region[2] = '\0';
djsollen@google.come63793a2012-03-21 15:39:03 +0000144}
djsollen@google.come63793a2012-03-21 15:39:03 +0000145
146/**
147 * Use the current system locale (language and region) to open the best matching
148 * customization. For example, when the language is Japanese, the sequence might be:
149 * /system/etc/fallback_fonts-ja-JP.xml
150 * /system/etc/fallback_fonts-ja.xml
151 * /system/etc/fallback_fonts.xml
152 */
153FILE* openLocalizedFile(const char* origname) {
154 FILE* file = 0;
djsollen@google.come63793a2012-03-21 15:39:03 +0000155 SkString basename;
156 SkString filename;
djsollen@google.comfc9054d2012-05-10 16:13:38 +0000157 AndroidLocale locale;
djsollen@google.come63793a2012-03-21 15:39:03 +0000158
159 basename.set(origname);
160 // Remove the .xml suffix. We'll add it back in a moment.
161 if (basename.endsWith(".xml")) {
162 basename.resize(basename.size()-4);
163 }
djsollen@google.comfc9054d2012-05-10 16:13:38 +0000164 getLocale(locale);
djsollen@google.come63793a2012-03-21 15:39:03 +0000165 // Try first with language and region
djsollen@google.comfc9054d2012-05-10 16:13:38 +0000166 filename.printf("%s-%s-%s.xml", basename.c_str(), locale.language, locale.region);
djsollen@google.come63793a2012-03-21 15:39:03 +0000167 file = fopen(filename.c_str(), "r");
168 if (!file) {
169 // If not found, try next with just language
djsollen@google.comfc9054d2012-05-10 16:13:38 +0000170 filename.printf("%s-%s.xml", basename.c_str(), locale.language);
djsollen@google.come63793a2012-03-21 15:39:03 +0000171 file = fopen(filename.c_str(), "r");
djsollen@google.come63793a2012-03-21 15:39:03 +0000172
djsollen@google.comfc9054d2012-05-10 16:13:38 +0000173 if (!file) {
174 // If still not found, try just the original name
175 file = fopen(origname, "r");
176 }
djsollen@google.come63793a2012-03-21 15:39:03 +0000177 }
178 return file;
179}
180
djsollen@google.com58629292011-11-03 13:08:29 +0000181/**
182 * This function parses the given filename and stores the results in the given
183 * families array.
184 */
185void parseConfigFile(const char *filename, SkTDArray<FontFamily*> &families) {
186 XML_Parser parser = XML_ParserCreate(NULL);
187 FamilyData *familyData = new FamilyData(&parser, families);
188 XML_SetUserData(parser, familyData);
189 XML_SetElementHandler(parser, startElementHandler, endElementHandler);
djsollen@google.come63793a2012-03-21 15:39:03 +0000190 FILE *file = openLocalizedFile(filename);
djsollen@google.com58629292011-11-03 13:08:29 +0000191 // Some of the files we attempt to parse (in particular, /vendor/etc/fallback_fonts.xml)
192 // are optional - failure here is okay because one of these optional files may not exist.
193 if (file == NULL) {
194 return;
195 }
196 char buffer[512];
197 bool done = false;
198 while (!done) {
199 fgets(buffer, sizeof(buffer), file);
200 int len = strlen(buffer);
201 if (feof(file) != 0) {
202 done = true;
203 }
204 XML_Parse(parser, buffer, len, done);
205 }
206}
207
djsollen@google.come63793a2012-03-21 15:39:03 +0000208void getSystemFontFamilies(SkTDArray<FontFamily*> &fontFamilies) {
djsollen@google.com58629292011-11-03 13:08:29 +0000209 parseConfigFile(SYSTEM_FONTS_FILE, fontFamilies);
djsollen@google.come63793a2012-03-21 15:39:03 +0000210}
211
212void getFallbackFontFamilies(SkTDArray<FontFamily*> &fallbackFonts) {
213 SkTDArray<FontFamily*> vendorFonts;
djsollen@google.com58629292011-11-03 13:08:29 +0000214 parseConfigFile(FALLBACK_FONTS_FILE, fallbackFonts);
215 parseConfigFile(VENDOR_FONTS_FILE, vendorFonts);
216
217 // This loop inserts the vendor fallback fonts in the correct order in the
218 // overall fallbacks list.
219 int currentOrder = -1;
220 for (int i = 0; i < vendorFonts.count(); ++i) {
221 FontFamily* family = vendorFonts[i];
222 int order = family->order;
223 if (order < 0) {
224 if (currentOrder < 0) {
225 // Default case - just add it to the end of the fallback list
226 *fallbackFonts.append() = family;
227 } else {
228 // no order specified on this font, but we're incrementing the order
229 // based on an earlier order insertion request
230 *fallbackFonts.insert(currentOrder++) = family;
231 }
232 } else {
233 // Add the font into the fallback list in the specified order. Set
234 // currentOrder for correct placement of other fonts in the vendor list.
235 *fallbackFonts.insert(order) = family;
236 currentOrder = order + 1;
237 }
238 }
djsollen@google.come63793a2012-03-21 15:39:03 +0000239}
240
241/**
242 * Loads data on font families from various expected configuration files. The
243 * resulting data is returned in the given fontFamilies array.
244 */
245void getFontFamilies(SkTDArray<FontFamily*> &fontFamilies) {
246 SkTDArray<FontFamily*> fallbackFonts;
247
248 getSystemFontFamilies(fontFamilies);
249 getFallbackFontFamilies(fallbackFonts);
250
djsollen@google.com58629292011-11-03 13:08:29 +0000251 // Append all fallback fonts to system fonts
252 for (int i = 0; i < fallbackFonts.count(); ++i) {
253 *fontFamilies.append() = fallbackFonts[i];
254 }
255}
djsollen@google.com5df2a992012-06-25 13:58:22 +0000256
257void getTestFontFamilies(SkTDArray<FontFamily*> &fontFamilies,
258 const char* testMainConfigFile,
259 const char* testFallbackConfigFile) {
260 parseConfigFile(testMainConfigFile, fontFamilies);
261
262 SkTDArray<FontFamily*> fallbackFonts;
263 parseConfigFile(testFallbackConfigFile, fallbackFonts);
264
265 // Append all fallback fonts to system fonts
266 for (int i = 0; i < fallbackFonts.count(); ++i) {
267 *fontFamilies.append() = fallbackFonts[i];
268 }
269}