svg_loader: handling svgs without viewBox/viewPort

Additionally:
- cases of inforrect viewBox values are handled
- cases of zero width/height of a viewBox and/or viewPort

@Issue: https://github.com/Samsung/thorvg/issues/1239
This commit is contained in:
Mira Grudzinska 2023-02-01 23:46:48 +01:00 committed by Hermet Park
parent c9657e3e68
commit 9e8980a1f7
7 changed files with 93 additions and 37 deletions

View file

@ -98,6 +98,7 @@ Result Picture::size(float w, float h) noexcept
Result Picture::size(float* w, float* h) const noexcept
{
if (!pImpl->loader) return Result::InsufficientCondition;
pImpl->reload();
if (w) *w = pImpl->w;
if (h) *h = pImpl->h;
return Result::Success;

View file

@ -98,6 +98,10 @@ struct Picture::Impl
paint = p.release();
loader->close();
if (w != loader->w || h != loader->h) {
if (!resizing) {
w = loader->w;
h = loader->h;
}
loader->resize(paint, w, h);
resizing = false;
}
@ -155,9 +159,10 @@ struct Picture::Impl
return false;
}
bool viewbox(float* x, float* y, float* w, float* h) const
bool viewbox(float* x, float* y, float* w, float* h)
{
if (!loader) return false;
reload();
if (x) *x = loader->vx;
if (y) *y = loader->vy;
if (w) *w = loader->vw;

View file

@ -831,13 +831,16 @@ static bool _attrParseSvgNode(void* data, const char* key, const char* value)
if (!strcmp(key, "width")) {
doc->w = _toFloat(loader->svgParse, value, SvgParserLengthType::Horizontal);
doc->viewFlag = (SvgViewFlag)((uint32_t)doc->viewFlag | (uint32_t)SvgViewFlag::Width);
} else if (!strcmp(key, "height")) {
doc->h = _toFloat(loader->svgParse, value, SvgParserLengthType::Vertical);
doc->viewFlag = (SvgViewFlag)((uint32_t)doc->viewFlag | (uint32_t)SvgViewFlag::Height);
} else if (!strcmp(key, "viewBox")) {
if (_parseNumber(&value, &doc->vx)) {
if (_parseNumber(&value, &doc->vy)) {
if (_parseNumber(&value, &doc->vw)) {
_parseNumber(&value, &doc->vh);
if (_parseNumber(&value, &doc->vh)) {
doc->viewFlag = (SvgViewFlag)((uint32_t)doc->viewFlag | (uint32_t)SvgViewFlag::Viewbox);
loader->svgParse->global.h = doc->vh;
}
loader->svgParse->global.w = doc->vw;
@ -845,6 +848,15 @@ static bool _attrParseSvgNode(void* data, const char* key, const char* value)
loader->svgParse->global.y = doc->vy;
}
loader->svgParse->global.x = doc->vx;
}
if (((uint32_t)doc->viewFlag & (uint32_t)SvgViewFlag::Viewbox) && (doc->vw < 0.0f || doc->vh < 0.0f)) {
doc->viewFlag = (SvgViewFlag)((uint32_t)doc->viewFlag & ~(uint32_t)SvgViewFlag::Viewbox);
TVGLOG("SVG", "Negative values of the <viewBox> width and/or height - the attribute invalidated.");
}
if (!((uint32_t)doc->viewFlag & (uint32_t)SvgViewFlag::Viewbox)) {
loader->svgParse->global.x = loader->svgParse->global.y = 0.0f;
loader->svgParse->global.w = loader->svgParse->global.h = 1.0f;
}
} else if (!strcmp(key, "preserveAspectRatio")) {
_parseAspectRatio(&value, &doc->align, &doc->meetOrSlice);
} else if (!strcmp(key, "style")) {
@ -1289,22 +1301,22 @@ static SvgNode* _createSvgNode(SvgLoaderData* loader, SvgNode* parent, const cha
if (!loader->svgParse->node) return nullptr;
SvgDocNode* doc = &(loader->svgParse->node->node.doc);
loader->svgParse->global.w = 0;
loader->svgParse->global.h = 0;
loader->svgParse->global.w = 1.0f;
loader->svgParse->global.h = 1.0f;
doc->align = AspectRatioAlign::XMidYMid;
doc->meetOrSlice = AspectRatioMeetOrSlice::Meet;
doc->viewFlag = SvgViewFlag::None;
func(buf, bufLength, _attrParseSvgNode, loader);
if (loader->svgParse->global.w == 0) {
if (doc->w < FLT_EPSILON) loader->svgParse->global.w = 1;
else loader->svgParse->global.w = doc->w;
if (!((uint32_t)doc->viewFlag & (uint32_t)SvgViewFlag::Viewbox)) {
if ((uint32_t)doc->viewFlag & (uint32_t)SvgViewFlag::Width) {
loader->svgParse->global.w = doc->w;
}
if ((uint32_t)doc->viewFlag & (uint32_t)SvgViewFlag::Height) {
loader->svgParse->global.h = doc->h;
}
if (loader->svgParse->global.h == 0) {
if (doc->h < FLT_EPSILON) loader->svgParse->global.h = 1;
else loader->svgParse->global.h = doc->h;
}
return loader->svgParse->node;
}
@ -3192,7 +3204,7 @@ void SvgLoader::run(unsigned tid)
_updateStyle(loaderData.doc, nullptr);
}
root = svgSceneBuild(loaderData.doc, vx, vy, vw, vh, w, h, align, meetOrSlice, svgPath);
root = svgSceneBuild(loaderData.doc, vx, vy, vw, vh, w, h, align, meetOrSlice, svgPath, viewFlag);
}
@ -3205,28 +3217,37 @@ bool SvgLoader::header()
if (!loaderData.svgParse) return false;
loaderData.svgParse->flags = SvgStopStyleFlags::StopDefault;
viewFlag = SvgViewFlag::None;
simpleXmlParse(content, size, true, _svgLoaderParserForValidCheck, &(loaderData));
if (loaderData.doc && loaderData.doc->type == SvgNodeType::Doc) {
//Return the brief resource info such as viewbox:
vx = loaderData.doc->node.doc.vx;
vy = loaderData.doc->node.doc.vy;
w = vw = loaderData.doc->node.doc.vw;
h = vh = loaderData.doc->node.doc.vh;
//Override size
if (loaderData.doc->node.doc.w > 0) {
w = loaderData.doc->node.doc.w;
if (vw < FLT_EPSILON) vw = w;
}
if (loaderData.doc->node.doc.h > 0) {
h = loaderData.doc->node.doc.h;
if (vh < FLT_EPSILON) vh = h;
}
viewFlag = loaderData.doc->node.doc.viewFlag;
align = loaderData.doc->node.doc.align;
meetOrSlice = loaderData.doc->node.doc.meetOrSlice;
w = 1.0f;
h = 1.0f;
//Return the brief resource info such as viewbox:
if ((uint32_t)viewFlag & (uint32_t)SvgViewFlag::Width) {
w = loaderData.doc->node.doc.w;
}
if ((uint32_t)viewFlag & (uint32_t)SvgViewFlag::Height) {
h = loaderData.doc->node.doc.h;
}
//Override size
if ((uint32_t)viewFlag & (uint32_t)SvgViewFlag::Viewbox) {
vx = loaderData.doc->node.doc.vx;
vy = loaderData.doc->node.doc.vy;
vw = loaderData.doc->node.doc.vw;
vh = loaderData.doc->node.doc.vh;
if (!((uint32_t)viewFlag & (uint32_t)SvgViewFlag::Width)) w = vw;
if (!((uint32_t)viewFlag & (uint32_t)SvgViewFlag::Height)) h = vh;
} else {
vw = w;
vh = h;
}
} else {
TVGLOG("SVG", "No SVG File. There is no <svg/>");
return false;
@ -3292,6 +3313,12 @@ bool SvgLoader::read()
{
if (!content || size == 0) return false;
if (((uint32_t)viewFlag & (uint32_t)SvgViewFlag::Viewbox) &&
(fabsf(vw) <= FLT_EPSILON || fabsf(vh) <= FLT_EPSILON)) {
TVGLOG("SVG", "The <viewBox> width and/or height set to 0 - rendering disabled.");
return false;
}
TaskScheduler::request(this);
return true;

View file

@ -51,6 +51,7 @@ public:
unique_ptr<Paint> paint() override;
private:
SvgViewFlag viewFlag = SvgViewFlag::None;
AspectRatioAlign align = AspectRatioAlign::XMidYMid;
AspectRatioMeetOrSlice meetOrSlice = AspectRatioMeetOrSlice::Meet;

View file

@ -146,6 +146,14 @@ enum class SvgParserLengthType
Other
};
enum class SvgViewFlag
{
None = 0x0,
Width = 0x01, //viewPort width
Height = 0x02, //viewPort height
Viewbox = 0x04 //viewBox x,y,w,h - used only if all 4 are correctly set
};
enum class AspectRatioAlign
{
None,
@ -174,6 +182,7 @@ struct SvgDocNode
float vy;
float vw;
float vh;
SvgViewFlag viewFlag;
SvgNode* defs;
SvgNode* style;
AspectRatioAlign align;

View file

@ -66,7 +66,6 @@ struct Box
float x, y, w, h;
};
static bool _appendShape(SvgNode* node, Shape* shape, const Box& vBox, const string& svgPath);
static unique_ptr<Scene> _sceneBuildHelper(const SvgNode* node, const Box& vBox, const string& svgPath, bool mask, int depth, bool* isMaskWhite = nullptr);
@ -755,11 +754,24 @@ static unique_ptr<Scene> _sceneBuildHelper(const SvgNode* node, const Box& vBox,
}
static void _applySvgViewFlag(const Scene* scene, float& vx, float& vy, float& vw, float& vh, float& w, float& h, SvgViewFlag viewFlag)
{
if (!((uint32_t)viewFlag & (uint32_t)SvgViewFlag::Viewbox)) {
scene->bounds(nullptr, nullptr, &vw, &vh, false);
vx = 0.0f;
vy = 0.0f;
if ((uint32_t)viewFlag & (uint32_t)SvgViewFlag::Width) vw = w;
if ((uint32_t)viewFlag & (uint32_t)SvgViewFlag::Height) vh = h;
}
if (!((uint32_t)viewFlag & (uint32_t)SvgViewFlag::Width)) w = vw;
if (!((uint32_t)viewFlag & (uint32_t)SvgViewFlag::Height)) h = vh;
}
/************************************************************************/
/* External Class Implementation */
/************************************************************************/
unique_ptr<Scene> svgSceneBuild(SvgNode* node, float vx, float vy, float vw, float vh, float w, float h, AspectRatioAlign align, AspectRatioMeetOrSlice meetOrSlice, const string& svgPath)
unique_ptr<Scene> svgSceneBuild(SvgNode* node, float& vx, float& vy, float& vw, float& vh, float& w, float& h, AspectRatioAlign align, AspectRatioMeetOrSlice meetOrSlice, const string& svgPath, SvgViewFlag viewFlag)
{
//TODO: aspect ratio is valid only if viewBox was set
@ -767,9 +779,10 @@ unique_ptr<Scene> svgSceneBuild(SvgNode* node, float vx, float vy, float vw, flo
Box vBox = {vx, vy, vw, vh};
auto docNode = _sceneBuildHelper(node, vBox, svgPath, false, 0);
_applySvgViewFlag(docNode.get(), vx, vy, vw, vh, w, h, viewFlag);
if (!mathEqual(w, vw) || !mathEqual(h, vh)) {
Matrix m = _calculateAspectRatioMatrix(align, meetOrSlice, w, h, vBox);
Matrix m = _calculateAspectRatioMatrix(align, meetOrSlice, w, h, {vx, vy, vw, vh});
docNode->transform(m);
} else if (!mathZero(vx) || !mathZero(vy)) {
docNode->translate(-vx, -vy);

View file

@ -25,6 +25,6 @@
#include "tvgCommon.h"
unique_ptr<Scene> svgSceneBuild(SvgNode* node, float vx, float vy, float vw, float vh, float w, float h, AspectRatioAlign align, AspectRatioMeetOrSlice meetOrSlice, const string& svgPath);
unique_ptr<Scene> svgSceneBuild(SvgNode* node, float& vx, float& vy, float& vw, float& vh, float& w, float& h, AspectRatioAlign align, AspectRatioMeetOrSlice meetOrSlice, const string& svgPath, SvgViewFlag viewFlag);
#endif //_TVG_SVG_SCENE_BUILDER_H_