mirror of
https://github.com/thorvg/thorvg.git
synced 2025-06-08 13:43:43 +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 "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);
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -272,6 +272,7 @@ const char* simpleXmlNodeTypeToString(TVG_UNUSED SvgNodeType type)
|
|||
"Video",
|
||||
"ClipPath",
|
||||
"Mask",
|
||||
"Symbol",
|
||||
"Unknown",
|
||||
};
|
||||
return TYPE_NAMES[(int) type];
|
||||
|
|
Loading…
Add table
Reference in a new issue