/* * Copyright 2011 The Android Open Source Project * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #include "SkFontConfigParser_android.h" #include "SkFontMgr_android.h" #include "SkStream.h" #include "SkTDArray.h" #include "SkTSearch.h" #include "SkTypeface.h" #include #include #include #include #include // From Android version LMP onwards, all font files collapse into // /system/etc/fonts.xml. Instead of trying to detect which version // we're on, try to open fonts.xml; if that fails, fall back to the // older filename. #define LMP_SYSTEM_FONTS_FILE "/system/etc/fonts.xml" #define OLD_SYSTEM_FONTS_FILE "/system/etc/system_fonts.xml" #define FALLBACK_FONTS_FILE "/system/etc/fallback_fonts.xml" #define VENDOR_FONTS_FILE "/vendor/etc/fallback_fonts.xml" #define LOCALE_FALLBACK_FONTS_SYSTEM_DIR "/system/etc" #define LOCALE_FALLBACK_FONTS_VENDOR_DIR "/vendor/etc" #define LOCALE_FALLBACK_FONTS_PREFIX "fallback_fonts-" #define LOCALE_FALLBACK_FONTS_SUFFIX ".xml" #ifndef SK_FONT_FILE_PREFIX # define SK_FONT_FILE_PREFIX "/fonts/" #endif /** * This file contains TWO parsers: one for JB and earlier (system_fonts.xml / * fallback_fonts.xml), one for LMP and later (fonts.xml). * We start with the JB parser, and if we detect a tag with * version 21 or higher we switch to the LMP parser. */ /** Used to track which tag is currently populating with data. * Only nameset and fileset are of interest for now. */ enum CurrentTag { kNo_CurrentTag, kNameSet_CurrentTag, kFileSet_CurrentTag }; /** * The FamilyData structure is passed around by the parser so that each handler * can read these variables that are relevant to the current parsing. */ struct FamilyData { FamilyData(XML_Parser parser, SkTDArray& families, const SkString& basePath, bool isFallback) : fParser(parser) , fFamilies(families) , fCurrentFamily(NULL) , fCurrentFontInfo(NULL) , fCurrentTag(kNo_CurrentTag) , fVersion(0) , fBasePath(basePath) , fIsFallback(isFallback) { }; XML_Parser fParser; // The expat parser doing the work, owned by caller SkTDArray& fFamilies; // The array to append families, owned by caller SkAutoTDelete fCurrentFamily; // The family being created, owned by this FontFileInfo* fCurrentFontInfo; // The fontInfo being created, owned by fCurrentFamily CurrentTag fCurrentTag; // The kind of tag currently being parsed. int fVersion; // The version of the file parsed. const SkString& fBasePath; // The current base path. const bool fIsFallback; // Indicates the file being parsed is a fallback file }; /** Parses a null terminated string into an integer type, checking for overflow. * http://www.w3.org/TR/html-markup/datatypes.html#common.data.integer.non-negative-def * * If the string cannot be parsed into 'value', returns false and does not change 'value'. */ template static bool parse_non_negative_integer(const char* s, T* value) { SK_COMPILE_ASSERT(std::numeric_limits::is_integer, T_must_be_integer); const T nMax = std::numeric_limits::max() / 10; const T dMax = std::numeric_limits::max() - (nMax * 10); T n = 0; for (; *s; ++s) { // Check if digit if (*s < '0' || '9' < *s) { return false; } int d = *s - '0'; // Check for overflow if (n > nMax || (n == nMax && d > dMax)) { return false; } n = (n * 10) + d; } *value = n; return true; } static bool memeq(const char* s1, const char* s2, size_t n1, size_t n2) { return n1 == n2 && 0 == memcmp(s1, s2, n1); } #define MEMEQ(c, s, n) memeq(c, s, sizeof(c) - 1, n) #define ATTS_NON_NULL(a, i) (a[i] != NULL && a[i+1] != NULL) namespace lmpParser { static void family_element_handler(FontFamily* family, const char** attributes) { // A non-fallback tag must have a canonical name attribute. // A fallback tag has no name, and may have lang and variant // attributes. family->fIsFallbackFont = true; for (size_t i = 0; ATTS_NON_NULL(attributes, i); i += 2) { const char* name = attributes[i]; const char* value = attributes[i+1]; size_t nameLen = strlen(name); size_t valueLen = strlen(value); if (MEMEQ("name", name, nameLen)) { SkAutoAsciiToLC tolc(value); family->fNames.push_back().set(tolc.lc()); family->fIsFallbackFont = false; } else if (MEMEQ("lang", name, nameLen)) { family->fLanguage = SkLanguage(value, valueLen); } else if (MEMEQ("variant", name, nameLen)) { // Value should be either elegant or compact. if (MEMEQ("elegant", value, valueLen)) { family->fVariant = kElegant_FontVariant; } else if (MEMEQ("compact", value, valueLen)) { family->fVariant = kCompact_FontVariant; } } } } static void XMLCALL font_file_name_handler(void* data, const char* s, int len) { FamilyData* self = static_cast(data); self->fCurrentFontInfo->fFileName.append(s, len); } static void font_element_handler(FamilyData* self, FontFileInfo* file, const char** attributes) { // A should have weight (integer) and style (normal, italic) attributes. // NOTE: we ignore the style. // The element should contain a filename. for (size_t i = 0; ATTS_NON_NULL(attributes, i); i += 2) { const char* name = attributes[i]; const char* value = attributes[i+1]; size_t nameLen = strlen(name); if (MEMEQ("weight", name, nameLen)) { if (!parse_non_negative_integer(value, &file->fWeight)) { SkDebugf("---- Font weight %s (INVALID)", value); } } else if (MEMEQ("index", name, nameLen)) { if (!parse_non_negative_integer(value, &file->fIndex)) { SkDebugf("---- Font index %s (INVALID)", value); } } } XML_SetCharacterDataHandler(self->fParser, font_file_name_handler); } static FontFamily* find_family(FamilyData* self, const SkString& familyName) { for (int i = 0; i < self->fFamilies.count(); i++) { FontFamily* candidate = self->fFamilies[i]; for (int j = 0; j < candidate->fNames.count(); j++) { if (candidate->fNames[j] == familyName) { return candidate; } } } return NULL; } static void alias_element_handler(FamilyData* self, const char** attributes) { // An must have name and to attributes. // It may have weight (integer). // If it *does not* have a weight, it is a variant name for a . // If it *does* have a weight, it names the (s) of a specific weight // from a . SkString aliasName; SkString to; int weight = 0; for (size_t i = 0; ATTS_NON_NULL(attributes, i); i += 2) { const char* name = attributes[i]; const char* value = attributes[i+1]; size_t nameLen = strlen(name); if (MEMEQ("name", name, nameLen)) { SkAutoAsciiToLC tolc(value); aliasName.set(tolc.lc()); } else if (MEMEQ("to", name, nameLen)) { to.set(value); } else if (MEMEQ("weight", name, nameLen)) { if (!parse_non_negative_integer(value, &weight)) { SkDebugf("---- Font weight %s (INVALID)", value); } } } // Assumes that the named family is already declared FontFamily* targetFamily = find_family(self, to); if (!targetFamily) { SkDebugf("---- Font alias target %s (NOT FOUND)", to.c_str()); return; } if (weight) { FontFamily* family = new FontFamily(targetFamily->fBasePath, self->fIsFallback); family->fNames.push_back().set(aliasName); for (int i = 0; i < targetFamily->fFonts.count(); i++) { if (targetFamily->fFonts[i].fWeight == weight) { family->fFonts.push_back(targetFamily->fFonts[i]); } } *self->fFamilies.append() = family; } else { targetFamily->fNames.push_back().set(aliasName); } } static void XMLCALL start_element_handler(void* data, const char* tag, const char** attributes) { FamilyData* self = static_cast(data); size_t len = strlen(tag); if (MEMEQ("family", tag, len)) { self->fCurrentFamily.reset(new FontFamily(self->fBasePath, self->fIsFallback)); family_element_handler(self->fCurrentFamily, attributes); } else if (MEMEQ("font", tag, len)) { FontFileInfo* file = &self->fCurrentFamily->fFonts.push_back(); self->fCurrentFontInfo = file; font_element_handler(self, file, attributes); } else if (MEMEQ("alias", tag, len)) { alias_element_handler(self, attributes); } } static void XMLCALL end_element_handler(void* data, const char* tag) { FamilyData* self = static_cast(data); size_t len = strlen(tag); if (MEMEQ("family", tag, len)) { *self->fFamilies.append() = self->fCurrentFamily.detach(); } else if (MEMEQ("font", tag, len)) { XML_SetCharacterDataHandler(self->fParser, NULL); } } } // lmpParser namespace jbParser { /** * Handler for arbitrary text. This is used to parse the text inside each name * or file tag. The resulting strings are put into the fNames or FontFileInfo arrays. */ static void XMLCALL text_handler(void* data, const char* s, int len) { FamilyData* self = static_cast(data); // Make sure we're in the right state to store this name information if (self->fCurrentFamily.get()) { switch (self->fCurrentTag) { case kNameSet_CurrentTag: { SkAutoAsciiToLC tolc(s, len); self->fCurrentFamily->fNames.back().append(tolc.lc(), len); break; } case kFileSet_CurrentTag: if (self->fCurrentFontInfo) { self->fCurrentFontInfo->fFileName.append(s, len); } break; default: // Noop - don't care about any text that's not in the Fonts or Names list break; } } } /** * Handler for font files. This processes the attributes for language and * variants then lets textHandler handle the actual file name */ static void font_file_element_handler(FamilyData* self, const char** attributes) { FontFamily& currentFamily = *self->fCurrentFamily.get(); FontFileInfo& newFileInfo = currentFamily.fFonts.push_back(); if (attributes) { for (size_t i = 0; ATTS_NON_NULL(attributes, i); i += 2) { const char* attributeName = attributes[i]; const char* attributeValue = attributes[i+1]; size_t nameLength = strlen(attributeName); size_t valueLength = strlen(attributeValue); if (MEMEQ("variant", attributeName, nameLength)) { const FontVariant prevVariant = currentFamily.fVariant; if (MEMEQ("elegant", attributeValue, valueLength)) { currentFamily.fVariant = kElegant_FontVariant; } else if (MEMEQ("compact", attributeValue, valueLength)) { currentFamily.fVariant = kCompact_FontVariant; } if (currentFamily.fFonts.count() > 1 && currentFamily.fVariant != prevVariant) { SkDebugf("Every font file within a family must have identical variants"); } } else if (MEMEQ("lang", attributeName, nameLength)) { SkLanguage prevLang = currentFamily.fLanguage; currentFamily.fLanguage = SkLanguage(attributeValue, valueLength); if (currentFamily.fFonts.count() > 1 && currentFamily.fLanguage != prevLang) { SkDebugf("Every font file within a family must have identical languages"); } } else if (MEMEQ("index", attributeName, nameLength)) { if (!parse_non_negative_integer(attributeValue, &newFileInfo.fIndex)) { SkDebugf("---- SystemFonts index=%s (INVALID)", attributeValue); } } } } self->fCurrentFontInfo = &newFileInfo; XML_SetCharacterDataHandler(self->fParser, text_handler); } /** * Handler for the start of a tag. The only tags we expect are familyset, family, * nameset, fileset, name, and file. */ static void XMLCALL start_element_handler(void* data, const char* tag, const char** attributes) { FamilyData* self = static_cast(data); size_t len = strlen(tag); if (MEMEQ("familyset", tag, len)) { // The familyset tag has an optional "version" attribute with an integer value >= 0 for (size_t i = 0; ATTS_NON_NULL(attributes, i); i += 2) { size_t nameLen = strlen(attributes[i]); if (!MEMEQ("version", attributes[i], nameLen)) continue; const char* valueString = attributes[i+1]; int version; if (parse_non_negative_integer(valueString, &version) && (version >= 21)) { XML_SetElementHandler(self->fParser, lmpParser::start_element_handler, lmpParser::end_element_handler); self->fVersion = version; } } } else if (MEMEQ("family", tag, len)) { self->fCurrentFamily.reset(new FontFamily(self->fBasePath, self->fIsFallback)); // The Family tag has an optional "order" attribute with an integer value >= 0 // If this attribute does not exist, the default value is -1 for (size_t i = 0; ATTS_NON_NULL(attributes, i); i += 2) { const char* valueString = attributes[i+1]; int value; if (parse_non_negative_integer(valueString, &value)) { self->fCurrentFamily->fOrder = value; } } } else if (MEMEQ("nameset", tag, len)) { self->fCurrentTag = kNameSet_CurrentTag; } else if (MEMEQ("fileset", tag, len)) { self->fCurrentTag = kFileSet_CurrentTag; } else if (MEMEQ("name", tag, len) && self->fCurrentTag == kNameSet_CurrentTag) { // If it's a Name, parse the text inside self->fCurrentFamily->fNames.push_back(); XML_SetCharacterDataHandler(self->fParser, text_handler); } else if (MEMEQ("file", tag, len) && self->fCurrentTag == kFileSet_CurrentTag) { // If it's a file, parse the attributes, then parse the text inside font_file_element_handler(self, attributes); } } /** * Handler for the end of tags. We only care about family, nameset, fileset, * name, and file. */ static void XMLCALL end_element_handler(void* data, const char* tag) { FamilyData* self = static_cast(data); size_t len = strlen(tag); if (MEMEQ("family", tag, len)) { // Done parsing a Family - store the created currentFamily in the families array *self->fFamilies.append() = self->fCurrentFamily.detach(); } else if (MEMEQ("nameset", tag, len)) { self->fCurrentTag = kNo_CurrentTag; } else if (MEMEQ("fileset", tag, len)) { self->fCurrentTag = kNo_CurrentTag; } else if ((MEMEQ("name", tag, len) && self->fCurrentTag == kNameSet_CurrentTag) || (MEMEQ("file", tag, len) && self->fCurrentTag == kFileSet_CurrentTag)) { // Disable the arbitrary text handler installed to load Name data XML_SetCharacterDataHandler(self->fParser, NULL); } } } // namespace jbParser static void XMLCALL xml_entity_decl_handler(void *data, const XML_Char *entityName, int is_parameter_entity, const XML_Char *value, int value_length, const XML_Char *base, const XML_Char *systemId, const XML_Char *publicId, const XML_Char *notationName) { FamilyData* self = static_cast(data); SkDebugf("Entity declaration %s found, stopping processing.", entityName); XML_StopParser(self->fParser, XML_FALSE); } static const XML_Memory_Handling_Suite sk_XML_alloc = { sk_malloc_throw, sk_realloc_throw, sk_free }; template struct remove_ptr {typedef T type;}; template struct remove_ptr {typedef T type;}; /** * This function parses the given filename and stores the results in the given * families array. Returns the version of the file, negative if the file does not exist. */ static int parse_config_file(const char* filename, SkTDArray& families, const SkString& basePath, bool isFallback) { SkFILEStream file(filename); // Some of the files we attempt to parse (in particular, /vendor/etc/fallback_fonts.xml) // are optional - failure here is okay because one of these optional files may not exist. if (!file.isValid()) { SkDebugf("File %s could not be opened.\n", filename); return -1; } SkAutoTCallVProc::type, XML_ParserFree> parser( XML_ParserCreate_MM(NULL, &sk_XML_alloc, NULL)); if (!parser) { SkDebugf("Could not create XML parser.\n"); return -1; } FamilyData self(parser, families, basePath, isFallback); XML_SetUserData(parser, &self); // Disable entity processing, to inhibit internal entity expansion. See expat CVE-2013-0340 XML_SetEntityDeclHandler(parser, xml_entity_decl_handler); // Start parsing oldschool; switch these in flight if we detect a newer version of the file. XML_SetElementHandler(parser, jbParser::start_element_handler, jbParser::end_element_handler); // One would assume it would be faster to have a buffer on the stack and call XML_Parse. // But XML_Parse will call XML_GetBuffer anyway and memmove the passed buffer into it. // (Unless XML_CONTEXT_BYTES is undefined, but all users define it.) // In debug, buffer a small odd number of bytes to detect slicing in XML_CharacterDataHandler. static const int bufferSize = 512 SkDEBUGCODE( - 507); bool done = false; while (!done) { void* buffer = XML_GetBuffer(parser, bufferSize); if (!buffer) { SkDebugf("Could not buffer enough to continue.\n"); return -1; } size_t len = file.read(buffer, bufferSize); done = file.isAtEnd(); XML_Status status = XML_ParseBuffer(parser, len, done); if (XML_STATUS_ERROR == status) { XML_Error error = XML_GetErrorCode(parser); int line = XML_GetCurrentLineNumber(parser); int column = XML_GetCurrentColumnNumber(parser); int index = XML_GetCurrentByteIndex(parser); const XML_LChar* errorString = XML_ErrorString(error); SkDebugf("Line: %d Column: %d (Offset: %d) Error %d: %s.\n", line, column, index, error, errorString); return -1; } } return self.fVersion; } /** Returns the version of the system font file actually found, negative if none. */ static int append_system_font_families(SkTDArray& fontFamilies, const SkString& basePath) { int initialCount = fontFamilies.count(); int version = parse_config_file(LMP_SYSTEM_FONTS_FILE, fontFamilies, basePath, false); if (version < 0 || fontFamilies.count() == initialCount) { version = parse_config_file(OLD_SYSTEM_FONTS_FILE, fontFamilies, basePath, false); } return version; } /** * In some versions of Android prior to Android 4.2 (JellyBean MR1 at API * Level 17) the fallback fonts for certain locales were encoded in their own * XML files with a suffix that identified the locale. We search the provided * directory for those files,add all of their entries to the fallback chain, and * include the locale as part of each entry. */ static void append_fallback_font_families_for_locale(SkTDArray& fallbackFonts, const char* dir, const SkString& basePath) { #if defined(SK_BUILD_FOR_ANDROID_FRAMEWORK) // The framework is beyond Android 4.2 and can therefore skip this function return; #endif SkAutoTCallIProc fontDirectory(opendir(dir)); if (NULL == fontDirectory) { return; } for (struct dirent* dirEntry; (dirEntry = readdir(fontDirectory));) { // The size of the prefix and suffix. static const size_t fixedLen = sizeof(LOCALE_FALLBACK_FONTS_PREFIX) - 1 + sizeof(LOCALE_FALLBACK_FONTS_SUFFIX) - 1; // The size of the prefix, suffix, and a minimum valid language code static const size_t minSize = fixedLen + 2; SkString fileName(dirEntry->d_name); if (fileName.size() < minSize || !fileName.startsWith(LOCALE_FALLBACK_FONTS_PREFIX) || !fileName.endsWith(LOCALE_FALLBACK_FONTS_SUFFIX)) { continue; } SkString locale(fileName.c_str() + sizeof(LOCALE_FALLBACK_FONTS_PREFIX) - 1, fileName.size() - fixedLen); SkString absoluteFilename; absoluteFilename.printf("%s/%s", dir, fileName.c_str()); SkTDArray langSpecificFonts; parse_config_file(absoluteFilename.c_str(), langSpecificFonts, basePath, true); for (int i = 0; i < langSpecificFonts.count(); ++i) { FontFamily* family = langSpecificFonts[i]; family->fLanguage = SkLanguage(locale); *fallbackFonts.append() = family; } } } static void append_system_fallback_font_families(SkTDArray& fallbackFonts, const SkString& basePath) { parse_config_file(FALLBACK_FONTS_FILE, fallbackFonts, basePath, true); append_fallback_font_families_for_locale(fallbackFonts, LOCALE_FALLBACK_FONTS_SYSTEM_DIR, basePath); } static void mixin_vendor_fallback_font_families(SkTDArray& fallbackFonts, const SkString& basePath) { SkTDArray vendorFonts; parse_config_file(VENDOR_FONTS_FILE, vendorFonts, basePath, true); append_fallback_font_families_for_locale(vendorFonts, LOCALE_FALLBACK_FONTS_VENDOR_DIR, basePath); // This loop inserts the vendor fallback fonts in the correct order in the // overall fallbacks list. int currentOrder = -1; for (int i = 0; i < vendorFonts.count(); ++i) { FontFamily* family = vendorFonts[i]; int order = family->fOrder; if (order < 0) { if (currentOrder < 0) { // Default case - just add it to the end of the fallback list *fallbackFonts.append() = family; } else { // no order specified on this font, but we're incrementing the order // based on an earlier order insertion request *fallbackFonts.insert(currentOrder++) = family; } } else { // Add the font into the fallback list in the specified order. Set // currentOrder for correct placement of other fonts in the vendor list. *fallbackFonts.insert(order) = family; currentOrder = order + 1; } } } void SkFontConfigParser::GetSystemFontFamilies(SkTDArray& fontFamilies) { // Version 21 of the system font configuration does not need any fallback configuration files. SkString basePath(getenv("ANDROID_ROOT")); basePath.append(SK_FONT_FILE_PREFIX, sizeof(SK_FONT_FILE_PREFIX) - 1); if (append_system_font_families(fontFamilies, basePath) >= 21) { return; } // Append all the fallback fonts to system fonts SkTDArray fallbackFonts; append_system_fallback_font_families(fallbackFonts, basePath); mixin_vendor_fallback_font_families(fallbackFonts, basePath); fontFamilies.append(fallbackFonts.count(), fallbackFonts.begin()); } void SkFontConfigParser::GetCustomFontFamilies(SkTDArray& fontFamilies, const SkString& basePath, const char* fontsXml, const char* fallbackFontsXml, const char* langFallbackFontsDir) { if (fontsXml) { parse_config_file(fontsXml, fontFamilies, basePath, false); } if (fallbackFontsXml) { parse_config_file(fallbackFontsXml, fontFamilies, basePath, true); } if (langFallbackFontsDir) { append_fallback_font_families_for_locale(fontFamilies, langFallbackFontsDir, basePath); } } SkLanguage SkLanguage::getParent() const { SkASSERT(!fTag.isEmpty()); const char* tag = fTag.c_str(); // strip off the rightmost "-.*" const char* parentTagEnd = strrchr(tag, '-'); if (parentTagEnd == NULL) { return SkLanguage(); } size_t parentTagLen = parentTagEnd - tag; return SkLanguage(tag, parentTagLen); }