2#include <unordered_map>
4#ifndef NOVASVG_DISABLE_EMBEDDED_FONT
88 bool isLessThan(
const RuleData& rule)
const {
return std::tie(m_specificity, m_position) < std::tie(rule.m_specificity, rule.m_position); }
100 size_t m_specificity;
110 return value.compare(subvalue) == 0;
115 return value.find(subvalue) != std::string_view::npos;
120 if(subvalue.empty() || subvalue.length() > value.length())
122 std::string_view input(value);
123 while(!input.empty()) {
125 std::string_view start(input);
126 while(!input.empty() && !
IS_WS(input.front()))
127 input.remove_prefix(1);
128 if(subvalue == start.substr(0, start.length() - input.length())) {
138 if(subvalue.empty() || subvalue.length() > value.length())
140 return subvalue == value.substr(0, subvalue.size());
145 if(subvalue.empty() || subvalue.length() > value.length())
147 return subvalue == value.substr(value.size() - subvalue.size(), subvalue.size());
153 return (value.length() == subvalue.length() || value.at(subvalue.length()) ==
'-');
157static bool matchAttributeValue(
const AttributeSelector& selector, std::string_view value)
160 return !value.empty();
162 return equals(value, selector.value);
164 return contains(value, selector.value);
166 return includes(value, selector.value);
170 return endswith(value, selector.value);
176static bool matchAttributeSelector(
const AttributeSelector& selector,
const SVGElement* element)
178 return matchAttributeValue(selector, element->getAttribute(selector.id));
181static bool matchSimpleSelector(
const SimpleSelector& selector,
const SVGElement* element);
183static bool matchPseudoClassSelector(
const PseudoClassSelector& selector,
const SVGElement* element)
186 return element->children().empty();
188 return element->isRootElement();
190 for(
const auto& subSelector : selector.subSelectors) {
191 for(
const auto& simpleSelector : subSelector) {
192 if(!matchSimpleSelector(simpleSelector, element)) {
202 for(
const auto& subSelector : selector.subSelectors) {
203 for(
const auto& simpleSelector : subSelector) {
204 if(matchSimpleSelector(simpleSelector, element)) {
214 return !element->previousElement();
216 return !element->nextElement();
218 return !(element->previousElement() || element->nextElement());
220 auto sibling = element->previousElement();
222 if(sibling->id() == element->id())
224 sibling = sibling->previousElement();
231 auto sibling = element->nextElement();
233 if(sibling->id() == element->id())
235 sibling = sibling->nextElement();
244static bool matchSimpleSelector(
const SimpleSelector& selector,
const SVGElement* element)
248 for(
const auto& sel : selector.attributeSelectors) {
249 if(!matchAttributeSelector(sel, element)) {
254 for(
const auto& sel : selector.pseudoClassSelectors) {
255 if(!matchPseudoClassSelector(sel, element)) {
263static bool matchSelector(
const Selector& selector,
const SVGElement* element)
267 auto it = selector.rbegin();
268 auto end = selector.rend();
269 if(!matchSimpleSelector(*it, element)) {
273 auto combinator = it->combinator;
280 element = element->parentElement();
284 element = element->previousElement();
290 if(element ==
nullptr)
292 if(matchSimpleSelector(*it, element)) {
293 combinator = it->combinator;
306 return matchSelector(m_selector, element);
318 output.push_back(input.front());
319 input.remove_prefix(1);
324static bool parseTagSelector(std::string_view& input, SimpleSelector& simpleSelector)
332 ch = char(std::tolower(static_cast<unsigned char>(ch)));
333 simpleSelector.tag = name;
340static bool parseIdSelector(std::string_view& input, SimpleSelector& simpleSelector)
347 simpleSelector.attributeSelectors.push_back(std::move(a));
351static bool parseClassSelector(std::string_view& input, SimpleSelector& simpleSelector)
358 simpleSelector.attributeSelectors.push_back(std::move(a));
362static bool parseAttributeSelector(std::string_view& input, SimpleSelector& simpleSelector)
386 if(input.empty() || !(input.front() ==
'\"' || input.front() ==
'\''))
388 auto quote = input.front();
389 input.remove_prefix(1);
390 auto n = input.find(quote);
391 if(n == std::string_view::npos)
393 a.value.assign(input.substr(0, n));
394 input.remove_prefix(n + 1);
401 simpleSelector.attributeSelectors.push_back(std::move(a));
405static bool parseSelectors(std::string_view& input,
SelectorList& selectors);
407static bool parsePseudoClassSelector(std::string_view& input, SimpleSelector& simpleSelector)
412 PseudoClassSelector selector;
413 if(name.compare(
"empty") == 0)
415 else if(name.compare(
"root") == 0)
417 else if(name.compare(
"not") == 0)
419 else if(name.compare(
"first-child") == 0)
421 else if(name.compare(
"last-child") == 0)
423 else if(name.compare(
"only-child") == 0)
425 else if(name.compare(
"first-of-type") == 0)
427 else if(name.compare(
"last-of-type") == 0)
429 else if(name.compare(
"only-of-type") == 0)
436 if(!parseSelectors(input, selector.subSelectors))
444 simpleSelector.pseudoClassSelectors.push_back(std::move(selector));
448static bool parseSimpleSelector(std::string_view& input, SimpleSelector& simpleSelector,
bool& failed)
450 auto consumed = parseTagSelector(input, simpleSelector);
453 failed = !parseIdSelector(input, simpleSelector);
455 failed = !parseClassSelector(input, simpleSelector);
457 failed = !parseAttributeSelector(input, simpleSelector);
459 failed = !parsePseudoClassSelector(input, simpleSelector);
464 return consumed && !failed;
470 while(!input.empty() &&
IS_WS(input.front())) {
472 input.remove_prefix(1);
484static bool parseSelector(std::string_view& input,
Selector& selector)
489 SimpleSelector simpleSelector(combinator);
490 if(!parseSimpleSelector(input, simpleSelector, failed))
492 selector.push_back(std::move(simpleSelector));
493 }
while(parseCombinator(input, combinator));
497static bool parseSelectors(std::string_view& input,
SelectorList& selectors)
501 if(!parseSelector(input, selector))
503 selectors.push_back(std::move(selector));
508static bool parseDeclarations(std::string_view& input,
DeclarationList& declarations)
521 std::string_view value(input);
522 while(!input.empty() && !(input.front() ==
'!' || input.front() ==
';' || input.front() ==
'}'))
523 input.remove_prefix(1);
524 value.remove_suffix(input.length());
527 Declaration declaration;
528 declaration.specificity = Specificity::Stylesheet;
530 declaration.value.assign(value);
532 ch = char(std::tolower(static_cast<unsigned char>(ch)));
533 declaration.name = name;
538 declaration.specificity = Specificity::StylesheetImportant;
543 declarations.push_back(std::move(declaration));
545 }
while(!input.empty() && input.front() !=
'}');
549static bool parseRule(std::string_view& input, Rule& rule)
551 if(!parseSelectors(input, rule.selectors))
553 return parseDeclarations(input, rule.declarations);
560static std::vector<std::string_view> splitTopLevelCommas(std::string_view value)
562 std::vector<std::string_view> parts;
565 for(
size_t i = 0; i < value.size(); ++i) {
572 }
else if(ch ==
',' && depth == 0) {
573 parts.push_back(value.substr(start, i - start));
577 parts.push_back(value.substr(start));
581static std::string_view stripQuotes(std::string_view value)
584 if(!value.empty() && (value.front() ==
'\'' || value.front() ==
'"')) {
585 auto quote = value.front();
586 value.remove_prefix(1);
587 if(!value.empty() && value.back() == quote)
588 value.remove_suffix(1);
597static FontFace fontFaceFromBase64(std::string_view payload)
599 size_t decodedLength = 0;
600 auto* decoded =
base64_decode(payload.data(),
int(payload.size()), &decodedLength);
601 if(decoded ==
nullptr)
603 return FontFace(decoded, decodedLength, [](
void* closure) { free(closure); }, decoded);
608static FontFace fontFaceFromZlibBase64(std::string_view payload)
610 size_t compressedLength = 0;
611 auto* compressed =
base64_decode(payload.data(),
int(payload.size()), &compressedLength);
612 if(compressed ==
nullptr)
615 char* inflated = stbi_zlib_decode_malloc(
reinterpret_cast<const char*
>(compressed),
int(compressedLength), &length);
617 if(inflated ==
nullptr || length <= 0) {
621 return FontFace(inflated,
size_t(length), [](
void* closure) { free(closure); }, inflated);
626#ifdef NOVASVG_DISABLE_EMBEDDED_FONT
633 auto build = [](
const char*
const* chunks) {
635 for(
auto chunk = chunks; *chunk; ++chunk)
637 return fontFaceFromZlibBase64(payload);
672static void parseFontFaceRule(std::string_view block)
674 auto familyValue = findRawDeclarationValue(block,
"font-family");
677 auto family = stripQuotes(*familyValue);
681 auto srcValue = findRawDeclarationValue(block,
"src");
687 const auto srcOffset = size_t(srcValue->data() - block.data());
688 const auto before = block.substr(0, srcOffset);
689 const auto after = block.substr(srcOffset + srcValue->size());
690 auto findOutsideSrc = [&](std::string_view property) {
691 auto value = findRawDeclarationValue(before, property);
692 return value ? value : findRawDeclarationValue(after, property);
696 if(
auto weightValue = findOutsideSrc(
"font-weight")) {
697 std::string weight(*weightValue);
698 for(
auto& ch : weight)
699 ch = char(std::tolower(static_cast<unsigned char>(ch)));
700 bold = weight.find(
"bold") != std::string::npos || std::strtof(weight.c_str(),
nullptr) >= 600.f;
704 if(
auto styleValue = findOutsideSrc(
"font-style")) {
705 std::string
style(*styleValue);
706 for(
auto& ch :
style)
707 ch = char(std::tolower(static_cast<unsigned char>(ch)));
708 italic =
style.find(
"italic") != std::string::npos ||
style.find(
"oblique") != std::string::npos;
711 for(
auto alternative : splitTopLevelCommas(*srcValue)) {
712 auto urlPos = alternative.find(
"url(");
713 if(urlPos == std::string_view::npos)
715 auto contentStart = urlPos + 4;
716 auto closeParen = alternative.find(
')', contentStart);
717 if(closeParen == std::string_view::npos)
719 auto url = stripQuotes(alternative.substr(contentStart, closeParen - contentStart));
721 if(url.compare(0, 5,
"data:") != 0)
723 auto base64Pos = url.find(
"base64,");
724 if(base64Pos == std::string_view::npos)
726 auto payload = url.substr(base64Pos + 7);
728 auto face = fontFaceFromBase64(payload);
737static RuleDataList parseStyleSheet(std::string_view input)
740 while(!input.empty()) {
743 std::string atKeyword;
745 for(
auto& ch : atKeyword)
746 ch = char(std::tolower(static_cast<unsigned char>(ch)));
748 if(atKeyword ==
"font-face") {
752 size_t blockLength = 0;
757 if(
auto* close =
static_cast<const char*
>(std::memchr(input.data(),
'}', input.size()))) {
758 const size_t n = size_t(close - input.data());
759 if(std::memchr(input.data(),
'{', n) ==
nullptr) {
765 while(blockLength < input.size() && depth > 0) {
766 auto ch = input[blockLength];
767 if(ch ==
'{') ++depth;
768 else if(ch ==
'}') --depth;
771 parseFontFaceRule(block.substr(0, depth == 0 ? blockLength - 1 : blockLength));
772 input.remove_prefix(blockLength);
778 while(!input.empty()) {
779 auto ch = input.front();
780 input.remove_prefix(1);
781 if(ch ==
';' && depth == 0)
783 if(ch ==
'{') ++depth;
784 else if(ch ==
'}' && depth > 0) {
795 if(!parseRule(input, rule))
797 for(
const auto& selector : rule.selectors) {
798 size_t specificity = 0;
799 for(
const auto& simpleSelector : selector) {
801 for(
const auto& attributeSelector : simpleSelector.attributeSelectors) {
802 specificity += (attributeSelector.id ==
PropertyID::Id) ? 0x10000 : 0x100;
804 for(
const auto& pseudoClassSelector : simpleSelector.pseudoClassSelectors) {
805 specificity += 0x100;
809 rules.emplace_back(selector, rule.declarations, specificity, rules.size());
816static SelectorList parseQuerySelectors(std::string_view input)
820 if(!parseSelectors(input, selectors)
833 auto bang = value.rfind(
'!');
834 if(bang == std::string::npos)
836 std::string_view flag(value);
837 flag.remove_prefix(bang + 1);
842 for(
size_t i = 0; i < flag.size(); ++i) {
843 if(std::tolower(
static_cast<unsigned char>(flag[i])) !=
"important"[i])
848 while(!value.empty() &&
IS_WS(value.back()))
862 while(!input.empty() && input.front() !=
';') {
863 value.push_back(input.front());
864 input.remove_prefix(1);
870 element->
setAttribute(important ? Specificity::InlineImportant : Specificity::InlineStyle, id, value);
878 auto start = value.find(
"/*");
879 while(start != std::string::npos) {
880 auto end = value.find(
"*/", start + 2);
881 value.erase(start, end - start + 2);
882 start = value.find(
"/*");
886inline bool decodeText(std::string_view input, std::string& output)
889 while(!input.empty()) {
892 auto amp = input.find(
'&');
893 if(amp == std::string_view::npos) {
894 output.append(input.data(), input.size());
898 output.append(input.data(), amp);
899 input.remove_prefix(amp + 1);
908 char c[5] = {0, 0, 0, 0, 0};
912 }
else if(cp < 0x800) {
914 c[1] = char((cp & 0x3F) | 0x80);
916 c[0] = char(cp | 0xC0);
917 }
else if(cp < 0x10000) {
919 c[2] = char((cp & 0x3F) | 0x80);
921 c[1] = char((cp & 0x3F) | 0x80);
923 c[0] = char(cp | 0xE0);
924 }
else if(cp < 0x200000) {
926 c[3] = char((cp & 0x3F) | 0x80);
928 c[2] = char((cp & 0x3F) | 0x80);
930 c[1] = char((cp & 0x3F) | 0x80);
932 c[0] = char(cp | 0xF0);
938 output.push_back(
'&');
940 output.push_back(
'<');
942 output.push_back(
'>');
944 output.push_back(
'\"');
946 output.push_back(
'\'');
969 output.push_back(input.front());
970 input.remove_prefix(1);
971 }
while(!input.empty() &&
IS_NAMECHAR(input.front()));
975NOVASVG_INLINE bool Document::parse(
const char* data,
size_t length)
981 auto handleText = [&](std::string_view
text,
bool in_cdata) {
998 auto node = std::make_unique<SVGTextNode>(
this);
999 node->setData(buffer);
1004 std::string_view
input(data, length);
1006 auto buffer = (
const uint8_t*)(data);
1008 const auto c1 = buffer[0];
1009 const auto c2 = buffer[1];
1010 const auto c3 = buffer[2];
1011 if(
c1 == 0xEF &&
c2 == 0xBB &&
c3 == 0xBF) {
1012 input.remove_prefix(3);
1016 while(!
input.empty()) {
1019 handleText(
text,
false);
1032 auto n =
input.find(
"?>");
1033 if(
n == std::string_view::npos)
1035 input.remove_prefix(
n + 2);
1041 auto n =
input.find(
"-->");
1042 if(
n == std::string_view::npos)
1044 handleText(
input.substr(0,
n),
false);
1045 input.remove_prefix(
n + 3);
1050 auto n =
input.find(
"]]>");
1051 if(
n == std::string_view::npos)
1053 handleText(
input.substr(0,
n),
true);
1054 input.remove_prefix(
n + 3);
1059 while(!
input.empty() &&
input.front() !=
'>') {
1060 if(
input.front() ==
'[') {
1062 input.remove_prefix(1);
1063 while(!
input.empty() && depth > 0) {
1064 if(
input.front() ==
'[') ++depth;
1065 else if(
input.front() ==
']') --depth;
1066 input.remove_prefix(1);
1069 input.remove_prefix(1);
1103 SVGElement* element =
nullptr;
1113 if(m_rootElement ==
nullptr) {
1116 m_rootElement = std::make_unique<SVGRootElement>(
this);
1117 element = m_rootElement.get();
1120 element =
child.get();
1132 if(
input.empty() || !(
input.front() ==
'\"' ||
input.front() ==
'\''))
1135 input.remove_prefix(1);
1137 if(
n == std::string_view::npos)
1140 if(element !=
nullptr)
1149 m_rootElement->addElementById(buffer, element);
1150 element->setAttribute(Specificity::PresentationAttribute,
id, buffer);
1154 input.remove_prefix(
n + 1);
1159 if(element !=
nullptr) {
1166 static constexpr std::string_view
closeTag =
"</foreignObject>";
1168 if(
n == std::string_view::npos)
1170 static_cast<SVGForeignObjectElement*
>(element)->setRawContent(std::string(
input.substr(0,
n)));
1190 if(m_rootElement ==
nullptr ||
ignoring > 0 || !
input.empty())
1193 m_rootElement->build();
1218static bool isVoidHtmlTag(
const std::string& tag)
1220 static constexpr std::string_view names[] = {
"area",
"base",
"br",
"col",
"embed",
"hr",
"img",
1221 "input",
"link",
"meta",
"source",
"track",
"wbr"};
1222 for(
auto name : names) {
1230static std::string lowerCase(std::string_view
text)
1232 std::string out(
text);
1234 ch = char(std::tolower(static_cast<unsigned char>(ch)));
1240static std::vector<HtmlNode> parseHtmlNodes(std::string_view html)
1242 std::vector<HtmlNode> nodes;
1243 std::vector<int> open;
1245 while(pos < html.size()) {
1246 auto lt = html.find(
'<', pos);
1247 auto text = html.substr(pos, lt == std::string_view::npos ? std::string_view::npos : lt - pos);
1249 for(
char ch :
text) {
1251 nodes[open.back()].hasText =
true;
1257 if(lt == std::string_view::npos)
1259 if(html.compare(lt, 4,
"<!--") == 0) {
1260 auto end = html.find(
"-->", lt + 4);
1261 pos = end == std::string_view::npos ? html.size() : end + 3;
1268 while(gt < html.size() && (quote || html[gt] !=
'>')) {
1270 if(html[gt] == quote)
1272 }
else if(html[gt] ==
'"' || html[gt] ==
'\'') {
1278 auto inside = html.substr(lt + 1, gt - lt - 1);
1280 if(inside.empty() || inside.front() ==
'!' || inside.front() ==
'?')
1283 if(inside.front() ==
'/') {
1284 inside.remove_prefix(1);
1286 while(n < inside.size() && !
IS_WS(inside[n]))
1288 auto name = lowerCase(inside.substr(0, n));
1289 for(
size_t i = open.size(); i-- > 0;) {
1290 if(nodes[open[i]].tag == name) {
1301 while(n < inside.size() && !
IS_WS(inside[n]) && inside[n] !=
'/')
1303 node.tag = lowerCase(inside.substr(0, n));
1304 inside.remove_prefix(n);
1305 bool selfClosing =
false;
1306 while(!inside.empty()) {
1310 if(inside.front() ==
'/') {
1312 inside.remove_prefix(1);
1316 selfClosing =
false;
1318 while(m < inside.size() && !
IS_WS(inside[m]) && inside[m] !=
'=' && inside[m] !=
'/')
1320 auto name = lowerCase(inside.substr(0, m));
1321 inside.remove_prefix(m);
1324 if(!inside.empty() && inside.front() ==
'=') {
1325 inside.remove_prefix(1);
1327 if(!inside.empty() && (inside.front() ==
'"' || inside.front() ==
'\'')) {
1328 auto q = inside.front();
1329 inside.remove_prefix(1);
1330 auto end = inside.find(q);
1331 value.assign(inside.substr(0, end));
1332 inside.remove_prefix(end == std::string_view::npos ? inside.size() : end + 1);
1335 while(v < inside.size() && !
IS_WS(inside[v]))
1337 value.assign(inside.substr(0, v));
1338 inside.remove_prefix(v);
1343 node.className = value;
1344 else if(name ==
"id")
1346 else if(name ==
"style")
1350 node.parent = open.empty() ? -1 : open.back();
1351 nodes.push_back(std::move(node));
1352 if(!selfClosing && !isVoidHtmlTag(nodes.back().tag))
1353 open.push_back(
int(nodes.size()) - 1);
1363 explicit operator bool()
const {
return html ||
svg; }
1366static bool matchHtmlSimpleSelector(
const SimpleSelector& selector,
const HtmlNode& node)
1368 if(selector.id !=
ElementID::Star && (selector.tag.empty() || selector.tag != node.tag))
1370 if(!selector.pseudoClassSelectors.empty())
1372 for(
const auto& attribute : selector.attributeSelectors) {
1373 std::string_view value;
1375 value = node.className;
1380 if(!matchAttributeValue(attribute, value))
1390static bool matchHtmlSelector(
const Selector& selector,
const std::vector<HtmlNode>& nodes,
int index,
const SVGElement* owner)
1392 if(selector.empty())
1394 auto parentOf = [&](
const StyleNode& node) {
1396 if(node.html->parent >= 0)
1397 return StyleNode{&nodes[node.html->parent],
nullptr};
1398 return StyleNode{
nullptr, owner};
1401 return StyleNode{
nullptr, node.svg->parentElement()};
1403 auto matches = [](
const SimpleSelector& simple,
const StyleNode& node) {
1404 return node.html ? matchHtmlSimpleSelector(simple, *node.html) : matchSimpleSelector(simple, node.svg);
1407 StyleNode node{&nodes[index],
nullptr};
1408 auto it = selector.rbegin();
1409 auto end = selector.rend();
1410 if(!matches(*it, node))
1412 auto combinator = it->combinator;
1417 node = parentOf(node);
1420 if(matches(*it, node)) {
1421 combinator = it->combinator;
1435 void offer(
int priority,
const std::string& candidate)
1445static void offerInlineStyle(std::string_view
style, CascadedValue& color, CascadedValue& background)
1447 while(!
style.empty()) {
1448 auto end =
style.find(
';');
1449 auto declaration =
style.substr(0, end);
1450 style.remove_prefix(end == std::string_view::npos ?
style.size() : end + 1);
1451 auto colon = declaration.find(
':');
1452 if(colon == std::string_view::npos)
1454 auto nameText = declaration.substr(0, colon);
1456 auto name = lowerCase(nameText);
1457 std::string value(declaration.substr(colon + 1));
1458 const int priority =
stripImportantFlag(value) ? Specificity::InlineImportant : Specificity::InlineStyle;
1459 std::string_view trimmed(value);
1462 color.offer(priority, std::string(trimmed));
1463 else if(name ==
"background-color")
1464 background.offer(priority, std::string(trimmed));
1479static void resolveForeignObjectStyle(SVGForeignObjectElement* foreignObject,
const RuleDataList& rules)
1481 const auto nodes = parseHtmlNodes(foreignObject->rawContent());
1482 std::vector<const RuleData*> relevant;
1483 for(
const auto& rule : rules) {
1484 for(
const auto& declaration : rule.declarations()) {
1485 if(declaration.name ==
"color" || declaration.name ==
"background-color") {
1486 relevant.push_back(&rule);
1492 std::vector<std::optional<Color>> colorOf(nodes.size());
1493 std::optional<Color> textColor, textColorOfLastNode, background;
1494 bool anyText =
false;
1495 for(
size_t i = 0; i < nodes.size(); ++i) {
1496 CascadedValue color, box;
1497 for(
const auto* rule : relevant) {
1498 if(!matchHtmlSelector(rule->selector(), nodes,
int(i), foreignObject))
1500 for(
const auto& declaration : rule->declarations()) {
1501 if(declaration.name ==
"color")
1502 color.offer(declaration.specificity, declaration.value);
1503 else if(declaration.name ==
"background-color")
1504 box.offer(declaration.specificity, declaration.value);
1508 offerInlineStyle(nodes[i].
style, color, box);
1510 std::optional<Color> declared;
1511 if(color.specificity >= 0)
1512 declared = parseCssColor(color.value);
1513 colorOf[i] = declared ? declared : (nodes[i].parent >= 0 ? colorOf[nodes[i].parent] : std::nullopt);
1514 textColorOfLastNode = colorOf[i];
1515 if(nodes[i].hasText) {
1516 textColor = colorOf[i];
1520 if(box.specificity >= 0) {
1521 if(
auto fill = parseCssColor(box.value); fill && fill->isVisible())
1526 foreignObject->setHtmlStyle(anyText ? textColor : textColorOfLastNode, background);
1532static RuleDataList parseStyleSheets(
const std::vector<std::string>& sheets)
1535 for(
const auto& sheet : sheets) {
1536 for(
auto& rule : parseStyleSheet(sheet))
1537 all.emplace_back(rule.selector(), rule.declarations(), rule.specificity(), all.size());
1540 std::sort(all.begin(), all.end());
1554 for(
size_t i = 0; i < rules.size(); ++i) {
1555 const auto& selector = rules[i].selector();
1556 if(selector.empty())
1558 const auto& last = selector.back();
1560 m_byTag[size_t(last.id)].push_back(uint32_t(i));
1564 const std::string* className =
nullptr;
1565 for(
const auto& attribute : last.attributeSelectors) {
1567 && !attribute.value.empty()) {
1568 className = &attribute.value;
1574 m_byClass[*className].push_back(uint32_t(i));
1576 m_other.push_back(uint32_t(i));
1584 const auto& tagged = m_byTag[size_t(element->
id())];
1585 out.insert(out.end(), tagged.begin(), tagged.end());
1586 out.insert(out.end(), m_other.begin(), m_other.end());
1587 if(!m_byClass.empty()) {
1589 while(!classes.empty()) {
1592 while(n < classes.size() && !
IS_WS(classes[n]))
1595 auto it = m_byClass.find(std::string(classes.substr(0, n)));
1596 if(it != m_byClass.end())
1597 out.insert(out.end(), it->second.begin(), it->second.end());
1599 classes.remove_prefix(n);
1603 std::sort(out.begin(), out.end());
1604 out.erase(std::unique(out.begin(), out.end()), out.end());
1608 std::vector<std::vector<uint32_t>> m_byTag;
1609 std::unordered_map<std::string, std::vector<uint32_t>> m_byClass;
1610 std::vector<uint32_t> m_other;
1617 if(!
rules.empty()) {
1619 std::vector<uint32_t> candidates;
1620 m_rootElement->transverse([&](
SVGElement* element) {
1622 for(
auto i : candidates) {
1624 if(
rule.match(element)) {
1635 m_rootElement->addStyleSheet(
content);
1643 m_rootElement->transverse([&](
SVGElement* element) {
1647 const auto&
sheets = m_rootElement->styleSheets();
1661 auto selectors = parseQuerySelectors(
content);
1662 if(selectors.empty())
1665 m_rootElement->transverse([&](
SVGElement* element) {
1666 for(
const auto& selector : selectors) {
1667 if(matchSelector(selector, element)) {
1668 elements.push_back(element);
void applyStyleSheet(const std::string &content)
Applies a CSS stylesheet to the document.
float width() const
Returns the intrinsic width of the document in pixels.
ElementList querySelectorAll(const std::string &content) const
Selects all elements that match the given CSS selector(s).
bool addFontFace(const std::string &family, bool bold, bool italic, const FontFace &face)
size_t specificity() const
const DeclarationList & declarations() const
bool match(const SVGElement *element) const
const Selector & selector() const
bool isLessThan(const RuleData &rule) const
RuleData(const Selector &selector, const DeclarationList &declarations, size_t specificity, size_t position)
RuleIndex(const RuleDataList &rules)
void candidates(const SVGElement *element, std::vector< uint32_t > &out) const
const std::string & getAttribute(std::string_view name) const
static std::unique_ptr< SVGElement > create(Document *document, ElementID id)
bool setAttribute(std::string_view name, const std::string &value)
#define NOVASVG_MIN(a, b)
constexpr const char *const kEmbeddedFontBoldItalicZlibBase64[]
constexpr const char *const kEmbeddedFontZlibBase64[]
constexpr const char *const kEmbeddedFontBoldZlibBase64[]
constexpr const char *const kEmbeddedFontItalicZlibBase64[]
NOVASVG_INLINE uint8_t * base64_decode(const char *data, int length, size_t *out_length)
Main namespace for the NovaSVG library.
constexpr bool IS_WS(int cc)
bool readCSSIdentifier(std::string_view &input, std::string &output)
NOVASVG_INLINE constexpr bool equals(std::string_view value, std::string_view subvalue)
bool stripImportantFlag(std::string &value)
bool readIdentifier(std::string_view &input, std::string &output)
FontFace embeddedFallbackFont(bool bold=false, bool italic=false)
bool parseInteger(std::string_view &input, T &integer, int base=10)
FontFaceCache * fontFaceCache()
NOVASVG_INLINE constexpr bool endswith(std::string_view value, std::string_view subvalue)
std::vector< Element > ElementList
constexpr bool skipDelimiter(std::string_view &input, char delimiter)
std::vector< RuleData > RuleDataList
PropertyID propertyid(std::string_view name)
constexpr bool IS_CSS_NAMECHAR(int c)
constexpr bool skipString(std::string_view &input, std::string_view value)
constexpr bool skipDelimiterAndOptionalSpaces(std::string_view &input, char delimiter)
ElementID elementid(std::string_view name)
constexpr bool IS_NUM(int cc)
constexpr bool IS_STARTNAMECHAR(int c)
constexpr void stripLeadingAndTrailingSpaces(std::string_view &input)
PropertyID csspropertyid(std::string_view name)
NOVASVG_INLINE constexpr bool includes(std::string_view value, std::string_view subvalue)
constexpr bool IS_ALPHA(int cc)
constexpr bool IS_NAMECHAR(int c)
constexpr bool IS_CSS_STARTNAMECHAR(int c)
NOVASVG_INLINE constexpr bool dashequals(std::string_view value, std::string_view subvalue)
std::vector< Selector > SelectorList
std::vector< SimpleSelector > Selector
bool operator<(const RuleData &a, const RuleData &b)
NOVASVG_INLINE constexpr bool contains(std::string_view value, std::string_view subvalue)
constexpr bool skipOptionalSpaces(std::string_view &input)
NOVASVG_INLINE constexpr bool startswith(std::string_view value, std::string_view subvalue)
bool decodeText(std::string_view input, std::string &output)
std::vector< Declaration > DeclarationList
constexpr bool skipOptionalSpacesOrDelimiter(std::string_view &input, char delimiter)
constexpr void stripTrailingSpaces(std::string_view &input)
void parseInlineStyle(std::string_view input, SVGElement *element)
void removeStyleComments(std::string &value)
void offer(int priority, const std::string &candidate)
SelectorList subSelectors
DeclarationList declarations
SimpleSelector(Combinator combinator)
std::vector< AttributeSelector > attributeSelectors
std::vector< PseudoClassSelector > pseudoClassSelectors
std::string text
UTF-8; ' ' marks a forced line break ( ,.