mirror of
https://github.com/thorvg/thorvg.git
synced 2025-06-09 06:04:03 +00:00
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:
parent
ccce0d7641
commit
70ed0653f1
4 changed files with 134 additions and 16 deletions
|
@ -61,6 +61,7 @@
|
||||||
#include "tvgSvgSceneBuilder.h"
|
#include "tvgSvgSceneBuilder.h"
|
||||||
#include "tvgSvgUtil.h"
|
#include "tvgSvgUtil.h"
|
||||||
#include "tvgSvgCssStyle.h"
|
#include "tvgSvgCssStyle.h"
|
||||||
|
#include "tvgMath.h"
|
||||||
|
|
||||||
/************************************************************************/
|
/************************************************************************/
|
||||||
/* Internal Class Implementation */
|
/* 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;
|
if (!strcmp(value, "none")) doc->preserveAspect = false;
|
||||||
} else if (!strcmp(key, "style")) {
|
} else if (!strcmp(key, "style")) {
|
||||||
return simpleXmlParseW3CAttribute(value, strlen(value), _parseStyleAttr, loader);
|
return simpleXmlParseW3CAttribute(value, strlen(value), _parseStyleAttr, loader);
|
||||||
}
|
|
||||||
#ifdef THORVG_LOG_ENABLED
|
#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);
|
TVGLOG("SVG", "Unsupported attributes used [Elements type: Svg][Attribute: %s][Value: %s]", key, value);
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
else {
|
} else {
|
||||||
return _parseStyleAttr(loader, key, value, false);
|
return _parseStyleAttr(loader, key, value, false);
|
||||||
}
|
}
|
||||||
return true;
|
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)
|
static SvgNode* _createNode(SvgNode* parent, SvgNodeType type)
|
||||||
{
|
{
|
||||||
SvgNode* node = (SvgNode*)calloc(1, sizeof(SvgNode));
|
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;
|
if (!loader->svgParse->node) return nullptr;
|
||||||
|
|
||||||
func(buf, bufLength, _attrParseCssStyleNode, loader);
|
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;
|
return loader->svgParse->node;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2044,6 +2088,9 @@ static void _clonePostponedNodes(Array<SvgNodeIdPair>* cloneNodes, SvgNode* doc)
|
||||||
auto nodeFrom = _findChildById(defs, nodeIdPair.id);
|
auto nodeFrom = _findChildById(defs, nodeIdPair.id);
|
||||||
if (!nodeFrom) nodeFrom = _findChildById(doc, nodeIdPair.id);
|
if (!nodeFrom) nodeFrom = _findChildById(doc, nodeIdPair.id);
|
||||||
_cloneNode(nodeFrom, nodeIdPair.node, 0);
|
_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);
|
free(nodeIdPair.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2085,6 +2132,7 @@ static bool _attrParseUseNode(void* data, const char* key, const char* value)
|
||||||
nodeFrom = _findChildById(defs, id);
|
nodeFrom = _findChildById(defs, id);
|
||||||
if (nodeFrom) {
|
if (nodeFrom) {
|
||||||
_cloneNode(nodeFrom, node, 0);
|
_cloneNode(nodeFrom, node, 0);
|
||||||
|
if (nodeFrom->type == SvgNodeType::Symbol) use->symbol = nodeFrom;
|
||||||
free(id);
|
free(id);
|
||||||
} else {
|
} else {
|
||||||
//some svg export software include <defs> element at the end of the file
|
//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
|
//after the whole file is parsed
|
||||||
_postpone(loader->cloneNodes, node, id);
|
_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 {
|
} else {
|
||||||
return _attrParseGNode(data, key, value);
|
return _attrParseGNode(data, key, value);
|
||||||
}
|
}
|
||||||
|
@ -2140,7 +2192,8 @@ static constexpr struct
|
||||||
{"svg", sizeof("svg"), _createSvgNode},
|
{"svg", sizeof("svg"), _createSvgNode},
|
||||||
{"mask", sizeof("mask"), _createMaskNode},
|
{"mask", sizeof("mask"), _createMaskNode},
|
||||||
{"clipPath", sizeof("clipPath"), _createClipPathNode},
|
{"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")},
|
{"defs", sizeof("defs")},
|
||||||
{"mask", sizeof("mask")},
|
{"mask", sizeof("mask")},
|
||||||
{"clipPath", sizeof("clipPath")},
|
{"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
|
#ifdef THORVG_LOG_ENABLED
|
||||||
auto type = simpleXmlNodeTypeToString(node->type);
|
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->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 (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.nodesToStyle.count > 0) cssApplyStyleToPostponeds(loaderData.nodesToStyle, loaderData.cssStyle);
|
||||||
if (loaderData.cssStyle) cssUpdateStyle(loaderData.doc, 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);
|
_updateComposite(loaderData.doc, loaderData.doc);
|
||||||
if (defs) _updateComposite(loaderData.doc, defs);
|
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 (loaderData.gradients.count > 0) _updateGradient(loaderData.doc, &loaderData.gradients);
|
||||||
if (defs) _updateGradient(loaderData.doc, &defs->node.defs.gradients);
|
if (defs) _updateGradient(loaderData.doc, &defs->node.defs.gradients);
|
||||||
|
|
||||||
|
|
|
@ -52,6 +52,7 @@ enum class SvgNodeType
|
||||||
ClipPath,
|
ClipPath,
|
||||||
Mask,
|
Mask,
|
||||||
CssStyle,
|
CssStyle,
|
||||||
|
Symbol,
|
||||||
Unknown
|
Unknown
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -166,12 +167,20 @@ struct SvgDefsNode
|
||||||
Array<SvgStyleGradient*> gradients;
|
Array<SvgStyleGradient*> gradients;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct SvgSymbolNode
|
||||||
|
{
|
||||||
|
float w, h;
|
||||||
|
float vx, vy, vw, vh;
|
||||||
|
bool preserveAspect;
|
||||||
|
bool overflowVisible;
|
||||||
|
};
|
||||||
|
|
||||||
struct SvgUseNode
|
struct SvgUseNode
|
||||||
{
|
{
|
||||||
float x, y, w, h;
|
float x, y, w, h;
|
||||||
|
SvgNode* symbol;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
struct SvgEllipseNode
|
struct SvgEllipseNode
|
||||||
{
|
{
|
||||||
float cx;
|
float cx;
|
||||||
|
@ -375,6 +384,7 @@ struct SvgNode
|
||||||
SvgMaskNode mask;
|
SvgMaskNode mask;
|
||||||
SvgClipNode clip;
|
SvgClipNode clip;
|
||||||
SvgCssStyleNode cssStyle;
|
SvgCssStyleNode cssStyle;
|
||||||
|
SvgSymbolNode symbol;
|
||||||
} node;
|
} node;
|
||||||
bool display;
|
bool display;
|
||||||
~SvgNode();
|
~SvgNode();
|
||||||
|
|
|
@ -73,7 +73,7 @@ static unique_ptr<Scene> _sceneBuildHelper(const SvgNode* node, const Box& vBox,
|
||||||
|
|
||||||
static inline bool _isGroupType(SvgNodeType type)
|
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;
|
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)
|
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);
|
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) {
|
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 mTranslate = {1, 0, node->node.use.x, 0, 1, node->node.use.y, 0, 0, 1};
|
||||||
Matrix transform = scene->transform();
|
mUseTransform = mathMultiply(&mUseTransform, &mTranslate);
|
||||||
m = mathMultiply(&transform, &m);
|
|
||||||
scene->transform(m);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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) {
|
if (node->node.use.w > 0.0f && node->node.use.h > 0.0f) {
|
||||||
//TODO: handle width/height properties
|
//TODO: handle width/height properties
|
||||||
}
|
}
|
||||||
|
|
||||||
return scene;
|
return finalScene;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -272,6 +272,7 @@ const char* simpleXmlNodeTypeToString(TVG_UNUSED SvgNodeType type)
|
||||||
"Video",
|
"Video",
|
||||||
"ClipPath",
|
"ClipPath",
|
||||||
"Mask",
|
"Mask",
|
||||||
|
"Symbol",
|
||||||
"Unknown",
|
"Unknown",
|
||||||
};
|
};
|
||||||
return TYPE_NAMES[(int) type];
|
return TYPE_NAMES[(int) type];
|
||||||
|
|
Loading…
Add table
Reference in a new issue