#include "tvgSvgSceneBuilder.h"
#include "tvgSvgUtil.h"
#include "tvgSvgCssStyle.h"
+#include "tvgMath.h"
/************************************************************************/
/* Internal Class Implementation */
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;
}
+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));
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;
}
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);
}
}
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
//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);
}
{"svg", sizeof("svg"), _createSvgNode},
{"mask", sizeof("mask"), _createMaskNode},
{"clipPath", sizeof("clipPath"), _createClipPathNode},
- {"style", sizeof("style"), _createCssStyleNode}
+ {"style", sizeof("style"), _createCssStyleNode},
+ {"symbol", sizeof("symbol"), _createSymbolNode}
};
{"defs", sizeof("defs")},
{"mask", sizeof("mask")},
{"clipPath", sizeof("clipPath")},
- {"style", sizeof("style")}
+ {"style", sizeof("style")},
+ {"symbol", sizeof("symbol")}
};
#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);
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);
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;
}
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;
}