michael@0: /* michael@0: * Copyright 2011 The Android Open Source Project michael@0: * michael@0: * Use of this source code is governed by a BSD-style license that can be michael@0: * found in the LICENSE file. michael@0: */ michael@0: michael@0: #include "SkFontConfigParser_android.h" michael@0: #include "SkTDArray.h" michael@0: #include "SkTypeface.h" michael@0: michael@0: #include michael@0: #include michael@0: #include michael@0: michael@0: #define SYSTEM_FONTS_FILE "/system/etc/system_fonts.xml" michael@0: #define FALLBACK_FONTS_FILE "/system/etc/fallback_fonts.xml" michael@0: #define VENDOR_FONTS_FILE "/vendor/etc/fallback_fonts.xml" michael@0: michael@0: // These defines are used to determine the kind of tag that we're currently michael@0: // populating with data. We only care about the sibling tags nameset and fileset michael@0: // for now. michael@0: #define NO_TAG 0 michael@0: #define NAMESET_TAG 1 michael@0: #define FILESET_TAG 2 michael@0: michael@0: /** michael@0: * The FamilyData structure is passed around by the parser so that each handler michael@0: * can read these variables that are relevant to the current parsing. michael@0: */ michael@0: struct FamilyData { michael@0: FamilyData(XML_Parser *parserRef, SkTDArray &familiesRef) : michael@0: parser(parserRef), michael@0: families(familiesRef), michael@0: currentFamily(NULL), michael@0: currentFontInfo(NULL), michael@0: currentTag(NO_TAG) {}; michael@0: michael@0: XML_Parser *parser; // The expat parser doing the work michael@0: SkTDArray &families; // The array that each family is put into as it is parsed michael@0: FontFamily *currentFamily; // The current family being created michael@0: FontFileInfo *currentFontInfo; // The current fontInfo being created michael@0: int currentTag; // A flag to indicate whether we're in nameset/fileset tags michael@0: }; michael@0: michael@0: /** michael@0: * Handler for arbitrary text. This is used to parse the text inside each name michael@0: * or file tag. The resulting strings are put into the fNames or FontFileInfo arrays. michael@0: */ michael@0: static void textHandler(void *data, const char *s, int len) { michael@0: FamilyData *familyData = (FamilyData*) data; michael@0: // Make sure we're in the right state to store this name information michael@0: if (familyData->currentFamily && michael@0: (familyData->currentTag == NAMESET_TAG || familyData->currentTag == FILESET_TAG)) { michael@0: // Malloc new buffer to store the string michael@0: char *buff; michael@0: buff = (char*) malloc((len + 1) * sizeof(char)); michael@0: strncpy(buff, s, len); michael@0: buff[len] = '\0'; michael@0: switch (familyData->currentTag) { michael@0: case NAMESET_TAG: michael@0: *(familyData->currentFamily->fNames.append()) = buff; michael@0: break; michael@0: case FILESET_TAG: michael@0: if (familyData->currentFontInfo) { michael@0: familyData->currentFontInfo->fFileName = buff; michael@0: } michael@0: break; michael@0: default: michael@0: // Noop - don't care about any text that's not in the Fonts or Names list michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Handler for font files. This processes the attributes for language and michael@0: * variants then lets textHandler handle the actual file name michael@0: */ michael@0: static void fontFileElementHandler(FamilyData *familyData, const char **attributes) { michael@0: FontFileInfo* newFileInfo = new FontFileInfo(); michael@0: if (attributes) { michael@0: int currentAttributeIndex = 0; michael@0: while (attributes[currentAttributeIndex]) { michael@0: const char* attributeName = attributes[currentAttributeIndex]; michael@0: const char* attributeValue = attributes[currentAttributeIndex+1]; michael@0: int nameLength = strlen(attributeName); michael@0: int valueLength = strlen(attributeValue); michael@0: if (strncmp(attributeName, "variant", nameLength) == 0) { michael@0: if (strncmp(attributeValue, "elegant", valueLength) == 0) { michael@0: newFileInfo->fPaintOptions.setFontVariant(SkPaintOptionsAndroid::kElegant_Variant); michael@0: } else if (strncmp(attributeValue, "compact", valueLength) == 0) { michael@0: newFileInfo->fPaintOptions.setFontVariant(SkPaintOptionsAndroid::kCompact_Variant); michael@0: } michael@0: } else if (strncmp(attributeName, "lang", nameLength) == 0) { michael@0: newFileInfo->fPaintOptions.setLanguage(attributeValue); michael@0: } michael@0: //each element is a pair of attributeName/attributeValue string pairs michael@0: currentAttributeIndex += 2; michael@0: } michael@0: } michael@0: *(familyData->currentFamily->fFontFiles.append()) = newFileInfo; michael@0: familyData->currentFontInfo = newFileInfo; michael@0: XML_SetCharacterDataHandler(*familyData->parser, textHandler); michael@0: } michael@0: michael@0: /** michael@0: * Handler for the start of a tag. The only tags we expect are family, nameset, michael@0: * fileset, name, and file. michael@0: */ michael@0: static void startElementHandler(void *data, const char *tag, const char **atts) { michael@0: FamilyData *familyData = (FamilyData*) data; michael@0: int len = strlen(tag); michael@0: if (strncmp(tag, "family", len)== 0) { michael@0: familyData->currentFamily = new FontFamily(); michael@0: familyData->currentFamily->order = -1; michael@0: // The Family tag has an optional "order" attribute with an integer value >= 0 michael@0: // If this attribute does not exist, the default value is -1 michael@0: for (int i = 0; atts[i] != NULL; i += 2) { michael@0: const char* valueString = atts[i+1]; michael@0: int value; michael@0: int len = sscanf(valueString, "%d", &value); michael@0: if (len > 0) { michael@0: familyData->currentFamily->order = value; michael@0: } michael@0: } michael@0: } else if (len == 7 && strncmp(tag, "nameset", len) == 0) { michael@0: familyData->currentTag = NAMESET_TAG; michael@0: } else if (len == 7 && strncmp(tag, "fileset", len) == 0) { michael@0: familyData->currentTag = FILESET_TAG; michael@0: } else if (strncmp(tag, "name", len) == 0 && familyData->currentTag == NAMESET_TAG) { michael@0: // If it's a Name, parse the text inside michael@0: XML_SetCharacterDataHandler(*familyData->parser, textHandler); michael@0: } else if (strncmp(tag, "file", len) == 0 && familyData->currentTag == FILESET_TAG) { michael@0: // If it's a file, parse the attributes, then parse the text inside michael@0: fontFileElementHandler(familyData, atts); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Handler for the end of tags. We only care about family, nameset, fileset, michael@0: * name, and file. michael@0: */ michael@0: static void endElementHandler(void *data, const char *tag) { michael@0: FamilyData *familyData = (FamilyData*) data; michael@0: int len = strlen(tag); michael@0: if (strncmp(tag, "family", len)== 0) { michael@0: // Done parsing a Family - store the created currentFamily in the families array michael@0: *familyData->families.append() = familyData->currentFamily; michael@0: familyData->currentFamily = NULL; michael@0: } else if (len == 7 && strncmp(tag, "nameset", len) == 0) { michael@0: familyData->currentTag = NO_TAG; michael@0: } else if (len == 7 && strncmp(tag, "fileset", len) == 0) { michael@0: familyData->currentTag = NO_TAG; michael@0: } else if ((strncmp(tag, "name", len) == 0 && familyData->currentTag == NAMESET_TAG) || michael@0: (strncmp(tag, "file", len) == 0 && familyData->currentTag == FILESET_TAG)) { michael@0: // Disable the arbitrary text handler installed to load Name data michael@0: XML_SetCharacterDataHandler(*familyData->parser, NULL); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * This function parses the given filename and stores the results in the given michael@0: * families array. michael@0: */ michael@0: static void parseConfigFile(const char *filename, SkTDArray &families) { michael@0: michael@0: FILE* file = NULL; michael@0: michael@0: #if !defined(SK_BUILD_FOR_ANDROID_FRAMEWORK) michael@0: // if we are using a version of Android prior to Android 4.2 (JellyBean MR1 michael@0: // at API Level 17) then we need to look for files with a different suffix. michael@0: char sdkVersion[PROP_VALUE_MAX]; michael@0: __system_property_get("ro.build.version.sdk", sdkVersion); michael@0: const int sdkVersionInt = atoi(sdkVersion); michael@0: michael@0: if (0 != *sdkVersion && sdkVersionInt < 17) { michael@0: SkString basename; michael@0: SkString updatedFilename; michael@0: SkString locale = SkFontConfigParser::GetLocale(); michael@0: michael@0: basename.set(filename); michael@0: // Remove the .xml suffix. We'll add it back in a moment. michael@0: if (basename.endsWith(".xml")) { michael@0: basename.resize(basename.size()-4); michael@0: } michael@0: // Try first with language and region michael@0: updatedFilename.printf("%s-%s.xml", basename.c_str(), locale.c_str()); michael@0: file = fopen(updatedFilename.c_str(), "r"); michael@0: if (!file) { michael@0: // If not found, try next with just language michael@0: updatedFilename.printf("%s-%.2s.xml", basename.c_str(), locale.c_str()); michael@0: file = fopen(updatedFilename.c_str(), "r"); michael@0: } michael@0: } michael@0: #endif michael@0: michael@0: if (NULL == file) { michael@0: file = fopen(filename, "r"); michael@0: } michael@0: michael@0: // Some of the files we attempt to parse (in particular, /vendor/etc/fallback_fonts.xml) michael@0: // are optional - failure here is okay because one of these optional files may not exist. michael@0: if (NULL == file) { michael@0: return; michael@0: } michael@0: michael@0: XML_Parser parser = XML_ParserCreate(NULL); michael@0: FamilyData *familyData = new FamilyData(&parser, families); michael@0: XML_SetUserData(parser, familyData); michael@0: XML_SetElementHandler(parser, startElementHandler, endElementHandler); michael@0: michael@0: char buffer[512]; michael@0: bool done = false; michael@0: while (!done) { michael@0: fgets(buffer, sizeof(buffer), file); michael@0: int len = strlen(buffer); michael@0: if (feof(file) != 0) { michael@0: done = true; michael@0: } michael@0: XML_Parse(parser, buffer, len, done); michael@0: } michael@0: XML_ParserFree(parser); michael@0: fclose(file); michael@0: } michael@0: michael@0: static void getSystemFontFamilies(SkTDArray &fontFamilies) { michael@0: parseConfigFile(SYSTEM_FONTS_FILE, fontFamilies); michael@0: } michael@0: michael@0: static void getFallbackFontFamilies(SkTDArray &fallbackFonts) { michael@0: SkTDArray vendorFonts; michael@0: parseConfigFile(FALLBACK_FONTS_FILE, fallbackFonts); michael@0: parseConfigFile(VENDOR_FONTS_FILE, vendorFonts); michael@0: michael@0: // This loop inserts the vendor fallback fonts in the correct order in the michael@0: // overall fallbacks list. michael@0: int currentOrder = -1; michael@0: for (int i = 0; i < vendorFonts.count(); ++i) { michael@0: FontFamily* family = vendorFonts[i]; michael@0: int order = family->order; michael@0: if (order < 0) { michael@0: if (currentOrder < 0) { michael@0: // Default case - just add it to the end of the fallback list michael@0: *fallbackFonts.append() = family; michael@0: } else { michael@0: // no order specified on this font, but we're incrementing the order michael@0: // based on an earlier order insertion request michael@0: *fallbackFonts.insert(currentOrder++) = family; michael@0: } michael@0: } else { michael@0: // Add the font into the fallback list in the specified order. Set michael@0: // currentOrder for correct placement of other fonts in the vendor list. michael@0: *fallbackFonts.insert(order) = family; michael@0: currentOrder = order + 1; michael@0: } michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Loads data on font families from various expected configuration files. The michael@0: * resulting data is returned in the given fontFamilies array. michael@0: */ michael@0: void SkFontConfigParser::GetFontFamilies(SkTDArray &fontFamilies) { michael@0: michael@0: getSystemFontFamilies(fontFamilies); michael@0: michael@0: // Append all the fallback fonts to system fonts michael@0: SkTDArray fallbackFonts; michael@0: getFallbackFontFamilies(fallbackFonts); michael@0: for (int i = 0; i < fallbackFonts.count(); ++i) { michael@0: fallbackFonts[i]->fIsFallbackFont = true; michael@0: *fontFamilies.append() = fallbackFonts[i]; michael@0: } michael@0: } michael@0: michael@0: void SkFontConfigParser::GetTestFontFamilies(SkTDArray &fontFamilies, michael@0: const char* testMainConfigFile, michael@0: const char* testFallbackConfigFile) { michael@0: parseConfigFile(testMainConfigFile, fontFamilies); michael@0: michael@0: SkTDArray fallbackFonts; michael@0: parseConfigFile(testFallbackConfigFile, fallbackFonts); michael@0: michael@0: // Append all fallback fonts to system fonts michael@0: for (int i = 0; i < fallbackFonts.count(); ++i) { michael@0: fallbackFonts[i]->fIsFallbackFont = true; michael@0: *fontFamilies.append() = fallbackFonts[i]; michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Read the persistent locale. michael@0: */ michael@0: SkString SkFontConfigParser::GetLocale() michael@0: { michael@0: char propLang[PROP_VALUE_MAX], propRegn[PROP_VALUE_MAX]; michael@0: __system_property_get("persist.sys.language", propLang); michael@0: __system_property_get("persist.sys.country", propRegn); michael@0: michael@0: if (*propLang == 0 && *propRegn == 0) { michael@0: /* Set to ro properties, default is en_US */ michael@0: __system_property_get("ro.product.locale.language", propLang); michael@0: __system_property_get("ro.product.locale.region", propRegn); michael@0: if (*propLang == 0 && *propRegn == 0) { michael@0: strcpy(propLang, "en"); michael@0: strcpy(propRegn, "US"); michael@0: } michael@0: } michael@0: michael@0: SkString locale(6); michael@0: char* localeCStr = locale.writable_str(); michael@0: michael@0: strncpy(localeCStr, propLang, 2); michael@0: localeCStr[2] = '-'; michael@0: strncpy(&localeCStr[3], propRegn, 2); michael@0: localeCStr[5] = '\0'; michael@0: michael@0: return locale; michael@0: }