diff --git a/src/loaders/svg/tvgSvgLoader.cpp b/src/loaders/svg/tvgSvgLoader.cpp index a66deb44..a7e3ef42 100644 --- a/src/loaders/svg/tvgSvgLoader.cpp +++ b/src/loaders/svg/tvgSvgLoader.cpp @@ -61,6 +61,7 @@ #include "tvgSvgSceneBuilder.h" #include "tvgSvgUtil.h" #include "tvgSvgCssStyle.h" +#include "tvgMath.h" /************************************************************************/ /* Internal Class Implementation */ @@ -801,13 +802,11 @@ static bool _attrParseSvgNode(void* data, const char* key, const char* value) if (!strcmp(value, "none")) doc->preserveAspect = false; } else if (!strcmp(key, "style")) { return simpleXmlParseW3CAttribute(value, strlen(value), _parseStyleAttr, loader); - } #ifdef THORVG_LOG_ENABLED - else if ((!strcmp(key, "x") || !strcmp(key, "y")) && fabsf(svgUtilStrtof(value, nullptr)) > FLT_EPSILON) { + } else if ((!strcmp(key, "x") || !strcmp(key, "y")) && fabsf(svgUtilStrtof(value, nullptr)) > FLT_EPSILON) { TVGLOG("SVG", "Unsupported attributes used [Elements type: Svg][Attribute: %s][Value: %s]", key, value); - } #endif - else { + } else { return _parseStyleAttr(loader, key, value, false); } return true; @@ -1137,6 +1136,36 @@ static bool _attrParseCssStyleNode(void* data, const char* key, const char* valu } +static bool _attrParseSymbolNode(void* data, const char* key, const char* value) +{ + SvgLoaderData* loader = (SvgLoaderData*)data; + SvgNode* node = loader->svgParse->node; + SvgSymbolNode* symbol = &(node->node.symbol); + + if (!strcmp(key, "viewBox")) { + if (_parseNumber(&value, &symbol->vx)) { + if (_parseNumber(&value, &symbol->vy)) { + if (_parseNumber(&value, &symbol->vw)) { + _parseNumber(&value, &symbol->vh); + } + } + } + } else if (!strcmp(key, "width")) { + symbol->w = _toFloat(loader->svgParse, value, SvgParserLengthType::Horizontal); + } else if (!strcmp(key, "height")) { + symbol->h = _toFloat(loader->svgParse, value, SvgParserLengthType::Vertical); + } else if (!strcmp(key, "preserveAspectRatio")) { + if (!strcmp(value, "none")) symbol->preserveAspect = false; + } else if (!strcmp(key, "overflow")) { + if (!strcmp(value, "visible")) symbol->overflowVisible = true; + } else { + return _attrParseGNode(data, key, value); + } + + return true; +} + + static SvgNode* _createNode(SvgNode* parent, SvgNodeType type) { SvgNode* node = (SvgNode*)calloc(1, sizeof(SvgNode)); @@ -1268,6 +1297,21 @@ static SvgNode* _createCssStyleNode(SvgLoaderData* loader, SvgNode* parent, cons if (!loader->svgParse->node) return nullptr; func(buf, bufLength, _attrParseCssStyleNode, loader); + + return loader->svgParse->node; +} + + +static SvgNode* _createSymbolNode(SvgLoaderData* loader, SvgNode* parent, const char* buf, unsigned bufLength, parseAttributes func) +{ + loader->svgParse->node = _createNode(parent, SvgNodeType::Symbol); + if (!loader->svgParse->node) return nullptr; + + loader->svgParse->node->display = false; + loader->svgParse->node->node.symbol.preserveAspect = true; + + func(buf, bufLength, _attrParseSymbolNode, loader); + return loader->svgParse->node; } @@ -2044,6 +2088,9 @@ static void _clonePostponedNodes(Array* cloneNodes, SvgNode* doc) auto nodeFrom = _findChildById(defs, nodeIdPair.id); if (!nodeFrom) nodeFrom = _findChildById(doc, nodeIdPair.id); _cloneNode(nodeFrom, nodeIdPair.node, 0); + if (nodeFrom && nodeFrom->type == SvgNodeType::Symbol && nodeIdPair.node->type == SvgNodeType::Use) { + nodeIdPair.node->node.use.symbol = nodeFrom; + } free(nodeIdPair.id); } } @@ -2085,6 +2132,7 @@ static bool _attrParseUseNode(void* data, const char* key, const char* value) nodeFrom = _findChildById(defs, id); if (nodeFrom) { _cloneNode(nodeFrom, node, 0); + if (nodeFrom->type == SvgNodeType::Symbol) use->symbol = nodeFrom; free(id); } else { //some svg export software include element at the end of the file @@ -2092,6 +2140,10 @@ static bool _attrParseUseNode(void* data, const char* key, const char* value) //after the whole file is parsed _postpone(loader->cloneNodes, node, id); } +#ifdef THORVG_LOG_ENABLED + } else if ((!strcmp(key, "width") || !strcmp(key, "height")) && fabsf(svgUtilStrtof(value, nullptr)) > FLT_EPSILON) { + TVGLOG("SVG", "Unsupported attributes used [Elements type: Use][Attribute: %s][Value: %s]", key, value); +#endif } else { return _attrParseGNode(data, key, value); } @@ -2140,7 +2192,8 @@ static constexpr struct {"svg", sizeof("svg"), _createSvgNode}, {"mask", sizeof("mask"), _createMaskNode}, {"clipPath", sizeof("clipPath"), _createClipPathNode}, - {"style", sizeof("style"), _createCssStyleNode} + {"style", sizeof("style"), _createCssStyleNode}, + {"symbol", sizeof("symbol"), _createSymbolNode} }; @@ -2571,7 +2624,8 @@ static constexpr struct {"defs", sizeof("defs")}, {"mask", sizeof("mask")}, {"clipPath", sizeof("clipPath")}, - {"style", sizeof("style")} + {"style", sizeof("style")}, + {"symbol", sizeof("symbol")} }; @@ -2757,7 +2811,7 @@ static void _inefficientNodeCheck(TVG_UNUSED SvgNode* node) #ifdef THORVG_LOG_ENABLED auto type = simpleXmlNodeTypeToString(node->type); - if (!node->display && node->type != SvgNodeType::ClipPath) TVGLOG("SVG", "Inefficient elements used [Display is none][Node Type : %s]", type); + if (!node->display && node->type != SvgNodeType::ClipPath && node->type != SvgNodeType::Symbol) TVGLOG("SVG", "Inefficient elements used [Display is none][Node Type : %s]", type); if (node->style->opacity == 0) TVGLOG("SVG", "Inefficient elements used [Opacity is zero][Node Type : %s]", type); if (node->style->fill.opacity == 0 && node->style->stroke.opacity == 0) TVGLOG("SVG", "Inefficient elements used [Fill opacity and stroke opacity are zero][Node Type : %s]", type); @@ -3051,11 +3105,11 @@ void SvgLoader::run(unsigned tid) if (loaderData.nodesToStyle.count > 0) cssApplyStyleToPostponeds(loaderData.nodesToStyle, loaderData.cssStyle); if (loaderData.cssStyle) cssUpdateStyle(loaderData.doc, loaderData.cssStyle); + if (loaderData.cloneNodes.count > 0) _clonePostponedNodes(&loaderData.cloneNodes, loaderData.doc); + _updateComposite(loaderData.doc, loaderData.doc); if (defs) _updateComposite(loaderData.doc, defs); - if (loaderData.cloneNodes.count > 0) _clonePostponedNodes(&loaderData.cloneNodes, loaderData.doc); - if (loaderData.gradients.count > 0) _updateGradient(loaderData.doc, &loaderData.gradients); if (defs) _updateGradient(loaderData.doc, &defs->node.defs.gradients); diff --git a/src/loaders/svg/tvgSvgLoaderCommon.h b/src/loaders/svg/tvgSvgLoaderCommon.h index d9e98286..1bb54052 100644 --- a/src/loaders/svg/tvgSvgLoaderCommon.h +++ b/src/loaders/svg/tvgSvgLoaderCommon.h @@ -52,6 +52,7 @@ enum class SvgNodeType ClipPath, Mask, CssStyle, + Symbol, Unknown }; @@ -166,12 +167,20 @@ struct SvgDefsNode Array gradients; }; +struct SvgSymbolNode +{ + float w, h; + float vx, vy, vw, vh; + bool preserveAspect; + bool overflowVisible; +}; + struct SvgUseNode { float x, y, w, h; + SvgNode* symbol; }; - struct SvgEllipseNode { float cx; @@ -375,6 +384,7 @@ struct SvgNode SvgMaskNode mask; SvgClipNode clip; SvgCssStyleNode cssStyle; + SvgSymbolNode symbol; } node; bool display; ~SvgNode(); diff --git a/src/loaders/svg/tvgSvgSceneBuilder.cpp b/src/loaders/svg/tvgSvgSceneBuilder.cpp index 737d99be..2646c480 100644 --- a/src/loaders/svg/tvgSvgSceneBuilder.cpp +++ b/src/loaders/svg/tvgSvgSceneBuilder.cpp @@ -73,7 +73,7 @@ static unique_ptr _sceneBuildHelper(const SvgNode* node, const Box& vBox, static inline bool _isGroupType(SvgNodeType type) { - if (type == SvgNodeType::Doc || type == SvgNodeType::G || type == SvgNodeType::Use || type == SvgNodeType::ClipPath) return true; + if (type == SvgNodeType::Doc || type == SvgNodeType::G || type == SvgNodeType::Use || type == SvgNodeType::ClipPath || type == SvgNodeType::Symbol) return true; return false; } @@ -562,19 +562,72 @@ static unique_ptr _imageBuildHelper(SvgNode* node, const Box& vBox, con static unique_ptr _useBuildHelper(const SvgNode* node, const Box& vBox, const string& svgPath, bool* isMaskWhite) { + unique_ptr finalScene; auto scene = _sceneBuildHelper(node, vBox, svgPath, false, isMaskWhite); + // mUseTransform = mUseTransform * mTranslate + Matrix mUseTransform = {1, 0, 0, 0, 1, 0, 0, 0, 1}; + if (node->transform) mUseTransform = *node->transform; if (node->node.use.x != 0.0f || node->node.use.y != 0.0f) { - Matrix m = {1, 0, node->node.use.x, 0, 1, node->node.use.y, 0, 0, 1}; - Matrix transform = scene->transform(); - m = mathMultiply(&transform, &m); - scene->transform(m); + Matrix mTranslate = {1, 0, node->node.use.x, 0, 1, node->node.use.y, 0, 0, 1}; + mUseTransform = mathMultiply(&mUseTransform, &mTranslate); } + + if (node->node.use.symbol && !node->node.use.symbol->node.symbol.overflowVisible) { + auto symbol = node->node.use.symbol->node.symbol; + + Matrix mViewBox = {1, 0, 0, 0, 1, 0, 0, 0, 1}; + if ((!mathEqual(symbol.w, symbol.vw) || !mathEqual(symbol.h, symbol.vh)) && symbol.vw > 0 && symbol.vh > 0) { + auto sx = symbol.w / symbol.vw; + auto sy = symbol.h / symbol.vh; + if (symbol.preserveAspect) { + if (sx < sy) sy = sx; + else sx = sy; + } + + auto tvx = symbol.vx * sx; + auto tvy = symbol.vy * sy; + auto tvw = symbol.vw * sx; + auto tvh = symbol.vh * sy; + if (tvw > tvh) tvy -= (symbol.h - tvh) * 0.5f; + else tvx -= (symbol.w - tvw) * 0.5f; + + mViewBox = {sx, 0, -tvx, 0, sy, -tvy, 0, 0, 1}; + } else if (!mathZero(symbol.vx) || !mathZero(symbol.vy)) { + mViewBox = {1, 0, -symbol.vx, 0, 1, -symbol.vy, 0, 0, 1}; + } + + // mSceneTransform = mUseTransform * mViewBox + Matrix mSceneTransform = mathMultiply(&mUseTransform, &mViewBox); + scene->transform(mSceneTransform); + + auto viewBoxClip = Shape::gen(); + viewBoxClip->appendRect(0, 0, symbol.w, symbol.h, 0, 0); + // mClipTransform = mUseTransform * mSymbolTransform + Matrix mClipTransform = mUseTransform; + if (node->node.use.symbol->transform) { + mClipTransform = mathMultiply(&mUseTransform, node->node.use.symbol->transform); + } + viewBoxClip->transform(mClipTransform); + + auto compositeLayer = Scene::gen(); + compositeLayer->composite(move(viewBoxClip), CompositeMethod::ClipPath); + compositeLayer->push(move(scene)); + + auto root = Scene::gen(); + root->push(move(compositeLayer)); + + finalScene = move(root); + } else if (node->node.use.x != 0.0f || node->node.use.y != 0.0f) { + scene->transform(mUseTransform); + finalScene = move(scene); + } + if (node->node.use.w > 0.0f && node->node.use.h > 0.0f) { //TODO: handle width/height properties } - return scene; + return finalScene; } diff --git a/src/loaders/svg/tvgXmlParser.cpp b/src/loaders/svg/tvgXmlParser.cpp index aa93870f..a12689c7 100644 --- a/src/loaders/svg/tvgXmlParser.cpp +++ b/src/loaders/svg/tvgXmlParser.cpp @@ -272,6 +272,7 @@ const char* simpleXmlNodeTypeToString(TVG_UNUSED SvgNodeType type) "Video", "ClipPath", "Mask", + "Symbol", "Unknown", }; return TYPE_NAMES[(int) type];