ES-DE/source/parser.cpp

2071 lines
57 KiB
C++
Raw Normal View History

#include "parser.h"
#include "parserutils.h"
#include "layoutcontext.h"
#include "clippathelement.h"
#include "defselement.h"
#include "gelement.h"
#include "geometryelement.h"
#include "markerelement.h"
#include "maskelement.h"
#include "paintelement.h"
#include "stopelement.h"
#include "svgelement.h"
#include "symbolelement.h"
#include "useelement.h"
#include "styleelement.h"
namespace lunasvg {
Length Parser::parseLength(const std::string& string, LengthNegativeValuesMode mode, const Length& defaultValue)
{
if(string.empty())
return defaultValue;
auto ptr = string.data();
auto end = ptr + string.size();
double value;
LengthUnits units;
if(!parseLength(ptr, end, value, units, mode))
return defaultValue;
return Length{value, units};
}
LengthList Parser::parseLengthList(const std::string& string, LengthNegativeValuesMode mode)
{
if(string.empty())
return LengthList{};
auto ptr = string.data();
auto end = ptr + string.size();
double value;
LengthUnits units;
LengthList values;
while(ptr < end)
{
if(!parseLength(ptr, end, value, units, mode))
break;
values.emplace_back(value, units);
Utils::skipWsComma(ptr, end);
}
return values;
}
double Parser::parseNumber(const std::string& string, double defaultValue)
{
if(string.empty())
return defaultValue;
auto ptr = string.data();
auto end = ptr + string.size();
double value;
if(!Utils::parseNumber(ptr, end, value))
return defaultValue;
return value;
}
double Parser::parseNumberPercentage(const std::string& string, double defaultValue)
{
if(string.empty())
return defaultValue;
auto ptr = string.data();
auto end = ptr + string.size();
double value;
if(!Utils::parseNumber(ptr, end, value))
return defaultValue;
if(Utils::skipDesc(ptr, end, '%'))
value /= 100.0;
return value < 0.0 ? 0.0 : value > 1.0 ? 1.0 : value;
}
PointList Parser::parsePointList(const std::string& string)
{
if(string.empty())
return PointList{};
auto ptr = string.data();
auto end = ptr + string.size();
double x;
double y;
PointList values;
while(ptr < end)
{
if(!Utils::parseNumber(ptr, end, x)
|| !Utils::skipWsComma(ptr, end)
|| !Utils::parseNumber(ptr, end, y))
break;
values.emplace_back(x, y);
Utils::skipWsComma(ptr, end);
}
return values;
}
Transform Parser::parseTransform(const std::string& string)
{
if(string.empty())
return Transform{};
auto ptr = string.data();
auto end = ptr + string.size();
TransformType type;
double values[6];
int count;
Transform transform;
while(ptr < end)
{
if(!parseTransform(ptr, end, type, values, count))
break;
Utils::skipWsComma(ptr, end);
switch(type) {
case TransformType::Matrix:
transform.transform(values[0], values[1], values[2], values[3], values[4], values[5]);
break;
case TransformType::Rotate:
if(count == 1)
transform.rotate(values[0], 0, 0);
else
transform.rotate(values[0], values[1], values[2]);
break;
case TransformType::Scale:
if(count == 1)
transform.scale(values[0], values[0]);
else
transform.scale(values[0], values[1]);
break;
case TransformType::SkewX:
transform.shear(values[0], 0);
break;
case TransformType::SkewY:
transform.shear(0, values[0]);
break;
case TransformType::Translate:
if(count == 1)
transform.translate(values[0], 0);
else
transform.translate(values[0], values[1]);
break;
}
}
return transform;
}
Path Parser::parsePath(const std::string& string)
{
auto ptr = string.data();
auto end = ptr + string.size();
if(ptr >= end || !(*ptr == 'M' || *ptr == 'm'))
return Path{};
auto command = *ptr++;
auto lastCommand = command;
double c[6];
bool f[2];
Point startPoint;
Point currentPoint;
Point controlPoint;
Path path;
while(true)
{
Utils::skipWs(ptr, end);
if(command == 'M' || command == 'm') {
if(!parseNumberList(ptr, end, c, 2))
return path;
if(command == 'm')
{
c[0] += currentPoint.x;
c[1] += currentPoint.y;
}
path.moveTo(c[0], c[1]);
startPoint.x = currentPoint.x = c[0];
startPoint.y = currentPoint.y = c[1];
command = command == 'm' ? 'l' : 'L';
} else if(command == 'L' || command == 'l') {
if(!parseNumberList(ptr, end, c, 2))
return path;
if(command == 'l')
{
c[0] += currentPoint.x;
c[1] += currentPoint.y;
}
path.lineTo(c[0], c[1]);
currentPoint.x = c[0];
currentPoint.y = c[1];
} else if(command == 'H' || command == 'h') {
if(!parseNumberList(ptr, end, c, 1))
return path;
if(command == 'h')
c[0] += currentPoint.x;
path.lineTo(c[0], currentPoint.y);
currentPoint.x = c[0];
} else if(command == 'V' || command == 'v') {
if(!parseNumberList(ptr, end, c + 1, 1))
return path;
if(command == 'v')
c[1] += currentPoint.y;
path.lineTo(currentPoint.x, c[1]);
currentPoint.y = c[1];
} else if(command == 'Q' || command == 'q') {
if(!parseNumberList(ptr, end, c, 4))
return path;
if(command == 'q')
{
c[0] += currentPoint.x;
c[1] += currentPoint.y;
c[2] += currentPoint.x;
c[3] += currentPoint.y;
}
path.quadTo(currentPoint.x, currentPoint.y, c[0], c[1], c[2], c[3]);
controlPoint.x = c[0];
controlPoint.y = c[1];
currentPoint.x = c[2];
currentPoint.y = c[3];
} else if(command == 'C' || command == 'c') {
if(!parseNumberList(ptr, end, c, 6))
return path;
if(command == 'c')
{
c[0] += currentPoint.x;
c[1] += currentPoint.y;
c[2] += currentPoint.x;
c[3] += currentPoint.y;
c[4] += currentPoint.x;
c[5] += currentPoint.y;
}
path.cubicTo(c[0], c[1], c[2], c[3], c[4], c[5]);
controlPoint.x = c[2];
controlPoint.y = c[3];
currentPoint.x = c[4];
currentPoint.y = c[5];
} else if(command == 'T' || command == 't') {
if(lastCommand != 'Q' && lastCommand != 'q' && lastCommand != 'T' && lastCommand != 't') {
c[0] = currentPoint.x;
c[1] = currentPoint.y;
} else {
c[0] = 2 * currentPoint.x - controlPoint.x;
c[1] = 2 * currentPoint.y - controlPoint.y;
}
if(!parseNumberList(ptr, end, c + 2, 2))
return path;
if(command == 't')
{
c[2] += currentPoint.x;
c[3] += currentPoint.y;
}
path.quadTo(currentPoint.x, currentPoint.y, c[0], c[1], c[2], c[3]);
controlPoint.x = c[0];
controlPoint.y = c[1];
currentPoint.x = c[2];
currentPoint.y = c[3];
} else if(command == 'S' || command == 's') {
if(lastCommand != 'C' && lastCommand != 'c' && lastCommand != 'S' && lastCommand != 's') {
c[0] = currentPoint.x;
c[1] = currentPoint.y;
} else {
c[0] = 2 * currentPoint.x - controlPoint.x;
c[1] = 2 * currentPoint.y - controlPoint.y;
}
if(!parseNumberList(ptr, end, c + 2, 4))
return path;
if(command == 's')
{
c[2] += currentPoint.x;
c[3] += currentPoint.y;
c[4] += currentPoint.x;
c[5] += currentPoint.y;
}
path.cubicTo(c[0], c[1], c[2], c[3], c[4], c[5]);
controlPoint.x = c[2];
controlPoint.y = c[3];
currentPoint.x = c[4];
currentPoint.y = c[5];
} else if(command == 'A' || command == 'a') {
if(!parseNumberList(ptr, end, c, 3)
|| !parseArcFlag(ptr, end, f[0])
|| !parseArcFlag(ptr, end, f[1])
|| !parseNumberList(ptr, end, c + 3, 2))
return path;
if(command == 'a')
{
c[3] += currentPoint.x;
c[4] += currentPoint.y;
}
path.arcTo(currentPoint.x, currentPoint.y, c[0], c[1], c[2], f[0], f[1], c[3], c[4]);
currentPoint.x = c[3];
currentPoint.y = c[4];
} else if(command == 'Z' || command == 'z') {
path.close();
currentPoint.x = startPoint.x;
currentPoint.y = startPoint.y;
} else {
return path;
}
Utils::skipWsComma(ptr, end);
if(ptr >= end)
break;
lastCommand = command;
if(IS_ALPHA(*ptr))
command = *ptr++;
}
return path;
}
std::string Parser::parseUrl(const std::string& string)
{
if(string.empty())
return std::string{};
auto ptr = string.data();
auto end = ptr + string.size();
std::string value;
parseUrlFragment(ptr, end, value);
return value;
}
std::string Parser::parseHref(const std::string& string)
{
if(string.size() > 1 && string.front() == '#')
return string.substr(1);
return std::string{};
}
Rect Parser::parseViewBox(const std::string& string)
{
if(string.empty())
return Rect::Invalid;
auto ptr = string.data();
auto end = ptr + string.size();
double x;
double y;
double w;
double h;
if(!Utils::parseNumber(ptr, end, x)
|| !Utils::skipWsComma(ptr, end)
|| !Utils::parseNumber(ptr, end, y)
|| !Utils::skipWsComma(ptr, end)
|| !Utils::parseNumber(ptr, end, w)
|| !Utils::skipWsComma(ptr, end)
|| !Utils::parseNumber(ptr, end, h))
return Rect::Invalid;
if(w < 0.0 || h < 0.0)
return Rect::Invalid;
return Rect{x, y, w, h};
}
PreserveAspectRatio Parser::parsePreserveAspectRatio(const std::string& string)
{
if(string.empty())
return PreserveAspectRatio{};
auto ptr = string.data();
auto end = ptr + string.size();
Align align{Align::xMidYMid};
MeetOrSlice scale{MeetOrSlice::Meet};
if(Utils::skipDesc(ptr, end, "none"))
align = Align::None;
else if(Utils::skipDesc(ptr, end, "xMinYMin"))
align = Align::xMinYMin;
else if(Utils::skipDesc(ptr, end, "xMidYMin"))
align = Align::xMidYMin;
else if(Utils::skipDesc(ptr, end, "xMaxYMin"))
align = Align::xMaxYMin;
else if(Utils::skipDesc(ptr, end, "xMinYMid"))
align = Align::xMinYMid;
else if(Utils::skipDesc(ptr, end, "xMidYMid"))
align = Align::xMidYMid;
else if(Utils::skipDesc(ptr, end, "xMaxYMid"))
align = Align::xMaxYMid;
else if(Utils::skipDesc(ptr, end, "xMinYMax"))
align = Align::xMinYMax;
else if(Utils::skipDesc(ptr, end, "xMidYMax"))
align = Align::xMidYMax;
else if(Utils::skipDesc(ptr, end, "xMaxYMax"))
align = Align::xMaxYMax;
else
return PreserveAspectRatio{};
Utils::skipWs(ptr, end);
if(Utils::skipDesc(ptr, end, "slice"))
scale = MeetOrSlice::Slice;
else
scale = MeetOrSlice::Meet;
return PreserveAspectRatio{align, scale};
}
static const double pi = 3.14159265358979323846;
Angle Parser::parseAngle(const std::string& string)
{
if(string.empty())
return Angle{};
auto ptr = string.data();
auto end = ptr + string.size();
if(Utils::skipDesc(ptr, end, "auto"))
return MarkerOrient::Auto;
double value;
if(!Utils::parseNumber(ptr, end, value))
return Angle{};
if(Utils::skipDesc(ptr, end, "rad"))
value *= 180.0 / pi;
else if(Utils::skipDesc(ptr, end, "grad"))
value *= 360.0 / 400.0;
return Angle{value, MarkerOrient::Angle};
}
MarkerUnits Parser::parseMarkerUnits(const std::string& string)
{
if(string.empty())
return MarkerUnits::StrokeWidth;
if(string.compare("userSpaceOnUse") == 0)
return MarkerUnits::UserSpaceOnUse;
return MarkerUnits::StrokeWidth;
}
SpreadMethod Parser::parseSpreadMethod(const std::string& string)
{
if(string.empty())
return SpreadMethod::Pad;
if(string.compare("repeat") == 0)
return SpreadMethod::Repeat;
if(string.compare("reflect") == 0)
return SpreadMethod::Reflect;
return SpreadMethod::Pad;
}
Units Parser::parseUnits(const std::string& string, Units defaultValue)
{
if(string.empty())
return defaultValue;
if(string.compare("userSpaceOnUse") == 0)
return Units::UserSpaceOnUse;
if(string.compare("objectBoundingBox") == 0)
return Units::ObjectBoundingBox;
return defaultValue;
}
static const std::map<std::string, unsigned int> colormap = {
{"aliceblue", 0xF0F8FF},
{"antiquewhite", 0xFAEBD7},
{"aqua", 0x00FFFF},
{"aquamarine", 0x7FFFD4},
{"azure", 0xF0FFFF},
{"beige", 0xF5F5DC},
{"bisque", 0xFFE4C4},
{"black", 0x000000},
{"blanchedalmond", 0xFFEBCD},
{"blue", 0x0000FF},
{"blueviolet", 0x8A2BE2},
{"brown", 0xA52A2A},
{"burlywood", 0xDEB887},
{"cadetblue", 0x5F9EA0},
{"chartreuse", 0x7FFF00},
{"chocolate", 0xD2691E},
{"coral", 0xFF7F50},
{"cornflowerblue", 0x6495ED},
{"cornsilk", 0xFFF8DC},
{"crimson", 0xDC143C},
{"cyan", 0x00FFFF},
{"darkblue", 0x00008B},
{"darkcyan", 0x008B8B},
{"darkgoldenrod", 0xB8860B},
{"darkgray", 0xA9A9A9},
{"darkgreen", 0x006400},
{"darkgrey", 0xA9A9A9},
{"darkkhaki", 0xBDB76B},
{"darkmagenta", 0x8B008B},
{"darkolivegreen", 0x556B2F},
{"darkorange", 0xFF8C00},
{"darkorchid", 0x9932CC},
{"darkred", 0x8B0000},
{"darksalmon", 0xE9967A},
{"darkseagreen", 0x8FBC8F},
{"darkslateblue", 0x483D8B},
{"darkslategray", 0x2F4F4F},
{"darkslategrey", 0x2F4F4F},
{"darkturquoise", 0x00CED1},
{"darkviolet", 0x9400D3},
{"deeppink", 0xFF1493},
{"deepskyblue", 0x00BFFF},
{"dimgray", 0x696969},
{"dimgrey", 0x696969},
{"dodgerblue", 0x1E90FF},
{"firebrick", 0xB22222},
{"floralwhite", 0xFFFAF0},
{"forestgreen", 0x228B22},
{"fuchsia", 0xFF00FF},
{"gainsboro", 0xDCDCDC},
{"ghostwhite", 0xF8F8FF},
{"gold", 0xFFD700},
{"goldenrod", 0xDAA520},
{"gray", 0x808080},
{"green", 0x008000},
{"greenyellow", 0xADFF2F},
{"grey", 0x808080},
{"honeydew", 0xF0FFF0},
{"hotpink", 0xFF69B4},
{"indianred", 0xCD5C5C},
{"indigo", 0x4B0082},
{"ivory", 0xFFFFF0},
{"khaki", 0xF0E68C},
{"lavender", 0xE6E6FA},
{"lavenderblush", 0xFFF0F5},
{"lawngreen", 0x7CFC00},
{"lemonchiffon", 0xFFFACD},
{"lightblue", 0xADD8E6},
{"lightcoral", 0xF08080},
{"lightcyan", 0xE0FFFF},
{"lightgoldenrodyellow", 0xFAFAD2},
{"lightgray", 0xD3D3D3},
{"lightgreen", 0x90EE90},
{"lightgrey", 0xD3D3D3},
{"lightpink", 0xFFB6C1},
{"lightsalmon", 0xFFA07A},
{"lightseagreen", 0x20B2AA},
{"lightskyblue", 0x87CEFA},
{"lightslategray", 0x778899},
{"lightslategrey", 0x778899},
{"lightsteelblue", 0xB0C4DE},
{"lightyellow", 0xFFFFE0},
{"lime", 0x00FF00},
{"limegreen", 0x32CD32},
{"linen", 0xFAF0E6},
{"magenta", 0xFF00FF},
{"maroon", 0x800000},
{"mediumaquamarine", 0x66CDAA},
{"mediumblue", 0x0000CD},
{"mediumorchid", 0xBA55D3},
{"mediumpurple", 0x9370DB},
{"mediumseagreen", 0x3CB371},
{"mediumslateblue", 0x7B68EE},
{"mediumspringgreen", 0x00FA9A},
{"mediumturquoise", 0x48D1CC},
{"mediumvioletred", 0xC71585},
{"midnightblue", 0x191970},
{"mintcream", 0xF5FFFA},
{"mistyrose", 0xFFE4E1},
{"moccasin", 0xFFE4B5},
{"navajowhite", 0xFFDEAD},
{"navy", 0x000080},
{"oldlace", 0xFDF5E6},
{"olive", 0x808000},
{"olivedrab", 0x6B8E23},
{"orange", 0xFFA500},
{"orangered", 0xFF4500},
{"orchid", 0xDA70D6},
{"palegoldenrod", 0xEEE8AA},
{"palegreen", 0x98FB98},
{"paleturquoise", 0xAFEEEE},
{"palevioletred", 0xDB7093},
{"papayawhip", 0xFFEFD5},
{"peachpuff", 0xFFDAB9},
{"peru", 0xCD853F},
{"pink", 0xFFC0CB},
{"plum", 0xDDA0DD},
{"powderblue", 0xB0E0E6},
{"purple", 0x800080},
{"rebeccapurple", 0x663399},
{"red", 0xFF0000},
{"rosybrown", 0xBC8F8F},
{"royalblue", 0x4169E1},
{"saddlebrown", 0x8B4513},
{"salmon", 0xFA8072},
{"sandybrown", 0xF4A460},
{"seagreen", 0x2E8B57},
{"seashell", 0xFFF5EE},
{"sienna", 0xA0522D},
{"silver", 0xC0C0C0},
{"skyblue", 0x87CEEB},
{"slateblue", 0x6A5ACD},
{"slategray", 0x708090},
{"slategrey", 0x708090},
{"snow", 0xFFFAFA},
{"springgreen", 0x00FF7F},
{"steelblue", 0x4682B4},
{"tan", 0xD2B48C},
{"teal", 0x008080},
{"thistle", 0xD8BFD8},
{"tomato", 0xFF6347},
{"turquoise", 0x40E0D0},
{"violet", 0xEE82EE},
{"wheat", 0xF5DEB3},
{"white", 0xFFFFFF},
{"whitesmoke", 0xF5F5F5},
{"yellow", 0xFFFF00},
{"yellowgreen", 0x9ACD32}
};
Color Parser::parseColor(const std::string& string, const StyledElement* element, const Color& defaultValue)
{
if(string.empty())
return defaultValue;
auto ptr = string.data();
auto end = ptr + string.size();
if(Utils::skipDesc(ptr, end, '#'))
{
auto start = ptr;
unsigned int value;
if(!Utils::parseInteger(ptr, end, value, 16))
return defaultValue;
auto n = ptr - start;
if(n != 3 && n != 6)
return defaultValue;
if(n == 3)
{
value = ((value&0xf00) << 8) | ((value&0x0f0) << 4) | (value&0x00f);
value |= value << 4;
}
return Color(value | 0xFF000000);
}
if(Utils::skipDesc(ptr, end, "rgb("))
{
int r, g, b;
if(!Utils::skipWs(ptr, end)
|| !parseColorComponent(ptr, end, r)
|| !Utils::skipWsComma(ptr, end)
|| !parseColorComponent(ptr, end, g)
|| !Utils::skipWsComma(ptr, end)
|| !parseColorComponent(ptr, end, b)
|| !Utils::skipWs(ptr, end)
|| !Utils::skipDesc(ptr, end, ')'))
return defaultValue;
return Color(r, g, b, 255);
}
if(Utils::skipDesc(ptr, end, "none"))
return Color::Transparent;
if(Utils::skipDesc(ptr, end, "currentColor"))
return element->color();
auto it = colormap.find(string);
if(it == colormap.end())
return defaultValue;
return Color(it->second | 0xFF000000);
}
Paint Parser::parsePaint(const std::string& string, const StyledElement* element, const Color& defaultValue)
{
if(string.empty())
return defaultValue;
auto ptr = string.data();
auto end = ptr + string.size();
std::string ref;
if(!parseUrlFragment(ptr, end, ref))
return parseColor(string, element, defaultValue);
std::string fallback{ptr, end};
if(fallback.empty())
return Paint{ref, Color::Transparent};
return Paint{ref, parseColor(fallback, element, defaultValue)};
}
WindRule Parser::parseWindRule(const std::string& string)
{
if(string.empty())
return WindRule::NonZero;
if(string.compare("evenodd") == 0)
return WindRule::EvenOdd;
return WindRule::NonZero;
}
LineCap Parser::parseLineCap(const std::string& string)
{
if(string.empty())
return LineCap::Butt;
if(string.compare("round") == 0)
return LineCap::Round;
if(string.compare("square") == 0)
return LineCap::Square;
return LineCap::Butt;
}
LineJoin Parser::parseLineJoin(const std::string& string)
{
if(string.empty())
return LineJoin::Miter;
if(string.compare("bevel") == 0)
return LineJoin::Bevel;
if(string.compare("round") == 0)
return LineJoin::Round;
return LineJoin::Miter;
}
Display Parser::parseDisplay(const std::string& string)
{
if(string.empty())
return Display::Inline;
if(string.compare("none") == 0)
return Display::None;
return Display::Inline;
}
Visibility Parser::parseVisibility(const std::string& string)
{
if(string.empty())
return Visibility::Visible;
if(string.compare("visible") == 0)
return Visibility::Visible;
return Visibility::Hidden;
}
Overflow Parser::parseOverflow(const std::string& string, Overflow defaultValue)
{
if(string.empty())
return defaultValue;
if(string.compare("visible") == 0)
return Overflow::Visible;
if(string.compare("hidden") == 0)
return Overflow::Hidden;
return defaultValue;
}
bool Parser::parseLength(const char*& ptr, const char* end, double& value, LengthUnits& units, LengthNegativeValuesMode mode)
{
if(!Utils::parseNumber(ptr, end, value))
return false;
if(mode == ForbidNegativeLengths && value < 0.0)
return false;
char c[2] = {0, 0};
if(ptr + 0 < end) c[0] = ptr[0];
if(ptr + 1 < end) c[1] = ptr[1];
switch(c[0]) {
case '%':
units = LengthUnits::Percent;
ptr += 1;
break;
case 'p':
if(c[1] == 'x')
units = LengthUnits::Px;
else if(c[1] == 'c')
units = LengthUnits::Pc;
else if(ptr[1] == 't')
units = LengthUnits::Pt;
else
return false;
ptr += 2;
break;
case 'i':
if(c[1] == 'n')
units = LengthUnits::In;
else
return false;
ptr += 2;
break;
case 'c':
if(c[1] == 'm')
units = LengthUnits::Cm;
else
return false;
ptr += 2;
break;
case 'm':
if(c[1] == 'm')
units = LengthUnits::Mm;
else
return false;
ptr += 2;
break;
case 'e':
if(c[1] == 'm')
units = LengthUnits::Em;
else if(c[1] == 'x')
units = LengthUnits::Ex;
else
return false;
ptr += 2;
break;
default:
units = LengthUnits::Number;
break;
}
return true;
}
bool Parser::parseNumberList(const char*& ptr, const char* end, double* values, int count)
{
for(int i = 0;i < count;i++)
{
if(!Utils::parseNumber(ptr, end, values[i]))
return false;
Utils::skipWsComma(ptr, end);
}
return true;
}
bool Parser::parseArcFlag(const char*& ptr, const char* end, bool& flag)
{
if(ptr < end && *ptr == '0')
flag = false;
else if(ptr < end && *ptr == '1')
flag = true;
else
return false;
++ptr;
Utils::skipWsComma(ptr, end);
return true;
}
bool Parser::parseColorComponent(const char*& ptr, const char* end, int& component)
{
double value = 0;
if(!Utils::parseNumber(ptr, end, value))
return false;
if(Utils::skipDesc(ptr, end, '%'))
value *= 2.55;
value = std::clamp(value, 0.0, 255.0);
component = static_cast<int>(std::round(value));
return true;
}
bool Parser::parseUrlFragment(const char*& ptr, const char* end, std::string& ref)
{
if(!Utils::skipDesc(ptr, end, "url(")
|| !Utils::skipWs(ptr, end)) {
return false;
}
switch(*ptr) {
case '\'':
case '"': {
auto delim = *ptr;
++ptr; // delim
if(!Utils::skipWs(ptr, end) || *ptr != '#')
return false;
++ptr; // #
if(!Utils::readUntil(ptr, end, delim, ref))
return false;
++ptr; // delim
break;
}
case '#':
++ptr; // #
Utils::readUntil(ptr, end, ')', ref);
break;
default:
return false;
}
if(ptr >= end || *ptr != ')')
return false;
++ptr; // )
Utils::skipWs(ptr, end);
return true;
}
bool Parser::parseTransform(const char*& ptr, const char* end, TransformType& type, double* values, int& count)
{
int required = 0;
int optional = 0;
if(Utils::skipDesc(ptr, end, "matrix"))
{
type = TransformType::Matrix;
required = 6;
optional = 0;
}
else if(Utils::skipDesc(ptr, end, "rotate"))
{
type = TransformType::Rotate;
required = 1;
optional = 2;
}
else if(Utils::skipDesc(ptr, end, "scale"))
{
type = TransformType::Scale;
required = 1;
optional = 1;
}
else if(Utils::skipDesc(ptr, end, "skewX"))
{
type = TransformType::SkewX;
required = 1;
optional = 0;
}
else if(Utils::skipDesc(ptr, end, "skewY"))
{
type = TransformType::SkewY;
required = 1;
optional = 0;
}
else if(Utils::skipDesc(ptr, end, "translate"))
{
type = TransformType::Translate;
required = 1;
optional = 1;
}
else
{
return false;
}
Utils::skipWs(ptr, end);
if(ptr >= end || *ptr != '(')
return false;
++ptr;
int maxCount = required + optional;
count = 0;
Utils::skipWs(ptr, end);
while(count < maxCount)
{
if(!Utils::parseNumber(ptr, end, values[count]))
break;
++count;
Utils::skipWsComma(ptr, end);
}
if(ptr >= end || *ptr != ')' || !(count == required || count == maxCount))
return false;
++ptr;
return true;
}
static const std::map<std::string, ElementID> elementmap = {
{"circle", ElementID::Circle},
{"clipPath", ElementID::ClipPath},
{"defs", ElementID::Defs},
{"ellipse", ElementID::Ellipse},
{"g", ElementID::G},
{"line", ElementID::Line},
{"linearGradient", ElementID::LinearGradient},
{"marker", ElementID::Marker},
{"mask", ElementID::Mask},
{"path", ElementID::Path},
{"pattern", ElementID::Pattern},
{"polygon", ElementID::Polygon},
{"polyline", ElementID::Polyline},
{"radialGradient", ElementID::RadialGradient},
{"rect", ElementID::Rect},
{"stop", ElementID::Stop},
{"style", ElementID::Style},
{"solidColor", ElementID::SolidColor},
{"svg", ElementID::Svg},
{"symbol", ElementID::Symbol},
{"use", ElementID::Use}
};
static const std::map<std::string, PropertyID> propertymap = {
{"class", PropertyID::Class},
{"clipPathUnits", PropertyID::ClipPathUnits},
{"cx", PropertyID::Cx},
{"cy", PropertyID::Cy},
{"d", PropertyID::D},
{"fx", PropertyID::Fx},
{"fy", PropertyID::Fy},
{"gradientTransform", PropertyID::GradientTransform},
{"gradientUnits", PropertyID::GradientUnits},
{"height", PropertyID::Height},
{"href", PropertyID::Href},
{"id", PropertyID::Id},
{"markerHeight", PropertyID::MarkerHeight},
{"markerUnits", PropertyID::MarkerUnits},
{"markerWidth", PropertyID::MarkerWidth},
{"maskContentUnits", PropertyID::MaskContentUnits},
{"maskUnits", PropertyID::MaskUnits},
{"offset", PropertyID::Offset},
{"orient", PropertyID::Orient},
{"patternContentUnits", PropertyID::PatternContentUnits},
{"patternTransform", PropertyID::PatternTransform},
{"patternUnits", PropertyID::PatternUnits},
{"points", PropertyID::Points},
{"preserveAspectRatio", PropertyID::PreserveAspectRatio},
{"r", PropertyID::R},
{"refX", PropertyID::RefX},
{"refY", PropertyID::RefY},
{"rx", PropertyID::Rx},
{"ry", PropertyID::Ry},
{"spreadMethod", PropertyID::SpreadMethod},
{"style", PropertyID::Style},
{"transform", PropertyID::Transform},
{"viewBox", PropertyID::ViewBox},
{"width", PropertyID::Width},
{"x", PropertyID::X},
{"x1", PropertyID::X1},
{"x2", PropertyID::X2},
{"xlink:href", PropertyID::Href},
{"y", PropertyID::Y},
{"y1", PropertyID::Y1},
{"y2", PropertyID::Y2}
};
static const std::map<std::string, PropertyID> csspropertymap = {
{"clip-path", PropertyID::Clip_Path},
{"clip-rule", PropertyID::Clip_Rule},
{"color", PropertyID::Color},
{"display", PropertyID::Display},
{"fill", PropertyID::Fill},
{"fill-opacity", PropertyID::Fill_Opacity},
{"fill-rule", PropertyID::Fill_Rule},
{"marker-end", PropertyID::Marker_End},
{"marker-mid", PropertyID::Marker_Mid},
{"marker-start", PropertyID::Marker_Start},
{"mask", PropertyID::Mask},
{"opacity", PropertyID::Opacity},
{"overflow", PropertyID::Overflow},
{"solid-color", PropertyID::Solid_Color},
{"solid-opacity", PropertyID::Solid_Opacity},
{"stop-color", PropertyID::Stop_Color},
{"stop-opacity", PropertyID::Stop_Opacity},
{"stroke", PropertyID::Stroke},
{"stroke-dasharray", PropertyID::Stroke_Dasharray},
{"stroke-dashoffset", PropertyID::Stroke_Dashoffset},
{"stroke-linecap", PropertyID::Stroke_Linecap},
{"stroke-linejoin", PropertyID::Stroke_Linejoin},
{"stroke-miterlimit", PropertyID::Stroke_Miterlimit},
{"stroke-opacity", PropertyID::Stroke_Opacity},
{"stroke-width", PropertyID::Stroke_Width},
{"visibility", PropertyID::Visibility}
};
static inline ElementID elementId(const std::string& name)
{
auto it = elementmap.find(name);
if(it == elementmap.end())
return ElementID::Unknown;
return it->second;
}
static inline PropertyID cssPropertyId(const std::string& name)
{
auto it = csspropertymap.find(name);
if(it == csspropertymap.end())
return PropertyID::Unknown;
return it->second;
}
static inline PropertyID propertyId(const std::string& name)
{
auto it = propertymap.find(name);
if(it == propertymap.end())
return cssPropertyId(name);
return it->second;
}
bool RuleData::match(const Element* element) const
{
if(m_selector.empty())
return false;
if(m_selector.size() == 1)
return matchSimpleSelector(m_selector.front(), element);
auto it = m_selector.rbegin();
auto end = m_selector.rend();
if(!matchSimpleSelector(*it, element))
return false;
++it;
while(it != end)
{
switch(it->combinator) {
case SimpleSelector::Combinator::Child:
case SimpleSelector::Combinator::Descendant:
element = element->parent;
break;
case SimpleSelector::Combinator::DirectAdjacent:
case SimpleSelector::Combinator::InDirectAdjacent:
element = element->previousElement();
break;
}
if(element == nullptr)
return false;
auto match = matchSimpleSelector(*it, element);
if(!match && (it->combinator != SimpleSelector::Combinator::Descendant && it->combinator != SimpleSelector::Combinator::InDirectAdjacent))
return false;
if(match || (it->combinator != SimpleSelector::Combinator::Descendant && it->combinator != SimpleSelector::Combinator::InDirectAdjacent))
++it;
}
return true;
}
bool RuleData::matchSimpleSelector(const SimpleSelector& selector, const Element* element) const
{
if(selector.id != ElementID::Star && selector.id != element->id)
return false;
for(auto& sel : selector.attributeSelectors)
if(!matchAttributeSelector(sel, element))
return false;
for(auto& sel : selector.pseudoClassSelectors)
if(!matchPseudoClassSelector(sel, element))
return false;
return true;
}
bool RuleData::matchAttributeSelector(const AttributeSelector& selector, const Element* element) const
{
auto& value = element->get(selector.id);
if(value.empty())
return false;
if(selector.matchType == AttributeSelector::MatchType::None)
return true;
if(selector.matchType == AttributeSelector::MatchType::Equal)
return selector.value == value;
if(selector.matchType == AttributeSelector::MatchType::Includes)
{
auto ptr = value.data();
auto end = ptr + value.size();
while(ptr < end)
{
auto start = ptr;
while(ptr < end && !IS_WS(*ptr))
++ptr;
if(selector.value == std::string(start, ptr))
return true;
Utils::skipWs(ptr, end);
}
return false;
}
auto starts_with = [](const std::string& string, const std::string& prefix) {
if(prefix.empty() || prefix.size() > string.size())
return false;
return string.compare(0, prefix.size(), prefix) == 0;
};
auto ends_with = [](const std::string& string, const std::string& suffix) {
if(suffix.empty() || suffix.size() > string.size())
return false;
return string.compare(string.size() - suffix.size(), suffix.size(), suffix) == 0;
};
if(selector.matchType == AttributeSelector::MatchType::DashMatch)
{
if(selector.value == value)
return true;
return starts_with(value, selector.value + '-');
}
if(selector.matchType == AttributeSelector::MatchType::StartsWith)
return starts_with(value, selector.value);
if(selector.matchType == AttributeSelector::MatchType::EndsWith)
return ends_with(value, selector.value);
if(selector.matchType == AttributeSelector::MatchType::Contains)
return value.find(selector.value) != std::string::npos;
return false;
}
bool RuleData::matchPseudoClassSelector(const PseudoClassSelector& selector, const Element* element) const
{
if(selector.type == PseudoClassSelector::Type::Empty)
return element->children.empty();
if(selector.type == PseudoClassSelector::Type::Root)
return element->parent == nullptr;
if(selector.type == PseudoClassSelector::Type::Is)
{
for(auto& selector : selector.subSelectors) {
for(auto& sel : selector) {
if(!matchSimpleSelector(sel, element)) {
return false;
}
}
}
return true;
}
if(selector.type == PseudoClassSelector::Type::Not)
{
for(auto& selector : selector.subSelectors) {
for(auto& sel : selector) {
if(matchSimpleSelector(sel, 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 = element->previousElement();
}
return true;
}
if(selector.type == PseudoClassSelector::Type::LastOfType)
{
auto sibling = element->nextElement();
while(sibling)
{
if(sibling->id == element->id)
return false;
sibling = element->nextElement();
}
return true;
}
return false;
}
void StyleSheet::parse(const std::string& content)
{
CSSParser::parseSheet(this, content);
}
void StyleSheet::add(const Rule& rule)
{
for(auto& selector : rule.selectors) {
uint32_t specificity = 0;
for(auto& simpleSelector : selector) {
specificity += (simpleSelector.id == ElementID::Star) ? 0x0 : 0x1;
for(auto& attributeSelector : simpleSelector.attributeSelectors) {
specificity += (attributeSelector.id == PropertyID::Id) ? 0x10000 : 0x100;
}
}
m_rules.emplace(selector, rule.declarations, specificity, m_position);
}
m_position += 1;
}
std::vector<const PropertyList*> StyleSheet::match(const Element *element) const
{
std::vector<const PropertyList*> declarations;
for(auto& rule : m_rules) {
if(!rule.match(element))
continue;
declarations.push_back(&rule.properties());
}
return declarations;
}
#define IS_STARTNAMECHAR(c) (IS_ALPHA(c) || (c) == '_' || (c) == ':')
#define IS_NAMECHAR(c) (IS_STARTNAMECHAR(c) || IS_NUM(c) || (c) == '-' || (c) == '.')
static inline bool readIdentifier(const char*& ptr, const char* end, std::string& value)
{
if(ptr >= end || !IS_STARTNAMECHAR(*ptr))
return false;
auto start = ptr;
++ptr;
while(ptr < end && IS_NAMECHAR(*ptr))
++ptr;
value.assign(start, ptr);
return true;
}
#define IS_CSS_STARTNAMECHAR(c) (IS_ALPHA(c) || (c) == '_' || (c) == '-')
#define IS_CSS_NAMECHAR(c) (IS_CSS_STARTNAMECHAR(c) || IS_NUM(c))
static inline bool readCSSIdentifier(const char*& ptr, const char* end, std::string& value)
{
if(ptr >= end || !IS_CSS_STARTNAMECHAR(*ptr))
return false;
auto start = ptr;
++ptr;
while(ptr < end && IS_CSS_NAMECHAR(*ptr))
++ptr;
value.assign(start, ptr);
return true;
}
bool CSSParser::parseSheet(StyleSheet* sheet, const std::string& value)
{
auto ptr = value.data();
auto end = ptr + value.size();
Rule rule;
while(ptr < end)
{
Utils::skipWs(ptr, end);
if(Utils::skipDesc(ptr, end, '@'))
{
if(!parseAtRule(ptr, end))
return false;
continue;
}
if(!parseRule(ptr, end, rule))
return false;
sheet->add(rule);
}
return true;
}
bool CSSParser::parseAtRule(const char*& ptr, const char* end)
{
int depth = 0;
while(ptr < end)
{
auto ch = *ptr;
++ptr;
if(ch == ';' && depth == 0)
break;
if(ch == '{') ++depth;
else if(ch == '}' && depth > 0)
{
if(depth == 1)
break;
--depth;
}
}
return true;
}
bool CSSParser::parseRule(const char*& ptr, const char* end, Rule& rule)
{
if(!parseSelectors(ptr, end, rule.selectors))
return false;
if(!parseDeclarations(ptr, end, rule.declarations))
return false;
return true;
}
bool CSSParser::parseSelectors(const char*& ptr, const char* end, SelectorList& selectors)
{
selectors.clear();
Selector selector;
if(!parseSelector(ptr, end, selector))
return false;
selectors.push_back(selector);
while(Utils::skipDesc(ptr, end, ','))
{
Utils::skipWs(ptr, end);
Selector selector;
if(!parseSelector(ptr, end, selector))
return false;
selectors.push_back(selector);
}
return true;
}
bool CSSParser::parseDeclarations(const char*& ptr, const char* end, PropertyList& declarations)
{
declarations.clear();
if(!Utils::skipDesc(ptr, end, '{'))
return false;
std::string name;
std::string value;
Utils::skipWs(ptr, end);
do {
if(!readCSSIdentifier(ptr, end, name))
return false;
Utils::skipWs(ptr, end);
if(!Utils::skipDesc(ptr, end, ':'))
return false;
Utils::skipWs(ptr, end);
auto start = ptr;
while(ptr < end && !(*ptr == '!' || *ptr == ';' || *ptr == '}'))
++ptr;
value.assign(start, Utils::rtrim(start, ptr));
int specificity = 0x10;
if(Utils::skipDesc(ptr, end, '!'))
{
if(!Utils::skipDesc(ptr, end, "important"))
return false;
specificity = 0x1000;
}
auto id = cssPropertyId(name);
if(id != PropertyID::Unknown)
declarations.set(id, value, specificity);
Utils::skipWsDelimiter(ptr, end, ';');
} while(ptr < end && *ptr != '}');
return Utils::skipDesc(ptr, end, '}');
}
#define IS_SELECTOR_STARTNAMECHAR(c) (IS_CSS_STARTNAMECHAR(c) || (c) == '*' || (c) == '#' || (c) == '.' || (c) == '[' || (c) == ':')
bool CSSParser::parseSelector(const char*& ptr, const char* end, Selector& selector)
{
do {
SimpleSelector simpleSelector;
if(!parseSimpleSelector(ptr, end, simpleSelector))
return false;
selector.push_back(simpleSelector);
Utils::skipWs(ptr, end);
} while(ptr < end && IS_SELECTOR_STARTNAMECHAR(*ptr));
return true;
}
bool CSSParser::parseSimpleSelector(const char*& ptr, const char* end, SimpleSelector& simpleSelector)
{
std::string name;
if(Utils::skipDesc(ptr, end, '*'))
simpleSelector.id = ElementID::Star;
else if(readCSSIdentifier(ptr, end, name))
simpleSelector.id = elementId(name);
while(ptr < end)
{
if(Utils::skipDesc(ptr, end, '#'))
{
AttributeSelector a;
a.id = PropertyID::Id;
a.matchType = AttributeSelector::MatchType::Equal;
if(!readCSSIdentifier(ptr, end, a.value))
return false;
simpleSelector.attributeSelectors.push_back(a);
continue;
}
if(Utils::skipDesc(ptr, end, '.'))
{
AttributeSelector a;
a.id = PropertyID::Class;
a.matchType = AttributeSelector::MatchType::Includes;
if(!readCSSIdentifier(ptr, end, a.value))
return false;
simpleSelector.attributeSelectors.push_back(a);
continue;
}
if(Utils::skipDesc(ptr, end, '['))
{
Utils::skipWs(ptr, end);
if(!readCSSIdentifier(ptr, end, name))
return false;
AttributeSelector a;
a.id = propertyId(name);
if(Utils::skipDesc(ptr, end, '='))
a.matchType = AttributeSelector::MatchType::Equal;
else if(Utils::skipDesc(ptr, end, "~="))
a.matchType = AttributeSelector::MatchType::Includes;
else if(Utils::skipDesc(ptr, end, "|="))
a.matchType = AttributeSelector::MatchType::DashMatch;
else if(Utils::skipDesc(ptr, end, "^="))
a.matchType = AttributeSelector::MatchType::StartsWith;
else if(Utils::skipDesc(ptr, end, "$="))
a.matchType = AttributeSelector::MatchType::EndsWith;
else if(Utils::skipDesc(ptr, end, "*="))
a.matchType = AttributeSelector::MatchType::Contains;
if(a.matchType != AttributeSelector::MatchType::None)
{
Utils::skipWs(ptr, end);
if(!readCSSIdentifier(ptr, end, a.value))
{
if(ptr >= end || !(*ptr == '\"' || *ptr == '\''))
return false;
auto quote = *ptr;
++ptr;
if(!Utils::readUntil(ptr, end, quote, a.value))
return false;
++ptr;
}
}
Utils::skipWs(ptr, end);
if(!Utils::skipDesc(ptr, end, ']'))
return false;
simpleSelector.attributeSelectors.push_back(a);
continue;
}
if(Utils::skipDesc(ptr, end, ':'))
{
if(!readCSSIdentifier(ptr, end, 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)
{
if(!Utils::skipDesc(ptr, end, '('))
return false;
Utils::skipWs(ptr, end);
if(!parseSelectors(ptr, end, selector.subSelectors))
return false;
Utils::skipWs(ptr, end);
if(!Utils::skipDesc(ptr, end, ')'))
return false;
}
simpleSelector.pseudoClassSelectors.push_back(selector);
continue;
}
break;
}
Utils::skipWs(ptr, end);
if(Utils::skipDesc(ptr, end, '>'))
simpleSelector.combinator = SimpleSelector::Combinator::Child;
else if(Utils::skipDesc(ptr, end, '+'))
simpleSelector.combinator = SimpleSelector::Combinator::DirectAdjacent;
else if(Utils::skipDesc(ptr, end, '~'))
simpleSelector.combinator = SimpleSelector::Combinator::InDirectAdjacent;
return true;
}
static inline std::unique_ptr<Element> createElement(ElementID id)
{
switch(id) {
case ElementID::Svg:
return std::make_unique<SVGElement>();
case ElementID::Path:
return std::make_unique<PathElement>();
case ElementID::G:
return std::make_unique<GElement>();
case ElementID::Rect:
return std::make_unique<RectElement>();
case ElementID::Circle:
return std::make_unique<CircleElement>();
case ElementID::Ellipse:
return std::make_unique<EllipseElement>();
case ElementID::Line:
return std::make_unique<LineElement>();
case ElementID::Defs:
return std::make_unique<DefsElement>();
case ElementID::Polygon:
return std::make_unique<PolygonElement>();
case ElementID::Polyline:
return std::make_unique<PolylineElement>();
case ElementID::Stop:
return std::make_unique<StopElement>();
case ElementID::LinearGradient:
return std::make_unique<LinearGradientElement>();
case ElementID::RadialGradient:
return std::make_unique<RadialGradientElement>();
case ElementID::Symbol:
return std::make_unique<SymbolElement>();
case ElementID::Use:
return std::make_unique<UseElement>();
case ElementID::Pattern:
return std::make_unique<PatternElement>();
case ElementID::Mask:
return std::make_unique<MaskElement>();
case ElementID::ClipPath:
return std::make_unique<ClipPathElement>();
case ElementID::SolidColor:
return std::make_unique<SolidColorElement>();
case ElementID::Marker:
return std::make_unique<MarkerElement>();
case ElementID::Style:
return std::make_unique<StyleElement>();
default:
break;
}
return nullptr;
}
static inline bool decodeText(const char* ptr, const char* end, std::string& value)
{
value.clear();
while(ptr < end)
{
auto ch = *ptr;
++ptr;
if(ch != '&')
{
value.push_back(ch);
continue;
}
if(Utils::skipDesc(ptr, end, '#'))
{
int base = 10;
if(Utils::skipDesc(ptr, end, 'x'))
base = 16;
unsigned int cp;
if(!Utils::parseInteger(ptr, end, cp, base))
return false;
char c[5] = {0, 0, 0, 0, 0};
if(cp < 0x80)
{
c[1] = 0;
c[0] = cp;
}
else if(cp < 0x800)
{
c[2] = 0;
c[1] = (cp & 0x3F) | 0x80;
cp >>= 6;
c[0] = cp | 0xC0;
}
else if(cp < 0x10000)
{
c[3] = 0;
c[2] = (cp & 0x3F) | 0x80;
cp >>= 6;
c[1] = (cp & 0x3F) | 0x80;
cp >>= 6;
c[0] = cp | 0xE0;
}
else if(cp < 0x200000)
{
c[4] = 0;
c[3] = (cp & 0x3F) | 0x80;
cp >>= 6;
c[2] = (cp & 0x3F) | 0x80;
cp >>= 6;
c[1] = (cp & 0x3F) | 0x80;
cp >>= 6;
c[0] = cp | 0xF0;
}
value.append(c);
}
else
{
if(Utils::skipDesc(ptr, end, "amp"))
value.push_back('&');
else if(Utils::skipDesc(ptr, end, "lt"))
value.push_back('<');
else if(Utils::skipDesc(ptr, end, "gt"))
value.push_back('>');
else if(Utils::skipDesc(ptr, end, "quot"))
value.push_back('\"');
else if(Utils::skipDesc(ptr, end, "apos"))
value.push_back('\'');
else
return false;
}
if(!Utils::skipDesc(ptr, end, ';'))
return false;
}
return true;
}
static inline void parseStyle(const std::string& string, Element* element)
{
auto ptr = string.data();
auto end = ptr + string.size();
std::string name;
std::string value;
Utils::skipWs(ptr, end);
while(ptr < end && readCSSIdentifier(ptr, end, name))
{
Utils::skipWs(ptr, end);
if(!Utils::skipDesc(ptr, end, ':'))
return;
Utils::skipWs(ptr, end);
auto start = ptr;
while(ptr < end && *ptr != ';')
++ptr;
value.assign(start, Utils::rtrim(start, ptr));
auto id = cssPropertyId(name);
if(id != PropertyID::Unknown)
element->set(id, value, 0x100);
Utils::skipWsDelimiter(ptr, end, ';');
}
}
TreeBuilder::TreeBuilder() = default;
TreeBuilder::~TreeBuilder() = default;
bool TreeBuilder::parse(const char* data, std::size_t size)
{
auto ptr = data;
auto end = ptr + size;
StyleSheet styleSheet;
Element* current = nullptr;
std::string name;
std::string value;
int ignoring = 0;
auto remove_comments = [](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("/*");
}
};
auto handle_text = [&](const char* start, const char* end, bool in_cdata) {
if(ignoring > 0 || current == nullptr || current->id != ElementID::Style)
return;
if(in_cdata)
value.assign(start, end);
else
decodeText(start, end, value);
remove_comments(value);
styleSheet.parse(value);
};
while(ptr < end)
{
auto start = ptr;
if(!Utils::skipUntil(ptr, end, '<'))
break;
handle_text(start, ptr, false);
ptr += 1;
if(ptr < end && *ptr == '/')
{
if(current == nullptr && ignoring == 0)
return false;
++ptr;
if(!readIdentifier(ptr, end, name))
return false;
Utils::skipWs(ptr, end);
if(ptr >= end || *ptr != '>')
return false;
if(ignoring > 0)
--ignoring;
else
current = current->parent;
++ptr;
continue;
}
if(ptr < end && *ptr == '?')
{
++ptr;
if(!readIdentifier(ptr, end, name))
return false;
if(!Utils::skipUntil(ptr, end, "?>"))
return false;
ptr += 2;
continue;
}
if(ptr < end && *ptr == '!')
{
++ptr;
if(Utils::skipDesc(ptr, end, "--"))
{
start = ptr;
if(!Utils::skipUntil(ptr, end, "-->"))
return false;
handle_text(start, ptr, false);
ptr += 3;
continue;
}
if(Utils::skipDesc(ptr, end, "[CDATA["))
{
start = ptr;
if(!Utils::skipUntil(ptr, end, "]]>"))
return false;
handle_text(start, ptr, true);
ptr += 3;
continue;
}
if(Utils::skipDesc(ptr, end, "DOCTYPE"))
{
while(ptr < end && *ptr != '>')
{
if(*ptr == '[')
{
++ptr;
int depth = 1;
while(ptr < end && depth > 0)
{
if(*ptr == '[') ++depth;
else if(*ptr == ']') --depth;
++ptr;
}
}
else
{
++ptr;
}
}
if(ptr >= end || *ptr != '>')
return false;
ptr += 1;
continue;
}
return false;
}
if(!readIdentifier(ptr, end, name))
return false;
auto id = ignoring == 0 ? elementId(name) : ElementID::Unknown;
if(id == ElementID::Unknown)
++ignoring;
Element* element = nullptr;
if(ignoring == 0)
{
if(m_rootElement && current == nullptr)
return false;
if(m_rootElement == nullptr)
{
if(id != ElementID::Svg)
return false;
m_rootElement = std::make_unique<SVGElement>();
element = m_rootElement.get();
}
else
{
auto child = createElement(id);
element = child.get();
current->addChild(std::move(child));
}
}
Utils::skipWs(ptr, end);
while(ptr < end && readIdentifier(ptr, end, name))
{
Utils::skipWs(ptr, end);
if(ptr >= end || *ptr != '=')
return false;
++ptr;
Utils::skipWs(ptr, end);
if(ptr >= end || !(*ptr == '\"' || *ptr == '\''))
return false;
auto quote = *ptr;
++ptr;
Utils::skipWs(ptr, end);
start = ptr;
while(ptr < end && *ptr != quote)
++ptr;
if(ptr >= end || *ptr != quote)
return false;
auto id = element ? propertyId(name) : PropertyID::Unknown;
if(id != PropertyID::Unknown)
{
decodeText(start, Utils::rtrim(start, ptr), value);
if(id == PropertyID::Style)
{
remove_comments(value);
parseStyle(value, element);
}
else
{
if(id == PropertyID::Id)
m_idCache.emplace(value, element);
element->set(id, value, 0x1);
}
}
++ptr;
Utils::skipWs(ptr, end);
}
if(ptr < end && *ptr == '>')
{
if(element != nullptr)
current = element;
++ptr;
continue;
}
if(ptr < end && *ptr == '/')
{
++ptr;
if(ptr >= end || *ptr != '>')
return false;
if(ignoring > 0)
--ignoring;
++ptr;
continue;
}
return false;
}
if(!m_rootElement || ptr < end || ignoring > 0)
return false;
if(!styleSheet.empty())
{
m_rootElement->transverse([&styleSheet](Node* node) {
if(node->isText())
return false;
auto element = static_cast<Element*>(node);
auto declarations = styleSheet.match(element);
for(auto& declaration : declarations)
element->properties.add(*declaration);
return false;
});
}
return true;
}
Element* TreeBuilder::getElementById(const std::string& id) const
{
auto it = m_idCache.find(id);
if(it == m_idCache.end())
return nullptr;
return it->second;
}
std::unique_ptr<LayoutSymbol> TreeBuilder::build() const
{
return m_rootElement->build(this);
}
} // namespace lunasvg