svg_loader: symbol tag implemented 74/289174/1
authorMira Grudzinska <m.grudzinska@samsung.com>
Tue, 1 Feb 2022 19:55:15 +0000 (20:55 +0100)
committerMichal Szczecinski <m.szczecinsk@partner.samsung.com>
Thu, 2 Mar 2023 08:11:28 +0000 (09:11 +0100)
The 'symbol' tag introduced. It can be used to define graphical
template objects which can be instantiated by a 'use' tag.

Change-Id: I48c000156e764c039a9d27e5a205a226909d2edf

src/loaders/svg/tvgSvgLoader.cpp
src/loaders/svg/tvgSvgLoaderCommon.h
src/loaders/svg/tvgSvgSceneBuilder.cpp
src/loaders/svg/tvgXmlParser.cpp

index 05d67f7..a16b2b2 100644 (file)
@@ -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<SvgNodeIdPair>* 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 <defs> 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")}
 };
 
 
@@ -2755,7 +2809,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);
 
@@ -3049,11 +3103,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);
 
index d9e9828..1bb5405 100644 (file)
@@ -52,6 +52,7 @@ enum class SvgNodeType
     ClipPath,
     Mask,
     CssStyle,
+    Symbol,
     Unknown
 };
 
@@ -166,12 +167,20 @@ struct SvgDefsNode
     Array<SvgStyleGradient*> 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();
index 737d99b..2646c48 100644 (file)
@@ -73,7 +73,7 @@ static unique_ptr<Scene> _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<Picture> _imageBuildHelper(SvgNode* node, const Box& vBox, con
 
 static unique_ptr<Scene> _useBuildHelper(const SvgNode* node, const Box& vBox, const string& svgPath, bool* isMaskWhite)
 {
+    unique_ptr<Scene> 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;
 }
 
 
index aa93870..a12689c 100644 (file)
@@ -272,6 +272,7 @@ const char* simpleXmlNodeTypeToString(TVG_UNUSED SvgNodeType type)
         "Video",
         "ClipPath",
         "Mask",
+        "Symbol",
         "Unknown",
     };
     return TYPE_NAMES[(int) type];