From d034a702eb3a59bff5c762816eef7210ed025e47 Mon Sep 17 00:00:00 2001 From: Chlumsky Date: Sun, 21 Apr 2024 18:31:39 +0200 Subject: [PATCH] Alternative XML parser interface --- CMakeLists.txt | 5 +- ext/import-svg.cpp | 591 +++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 553 insertions(+), 43 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7c776e0..44e8fe0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -145,6 +145,7 @@ if(NOT MSDFGEN_CORE_ONLY) set_property(TARGET msdfgen-ext PROPERTY MSVC_RUNTIME_LIBRARY "${MSDFGEN_MSVC_RUNTIME}") target_compile_definitions(msdfgen-ext INTERFACE MSDFGEN_EXTENSIONS) if(NOT MSDFGEN_DISABLE_SVG) + target_compile_definitions(msdfgen-ext PUBLIC MSDFGEN_USE_TINYXML2) target_link_libraries(msdfgen-ext PRIVATE tinyxml2::tinyxml2) else() target_compile_definitions(msdfgen-ext PUBLIC MSDFGEN_DISABLE_SVG) @@ -242,7 +243,9 @@ if(MSDFGEN_INSTALL) if(MSDFGEN_USE_SKIA) set(MSDFGEN_ADDITIONAL_DEFINES "${MSDFGEN_ADDITIONAL_DEFINES}\n#define MSDFGEN_USE_SKIA") endif() - if(MSDFGEN_DISABLE_SVG) + if(NOT MSDFGEN_DISABLE_SVG) + set(MSDFGEN_ADDITIONAL_DEFINES "${MSDFGEN_ADDITIONAL_DEFINES}\n#define MSDFGEN_USE_TINYXML2") + else() set(MSDFGEN_ADDITIONAL_DEFINES "${MSDFGEN_ADDITIONAL_DEFINES}\n#define MSDFGEN_DISABLE_SVG") endif() if(NOT MSDFGEN_DISABLE_PNG) diff --git a/ext/import-svg.cpp b/ext/import-svg.cpp index aa1f6ad..6a696ff 100644 --- a/ext/import-svg.cpp +++ b/ext/import-svg.cpp @@ -8,7 +8,16 @@ #include #include #include +#include +#include +#include + +#ifdef MSDFGEN_USE_TINYXML2 #include +#endif +#ifdef MSDFGEN_USE_DROPXML +#include +#endif #ifdef MSDFGEN_USE_SKIA #include @@ -36,6 +45,8 @@ MSDFGEN_EXT_PUBLIC const int SVG_IMPORT_INCOMPLETE_FLAG = 0x04; MSDFGEN_EXT_PUBLIC const int SVG_IMPORT_UNSUPPORTED_FEATURE_FLAG = 0x08; MSDFGEN_EXT_PUBLIC const int SVG_IMPORT_TRANSFORMATION_IGNORED_FLAG = 0x10; +#define FLAGS_FINAL(flags) (((flags)&(SVG_IMPORT_SUCCESS_FLAG|SVG_IMPORT_INCOMPLETE_FLAG|SVG_IMPORT_UNSUPPORTED_FEATURE_FLAG)) == (SVG_IMPORT_SUCCESS_FLAG|SVG_IMPORT_INCOMPLETE_FLAG|SVG_IMPORT_UNSUPPORTED_FEATURE_FLAG)) + static void skipExtraChars(const char *&pathDef) { while (*pathDef == ',' || *pathDef == ' ' || *pathDef == '\t' || *pathDef == '\r' || *pathDef == '\n') ++pathDef; @@ -137,46 +148,6 @@ static void addArcApproximate(Contour &contour, Point2 startPoint, Point2 endPoi } } -#define FLAGS_FINAL(flags) (((flags)&(SVG_IMPORT_SUCCESS_FLAG|SVG_IMPORT_INCOMPLETE_FLAG|SVG_IMPORT_UNSUPPORTED_FEATURE_FLAG)) == (SVG_IMPORT_SUCCESS_FLAG|SVG_IMPORT_INCOMPLETE_FLAG|SVG_IMPORT_UNSUPPORTED_FEATURE_FLAG)) - -static void findPathByForwardIndex(tinyxml2::XMLElement *&path, int &flags, int &skips, tinyxml2::XMLElement *parent, bool hasTransformation) { - for (tinyxml2::XMLElement *cur = parent->FirstChildElement(); cur && !FLAGS_FINAL(flags); cur = cur->NextSiblingElement()) { - if (!strcmp(cur->Name(), "path")) { - if (!skips--) { - path = cur; - flags |= SVG_IMPORT_SUCCESS_FLAG; - if (hasTransformation || cur->Attribute("transform")) - flags |= SVG_IMPORT_TRANSFORMATION_IGNORED_FLAG; - } else if (flags&SVG_IMPORT_SUCCESS_FLAG) - flags |= SVG_IMPORT_INCOMPLETE_FLAG; - } else if (!strcmp(cur->Name(), "g")) - findPathByForwardIndex(path, flags, skips, cur, hasTransformation || cur->Attribute("transform")); - else if (!strcmp(cur->Name(), "rect") || !strcmp(cur->Name(), "circle") || !strcmp(cur->Name(), "ellipse") || !strcmp(cur->Name(), "polygon")) - flags |= SVG_IMPORT_INCOMPLETE_FLAG; - else if (!strcmp(cur->Name(), "mask") || !strcmp(cur->Name(), "use")) - flags |= SVG_IMPORT_UNSUPPORTED_FEATURE_FLAG; - } -} - -static void findPathByBackwardIndex(tinyxml2::XMLElement *&path, int &flags, int &skips, tinyxml2::XMLElement *parent, bool hasTransformation) { - for (tinyxml2::XMLElement *cur = parent->LastChildElement(); cur && !FLAGS_FINAL(flags); cur = cur->PreviousSiblingElement()) { - if (!strcmp(cur->Name(), "path")) { - if (!skips--) { - path = cur; - flags |= SVG_IMPORT_SUCCESS_FLAG; - if (hasTransformation || cur->Attribute("transform")) - flags |= SVG_IMPORT_TRANSFORMATION_IGNORED_FLAG; - } else if (flags&SVG_IMPORT_SUCCESS_FLAG) - flags |= SVG_IMPORT_INCOMPLETE_FLAG; - } else if (!strcmp(cur->Name(), "g")) - findPathByBackwardIndex(path, flags, skips, cur, hasTransformation || cur->Attribute("transform")); - else if (!strcmp(cur->Name(), "rect") || !strcmp(cur->Name(), "circle") || !strcmp(cur->Name(), "ellipse") || !strcmp(cur->Name(), "polygon")) - flags |= SVG_IMPORT_INCOMPLETE_FLAG; - else if (!strcmp(cur->Name(), "mask") || !strcmp(cur->Name(), "use")) - flags |= SVG_IMPORT_UNSUPPORTED_FEATURE_FLAG; - } -} - bool buildShapeFromSvgPath(Shape &shape, const char *pathDef, double endpointSnapRange) { char nodeType = '\0'; char prevNodeType = '\0'; @@ -307,6 +278,46 @@ bool buildShapeFromSvgPath(Shape &shape, const char *pathDef, double endpointSna return true; } +#ifdef MSDFGEN_USE_TINYXML2 + +static void findPathByForwardIndex(tinyxml2::XMLElement *&path, int &flags, int &skips, tinyxml2::XMLElement *parent, bool hasTransformation) { + for (tinyxml2::XMLElement *cur = parent->FirstChildElement(); cur && !FLAGS_FINAL(flags); cur = cur->NextSiblingElement()) { + if (!strcmp(cur->Name(), "path")) { + if (!skips--) { + path = cur; + flags |= SVG_IMPORT_SUCCESS_FLAG; + if (hasTransformation || cur->Attribute("transform")) + flags |= SVG_IMPORT_TRANSFORMATION_IGNORED_FLAG; + } else if (flags&SVG_IMPORT_SUCCESS_FLAG) + flags |= SVG_IMPORT_INCOMPLETE_FLAG; + } else if (!strcmp(cur->Name(), "g")) + findPathByForwardIndex(path, flags, skips, cur, hasTransformation || cur->Attribute("transform")); + else if (!strcmp(cur->Name(), "rect") || !strcmp(cur->Name(), "circle") || !strcmp(cur->Name(), "ellipse") || !strcmp(cur->Name(), "polygon")) + flags |= SVG_IMPORT_INCOMPLETE_FLAG; + else if (!strcmp(cur->Name(), "mask") || !strcmp(cur->Name(), "use")) + flags |= SVG_IMPORT_UNSUPPORTED_FEATURE_FLAG; + } +} + +static void findPathByBackwardIndex(tinyxml2::XMLElement *&path, int &flags, int &skips, tinyxml2::XMLElement *parent, bool hasTransformation) { + for (tinyxml2::XMLElement *cur = parent->LastChildElement(); cur && !FLAGS_FINAL(flags); cur = cur->PreviousSiblingElement()) { + if (!strcmp(cur->Name(), "path")) { + if (!skips--) { + path = cur; + flags |= SVG_IMPORT_SUCCESS_FLAG; + if (hasTransformation || cur->Attribute("transform")) + flags |= SVG_IMPORT_TRANSFORMATION_IGNORED_FLAG; + } else if (flags&SVG_IMPORT_SUCCESS_FLAG) + flags |= SVG_IMPORT_INCOMPLETE_FLAG; + } else if (!strcmp(cur->Name(), "g")) + findPathByBackwardIndex(path, flags, skips, cur, hasTransformation || cur->Attribute("transform")); + else if (!strcmp(cur->Name(), "rect") || !strcmp(cur->Name(), "circle") || !strcmp(cur->Name(), "ellipse") || !strcmp(cur->Name(), "polygon")) + flags |= SVG_IMPORT_INCOMPLETE_FLAG; + else if (!strcmp(cur->Name(), "mask") || !strcmp(cur->Name(), "use")) + flags |= SVG_IMPORT_UNSUPPORTED_FEATURE_FLAG; + } +} + bool loadSvgShape(Shape &output, const char *filename, int pathIndex, Vector2 *dimensions) { tinyxml2::XMLDocument doc; if (doc.LoadFile(filename)) @@ -340,8 +351,190 @@ bool loadSvgShape(Shape &output, const char *filename, int pathIndex, Vector2 *d return buildShapeFromSvgPath(output, pd, ENDPOINT_SNAP_RANGE_PROPORTION*dims.length()); } +#endif + +#ifdef MSDFGEN_USE_DROPXML + +struct StrRange { + const char *start, *end; + inline StrRange() : start(), end() { } + inline StrRange(const char *start, const char *end) : start(start), end(end) { } + inline std::string str() const { return std::string(start, end); } +}; + +static bool matchName(const char *start, const char *end, const char *value) { + for (const char *c = start; c < end; ++c, ++value) { + if (*c != *value) + return false; + } + return !*value; +} + +static std::string xmlDecode(const char *start, const char *end) { + if (!dropXML::decode(start, end, nullptr, nullptr)) { + std::string buffer(end-start+1, '\0'); + if (!dropXML::decode(start, end, &buffer[0], &buffer[buffer.size()-1])) + return std::string(); + if (start == buffer.data()) { + buffer.resize(end-start, '\0'); + return (std::string &&) buffer; + } + } + return std::string(start, end); +} + +static double xmlGetDouble(const char *start, const char *end) { + double x = 0; + std::string decodedStr(xmlDecode(start, end)); + const char *strPtr = decodedStr.c_str(); + readDouble(x, strPtr); + return x; +} + +#define SVG_NAME_IS(x) matchName(nameStart, nameEnd, x) +#define SVG_DEC_VAL() xmlDecode(valueStart, valueEnd) +#define SVG_DOUBLEVAL() xmlGetDouble(valueStart, valueEnd) + +static bool readFile(std::vector &output, const char *filename) { + if (FILE *f = fopen(filename, "rb")) { + struct FileGuard { + FILE *f; + ~FileGuard() { + fclose(f); + } + } fileGuard = { f }; + if (fseek(f, 0, SEEK_END)) + return false; + long size = ftell(f); + if (size < 0) + return false; + output.resize(size); + if (!size) + return true; + if (fseek(f, 0, SEEK_SET)) + return false; + return fread(&output[0], 1, size, f) == (size_t) size; + } + return false; +} + +class BaseSvgConsumer { +public: + inline bool processingInstruction(const char *, const char *) { return true; } + inline bool doctype(const char *, const char *) { return true; } + inline bool text(const char *, const char *) { return true; } + inline bool cdata(const char *, const char *) { return true; } +}; + +class SvgPathAggregator : public BaseSvgConsumer { + enum { + IGNORED, + SVG, + G, + PATH + } curElement; + int ignoredDepth; + +public: + int flags; + Vector2 dimensions; + StrRange viewBox; + std::vector pathDefs; + + inline SvgPathAggregator() : curElement(IGNORED), ignoredDepth(0), flags(0) { } + + inline bool enterElement(const char *nameStart, const char *nameEnd) { + curElement = IGNORED; + if (ignoredDepth) + ++ignoredDepth; + else if (SVG_NAME_IS("svg")) + curElement = SVG; + else if (SVG_NAME_IS("g")) + curElement = G; + else if (SVG_NAME_IS("path")) + curElement = PATH; + else { + if (SVG_NAME_IS("rect") || SVG_NAME_IS("circle") || SVG_NAME_IS("ellipse") || SVG_NAME_IS("polygon")) + flags |= SVG_IMPORT_INCOMPLETE_FLAG; + else if (SVG_NAME_IS("mask") || SVG_NAME_IS("use")) + flags |= SVG_IMPORT_UNSUPPORTED_FEATURE_FLAG; + ++ignoredDepth; + } + return true; + } + + inline bool leaveElement(const char *, const char *) { + if (ignoredDepth) + --ignoredDepth; + return true; + } + + inline bool elementAttribute(const char *nameStart, const char *nameEnd, const char *valueStart, const char *valueEnd) { + switch (curElement) { + case IGNORED: + break; + case SVG: + if (SVG_NAME_IS("width")) + dimensions.x = xmlGetDouble(valueStart, valueEnd); + else if (SVG_NAME_IS("height")) + dimensions.y = xmlGetDouble(valueStart, valueEnd); + else if (SVG_NAME_IS("viewBox")) + viewBox = StrRange(valueStart, valueEnd); + break; + case PATH: + if (SVG_NAME_IS("d")) + pathDefs.push_back(StrRange(valueStart, valueEnd)); + // fallthrough + case G: + if (SVG_NAME_IS("transform")) + flags |= SVG_IMPORT_TRANSFORMATION_IGNORED_FLAG; + break; + } + return true; + } + + inline bool finishAttributes() { return true; } + inline bool finish() { return !ignoredDepth; } + +}; + +bool loadSvgShape(Shape &output, const char *filename, int pathIndex, Vector2 *dimensions) { + std::vector svgData; + if (!(readFile(svgData, filename) && !svgData.empty())) + return false; + + SvgPathAggregator pathAggregator; + if (!dropXML::parse(pathAggregator, &svgData[0], &svgData[0]+svgData.size())) + return false; + + if (pathIndex <= 0) { + if (pathIndex == 0) + pathIndex = -1; + pathIndex = pathAggregator.pathDefs.size()+pathIndex; + } else + --pathIndex; + if (!(pathIndex > 0 && pathIndex < (int) pathAggregator.pathDefs.size())) + return false; + + Vector2 dims(pathAggregator.dimensions); + if (pathAggregator.viewBox.start < pathAggregator.viewBox.end) { + std::string viewBoxStr = xmlDecode(pathAggregator.viewBox.start, pathAggregator.viewBox.end); + const char *viewBoxPtr = viewBoxStr.c_str(); + double left = 0, top = 0; + readDouble(left, viewBoxPtr) && readDouble(top, viewBoxPtr) && readDouble(dims.x, viewBoxPtr) && readDouble(dims.y, viewBoxPtr); + } + if (dimensions) + *dimensions = dims; + output.contours.clear(); + output.inverseYAxis = true; + return buildShapeFromSvgPath(output, xmlDecode(pathAggregator.pathDefs[pathIndex].start, pathAggregator.pathDefs[pathIndex].end).c_str(), ENDPOINT_SNAP_RANGE_PROPORTION*dims.length()); +} + +#endif + #ifndef MSDFGEN_USE_SKIA +#ifdef MSDFGEN_USE_TINYXML2 int loadSvgShape(Shape &output, Shape::Bounds &viewBox, const char *filename) { tinyxml2::XMLDocument doc; if (doc.LoadFile(filename)) @@ -372,6 +565,34 @@ int loadSvgShape(Shape &output, Shape::Bounds &viewBox, const char *filename) { return SVG_IMPORT_FAILURE; return flags; } +#endif + +#ifdef MSDFGEN_USE_DROPXML +int loadSvgShape(Shape &output, Shape::Bounds &viewBox, const char *filename) { + std::vector svgData; + if (!(readFile(svgData, filename) && !svgData.empty())) + return SVG_IMPORT_FAILURE; + + SvgPathAggregator pathAggregator; + if (!dropXML::parse(pathAggregator, &svgData[0], &svgData[0]+svgData.size()) || pathAggregator.pathDefs.empty()) + return SVG_IMPORT_FAILURE; + + viewBox.l = 0, viewBox.b = 0; + Vector2 dims(pathAggregator.dimensions); + if (pathAggregator.viewBox.start < pathAggregator.viewBox.end) { + std::string viewBoxStr = xmlDecode(pathAggregator.viewBox.start, pathAggregator.viewBox.end); + const char *viewBoxPtr = viewBoxStr.c_str(); + readDouble(viewBox.l, viewBoxPtr) && readDouble(viewBox.b, viewBoxPtr) && readDouble(dims.x, viewBoxPtr) && readDouble(dims.y, viewBoxPtr); + } + viewBox.r = viewBox.l+dims.x; + viewBox.t = viewBox.b+dims.y; + output.contours.clear(); + output.inverseYAxis = true; + if (!buildShapeFromSvgPath(output, xmlDecode(pathAggregator.pathDefs.back().start, pathAggregator.pathDefs.back().end).c_str(), ENDPOINT_SNAP_RANGE_PROPORTION*dims.length())) + return SVG_IMPORT_FAILURE; + return SVG_IMPORT_SUCCESS_FLAG|pathAggregator.flags; +} +#endif #else @@ -438,9 +659,9 @@ static SkMatrix parseTransformation(int &flags, const char *str) { } static SkMatrix combineTransformation(int &flags, const SkMatrix &parentTransformation, const char *transformationString, const char *transformationOriginString) { - if (transformationString) { + if (transformationString && *transformationString) { SkMatrix transformation = parseTransformation(flags, transformationString); - if (transformationOriginString) { + if (transformationOriginString && *transformationOriginString) { Point2 origin; if (readCoord(origin, transformationOriginString)) transformation = SkMatrix::Translate(SkScalar(origin.x), SkScalar(origin.y))*transformation*SkMatrix::Translate(SkScalar(-origin.x), SkScalar(-origin.y)); @@ -452,6 +673,8 @@ static SkMatrix combineTransformation(int &flags, const SkMatrix &parentTransfor return parentTransformation; } +#ifdef MSDFGEN_USE_TINYXML2 + static void gatherPaths(SkPath &fullPath, int &flags, tinyxml2::XMLElement *parent, const SkMatrix &transformation) { for (tinyxml2::XMLElement *cur = parent->FirstChildElement(); cur && !FLAGS_FINAL(flags); cur = cur->NextSiblingElement()) { if (!strcmp(cur->Name(), "g")) @@ -548,6 +771,290 @@ int loadSvgShape(Shape &output, Shape::Bounds &viewBox, const char *filename) { #endif +#ifdef MSDFGEN_USE_DROPXML + +int parseSvgShape(Shape &output, Shape::Bounds &viewBox, const char *svgData, size_t svgLength) { + + class SvgConsumer : public BaseSvgConsumer { + enum Element { + BEGINNING, + IGNORED, + SVG, + G, + PATH, + RECT, + CIRCLE, + ELLIPSE, + POLYGON + } curElement; + + // Current element attributes + struct ElementData { + StrRange transform, transformOrigin; + Vector2 pos, dims, radius; + StrRange pathDef; + bool fillRuleEvenOdd; + ElementData() : fillRuleEvenOdd(false) { } + } elem; + + int ignoredDepth; + SkMatrix transformation; + std::stack transformationStack; + + public: + int flags; + Vector2 dimensions; + Shape::Bounds viewBox; + SkPath fullPath; + + SvgConsumer() : curElement(BEGINNING), ignoredDepth(0), flags(0), viewBox() { } + + bool enterElement(const char *nameStart, const char *nameEnd) { + if (ignoredDepth) { + ++ignoredDepth; + return true; + } + if (curElement == BEGINNING && SVG_NAME_IS("svg")) + curElement = SVG; + else if (SVG_NAME_IS("g")) + curElement = G; + else if (SVG_NAME_IS("path")) + curElement = PATH; + else if (SVG_NAME_IS("rect")) + curElement = RECT; + else if (SVG_NAME_IS("circle")) + curElement = CIRCLE; + else if (SVG_NAME_IS("ellipse")) + curElement = ELLIPSE; + else if (SVG_NAME_IS("polygon")) + curElement = POLYGON; + else { + curElement = IGNORED; + ++ignoredDepth; + if (SVG_NAME_IS("mask") || SVG_NAME_IS("use")) + flags |= SVG_IMPORT_UNSUPPORTED_FEATURE_FLAG; + } + if (curElement != IGNORED) + elem = ElementData(); + return true; + } + + bool leaveElement(const char *nameStart, const char *nameEnd) { + if (ignoredDepth) { + --ignoredDepth; + return true; + } + if (SVG_NAME_IS("g")) { + if (transformationStack.empty()) + return false; + transformation = transformationStack.top(); + transformationStack.pop(); + } + return true; + } + + bool elementAttribute(const char *nameStart, const char *nameEnd, const char *valueStart, const char *valueEnd) { + switch (curElement) { + case BEGINNING: + case IGNORED: + break; + case SVG: + if (SVG_NAME_IS("width")) + dimensions.x = SVG_DOUBLEVAL(); + else if (SVG_NAME_IS("height")) + dimensions.y = SVG_DOUBLEVAL(); + else if (SVG_NAME_IS("viewBox")) { + std::string viewBoxStr(SVG_DEC_VAL()); + const char *strPtr = viewBoxStr.c_str(); + double w = 0, h = 0; + readDouble(viewBox.l, strPtr) && readDouble(viewBox.b, strPtr) && readDouble(w, strPtr) && readDouble(h, strPtr); + viewBox.r = viewBox.l+w; + viewBox.t = viewBox.b+h; + } + break; + case G: + break; + case PATH: + if (SVG_NAME_IS("d")) + elem.pathDef = StrRange(valueStart, valueEnd); + break; + case RECT: + if (SVG_NAME_IS("x")) + elem.pos.x = SVG_DOUBLEVAL(); + else if (SVG_NAME_IS("y")) + elem.pos.y = SVG_DOUBLEVAL(); + else if (SVG_NAME_IS("width")) + elem.dims.x = SVG_DOUBLEVAL(); + else if (SVG_NAME_IS("height")) + elem.dims.y = SVG_DOUBLEVAL(); + else if (SVG_NAME_IS("rx")) + elem.radius.x = SVG_DOUBLEVAL(); + else if (SVG_NAME_IS("ry")) + elem.radius.y = SVG_DOUBLEVAL(); + break; + case CIRCLE: + if (SVG_NAME_IS("cx")) + elem.pos.x = SVG_DOUBLEVAL(); + else if (SVG_NAME_IS("cy")) + elem.pos.y = SVG_DOUBLEVAL(); + else if (SVG_NAME_IS("r")) + elem.radius.x = SVG_DOUBLEVAL(); + break; + case ELLIPSE: + if (SVG_NAME_IS("cx")) + elem.pos.x = SVG_DOUBLEVAL(); + else if (SVG_NAME_IS("cy")) + elem.pos.y = SVG_DOUBLEVAL(); + else if (SVG_NAME_IS("rx")) + elem.radius.x = SVG_DOUBLEVAL(); + else if (SVG_NAME_IS("ry")) + elem.radius.y = SVG_DOUBLEVAL(); + break; + case POLYGON: + if (SVG_NAME_IS("points")) + elem.pathDef = StrRange(valueStart, valueEnd); + break; + } + switch (curElement) { + case PATH: + case RECT: + case CIRCLE: + case ELLIPSE: + case POLYGON: + if (SVG_NAME_IS("fill-rule")) + elem.fillRuleEvenOdd = SVG_DEC_VAL() == "evenodd"; + // fallthrough + case G: + if (SVG_NAME_IS("transform")) + elem.transform = StrRange(valueStart, valueEnd); + else if (SVG_NAME_IS("transform-origin")) + elem.transformOrigin = StrRange(valueStart, valueEnd); + break; + default:; + } + return true; + } + + bool finishAttributes() { + switch (curElement) { + case BEGINNING: + case IGNORED: + case SVG: + break; + case G: + transformationStack.push(transformation); + transformation = combineTransformation(flags, transformation, elem.transform.str().c_str(), elem.transformOrigin.str().c_str()); + break; + case PATH: + case RECT: + case CIRCLE: + case ELLIPSE: + case POLYGON: + { + SkPath curPath; + switch (curElement) { + case PATH: + if (!SkParsePath::FromSVGString(elem.pathDef.str().c_str(), &curPath)) { + flags |= SVG_IMPORT_PARTIAL_FAILURE_FLAG; + return true; + } + break; + case RECT: + { + if (!(elem.dims.x && elem.dims.y)) + return true; + SkRect rect = SkRect::MakeLTRB(elem.pos.x, elem.pos.y, elem.pos.x+elem.dims.x, elem.pos.y+elem.dims.y); + if (elem.radius.x || elem.radius.y) { + SkScalar rx = SkScalar(elem.radius.x), ry = SkScalar(elem.radius.y); + SkScalar radii[] = { rx, ry, rx, ry, rx, ry, rx, ry }; + curPath.addRoundRect(rect, radii); + } else + curPath.addRect(rect); + } + break; + case CIRCLE: + if (!elem.radius.x) + return true; + curPath.addCircle(elem.pos.x, elem.pos.y, elem.radius.x); + break; + case ELLIPSE: + if (!(elem.radius.x && elem.radius.y)) + return true; + curPath.addOval(SkRect::MakeLTRB(elem.pos.x-elem.radius.x, elem.pos.y-elem.radius.y, elem.pos.x+elem.radius.x, elem.pos.y+elem.radius.y)); + break; + case POLYGON: + { + if (elem.pathDef.start == elem.pathDef.end) { + flags |= SVG_IMPORT_PARTIAL_FAILURE_FLAG; + return true; + } + std::string pdStr = elem.pathDef.str(); + const char *pd = pdStr.c_str(); + Point2 point; + if (!readCoord(point, pd)) + return true; + curPath.moveTo(SkScalar(point.x), SkScalar(point.y)); + if (!readCoord(point, pd)) + return true; + do { + curPath.lineTo(SkScalar(point.x), SkScalar(point.y)); + } while (readCoord(point, pd)); + curPath.close(); + } + break; + default: + return true; + } + if (elem.fillRuleEvenOdd) + curPath.setFillType(SkPathFillType::kEvenOdd); + curPath.transform(combineTransformation(flags, transformation, elem.transform.str().c_str(), elem.transformOrigin.str().c_str())); + if (Op(fullPath, curPath, kUnion_SkPathOp, &fullPath)) + flags |= SVG_IMPORT_SUCCESS_FLAG; + else + flags |= SVG_IMPORT_PARTIAL_FAILURE_FLAG; + } + break; + } + return true; + } + + bool finish() { + return !ignoredDepth && transformationStack.empty(); + } + + }; + + SvgConsumer svg; + if (!( + dropXML::parse(svg, svgData, svgData+svgLength) && + (svg.flags&SVG_IMPORT_SUCCESS_FLAG) && + Simplify(svg.fullPath, &svg.fullPath) + )) + return SVG_IMPORT_FAILURE; + + shapeFromSkiaPath(output, svg.fullPath); + output.inverseYAxis = true; + output.orientContours(); + + viewBox = svg.viewBox; + if (svg.dimensions.x > 0 && viewBox.r == viewBox.l) + viewBox.r += svg.dimensions.x; + if (svg.dimensions.y > 0 && viewBox.t == viewBox.b) + viewBox.t += svg.dimensions.y; + return svg.flags; +} + +int loadSvgShape(Shape &output, Shape::Bounds &viewBox, const char *filename) { + std::vector svgData; + if (!readFile(svgData, filename)) + return SVG_IMPORT_FAILURE; + return parseSvgShape(output, viewBox, svgData.empty() ? NULL : &svgData[0], svgData.size()); +} + +#endif + +#endif + } #endif