Index: src/ports/SkFontConfigParser_android.cpp |
diff --git a/src/ports/SkFontConfigParser_android.cpp b/src/ports/SkFontConfigParser_android.cpp |
index 3f0ebbfc6b8f3f2e1547db559d26b778d15d4524..73e4c4773896afa4aaf2f7d59975da5e696557d5 100644 |
--- a/src/ports/SkFontConfigParser_android.cpp |
+++ b/src/ports/SkFontConfigParser_android.cpp |
@@ -19,10 +19,6 @@ |
#include <limits> |
#include <stdlib.h> |
-// 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" |
@@ -38,48 +34,81 @@ |
#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 <familyset> tag with |
- * version 21 or higher we switch to the LMP parser. |
+ * This file contains TWO 'familyset' handlers: |
+ * One for JB and earlier which works with |
+ * /system/etc/system_fonts.xml |
+ * /system/etc/fallback_fonts.xml |
+ * /vendor/etc/fallback_fonts.xml |
+ * /system/etc/fallback_fonts-XX.xml |
+ * /vendor/etc/fallback_fonts-XX.xml |
+ * and the other for LMP and later which works with |
+ * /system/etc/fonts.xml |
+ * |
+ * If the 'familyset' 'version' attribute is 21 or higher the LMP parser is used, otherwise the JB. |
*/ |
-/** 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 |
+struct FamilyData; |
+ |
+struct TagHandler { |
+ /** Called at the start tag. |
+ * Called immediately after the parent tag retuns this handler from a call to 'tag'. |
+ * Allows setting up for handling the tag content and processing attributes. |
+ * If NULL, will not be called. |
+ */ |
+ void (*start)(FamilyData* data, const char* tag, const char** attributes); |
+ |
+ /** Called at the end tag. |
+ * Allows post-processing of any accumulated information. |
+ * This will be the last call made in relation to the current tag. |
+ * If NULL, will not be called. |
+ */ |
+ void (*end)(FamilyData* data, const char* tag); |
+ |
+ /** Called when a nested tag is encountered. |
+ * This is responsible for determining how to handle the tag. |
+ * If the tag is not recognized, return NULL to skip the tag. |
+ * If NULL, all nested tags will be skipped. |
+ */ |
+ const TagHandler* (*tag)(FamilyData* data, const char* tag, const char** attributes); |
+ |
+ /** The character handler for this tag. |
+ * This is only active for character data contained directly in this tag (not sub-tags). |
+ * The first parameter will be castable to a FamilyData*. |
+ * If NULL, any character data in this tag will be ignored. |
+ */ |
+ XML_CharacterDataHandler chars; |
}; |
-/** |
- * The FamilyData structure is passed around by the parser so that each handler |
- * can read these variables that are relevant to the current parsing. |
- */ |
+/** Represents the current parsing state. */ |
struct FamilyData { |
FamilyData(XML_Parser parser, SkTDArray<FontFamily*>& families, |
- const SkString& basePath, bool isFallback, const char* filename) |
+ const SkString& basePath, bool isFallback, const char* filename, |
+ const TagHandler* topLevelHandler) |
: fParser(parser) |
, fFamilies(families) |
, fCurrentFamily(NULL) |
, fCurrentFontInfo(NULL) |
- , fCurrentTag(kNo_CurrentTag) |
, fVersion(0) |
, fBasePath(basePath) |
, fIsFallback(isFallback) |
, fFilename(filename) |
+ , fDepth(1) |
+ , fSkip(0) |
+ , fHandler(&topLevelHandler, 1) |
{ }; |
XML_Parser fParser; // The expat parser doing the work, owned by caller |
SkTDArray<FontFamily*>& fFamilies; // The array to append families, owned by caller |
SkAutoTDelete<FontFamily> 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 |
const char* fFilename; // The name of the file currently being parsed. |
+ |
+ int fDepth; // The current element depth of the parse. |
+ int fSkip; // The depth to stop skipping, 0 if not skipping. |
+ SkTDArray<const TagHandler*> fHandler; // The stack of current tag handlers. |
}; |
/** Parses a null terminated string into an integer type, checking for overflow. |
@@ -124,70 +153,107 @@ static bool memeq(const char* s1, const char* s2, size_t n1, size_t n2) { |
XML_GetCurrentColumnNumber(self->fParser), \ |
##__VA_ARGS__); |
-namespace lmpParser { |
+static bool is_whitespace(char c) { |
+ return c == ' ' || c == '\n'|| c == '\r' || c == '\t'; |
+} |
-static void family_element_handler(FamilyData* self, const char** attributes) { |
- // A <family> element without a 'name' (string) attribute is a fallback font. |
- // The element may have 'lang' (string) and 'variant' ("elegant", "compact") attributes. |
- // The element should contain <font> elements. |
- FontFamily* family = new FontFamily(self->fBasePath, true); |
- self->fCurrentFamily.reset(family); |
- 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)) { |
- if (MEMEQ("elegant", value, valueLen)) { |
- family->fVariant = kElegant_FontVariant; |
- } else if (MEMEQ("compact", value, valueLen)) { |
- family->fVariant = kCompact_FontVariant; |
- } |
- } |
+static void trim_string(SkString* s) { |
+ char* str = s->writable_str(); |
+ const char* start = str; // start is inclusive |
+ const char* end = start + s->size(); // end is exclusive |
+ while (is_whitespace(*start)) { ++start; } |
+ if (start != end) { |
+ --end; // make end inclusive |
+ while (is_whitespace(*end)) { --end; } |
+ ++end; // make end exclusive |
} |
+ size_t len = end - start; |
+ memmove(str, start, len); |
+ s->resize(len); |
} |
-static void XMLCALL font_file_name_handler(void* data, const char* s, int len) { |
- FamilyData* self = static_cast<FamilyData*>(data); |
- self->fCurrentFontInfo->fFileName.append(s, len); |
-} |
+namespace lmpParser { |
-static void font_element_handler(FamilyData* self, const char** attributes) { |
- // A <font> element should be contained in a <family> element. |
- // The element may have 'weight' (non-negative integer), 'style' ("normal", "italic"), |
- // and 'index' (non-negative integer) attributes. |
- // The element should contain a filename. |
- FontFileInfo& file = self->fCurrentFamily->fFonts.push_back(); |
- self->fCurrentFontInfo = &file; |
- 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)) { |
- SK_FONTCONFIGPARSER_WARNING("'%s' is an invalid weight", value); |
+static const TagHandler fontHandler = { |
+ /*start*/[](FamilyData* self, const char* tag, const char** attributes) { |
+ // 'weight' (non-negative integer) [default 0] |
+ // 'style' ("normal", "italic") [default "auto"] |
+ // 'index' (non-negative integer) [default 0] |
+ // The character data should be a filename. |
+ FontFileInfo& file = self->fCurrentFamily->fFonts.push_back(); |
+ self->fCurrentFontInfo = &file; |
+ 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)) { |
+ SK_FONTCONFIGPARSER_WARNING("'%s' is an invalid weight", value); |
+ } |
+ } else if (MEMEQ("style", name, nameLen)) { |
+ size_t valueLen = strlen(value); |
+ if (MEMEQ("normal", value, valueLen)) { |
+ file.fStyle = FontFileInfo::Style::kNormal; |
+ } else if (MEMEQ("italic", value, valueLen)) { |
+ file.fStyle = FontFileInfo::Style::kItalic; |
+ } |
+ } else if (MEMEQ("index", name, nameLen)) { |
+ if (!parse_non_negative_integer(value, &file.fIndex)) { |
+ SK_FONTCONFIGPARSER_WARNING("'%s' is an invalid index", value); |
+ } |
} |
- } else if (MEMEQ("style", name, nameLen)) { |
+ } |
+ }, |
+ /*end*/[](FamilyData* self, const char* tag) { |
+ trim_string(&self->fCurrentFontInfo->fFileName); |
+ }, |
+ /*tag*/NULL, |
+ /*chars*/[](void* data, const char* s, int len) { |
+ FamilyData* self = static_cast<FamilyData*>(data); |
+ self->fCurrentFontInfo->fFileName.append(s, len); |
+ } |
+}; |
+ |
+static const TagHandler familyHandler = { |
+ /*start*/[](FamilyData* self, const char* tag, const char** attributes) { |
+ // 'name' (string) [optional] |
+ // 'lang' (string) [default ""] |
+ // 'variant' ("elegant", "compact") [default "default"] |
+ // If there is no name, this is a fallback only font. |
+ FontFamily* family = new FontFamily(self->fBasePath, true); |
+ self->fCurrentFamily.reset(family); |
+ 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("normal", value, valueLen)) { |
- file.fStyle = FontFileInfo::Style::kNormal; |
- } else if (MEMEQ("italic", value, valueLen)) { |
- file.fStyle = FontFileInfo::Style::kItalic; |
- } |
- } else if (MEMEQ("index", name, nameLen)) { |
- if (!parse_non_negative_integer(value, &file.fIndex)) { |
- SK_FONTCONFIGPARSER_WARNING("'%s' is an invalid index", 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)) { |
+ if (MEMEQ("elegant", value, valueLen)) { |
+ family->fVariant = kElegant_FontVariant; |
+ } else if (MEMEQ("compact", value, valueLen)) { |
+ family->fVariant = kCompact_FontVariant; |
+ } |
} |
} |
- } |
- XML_SetCharacterDataHandler(self->fParser, font_file_name_handler); |
-} |
+ }, |
+ /*end*/[](FamilyData* self, const char* tag) { |
+ *self->fFamilies.append() = self->fCurrentFamily.detach(); |
+ }, |
+ /*tag*/[](FamilyData* self, const char* tag, const char** attributes) -> const TagHandler* { |
+ size_t len = strlen(tag); |
+ if (MEMEQ("font", tag, len)) { |
+ return &fontHandler; |
+ } |
+ return NULL; |
+ }, |
+ /*chars*/NULL, |
+}; |
static FontFamily* find_family(FamilyData* self, const SkString& familyName) { |
for (int i = 0; i < self->fFamilies.count(); i++) { |
@@ -201,242 +267,279 @@ static FontFamily* find_family(FamilyData* self, const SkString& familyName) { |
return NULL; |
} |
-static void alias_element_handler(FamilyData* self, const char** attributes) { |
- // A <alias> element must have 'name' (string) and 'to' (string) attributes. |
- // The element may also have a 'weight' (non-negative integer) attribute. |
- // The 'name' introduces a new family name. |
- // The 'to' specifies which (previous) <family> to alias. |
- // If it *does not* have a weight, 'name' is an alias for the entire 'to' <family>. |
- // If it *does* have a weight, 'name' is a new family consisting of the <font>(s) with 'weight' |
- // from the 'to' <family>. |
- |
- 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)) { |
- SK_FONTCONFIGPARSER_WARNING("'%s' is an invalid weight", value); |
+static const TagHandler aliasHandler = { |
+ /*start*/[](FamilyData* self, const char* tag, const char** attributes) { |
+ // 'name' (string) introduces a new family name. |
+ // 'to' (string) specifies which (previous) family to alias |
+ // 'weight' (non-negative integer) [optional] |
+ // If it *does not* have a weight, 'name' is an alias for the entire 'to' family. |
+ // If it *does* have a weight, 'name' is a new family consisting of |
+ // the font(s) with 'weight' from the 'to' family. |
+ |
+ 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)) { |
+ SK_FONTCONFIGPARSER_WARNING("'%s' is an invalid weight", value); |
+ } |
} |
} |
- } |
- // Assumes that the named family is already declared |
- FontFamily* targetFamily = find_family(self, to); |
- if (!targetFamily) { |
- SK_FONTCONFIGPARSER_WARNING("'%s' alias target not found", to.c_str()); |
- return; |
- } |
+ // Assumes that the named family is already declared |
+ FontFamily* targetFamily = find_family(self, to); |
+ if (!targetFamily) { |
+ SK_FONTCONFIGPARSER_WARNING("'%s' alias target not found", to.c_str()); |
+ return; |
+ } |
- if (weight) { |
- FontFamily* family = new FontFamily(targetFamily->fBasePath, self->fIsFallback); |
- family->fNames.push_back().set(aliasName); |
+ 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]); |
+ 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); |
} |
- *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<FamilyData*>(data); |
- size_t len = strlen(tag); |
- if (MEMEQ("family", tag, len)) { |
- family_element_handler(self, attributes); |
- } else if (MEMEQ("font", tag, len)) { |
- font_element_handler(self, attributes); |
- } else if (MEMEQ("alias", tag, len)) { |
- alias_element_handler(self, attributes); |
- } |
-} |
- |
-static bool is_whitespace(char c) { |
- return c == ' ' || c == '\n'|| c == '\r' || c == '\t'; |
-} |
- |
-static void trim_string(SkString* s) { |
- char* str = s->writable_str(); |
- const char* start = str; // start is inclusive |
- const char* end = start + s->size(); // end is exclusive |
- while (is_whitespace(*start)) { ++start; } |
- if (start != end) { |
- --end; // make end inclusive |
- while (is_whitespace(*end)) { --end; } |
- ++end; // make end exclusive |
- } |
- size_t len = end - start; |
- memmove(str, start, len); |
- s->resize(len); |
-} |
+ }, |
+ /*end*/NULL, |
+ /*tag*/NULL, |
+ /*chars*/NULL, |
+}; |
-static void XMLCALL end_element_handler(void* data, const char* tag) { |
- FamilyData* self = static_cast<FamilyData*>(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); |
- trim_string(&self->fCurrentFontInfo->fFileName); |
- } |
-} |
+static const TagHandler familySetHandler = { |
+ /*start*/[](FamilyData* self, const char* tag, const char** attributes) { }, |
+ /*end*/NULL, |
+ /*tag*/[](FamilyData* self, const char* tag, const char** attributes) -> const TagHandler* { |
+ size_t len = strlen(tag); |
+ if (MEMEQ("family", tag, len)) { |
+ return &familyHandler; |
+ } else if (MEMEQ("alias", tag, len)) { |
+ return &aliasHandler; |
+ } |
+ return NULL; |
+ }, |
+ /*chars*/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<FamilyData*>(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); |
+static const TagHandler fileHandler = { |
+ /*start*/[](FamilyData* self, const char* tag, const char** attributes) { |
+ // 'variant' ("elegant", "compact") [default "default"] |
+ // 'lang' (string) [default ""] |
+ // 'index' (non-negative integer) [default 0] |
+ // The character data should be a filename. |
+ 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* name = attributes[i]; |
+ const char* value = attributes[i+1]; |
+ size_t nameLen = strlen(name); |
+ size_t valueLen = strlen(value); |
+ if (MEMEQ("variant", name, nameLen)) { |
+ const FontVariant prevVariant = currentFamily.fVariant; |
+ if (MEMEQ("elegant", value, valueLen)) { |
+ currentFamily.fVariant = kElegant_FontVariant; |
+ } else if (MEMEQ("compact", value, valueLen)) { |
+ currentFamily.fVariant = kCompact_FontVariant; |
+ } |
+ if (currentFamily.fFonts.count() > 1 && currentFamily.fVariant != prevVariant) { |
+ SK_FONTCONFIGPARSER_WARNING("'%s' unexpected variant found\n" |
+ "Note: Every font file within a family must have identical variants.", |
+ value); |
+ } |
+ |
+ } else if (MEMEQ("lang", name, nameLen)) { |
+ SkLanguage prevLang = currentFamily.fLanguage; |
+ currentFamily.fLanguage = SkLanguage(value, valueLen); |
+ if (currentFamily.fFonts.count() > 1 && currentFamily.fLanguage != prevLang) { |
+ SK_FONTCONFIGPARSER_WARNING("'%s' unexpected language found\n" |
+ "Note: Every font file within a family must have identical languages.", |
+ value); |
+ } |
+ |
+ } else if (MEMEQ("index", name, nameLen)) { |
+ if (!parse_non_negative_integer(value, &newFileInfo.fIndex)) { |
+ SK_FONTCONFIGPARSER_WARNING("'%s' is an invalid index", value); |
+ } |
+ } |
} |
- break; |
- default: |
- // Noop - don't care about any text that's not in the Fonts or Names list |
- break; |
} |
+ self->fCurrentFontInfo = &newFileInfo; |
+ }, |
+ /*end*/NULL, |
+ /*tag*/NULL, |
+ /*chars*/[](void* data, const char* s, int len) { |
+ FamilyData* self = static_cast<FamilyData*>(data); |
+ self->fCurrentFontInfo->fFileName.append(s, len); |
} |
-} |
+}; |
-/** |
- * 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) { |
+static const TagHandler fileSetHandler = { |
+ /*start*/NULL, |
+ /*end*/NULL, |
+ /*tag*/[](FamilyData* self, const char* tag, const char** attributes) -> const TagHandler* { |
+ size_t len = strlen(tag); |
+ if (MEMEQ("file", tag, len)) { |
+ return &fileHandler; |
+ } |
+ return NULL; |
+ }, |
+ /*chars*/NULL, |
+}; |
+ |
+static const TagHandler nameHandler = { |
+ /*start*/[](FamilyData* self, const char* tag, const char** attributes) { |
+ // The character data should be a name for the font. |
+ self->fCurrentFamily->fNames.push_back(); |
+ }, |
+ /*end*/NULL, |
+ /*tag*/NULL, |
+ /*chars*/[](void* data, const char* s, int len) { |
+ FamilyData* self = static_cast<FamilyData*>(data); |
+ SkAutoAsciiToLC tolc(s, len); |
+ self->fCurrentFamily->fNames.back().append(tolc.lc(), len); |
+ } |
+}; |
+ |
+static const TagHandler nameSetHandler = { |
+ /*start*/NULL, |
+ /*end*/NULL, |
+ /*tag*/[](FamilyData* self, const char* tag, const char** attributes) -> const TagHandler* { |
+ size_t len = strlen(tag); |
+ if (MEMEQ("name", tag, len)) { |
+ return &nameHandler; |
+ } |
+ return NULL; |
+ }, |
+ /*chars*/NULL, |
+}; |
+ |
+static const TagHandler familyHandler = { |
+ /*start*/[](FamilyData* self, const char* tag, const char** attributes) { |
+ self->fCurrentFamily.reset(new FontFamily(self->fBasePath, self->fIsFallback)); |
+ // 'order' (non-negative integer) [default -1] |
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("variant", name, nameLen)) { |
- const FontVariant prevVariant = currentFamily.fVariant; |
- if (MEMEQ("elegant", value, valueLen)) { |
- currentFamily.fVariant = kElegant_FontVariant; |
- } else if (MEMEQ("compact", value, valueLen)) { |
- currentFamily.fVariant = kCompact_FontVariant; |
- } |
- if (currentFamily.fFonts.count() > 1 && currentFamily.fVariant != prevVariant) { |
- SK_FONTCONFIGPARSER_WARNING("'%s' unexpected variant found\n" |
- "Note: Every font file within a family must have identical variants.", |
- value); |
- } |
+ parse_non_negative_integer(value, &self->fCurrentFamily->fOrder); |
+ } |
+ }, |
+ /*end*/[](FamilyData* self, const char* tag) { |
+ *self->fFamilies.append() = self->fCurrentFamily.detach(); |
+ }, |
+ /*tag*/[](FamilyData* self, const char* tag, const char** attributes) -> const TagHandler* { |
+ size_t len = strlen(tag); |
+ if (MEMEQ("nameset", tag, len)) { |
+ return &nameSetHandler; |
+ } else if (MEMEQ("fileset", tag, len)) { |
+ return &fileSetHandler; |
+ } |
+ return NULL; |
+ }, |
+ /*chars*/NULL, |
+}; |
- } else if (MEMEQ("lang", name, nameLen)) { |
- SkLanguage prevLang = currentFamily.fLanguage; |
- currentFamily.fLanguage = SkLanguage(value, valueLen); |
- if (currentFamily.fFonts.count() > 1 && currentFamily.fLanguage != prevLang) { |
- SK_FONTCONFIGPARSER_WARNING("'%s' unexpected language found\n" |
- "Note: Every font file within a family must have identical languages.", |
- value); |
- } |
+static const TagHandler familySetHandler = { |
+ /*start*/NULL, |
+ /*end*/NULL, |
+ /*tag*/[](FamilyData* self, const char* tag, const char** attributes) -> const TagHandler* { |
+ size_t len = strlen(tag); |
+ if (MEMEQ("family", tag, len)) { |
+ return &familyHandler; |
+ } |
+ return NULL; |
+ }, |
+ /*chars*/NULL, |
+}; |
- } else if (MEMEQ("index", name, nameLen)) { |
- if (!parse_non_negative_integer(value, &newFileInfo.fIndex)) { |
- SK_FONTCONFIGPARSER_WARNING("'%s' is an invalid index", value); |
+} // namespace jbParser |
+ |
+static const TagHandler topLevelHandler = { |
+ /*start*/NULL, |
+ /*end*/NULL, |
+ /*tag*/[](FamilyData* self, const char* tag, const char** attributes) -> const TagHandler* { |
+ size_t len = strlen(tag); |
+ if (MEMEQ("familyset", tag, len)) { |
+ // 'version' (non-negative integer) [default 0] |
+ for (size_t i = 0; ATTS_NON_NULL(attributes, i); i += 2) { |
+ const char* name = attributes[i]; |
+ size_t nameLen = strlen(name); |
+ if (MEMEQ("version", name, nameLen)) { |
+ const char* value = attributes[i+1]; |
+ if (parse_non_negative_integer(value, &self->fVersion)) { |
+ if (self->fVersion >= 21) { |
+ return &lmpParser::familySetHandler; |
+ } |
+ } |
} |
} |
+ return &jbParser::familySetHandler; |
} |
- } |
- self->fCurrentFontInfo = &newFileInfo; |
- XML_SetCharacterDataHandler(self->fParser, text_handler); |
-} |
+ return NULL; |
+ }, |
+ /*chars*/NULL, |
+}; |
-/** |
- * 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) { |
+static void XMLCALL start_element_handler(void *data, const char *tag, const char **attributes) { |
FamilyData* self = static_cast<FamilyData*>(data); |
- size_t len = strlen(tag); |
- if (MEMEQ("familyset", tag, len)) { |
- // The <familyset> element has an optional 'version' (non-negative integer) attribute. |
- 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> element has an optional 'order' (non-negative integer) attribute. |
- // 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; |
+ |
+ if (!self->fSkip) { |
+ const TagHandler* parent = self->fHandler.top(); |
+ const TagHandler* child = parent->tag ? parent->tag(self, tag, attributes) : NULL; |
+ if (child) { |
+ if (child->start) { |
+ child->start(self, tag, attributes); |
} |
+ self->fHandler.push(child); |
+ XML_SetCharacterDataHandler(self->fParser, child->chars); |
+ } else { |
+ SK_FONTCONFIGPARSER_WARNING("'%s' tag not recognized, skipping", tag); |
+ XML_SetCharacterDataHandler(self->fParser, NULL); |
+ self->fSkip = self->fDepth; |
} |
- } 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); |
} |
+ |
+ ++self->fDepth; |
} |
-/** |
- * 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<FamilyData*>(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); |
+ --self->fDepth; |
+ |
+ if (!self->fSkip) { |
+ const TagHandler* child = self->fHandler.top(); |
+ if (child->end) { |
+ child->end(self, tag); |
+ } |
+ self->fHandler.pop(); |
+ const TagHandler* parent = self->fHandler.top(); |
+ XML_SetCharacterDataHandler(self->fParser, parent->chars); |
} |
-} |
-} // namespace jbParser |
+ if (self->fSkip == self->fDepth) { |
+ self->fSkip = 0; |
+ const TagHandler* parent = self->fHandler.top(); |
+ XML_SetCharacterDataHandler(self->fParser, parent->chars); |
+ } |
+} |
static void XMLCALL xml_entity_decl_handler(void *data, |
const XML_Char *entityName, |
@@ -485,14 +588,14 @@ static int parse_config_file(const char* filename, SkTDArray<FontFamily*>& famil |
return -1; |
} |
- FamilyData self(parser, families, basePath, isFallback, filename); |
+ FamilyData self(parser, families, basePath, isFallback, filename, &topLevelHandler); |
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); |
+ XML_SetElementHandler(parser, start_element_handler, 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. |