diff --git a/src/loaders/svg/tvgSvgLoader.cpp b/src/loaders/svg/tvgSvgLoader.cpp index 67a9838b..a5b6e90e 100644 --- a/src/loaders/svg/tvgSvgLoader.cpp +++ b/src/loaders/svg/tvgSvgLoader.cpp @@ -1485,7 +1485,7 @@ static constexpr struct }; -/* parse the attributes for a rect element. +/* parse the attributes for a line element. * https://www.w3.org/TR/SVG/shapes.html#LineElement */ static bool _attrParseLineNode(void* data, const char* key, const char* value) @@ -1534,10 +1534,72 @@ static string* _idFromHref(const char* href) { href = _skipSpace(href, nullptr); if ((*href) == '#') href++; + if(!strncmp(href, "file://", sizeof("file://") - 1)) href += sizeof("file://") - 1; return new string(href); } +static constexpr struct +{ + const char* tag; + SvgParserLengthType type; + int sz; + size_t offset; +} imageTags[] = { + {"x", SvgParserLengthType::Horizontal, sizeof("x"), offsetof(SvgRectNode, x)}, + {"y", SvgParserLengthType::Vertical, sizeof("y"), offsetof(SvgRectNode, y)}, + {"width", SvgParserLengthType::Horizontal, sizeof("width"), offsetof(SvgRectNode, w)}, + {"height", SvgParserLengthType::Vertical, sizeof("height"), offsetof(SvgRectNode, h)}, +}; + + +/* parse the attributes for a image element. + * https://www.w3.org/TR/SVG/embedded.html#ImageElement + */ +static bool _attrParseImageNode(void* data, const char* key, const char* value) +{ + SvgLoaderData* loader = (SvgLoaderData*)data; + SvgNode* node = loader->svgParse->node; + SvgImageNode* image = &(node->node.image); + unsigned char* array; + int sz = strlen(key); + + array = (unsigned char*)image; + for (unsigned int i = 0; i < sizeof(imageTags) / sizeof(imageTags[0]); i++) { + if (imageTags[i].sz - 1 == sz && !strncmp(imageTags[i].tag, key, sz)) { + *((float*)(array + imageTags[i].offset)) = _toFloat(loader->svgParse, value, imageTags[i].type); + return true; + } + } + + if (!strcmp(key, "href") || !strcmp(key, "xlink:href")) { + image->href = _idFromHref(value); + } else if (!strcmp(key, "id")) { + node->id = _copyId(value); + } else if (!strcmp(key, "style")) { + return simpleXmlParseW3CAttribute(value, _parseStyleAttr, loader); + } else if (!strcmp(key, "clip-path")) { + _handleClipPathAttr(loader, node, value); + } else if (!strcmp(key, "mask")) { + _handleMaskAttr(loader, node, value); + } else { + return _parseStyleAttr(loader, key, value); + } + return true; +} + + +static SvgNode* _createImageNode(SvgLoaderData* loader, SvgNode* parent, const char* buf, unsigned bufLength) +{ + loader->svgParse->node = _createNode(parent, SvgNodeType::Image); + + if (!loader->svgParse->node) return nullptr; + + simpleXmlParseAttributes(buf, bufLength, _attrParseImageNode, loader); + return loader->svgParse->node; +} + + static SvgNode* _getDefsNode(SvgNode* node) { if (!node) return nullptr; @@ -1785,7 +1847,8 @@ static constexpr struct {"polygon", sizeof("polygon"), _createPolygonNode}, {"rect", sizeof("rect"), _createRectNode}, {"polyline", sizeof("polyline"), _createPolylineNode}, - {"line", sizeof("line"), _createLineNode} + {"line", sizeof("line"), _createLineNode}, + {"image", sizeof("image"), _createImageNode} }; diff --git a/src/loaders/svg/tvgSvgLoaderCommon.h b/src/loaders/svg/tvgSvgLoaderCommon.h index 03e28876..165bd815 100644 --- a/src/loaders/svg/tvgSvgLoaderCommon.h +++ b/src/loaders/svg/tvgSvgLoaderCommon.h @@ -188,6 +188,15 @@ struct SvgLineNode float y2; }; +struct SvgImageNode +{ + float x; + float y; + float w; + float h; + string *href; +}; + struct SvgPathNode { string* path; @@ -321,6 +330,7 @@ struct SvgNode SvgRectNode rect; SvgPathNode path; SvgLineNode line; + SvgImageNode image; } node; bool display; }; diff --git a/src/loaders/svg/tvgSvgSceneBuilder.cpp b/src/loaders/svg/tvgSvgSceneBuilder.cpp index 711e473e..0da0190b 100644 --- a/src/loaders/svg/tvgSvgSceneBuilder.cpp +++ b/src/loaders/svg/tvgSvgSceneBuilder.cpp @@ -24,6 +24,7 @@ #include "tvgSvgLoaderCommon.h" #include "tvgSvgSceneBuilder.h" #include "tvgSvgPath.h" +#include "tvgSvgUtil.h" static bool _appendShape(SvgNode* node, Shape* shape, float vx, float vy, float vw, float vh); @@ -80,7 +81,7 @@ static unique_ptr _applyLinearGradientProperty(SvgStyleGradient* stops[i].b = colorStop->b; stops[i].a = (colorStop->a * opacity) / 255.0f; stops[i].offset = colorStop->offset; - // check the offset corner cases - refer to: https://svgwg.org/svg2-draft/pservers.html#StopNotes + //check the offset corner cases - refer to: https://svgwg.org/svg2-draft/pservers.html#StopNotes if (colorStop->offset < prevOffset) stops[i].offset = prevOffset; else if (colorStop->offset > 1) stops[i].offset = 1; prevOffset = stops[i].offset; @@ -146,7 +147,7 @@ static unique_ptr _applyRadialGradientProperty(SvgStyleGradient* stops[i].b = colorStop->b; stops[i].a = (colorStop->a * opacity) / 255.0f; stops[i].offset = colorStop->offset; - // check the offset corner cases - refer to: https://svgwg.org/svg2-draft/pservers.html#StopNotes + //check the offset corner cases - refer to: https://svgwg.org/svg2-draft/pservers.html#StopNotes if (colorStop->offset < prevOffset) stops[i].offset = prevOffset; else if (colorStop->offset > 1) stops[i].offset = 1; prevOffset = stops[i].offset; @@ -346,6 +347,113 @@ static bool _appendShape(SvgNode* node, Shape* shape, float vx, float vy, float return true; } +enum class imageMimeTypeEncoding +{ + base64 = 0x1, + utf8 = 0x2 +}; +constexpr imageMimeTypeEncoding operator|(imageMimeTypeEncoding a, imageMimeTypeEncoding b) { + return static_cast(static_cast(a) | static_cast(b)); +} +constexpr bool operator&(imageMimeTypeEncoding a, imageMimeTypeEncoding b) { + return (static_cast(a) & static_cast(b)); +} + +static constexpr struct +{ + const char* name; + int sz; + imageMimeTypeEncoding encoding; +} imageMimeTypes[] = { +#ifdef THORVG_JPG_LOADER_SUPPORT + {"jpeg", sizeof("jpeg"), imageMimeTypeEncoding::base64}, +#endif +#ifdef THORVG_PNG_LOADER_SUPPORT + {"png", sizeof("png"), imageMimeTypeEncoding::base64}, +#endif + {"svg+xml", sizeof("svg+xml"), imageMimeTypeEncoding::base64 | imageMimeTypeEncoding::utf8}, +}; + +static bool _isValidImageMimeTypeAndEncoding(const char** href, imageMimeTypeEncoding* encoding) { + if (strncmp(*href, "image/", sizeof("image/") - 1)) return false; //not allowed mime type + *href += sizeof("image/") - 1; + + //RFC2397 data:[][;base64], + //mediatype := [ type "/" subtype ] *( ";" parameter ) + //parameter := attribute "=" value + for (unsigned int i = 0; i < sizeof(imageMimeTypes) / sizeof(imageMimeTypes[0]); i++) { + if (!strncmp(*href, imageMimeTypes[i].name, imageMimeTypes[i].sz - 1)) { + *href += imageMimeTypes[i].sz - 1; + + while (**href && **href != ',') { + while (**href && **href != ';') ++(*href); + if (!**href) return false; + ++(*href); + + if (imageMimeTypes[i].encoding & imageMimeTypeEncoding::base64) { + if (!strncmp(*href, "base64,", sizeof("base64,") - 1)) { + *href += sizeof("base64,") - 1; + *encoding = imageMimeTypeEncoding::base64; + return true; //valid base64 + } + } + if (imageMimeTypes[i].encoding & imageMimeTypeEncoding::utf8) { + if (!strncmp(*href, "utf8,", sizeof("utf8,") - 1)) { + *href += sizeof("utf8,") - 1; + *encoding = imageMimeTypeEncoding::utf8; + return true; //valid utf8 + } + } + } + //no encoding defined + if (**href == ',' && (imageMimeTypes[i].encoding & imageMimeTypeEncoding::utf8)) { + ++(*href); + *encoding = imageMimeTypeEncoding::utf8; + return true; //allow no encoding defined if utf8 expected + } + return false; + } + } + return false; +} + +static unique_ptr _imageBuildHelper(SvgNode* node, float vx, float vy, float vw, float vh) +{ + if (!node->node.image.href) return nullptr; + auto picture = Picture::gen(); + + const char* href = (*node->node.image.href).c_str(); + if (!strncmp(href, "data:", sizeof("data:") - 1)) { + href += sizeof("data:") - 1; + imageMimeTypeEncoding encoding; + if (!_isValidImageMimeTypeAndEncoding(&href, &encoding)) return nullptr; //not allowed mime type or encoding + if (encoding == imageMimeTypeEncoding::base64) { + string decoded = svgUtilBase64Decode(href); + if (picture->load(decoded.c_str(), decoded.size(), true) != Result::Success) return nullptr; + } else { + string decoded = svgUtilURLDecode(href); + if (picture->load(decoded.c_str(), decoded.size(), true) != Result::Success) return nullptr; + } + + } else { + //TODO: protect against recursive svg image loading + if (picture->load(href) != Result::Success) return nullptr; + picture->size(node->node.image.w, node->node.image.h); + } + + float x, y, w, h; + if (picture->viewbox(&x, &y, &w, &h) == Result::Success && vw && vh) { + auto sx = node->node.image.w / w; + auto sy = node->node.image.h / h; + auto sxt = x * sx; + auto syt = y * sy; + Matrix m = {sx, 0, node->node.image.x - sxt, 0, sy, node->node.image.y - syt, 0, 0, 1}; + picture->transform(m); + } + + return picture; +} + static unique_ptr _sceneBuildHelper(const SvgNode* node, float vx, float vy, float vw, float vh) { @@ -358,6 +466,9 @@ static unique_ptr _sceneBuildHelper(const SvgNode* node, float vx, float for (uint32_t i = 0; i < node->child.count; ++i, ++child) { if (_isGroupType((*child)->type)) { scene->push(_sceneBuildHelper(*child, vx, vy, vw, vh)); + } else if ((*child)->type == SvgNodeType::Image) { + auto image = _imageBuildHelper(*child, vx, vy, vw, vh); + if (image) scene->push(move(image)); } else { auto shape = _shapeBuildHelper(*child, vx, vy, vw, vh); if (shape) scene->push(move(shape)); diff --git a/src/loaders/svg/tvgSvgUtil.cpp b/src/loaders/svg/tvgSvgUtil.cpp index 9383df57..8fd0d21e 100644 --- a/src/loaders/svg/tvgSvgUtil.cpp +++ b/src/loaders/svg/tvgSvgUtil.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include "tvgSvgUtil.h" /************************************************************************/ @@ -35,6 +36,20 @@ static inline bool _floatExact(float a, float b) return memcmp(&a, &b, sizeof (float)) == 0; } +static uint8_t _hexCharToDec(const char c) { + if (c >= 'a') return c - 'a' + 10; + else if (c >= 'A') return c - 'A' + 10; + else return c - '0'; +} + +static uint8_t _base64Value(const char chr) { + if (chr >= 'A' && chr <= 'Z') return chr - 'A'; + else if (chr >= 'a' && chr <= 'z') return chr - 'a' + ('Z' - 'A') + 1; + else if (chr >= '0' && chr <= '9') return chr - '0' + ('Z' - 'A') + ('z' - 'a') + 2; + else if (chr == '+' || chr == '-') return 62; + else return 63; +} + /************************************************************************/ /* External Class Implementation */ @@ -242,3 +257,52 @@ on_error: if (endPtr) *endPtr = (char *)nPtr; return 0.0f; } + +string svgUtilURLDecode(const char *src) { + unsigned int length = strlen(src); + if (!src || !length) return nullptr; + + string decoded; + decoded.reserve(length); + + char a, b; + while (*src) { + if (*src == '%' && + ((a = src[1]) && (b = src[2])) && + (isxdigit(a) && isxdigit(b))) { + decoded += (_hexCharToDec(a) << 4) + _hexCharToDec(b); + src+=3; + } else if (*src == '+') { + decoded += ' '; + src++; + } else { + decoded += *src++; + } + } + return decoded; +} + +string svgUtilBase64Decode(const char *src) { + unsigned int length = strlen(src); + if (!src || !length) return nullptr; + + string decoded; + decoded.reserve(3*(1+(length >> 2))); + + while (*src && *(src+1)) { + uint8_t value1 = _base64Value(src[0]); + uint8_t value2 = _base64Value(src[1]); + decoded += (value1 << 2) + ((value2 & 0x30) >> 4); + + if (!src[2] || src[2] == '=' || src[2] == '.') break; + uint8_t value3 = _base64Value(src[2]); + decoded += ((value2 & 0x0f) << 4) + ((value3 & 0x3c) >> 2); + + if (!src[3] || src[3] == '=' || src[3] == '.') break; + uint8_t value4 = _base64Value(src[3]); + decoded += ((value3 & 0x03) << 6) + value4; + src += 4; + } + return decoded; +} + diff --git a/src/loaders/svg/tvgSvgUtil.h b/src/loaders/svg/tvgSvgUtil.h index 1f2d9ec1..4320cfed 100644 --- a/src/loaders/svg/tvgSvgUtil.h +++ b/src/loaders/svg/tvgSvgUtil.h @@ -23,6 +23,11 @@ #ifndef _TVG_SVG_UTIL_H_ #define _TVG_SVG_UTIL_H_ +#include "tvgCommon.h" + float svgUtilStrtof(const char *nPtr, char **endPtr); +string svgUtilURLDecode(const char *src); +string svgUtilBase64Decode(const char *src); + #endif //_TVG_SVG_UTIL_H_