svg_loader: symbol tag implemented

The 'symbol' tag introduced. It can be used to define graphical
template objects which can be instantiated by a 'use' tag.
This commit is contained in:
Mira Grudzinska 2022-02-01 20:55:15 +01:00 committed by Hermet Park
parent ccce0d7641
commit 70ed0653f1
4 changed files with 134 additions and 16 deletions

View 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")}
};
@ -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);

View 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();

View 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;
}

View file

@ -272,6 +272,7 @@ const char* simpleXmlNodeTypeToString(TVG_UNUSED SvgNodeType type)
"Video",
"ClipPath",
"Mask",
"Symbol",
"Unknown",
};
return TYPE_NAMES[(int) type];