| /* |
| * Copyright 2015 Google Inc. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #include "cobalt/renderer/rasterizer/skia/skia/src/ports/SkFontConfigParser_cobalt.h" |
| |
| #include <libxml/parser.h> |
| #include <limits> |
| #include <stack> |
| #include <string> |
| |
| #include "base/memory/scoped_ptr.h" |
| #include "base/string_util.h" |
| #include "base/stringprintf.h" |
| #include "SkOSFile.h" |
| #include "SkTSearch.h" |
| |
| namespace { |
| |
| const char* kSystemFontsFile = "fonts.xml"; |
| |
| ///////////////////////////////////////////////////////////////////////////// |
| // Helpers |
| ///////////////////////////////////////////////////////////////////////////// |
| |
| std::string StringPrintVAndTrim(const char* message, va_list arguments) { |
| const std::string formatted_message = base::StringPrintV(message, arguments); |
| |
| std::string trimmed_message; |
| TrimWhitespace(formatted_message, TRIM_ALL, &trimmed_message); |
| |
| return trimmed_message; |
| } |
| |
| // https://www.w3.org/TR/html-markup/datatypes.html#common.data.integer.non-negative-def |
| template <typename T> |
| bool ParseNonNegativeInteger(const char* s, T* value) { |
| SK_COMPILE_ASSERT(std::numeric_limits<T>::is_integer, T_must_be_integer); |
| const T n_max = std::numeric_limits<T>::max() / 10; |
| const T d_max = std::numeric_limits<T>::max() - (n_max * 10); |
| T n = 0; |
| for (; *s; ++s) { |
| // Check if digit |
| if (!IsAsciiDigit(*s)) { |
| return false; |
| } |
| int d = *s - '0'; |
| // Check for overflow |
| if (n > n_max || (n == n_max && d > d_max)) { |
| SkDebugf("---- ParseNonNegativeInteger error: overflow)"); |
| return false; |
| } |
| n = (n * 10) + d; |
| } |
| *value = n; |
| return true; |
| } |
| |
| // The page range list is a comma-delimited list of character page ranges. Each |
| // page range can either consist of either a single integer value, or a pair of |
| // values separated by a hyphen (representing the min and max value of the |
| // range). All page values must fall between 0 and kMaxPageValue. Additionally, |
| // the page ranges must be provided in ascending order. Any failure to meet |
| // these expectations will result in a parsing error. |
| bool ParsePageRangeList(const char* s, font_character_map::PageRanges* ranges) { |
| const int16 n_max = font_character_map::kMaxPageValue / 10; |
| const int16 d_max = font_character_map::kMaxPageValue - (n_max * 10); |
| |
| int16 last_max = -1; |
| |
| while (*s) { |
| font_character_map::PageRange range_value; |
| |
| // Skip whitespace |
| while (IsAsciiWhitespace(*s)) { |
| ++s; |
| if (!*s) { |
| return true; |
| } |
| } |
| |
| for (int i = 0; i <= 1; ++i) { |
| if (!IsAsciiDigit(*s)) { |
| SkDebugf("---- ParsePageRangeList error: non-ascii digit page range"); |
| return false; |
| } |
| |
| int16 n = 0; |
| for (; *s; ++s) { |
| if (!IsAsciiDigit(*s)) { |
| break; |
| } |
| int d = *s - '0'; |
| // Check for overflow |
| if (n > n_max || (n == n_max && d > d_max)) { |
| SkDebugf("---- ParsePageRangeList error: page range overflow"); |
| return false; |
| } |
| |
| n = (n * 10) + d; |
| } |
| |
| if (i == 0) { |
| // Verify that this new range is larger than the previously encountered |
| // max. Ranges must appear in order. If it isn't, then the parsing has |
| // failed. |
| if (last_max >= n) { |
| SkDebugf("---- ParsePageRangeList error: pages unordered"); |
| return false; |
| } |
| |
| range_value.first = n; |
| |
| if (*s && *s == '-') { |
| ++s; |
| continue; |
| } else { |
| last_max = n; |
| range_value.second = n; |
| ranges->push_back(range_value); |
| break; |
| } |
| } else { |
| if (range_value.first <= n) { |
| last_max = n; |
| range_value.second = n; |
| ranges->push_back(range_value); |
| } else { |
| SkDebugf("---- ParsePageRangeList error: page range flipped"); |
| return false; |
| } |
| } |
| } |
| |
| if (*s) { |
| // Skip whitespace |
| while (IsAsciiWhitespace(*s)) { |
| ++s; |
| if (!*s) { |
| return true; |
| } |
| } |
| |
| if (*s == ',') { |
| ++s; |
| } else { |
| SkDebugf("---- ParsePageRangeList error: invalid character"); |
| return false; |
| } |
| } |
| } |
| |
| return true; |
| } |
| |
| ///////////////////////////////////////////////////////////////////////////// |
| // Libxml SAX Handlers |
| ///////////////////////////////////////////////////////////////////////////// |
| |
| typedef unsigned char xmlChar; |
| |
| void StartElement(void* context, const xmlChar* name, |
| const xmlChar** attribute_pairs); |
| void EndElement(void* context, const xmlChar* name); |
| void Characters(void* context, const xmlChar* ch, int len); |
| void ParserWarning(void* context, const char* message, ...); |
| void ParserError(void* context, const char* message, ...); |
| void ParserFatal(void* context, const char* message, ...); |
| |
| xmlSAXHandler xml_sax_handler = { |
| NULL, /* internalSubset */ |
| NULL, /* isStandalone */ |
| NULL, /* hasInternalSubset */ |
| NULL, /* hasExternalSubset */ |
| NULL, /* resolveEntity */ |
| NULL, /* getEntity */ |
| NULL, /* entityDecl */ |
| NULL, /* notationDecl */ |
| NULL, /* attributeDecl */ |
| NULL, /* elementDecl */ |
| NULL, /* unparsedEntityDecl */ |
| NULL, /* setDocumentLocator */ |
| NULL, /* startDocument */ |
| NULL, /* endDocument */ |
| &StartElement, /* startElement */ |
| &EndElement, /* endElement */ |
| NULL, /* reference */ |
| &Characters, /* characters */ |
| NULL, /* ignorableWhitespace */ |
| NULL, /* processingInstruction */ |
| NULL, /* comment */ |
| &ParserWarning, /* xmlParserWarning */ |
| &ParserError, /* xmlParserError */ |
| &ParserFatal, /* xmlParserFatalError */ |
| NULL, /* getParameterEntity */ |
| NULL, /* cdataBlock */ |
| NULL, /* externalSubset */ |
| 1, /* initialized */ |
| NULL, /* private */ |
| NULL, /* startElementNsSAX2Func */ |
| NULL, /* endElementNsSAX2Func */ |
| NULL /* xmlStructuredErrorFunc */ |
| }; |
| |
| enum ElementType { |
| kFamilyElementType, |
| kFontElementType, |
| kAliasElementType, |
| kOtherElementType, |
| }; |
| |
| // 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 { |
| explicit FamilyData(SkTDArray<FontFamily*>* families_array) |
| : families(families_array), current_font_info(NULL) {} |
| |
| // The array that each family is put into as it is parsed |
| SkTDArray<FontFamily*>* families; |
| // The current family being created. FamilyData owns this object while it is |
| // not NULL. |
| scoped_ptr<FontFamily> current_family; |
| // The current fontInfo being created. It is owned by FontFamily. |
| FontFileInfo* current_font_info; |
| std::stack<ElementType> element_stack; |
| }; |
| |
| void FamilyElementHandler(FontFamily* family, const char** attributes) { |
| if (attributes == NULL) { |
| return; |
| } |
| |
| // A <family> tag must have a canonical name attribute, a fallback attribute |
| // set to true, or both in order to be usable. If it is a fallback family, |
| // then it may have lang and pages attributes for use during fallback. |
| for (size_t i = 0; attributes[i] != NULL && attributes[i + 1] != NULL; |
| i += 2) { |
| const char* name = attributes[i]; |
| const char* value = attributes[i + 1]; |
| size_t name_len = strlen(name); |
| |
| if (name_len == 4 && strncmp(name, "name", name_len) == 0) { |
| SkAutoAsciiToLC to_lowercase(value); |
| family->names.push_back().set(to_lowercase.lc()); |
| } else if (name_len == 4 && strncmp("lang", name, name_len) == 0) { |
| family->language = SkLanguage(value); |
| } else if (name_len == 5 && strncmp("pages", name, name_len) == 0) { |
| if (!ParsePageRangeList(value, &family->page_ranges)) { |
| SkDebugf("---- Page ranges %s (INVALID)", value); |
| family->page_ranges.reset(); |
| } |
| } else if (name_len == 8 && strncmp("fallback", name, name_len) == 0) { |
| family->is_fallback_font = |
| strcmp("true", value) == 0 || strcmp("1", value) == 0; |
| } |
| } |
| } |
| |
| void FontElementHandler(FontFileInfo* file, const char** attributes) { |
| DCHECK(file != NULL); |
| |
| // A <font> should have following attributes: |
| // weight (integer), style (normal, italic), font_name (string), and |
| // postscript_name (string). |
| // The element should contain a filename. |
| |
| enum SeenAttributeFlags { |
| kSeenNone = 0, |
| kSeenFontFullName = 1, |
| kSeenFontPostscriptName = 1 << 1, |
| kSeenWeight = 1 << 2, |
| kSeenStyle = 1 << 3 |
| }; |
| |
| uint32_t seen_attributes_flag = kSeenNone; |
| for (size_t i = 0; attributes[i] != NULL && attributes[i + 1] != NULL; |
| i += 2) { |
| const char* name = attributes[i]; |
| const char* value = attributes[i + 1]; |
| |
| switch (strlen(name)) { |
| case 9: |
| if (strncmp("font_name", name, 9) == 0) { |
| file->full_font_name = value; |
| seen_attributes_flag |= kSeenFontFullName; |
| continue; |
| } |
| break; |
| case 15: |
| if (strncmp("postscript_name", name, 15) == 0) { |
| file->postscript_name = value; |
| seen_attributes_flag |= kSeenFontPostscriptName; |
| continue; |
| } |
| break; |
| case 6: |
| if (strncmp("weight", name, 6) == 0) { |
| if (!ParseNonNegativeInteger(value, &file->weight)) { |
| DLOG(WARNING) << "Invalid font weight [" << value << "]"; |
| file->weight = 0; |
| } else { |
| seen_attributes_flag |= kSeenWeight; |
| } |
| continue; |
| } |
| break; |
| case 5: |
| if (strncmp("style", name, 5) == 0) { |
| if (strncmp("italic", value, 6) == 0) { |
| file->style = FontFileInfo::kItalic_FontStyle; |
| seen_attributes_flag |= kSeenStyle; |
| continue; |
| } else if (strncmp("normal", value, 6) == 0) { |
| file->style = FontFileInfo::kNormal_FontStyle; |
| seen_attributes_flag |= kSeenStyle; |
| continue; |
| } else { |
| NOTREACHED() << "Unsupported style [" << value << "]"; |
| } |
| } |
| break; |
| default: |
| break; |
| } |
| |
| NOTREACHED() << "Unsupported attribute [" << name << "]"; |
| } |
| |
| DCHECK_EQ(seen_attributes_flag, kSeenFontFullName | kSeenFontPostscriptName | |
| kSeenWeight | kSeenStyle); |
| DCHECK(!file->full_font_name.isEmpty()); |
| DCHECK(!file->postscript_name.isEmpty()); |
| } |
| |
| FontFamily* FindFamily(FamilyData* family_data, const char* family_name) { |
| size_t name_len = strlen(family_name); |
| for (int i = 0; i < family_data->families->count(); i++) { |
| FontFamily* candidate = (*family_data->families)[i]; |
| for (int j = 0; j < candidate->names.count(); j++) { |
| if (!strncmp(candidate->names[j].c_str(), family_name, name_len) && |
| name_len == strlen(candidate->names[j].c_str())) { |
| return candidate; |
| } |
| } |
| } |
| |
| return NULL; |
| } |
| |
| void AliasElementHandler(FamilyData* family_data, const char** attributes) { |
| // An <alias> 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 <family>. |
| // If it *does* have a weight, it names the <font>(s) of a specific weight |
| // from a <family>. |
| |
| SkString alias_name; |
| SkString to; |
| for (size_t i = 0; attributes[i] != NULL && attributes[i + 1] != NULL; |
| i += 2) { |
| const char* name = attributes[i]; |
| const char* value = attributes[i + 1]; |
| size_t name_len = strlen(name); |
| if (name_len == 4 && strncmp("name", name, name_len) == 0) { |
| SkAutoAsciiToLC to_lowercase(value); |
| alias_name.set(to_lowercase.lc()); |
| } else if (name_len == 2 && strncmp("to", name, name_len) == 0) { |
| to.set(value); |
| } |
| } |
| |
| // Assumes that the named family is already declared |
| FontFamily* target_family = FindFamily(family_data, to.c_str()); |
| if (!target_family) { |
| SkDebugf("---- Font alias target %s (NOT FOUND)", to.c_str()); |
| return; |
| } else if (alias_name.size() == 0) { |
| SkDebugf("---- Font alias name for %s (NOT SET)", to.c_str()); |
| return; |
| } |
| |
| target_family->names.push_back().set(alias_name); |
| } |
| |
| bool FindWeight400(FontFamily* family) { |
| for (int i = 0; i < family->fonts.count(); i++) { |
| if (family->fonts[i].weight == 400) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| bool DesiredWeight(int weight) { return (weight == 400 || weight == 700); } |
| |
| int CountDesiredWeight(FontFamily* family) { |
| int count = 0; |
| for (int i = 0; i < family->fonts.count(); i++) { |
| if (DesiredWeight(family->fonts[i].weight)) { |
| count++; |
| } |
| } |
| return count; |
| } |
| |
| // To meet Skia's expectations, any family that contains weight=400 |
| // fonts should *only* contain {400,700} |
| void PurgeUndesiredWeights(FontFamily* family) { |
| int count = CountDesiredWeight(family); |
| for (int i = 1, j = 0; i < family->fonts.count(); i++) { |
| if (DesiredWeight(family->fonts[j].weight)) { |
| j++; |
| } |
| if ((i != j) && DesiredWeight(family->fonts[i].weight)) { |
| family->fonts[j] = family->fonts[i]; |
| } |
| } |
| family->fonts.resize_back(count); |
| } |
| |
| void FamilySetElementEndHandler(FamilyData* familyData) { |
| for (int i = 0; i < familyData->families->count(); i++) { |
| if (FindWeight400((*familyData->families)[i])) { |
| PurgeUndesiredWeights((*familyData->families)[i]); |
| } |
| } |
| } |
| |
| void StartElement(void* data, const xmlChar* xml_tag, |
| const xmlChar** xml_attribute_pairs) { |
| FamilyData* family_data = reinterpret_cast<FamilyData*>(data); |
| const char* tag = reinterpret_cast<const char*>(xml_tag); |
| const char** attribute_pairs = |
| reinterpret_cast<const char**>(xml_attribute_pairs); |
| size_t tag_len = strlen(tag); |
| |
| if (tag_len == 6 && strncmp("family", tag, tag_len) == 0) { |
| family_data->element_stack.push(kFamilyElementType); |
| family_data->current_family = make_scoped_ptr(new FontFamily()); |
| FamilyElementHandler(family_data->current_family.get(), attribute_pairs); |
| } else if (tag_len == 4 && strncmp("font", tag, tag_len) == 0) { |
| family_data->element_stack.push(kFontElementType); |
| FontFileInfo* file = &family_data->current_family->fonts.push_back(); |
| family_data->current_font_info = file; |
| FontElementHandler(file, attribute_pairs); |
| } else if (tag_len == 5 && strncmp("alias", tag, tag_len) == 0) { |
| family_data->element_stack.push(kAliasElementType); |
| AliasElementHandler(family_data, attribute_pairs); |
| } else { |
| family_data->element_stack.push(kOtherElementType); |
| } |
| } |
| |
| void EndElement(void* data, const xmlChar* xml_tag) { |
| FamilyData* family_data = reinterpret_cast<FamilyData*>(data); |
| const char* tag = reinterpret_cast<const char*>(xml_tag); |
| size_t tag_len = strlen(tag); |
| |
| if (tag_len == 9 && strncmp("familyset", tag, tag_len) == 0) { |
| FamilySetElementEndHandler(family_data); |
| } else if (tag_len == 6 && strncmp("family", tag, tag_len) == 0) { |
| if (family_data->current_family != NULL) { |
| *family_data->families->append() = family_data->current_family.release(); |
| } else { |
| SkDebugf("---- Encountered end family tag with no current family"); |
| } |
| } |
| |
| family_data->element_stack.pop(); |
| } |
| |
| void Characters(void* data, const xmlChar* xml_characters, int len) { |
| FamilyData* family_data = reinterpret_cast<FamilyData*>(data); |
| const char* characters = reinterpret_cast<const char*>(xml_characters); |
| |
| if (family_data->element_stack.size() > 0 && |
| family_data->element_stack.top() == kFontElementType) { |
| family_data->current_font_info->file_name.set(characters, len); |
| } |
| } |
| |
| void ParserWarning(void* context, const char* message, ...) { |
| va_list arguments; |
| va_start(arguments, message); |
| |
| SkDebugf("---- Parsing warning: %s", |
| StringPrintVAndTrim(message, arguments).c_str()); |
| } |
| |
| void ParserError(void* context, const char* message, ...) { |
| va_list arguments; |
| va_start(arguments, message); |
| |
| SkDebugf("---- Parsing error: %s", |
| StringPrintVAndTrim(message, arguments).c_str()); |
| } |
| |
| void ParserFatal(void* context, const char* message, ...) { |
| va_list arguments; |
| va_start(arguments, message); |
| |
| SkDebugf("---- Parsing fatal error: %s", |
| StringPrintVAndTrim(message, arguments).c_str()); |
| } |
| |
| // This function parses the given filename and stores the results in the given |
| // families array. |
| void ParseConfigFile(const char* directory, const char* filename, |
| SkTDArray<FontFamily*>* families) { |
| SkString file_path = SkOSPath::Join(directory, filename); |
| FILE* file = fopen(file_path.c_str(), "r"); |
| |
| if (NULL == file) { |
| SkDebugf("---- Failed to open %s", file_path.c_str()); |
| return; |
| } |
| |
| FamilyData family_data(families); |
| |
| xmlParserCtxtPtr xml_parser_context = |
| xmlCreatePushParserCtxt(&xml_sax_handler, &family_data, NULL, 0, NULL); |
| |
| if (!xml_parser_context) { |
| SkDebugf("---- Unable to create parsing context!"); |
| return; |
| } |
| |
| char buffer[512]; |
| bool done = false; |
| while (!done) { |
| fgets(buffer, sizeof(buffer), file); |
| size_t len = strlen(buffer); |
| if (feof(file) != 0) { |
| done = true; |
| } |
| xmlParseChunk(xml_parser_context, buffer, static_cast<int>(len), |
| 0 /*do not terminate*/); |
| } |
| xmlParseChunk(xml_parser_context, NULL, 0, 1 /*terminate*/); |
| xmlFreeParserCtxt(xml_parser_context); |
| |
| fclose(file); |
| } |
| |
| void GetSystemFontFamilies(const char* directory, |
| SkTDArray<FontFamily*>* font_families) { |
| ParseConfigFile(directory, kSystemFontsFile, font_families); |
| } |
| |
| } // namespace |
| |
| namespace SkFontConfigParser { |
| |
| // Loads data on font families from the configuration file. The resulting data |
| // is returned in the given fontFamilies array. |
| void GetFontFamilies(const char* directory, |
| SkTDArray<FontFamily*>* font_families) { |
| GetSystemFontFamilies(directory, font_families); |
| } |
| |
| } // namespace SkFontConfigParser |