#include "lunasvg.h" #include "svgelement.h" #include "svgparserutils.h" #include namespace lunasvg { struct SimpleSelector; using Selector = std::vector; using SelectorList = std::vector; struct AttributeSelector { enum class MatchType { None, Equals, Contains, Includes, StartsWith, EndsWith, DashEquals }; MatchType matchType{MatchType::None}; PropertyID id{PropertyID::Unknown}; std::string value; }; struct PseudoClassSelector { enum class Type { Unknown, Empty, Root, Is, Not, FirstChild, LastChild, OnlyChild, FirstOfType, LastOfType, OnlyOfType }; Type type{Type::Unknown}; SelectorList subSelectors; }; struct SimpleSelector { enum class Combinator { None, Descendant, Child, DirectAdjacent, InDirectAdjacent }; explicit SimpleSelector(Combinator combinator) : combinator(combinator) {} Combinator combinator{Combinator::Descendant}; ElementID id{ElementID::Star}; std::vector attributeSelectors; std::vector pseudoClassSelectors; }; struct Declaration { int specificity; PropertyID id; std::string value; }; using DeclarationList = std::vector; struct Rule { SelectorList selectors; DeclarationList declarations; }; class RuleData { public: RuleData(const Selector& selector, const DeclarationList& declarations, size_t specificity, size_t position) : m_selector(selector), m_declarations(declarations), m_specificity(specificity), m_position(position) {} bool isLessThan(const RuleData& rule) const { return std::tie(m_specificity, m_position) < std::tie(rule.m_specificity, rule.m_position); } const Selector& selector() const { return m_selector; } const DeclarationList& declarations() const { return m_declarations; } size_t specificity() const { return m_specificity; } size_t position() const { return m_position; } bool match(const SVGElement* element) const; private: Selector m_selector; DeclarationList m_declarations; size_t m_specificity; size_t m_position; }; inline bool operator<(const RuleData& a, const RuleData& b) { return a.isLessThan(b); } using RuleDataList = std::vector; constexpr bool equals(std::string_view value, std::string_view subvalue) { return value.compare(subvalue) == 0; } constexpr bool contains(std::string_view value, std::string_view subvalue) { return value.find(subvalue) != std::string_view::npos; } constexpr bool includes(std::string_view value, std::string_view subvalue) { if(subvalue.empty() || subvalue.length() > value.length()) return false; std::string_view input(value); while(!input.empty()) { skipOptionalSpaces(input); std::string_view start(input); while(!input.empty() && !IS_WS(input.front())) input.remove_prefix(1); if(subvalue == start.substr(0, start.length() - input.length())) { return true; } } return false; } constexpr bool startswith(std::string_view value, std::string_view subvalue) { if(subvalue.empty() || subvalue.length() > value.length()) return false; return subvalue == value.substr(0, subvalue.size()); } constexpr bool endswith(std::string_view value, std::string_view subvalue) { if(subvalue.empty() || subvalue.length() > value.length()) return false; return subvalue == value.substr(value.size() - subvalue.size(), subvalue.size()); } constexpr bool dashequals(std::string_view value, std::string_view subvalue) { if(startswith(value, subvalue)) return (value.length() == subvalue.length() || value.at(subvalue.length()) == '-'); return false; } static bool matchAttributeSelector(const AttributeSelector& selector, const SVGElement* element) { const auto& value = element->getAttribute(selector.id); if(selector.matchType == AttributeSelector::MatchType::None) return !value.empty(); if(selector.matchType == AttributeSelector::MatchType::Equals) return equals(value, selector.value); if(selector.matchType == AttributeSelector::MatchType::Contains) return contains(value, selector.value); if(selector.matchType == AttributeSelector::MatchType::Includes) return includes(value, selector.value); if(selector.matchType == AttributeSelector::MatchType::StartsWith) return startswith(value, selector.value); if(selector.matchType == AttributeSelector::MatchType::EndsWith) return endswith(value, selector.value); if(selector.matchType == AttributeSelector::MatchType::DashEquals) return dashequals(value, selector.value); return false; } static bool matchSimpleSelector(const SimpleSelector& selector, const SVGElement* element); static bool matchPseudoClassSelector(const PseudoClassSelector& selector, const SVGElement* element) { if(selector.type == PseudoClassSelector::Type::Empty) return element->children().empty(); if(selector.type == PseudoClassSelector::Type::Root) return element->isRootElement(); if(selector.type == PseudoClassSelector::Type::Is) { for(const auto& subSelector : selector.subSelectors) { for(const auto& simpleSelector : subSelector) { if(!matchSimpleSelector(simpleSelector, element)) { return false; } } } return true; } if(selector.type == PseudoClassSelector::Type::Not) { for(const auto& subSelector : selector.subSelectors) { for(const auto& simpleSelector : subSelector) { if(matchSimpleSelector(simpleSelector, element)) { return false; } } } return true; } if(selector.type == PseudoClassSelector::Type::FirstChild) return !element->previousElement(); if(selector.type == PseudoClassSelector::Type::LastChild) return !element->nextElement(); if(selector.type == PseudoClassSelector::Type::OnlyChild) return !(element->previousElement() || element->nextElement()); if(selector.type == PseudoClassSelector::Type::FirstOfType) { auto sibling = element->previousElement(); while(sibling) { if(sibling->id() == element->id()) return false; sibling = sibling->previousElement(); } return true; } if(selector.type == PseudoClassSelector::Type::LastOfType) { auto sibling = element->nextElement(); while(sibling) { if(sibling->id() == element->id()) return false; sibling = sibling->nextElement(); } return true; } return false; } static bool matchSimpleSelector(const SimpleSelector& selector, const SVGElement* element) { if(selector.id != ElementID::Star && selector.id != element->id()) return false; for(const auto& sel : selector.attributeSelectors) { if(!matchAttributeSelector(sel, element)) { return false; } } for(const auto& sel : selector.pseudoClassSelectors) { if(!matchPseudoClassSelector(sel, element)) { return false; } } return true; } static bool matchSelector(const Selector& selector, const SVGElement* element) { if(selector.empty()) return false; auto it = selector.rbegin(); auto end = selector.rend(); if(!matchSimpleSelector(*it, element)) { return false; } auto combinator = it->combinator; ++it; while(it != end) { switch(combinator) { case SimpleSelector::Combinator::Child: case SimpleSelector::Combinator::Descendant: element = element->parentElement(); break; case SimpleSelector::Combinator::DirectAdjacent: case SimpleSelector::Combinator::InDirectAdjacent: element = element->previousElement(); break; case SimpleSelector::Combinator::None: assert(false); } if(element == nullptr) return false; if(matchSimpleSelector(*it, element)) { combinator = it->combinator; ++it; } else if(combinator != SimpleSelector::Combinator::Descendant && combinator != SimpleSelector::Combinator::InDirectAdjacent) { return false; } } return true; } bool RuleData::match(const SVGElement* element) const { return matchSelector(m_selector, element); } constexpr bool IS_CSS_STARTNAMECHAR(int c) { return IS_ALPHA(c) || c == '_' || c == '-'; } constexpr bool IS_CSS_NAMECHAR(int c) { return IS_CSS_STARTNAMECHAR(c) || IS_NUM(c); } inline bool readCSSIdentifier(std::string_view& input, std::string& output) { if(input.empty() || !IS_CSS_STARTNAMECHAR(input.front())) return false; output.clear(); do { output.push_back(input.front()); input.remove_prefix(1); } while(!input.empty() && IS_CSS_NAMECHAR(input.front())); return true; } static bool parseTagSelector(std::string_view& input, SimpleSelector& simpleSelector) { std::string name; if(skipDelimiter(input, '*')) simpleSelector.id = ElementID::Star; else if(readCSSIdentifier(input, name)) simpleSelector.id = elementid(name); else return false; return true; } static bool parseIdSelector(std::string_view& input, SimpleSelector& simpleSelector) { AttributeSelector a; a.id = PropertyID::Id; a.matchType = AttributeSelector::MatchType::Equals; if(!readCSSIdentifier(input, a.value)) return false; simpleSelector.attributeSelectors.push_back(std::move(a)); return true; } static bool parseClassSelector(std::string_view& input, SimpleSelector& simpleSelector) { AttributeSelector a; a.id = PropertyID::Class; a.matchType = AttributeSelector::MatchType::Includes; if(!readCSSIdentifier(input, a.value)) return false; simpleSelector.attributeSelectors.push_back(std::move(a)); return true; } static bool parseAttributeSelector(std::string_view& input, SimpleSelector& simpleSelector) { std::string name; skipOptionalSpaces(input); if(!readCSSIdentifier(input, name)) return false; AttributeSelector a; a.id = propertyid(name); a.matchType = AttributeSelector::MatchType::None; if(skipDelimiter(input, '=')) a.matchType = AttributeSelector::MatchType::Equals; else if(skipString(input, "*=")) a.matchType = AttributeSelector::MatchType::Contains; else if(skipString(input, "~=")) a.matchType = AttributeSelector::MatchType::Includes; else if(skipString(input, "^=")) a.matchType = AttributeSelector::MatchType::StartsWith; else if(skipString(input, "$=")) a.matchType = AttributeSelector::MatchType::EndsWith; else if(skipString(input, "|=")) a.matchType = AttributeSelector::MatchType::DashEquals; if(a.matchType != AttributeSelector::MatchType::None) { skipOptionalSpaces(input); if(!readCSSIdentifier(input, a.value)) { if(input.empty() || !(input.front() == '\"' || input.front() == '\'')) return false; auto quote = input.front(); input.remove_prefix(1); auto n = input.find(quote); if(n == std::string_view::npos) return false; a.value.assign(input.substr(0, n)); input.remove_prefix(n + 1); } } skipOptionalSpaces(input); if(!skipDelimiter(input, ']')) return false; simpleSelector.attributeSelectors.push_back(std::move(a)); return true; } static bool parseSelectors(std::string_view& input, SelectorList& selectors); static bool parsePseudoClassSelector(std::string_view& input, SimpleSelector& simpleSelector) { std::string name; if(!readCSSIdentifier(input, name)) return false; PseudoClassSelector selector; if(name.compare("empty") == 0) selector.type = PseudoClassSelector::Type::Empty; else if(name.compare("root") == 0) selector.type = PseudoClassSelector::Type::Root; else if(name.compare("not") == 0) selector.type = PseudoClassSelector::Type::Not; else if(name.compare("first-child") == 0) selector.type = PseudoClassSelector::Type::FirstChild; else if(name.compare("last-child") == 0) selector.type = PseudoClassSelector::Type::LastChild; else if(name.compare("only-child") == 0) selector.type = PseudoClassSelector::Type::OnlyChild; else if(name.compare("first-of-type") == 0) selector.type = PseudoClassSelector::Type::FirstOfType; else if(name.compare("last-of-type") == 0) selector.type = PseudoClassSelector::Type::LastOfType; else if(name.compare("only-of-type") == 0) selector.type = PseudoClassSelector::Type::OnlyOfType; if(selector.type == PseudoClassSelector::Type::Is || selector.type == PseudoClassSelector::Type::Not) { skipOptionalSpaces(input); if(!skipDelimiter(input, '(')) return false; skipOptionalSpaces(input); if(!parseSelectors(input, selector.subSelectors)) return false; skipOptionalSpaces(input); if(!skipDelimiter(input, ')')) { return false; } } simpleSelector.pseudoClassSelectors.push_back(std::move(selector)); return true; } static bool parseSimpleSelector(std::string_view& input, SimpleSelector& simpleSelector, bool& failed) { auto consumed = parseTagSelector(input, simpleSelector); do { if(skipDelimiter(input, '#')) failed = !parseIdSelector(input, simpleSelector); else if(skipDelimiter(input, '.')) failed = !parseClassSelector(input, simpleSelector); else if(skipDelimiter(input, '[')) failed = !parseAttributeSelector(input, simpleSelector); else if(skipDelimiter(input, ':')) failed = !parsePseudoClassSelector(input, simpleSelector); else break; consumed = true; } while(!failed); return consumed && !failed; } static bool parseCombinator(std::string_view& input, SimpleSelector::Combinator& combinator) { combinator = SimpleSelector::Combinator::None; while(!input.empty() && IS_WS(input.front())) { combinator = SimpleSelector::Combinator::Descendant; input.remove_prefix(1); } if(skipDelimiterAndOptionalSpaces(input, '>')) combinator = SimpleSelector::Combinator::Child; else if(skipDelimiterAndOptionalSpaces(input, '+')) combinator = SimpleSelector::Combinator::DirectAdjacent; else if(skipDelimiterAndOptionalSpaces(input, '~')) combinator = SimpleSelector::Combinator::InDirectAdjacent; return combinator != SimpleSelector::Combinator::None; } static bool parseSelector(std::string_view& input, Selector& selector) { auto combinator = SimpleSelector::Combinator::None; do { bool failed = false; SimpleSelector simpleSelector(combinator); if(!parseSimpleSelector(input, simpleSelector, failed)) return !failed && (combinator == SimpleSelector::Combinator::Descendant); selector.push_back(std::move(simpleSelector)); } while(parseCombinator(input, combinator)); return true; } static bool parseSelectors(std::string_view& input, SelectorList& selectors) { do { Selector selector; if(!parseSelector(input, selector)) return false; selectors.push_back(std::move(selector)); } while(skipDelimiterAndOptionalSpaces(input, ',')); return true; } static bool parseDeclarations(std::string_view& input, DeclarationList& declarations) { if(!skipDelimiter(input, '{')) return false; skipOptionalSpaces(input); do { std::string name; if(!readCSSIdentifier(input, name)) return false; skipOptionalSpaces(input); if(!skipDelimiter(input, ':')) return false; skipOptionalSpaces(input); std::string_view value(input); while(!input.empty() && !(input.front() == '!' || input.front() == ';' || input.front() == '}')) input.remove_prefix(1); value.remove_suffix(input.length()); stripTrailingSpaces(value); Declaration declaration; declaration.specificity = 0x10; declaration.id = csspropertyid(name); declaration.value.assign(value); if(skipDelimiter(input, '!')) { skipOptionalSpaces(input); if(!skipString(input, "important")) return false; declaration.specificity = 0x1000; } if(declaration.id != PropertyID::Unknown) declarations.push_back(std::move(declaration)); skipOptionalSpacesOrDelimiter(input, ';'); } while(!input.empty() && input.front() != '}'); return skipDelimiter(input, '}'); } static bool parseRule(std::string_view& input, Rule& rule) { if(!parseSelectors(input, rule.selectors)) return false; return parseDeclarations(input, rule.declarations); } static RuleDataList parseStyleSheet(std::string_view input) { RuleDataList rules; while(!input.empty()) { skipOptionalSpaces(input); if(skipDelimiter(input, '@')) { int depth = 0; while(!input.empty()) { auto ch = input.front(); input.remove_prefix(1); if(ch == ';' && depth == 0) break; if(ch == '{') ++depth; else if(ch == '}' && depth > 0) { if(depth == 1) break; --depth; } } continue; } Rule rule; if(!parseRule(input, rule)) break; for(const auto& selector : rule.selectors) { size_t specificity = 0; for(const auto& simpleSelector : selector) { specificity += (simpleSelector.id == ElementID::Star) ? 0x0 : 0x1; for(const auto& attributeSelector : simpleSelector.attributeSelectors) { specificity += (attributeSelector.id == PropertyID::Id) ? 0x10000 : 0x100; } for(const auto& pseudoClassSelector : simpleSelector.pseudoClassSelectors) { specificity += 0x100; } } rules.emplace_back(selector, rule.declarations, specificity, rules.size()); } } return rules; } static SelectorList parseQuerySelectors(std::string_view input) { SelectorList selectors; stripLeadingAndTrailingSpaces(input); if(!parseSelectors(input, selectors) || !input.empty()) { return SelectorList(); } return selectors; } inline void parseInlineStyle(std::string_view input, SVGElement* element) { std::string name; skipOptionalSpaces(input); while(readCSSIdentifier(input, name)) { skipOptionalSpaces(input); if(!skipDelimiter(input, ':')) return; std::string value; while(!input.empty() && input.front() != ';') { value.push_back(input.front()); input.remove_prefix(1); } auto id = csspropertyid(name); if(id != PropertyID::Unknown) element->setAttribute(0x100, id, value); skipOptionalSpacesOrDelimiter(input, ';'); } } inline void removeStyleComments(std::string& value) { auto start = value.find("/*"); while(start != std::string::npos) { auto end = value.find("*/", start + 2); value.erase(start, end - start + 2); start = value.find("/*"); } } inline bool decodeText(std::string_view input, std::string& output) { output.clear(); while(!input.empty()) { auto ch = input.front(); input.remove_prefix(1); if(ch != '&') { output.push_back(ch); continue; } if(skipDelimiter(input, '#')) { int base = 10; if(skipDelimiter(input, 'x')) base = 16; unsigned int cp; if(!parseInteger(input, cp, base)) return false; char c[5] = {0, 0, 0, 0, 0}; if(cp < 0x80) { c[1] = 0; c[0] = char(cp); } else if(cp < 0x800) { c[2] = 0; c[1] = char((cp & 0x3F) | 0x80); cp >>= 6; c[0] = char(cp | 0xC0); } else if(cp < 0x10000) { c[3] = 0; c[2] = char((cp & 0x3F) | 0x80); cp >>= 6; c[1] = char((cp & 0x3F) | 0x80); cp >>= 6; c[0] = char(cp | 0xE0); } else if(cp < 0x200000) { c[4] = 0; c[3] = char((cp & 0x3F) | 0x80); cp >>= 6; c[2] = char((cp & 0x3F) | 0x80); cp >>= 6; c[1] = char((cp & 0x3F) | 0x80); cp >>= 6; c[0] = char(cp | 0xF0); } output.append(c); } else { if(skipString(input, "amp")) { output.push_back('&'); } else if(skipString(input, "lt")) { output.push_back('<'); } else if(skipString(input, "gt")) { output.push_back('>'); } else if(skipString(input, "quot")) { output.push_back('\"'); } else if(skipString(input, "apos")) { output.push_back('\''); } else { return false; } } if(!skipDelimiter(input, ';')) { return false; } } return true; } constexpr bool IS_STARTNAMECHAR(int c) { return IS_ALPHA(c) || c == '_' || c == ':'; } constexpr bool IS_NAMECHAR(int c) { return IS_STARTNAMECHAR(c) || IS_NUM(c) || c == '-' || c == '.'; } inline bool readIdentifier(std::string_view& input, std::string& output) { if(input.empty() || !IS_STARTNAMECHAR(input.front())) return false; output.clear(); do { output.push_back(input.front()); input.remove_prefix(1); } while(!input.empty() && IS_NAMECHAR(input.front())); return true; } bool Document::parse(const char* data, size_t length) { std::string buffer; std::string styleSheet; SVGElement* currentElement = nullptr; int ignoring = 0; auto handleText = [&](std::string_view text, bool in_cdata) { if(text.empty() || currentElement == nullptr || ignoring > 0) return; if(currentElement->id() != ElementID::Text && currentElement->id() != ElementID::Tspan && currentElement->id() != ElementID::Style) { return; } if(in_cdata) { buffer.assign(text); } else { decodeText(text, buffer); } if(currentElement->id() == ElementID::Style) { removeStyleComments(buffer); styleSheet.append(buffer); } else { auto node = std::make_unique(this); node->setData(buffer); currentElement->addChild(std::move(node)); } }; std::string_view input(data, length); if(length >= 3) { auto buffer = (const uint8_t*)(data); const auto c1 = buffer[0]; const auto c2 = buffer[1]; const auto c3 = buffer[2]; if(c1 == 0xEF && c2 == 0xBB && c3 == 0xBF) { input.remove_prefix(3); } } while(!input.empty()) { if(currentElement) { auto text = input.substr(0, input.find('<')); handleText(text, false); input.remove_prefix(text.length()); } else { if(!skipOptionalSpaces(input)) { break; } } if(!skipDelimiter(input, '<')) return false; if(skipDelimiter(input, '?')) { if(!readIdentifier(input, buffer)) return false; auto n = input.find("?>"); if(n == std::string_view::npos) return false; input.remove_prefix(n + 2); continue; } if(skipDelimiter(input, '!')) { if(skipString(input, "--")) { auto n = input.find("-->"); if(n == std::string_view::npos) return false; handleText(input.substr(0, n), false); input.remove_prefix(n + 3); continue; } if(skipString(input, "[CDATA[")) { auto n = input.find("]]>"); if(n == std::string_view::npos) return false; handleText(input.substr(0, n), true); input.remove_prefix(n + 3); continue; } if(skipString(input, "DOCTYPE")) { while(!input.empty() && input.front() != '>') { if(input.front() == '[') { int depth = 1; input.remove_prefix(1); while(!input.empty() && depth > 0) { if(input.front() == '[') ++depth; else if(input.front() == ']') --depth; input.remove_prefix(1); } } else { input.remove_prefix(1); } } if(!skipDelimiter(input, '>')) return false; continue; } return false; } if(skipDelimiter(input, '/')) { if(currentElement == nullptr && ignoring == 0) return false; if(!readIdentifier(input, buffer)) return false; if(ignoring == 0) { auto id = elementid(buffer); if(id != currentElement->id()) return false; currentElement = currentElement->parentElement(); } else { --ignoring; } skipOptionalSpaces(input); if(!skipDelimiter(input, '>')) return false; continue; } if(!readIdentifier(input, buffer)) return false; SVGElement* element = nullptr; if(ignoring > 0) { ++ignoring; } else { auto id = elementid(buffer); if(id == ElementID::Unknown) { ignoring = 1; } else { if(m_rootElement && currentElement == nullptr) return false; if(m_rootElement == nullptr) { if(id != ElementID::Svg) return false; m_rootElement = std::make_unique(this); element = m_rootElement.get(); } else { auto child = SVGElement::create(this, id); element = child.get(); currentElement->addChild(std::move(child)); } } } skipOptionalSpaces(input); while(readIdentifier(input, buffer)) { skipOptionalSpaces(input); if(!skipDelimiter(input, '=')) return false; skipOptionalSpaces(input); if(input.empty() || !(input.front() == '\"' || input.front() == '\'')) return false; auto quote = input.front(); input.remove_prefix(1); auto n = input.find(quote); if(n == std::string_view::npos) return false; auto id = PropertyID::Unknown; if(element != nullptr) id = propertyid(buffer); if(id != PropertyID::Unknown) { decodeText(input.substr(0, n), buffer); if(id == PropertyID::Style) { removeStyleComments(buffer); parseInlineStyle(buffer, element); } else { if(id == PropertyID::Id) m_rootElement->addElementById(buffer, element); element->setAttribute(0x1, id, buffer); } } input.remove_prefix(n + 1); skipOptionalSpaces(input); } if(skipDelimiter(input, '>')) { if(element != nullptr) currentElement = element; continue; } if(skipDelimiter(input, '/')) { if(!skipDelimiter(input, '>')) return false; if(ignoring > 0) --ignoring; continue; } return false; } if(m_rootElement == nullptr || ignoring > 0 || !input.empty()) return false; applyStyleSheet(styleSheet); m_rootElement->build(); return true; } void Document::applyStyleSheet(const std::string& content) { auto rules = parseStyleSheet(content); if(!rules.empty()) { std::sort(rules.begin(), rules.end()); m_rootElement->transverse([&rules](SVGElement* element) { for(const auto& rule : rules) { if(rule.match(element)) { for(const auto& declaration : rule.declarations()) { element->setAttribute(declaration.specificity, declaration.id, declaration.value); } } } }); } } ElementList Document::querySelectorAll(const std::string& content) const { auto selectors = parseQuerySelectors(content); if(selectors.empty()) return ElementList(); ElementList elements; m_rootElement->transverse([&](SVGElement* element) { for(const auto& selector : selectors) { if(matchSelector(selector, element)) { elements.push_back(element); break; } } }); return elements; } } // namespace lunasvg