svg_loader: <image> tag introduced

This patch introduces embeded <image> tag for svg files.
Images can be now loaded from local path or using data URI Scheme (RFC2397).
Base64 and utf8/uri are welcome.

@issue: #518
This commit is contained in:
Michal Maciola 2021-06-30 19:08:38 +02:00 committed by Hermet Park
parent af140a1fe5
commit 80fa97d366
5 changed files with 257 additions and 4 deletions

View file

@ -1485,7 +1485,7 @@ static constexpr struct
};
/* parse the attributes for a rect element.
/* parse the attributes for a line element.
* https://www.w3.org/TR/SVG/shapes.html#LineElement
*/
static bool _attrParseLineNode(void* data, const char* key, const char* value)
@ -1534,10 +1534,72 @@ static string* _idFromHref(const char* href)
{
href = _skipSpace(href, nullptr);
if ((*href) == '#') href++;
if(!strncmp(href, "file://", sizeof("file://") - 1)) href += sizeof("file://") - 1;
return new string(href);
}
static constexpr struct
{
const char* tag;
SvgParserLengthType type;
int sz;
size_t offset;
} imageTags[] = {
{"x", SvgParserLengthType::Horizontal, sizeof("x"), offsetof(SvgRectNode, x)},
{"y", SvgParserLengthType::Vertical, sizeof("y"), offsetof(SvgRectNode, y)},
{"width", SvgParserLengthType::Horizontal, sizeof("width"), offsetof(SvgRectNode, w)},
{"height", SvgParserLengthType::Vertical, sizeof("height"), offsetof(SvgRectNode, h)},
};
/* parse the attributes for a image element.
* https://www.w3.org/TR/SVG/embedded.html#ImageElement
*/
static bool _attrParseImageNode(void* data, const char* key, const char* value)
{
SvgLoaderData* loader = (SvgLoaderData*)data;
SvgNode* node = loader->svgParse->node;
SvgImageNode* image = &(node->node.image);
unsigned char* array;
int sz = strlen(key);
array = (unsigned char*)image;
for (unsigned int i = 0; i < sizeof(imageTags) / sizeof(imageTags[0]); i++) {
if (imageTags[i].sz - 1 == sz && !strncmp(imageTags[i].tag, key, sz)) {
*((float*)(array + imageTags[i].offset)) = _toFloat(loader->svgParse, value, imageTags[i].type);
return true;
}
}
if (!strcmp(key, "href") || !strcmp(key, "xlink:href")) {
image->href = _idFromHref(value);
} else if (!strcmp(key, "id")) {
node->id = _copyId(value);
} else if (!strcmp(key, "style")) {
return simpleXmlParseW3CAttribute(value, _parseStyleAttr, loader);
} else if (!strcmp(key, "clip-path")) {
_handleClipPathAttr(loader, node, value);
} else if (!strcmp(key, "mask")) {
_handleMaskAttr(loader, node, value);
} else {
return _parseStyleAttr(loader, key, value);
}
return true;
}
static SvgNode* _createImageNode(SvgLoaderData* loader, SvgNode* parent, const char* buf, unsigned bufLength)
{
loader->svgParse->node = _createNode(parent, SvgNodeType::Image);
if (!loader->svgParse->node) return nullptr;
simpleXmlParseAttributes(buf, bufLength, _attrParseImageNode, loader);
return loader->svgParse->node;
}
static SvgNode* _getDefsNode(SvgNode* node)
{
if (!node) return nullptr;
@ -1785,7 +1847,8 @@ static constexpr struct
{"polygon", sizeof("polygon"), _createPolygonNode},
{"rect", sizeof("rect"), _createRectNode},
{"polyline", sizeof("polyline"), _createPolylineNode},
{"line", sizeof("line"), _createLineNode}
{"line", sizeof("line"), _createLineNode},
{"image", sizeof("image"), _createImageNode}
};

View file

@ -188,6 +188,15 @@ struct SvgLineNode
float y2;
};
struct SvgImageNode
{
float x;
float y;
float w;
float h;
string *href;
};
struct SvgPathNode
{
string* path;
@ -321,6 +330,7 @@ struct SvgNode
SvgRectNode rect;
SvgPathNode path;
SvgLineNode line;
SvgImageNode image;
} node;
bool display;
};

View file

@ -24,6 +24,7 @@
#include "tvgSvgLoaderCommon.h"
#include "tvgSvgSceneBuilder.h"
#include "tvgSvgPath.h"
#include "tvgSvgUtil.h"
static bool _appendShape(SvgNode* node, Shape* shape, float vx, float vy, float vw, float vh);
@ -80,7 +81,7 @@ static unique_ptr<LinearGradient> _applyLinearGradientProperty(SvgStyleGradient*
stops[i].b = colorStop->b;
stops[i].a = (colorStop->a * opacity) / 255.0f;
stops[i].offset = colorStop->offset;
// check the offset corner cases - refer to: https://svgwg.org/svg2-draft/pservers.html#StopNotes
//check the offset corner cases - refer to: https://svgwg.org/svg2-draft/pservers.html#StopNotes
if (colorStop->offset < prevOffset) stops[i].offset = prevOffset;
else if (colorStop->offset > 1) stops[i].offset = 1;
prevOffset = stops[i].offset;
@ -146,7 +147,7 @@ static unique_ptr<RadialGradient> _applyRadialGradientProperty(SvgStyleGradient*
stops[i].b = colorStop->b;
stops[i].a = (colorStop->a * opacity) / 255.0f;
stops[i].offset = colorStop->offset;
// check the offset corner cases - refer to: https://svgwg.org/svg2-draft/pservers.html#StopNotes
//check the offset corner cases - refer to: https://svgwg.org/svg2-draft/pservers.html#StopNotes
if (colorStop->offset < prevOffset) stops[i].offset = prevOffset;
else if (colorStop->offset > 1) stops[i].offset = 1;
prevOffset = stops[i].offset;
@ -346,6 +347,113 @@ static bool _appendShape(SvgNode* node, Shape* shape, float vx, float vy, float
return true;
}
enum class imageMimeTypeEncoding
{
base64 = 0x1,
utf8 = 0x2
};
constexpr imageMimeTypeEncoding operator|(imageMimeTypeEncoding a, imageMimeTypeEncoding b) {
return static_cast<imageMimeTypeEncoding>(static_cast<int>(a) | static_cast<int>(b));
}
constexpr bool operator&(imageMimeTypeEncoding a, imageMimeTypeEncoding b) {
return (static_cast<int>(a) & static_cast<int>(b));
}
static constexpr struct
{
const char* name;
int sz;
imageMimeTypeEncoding encoding;
} imageMimeTypes[] = {
#ifdef THORVG_JPG_LOADER_SUPPORT
{"jpeg", sizeof("jpeg"), imageMimeTypeEncoding::base64},
#endif
#ifdef THORVG_PNG_LOADER_SUPPORT
{"png", sizeof("png"), imageMimeTypeEncoding::base64},
#endif
{"svg+xml", sizeof("svg+xml"), imageMimeTypeEncoding::base64 | imageMimeTypeEncoding::utf8},
};
static bool _isValidImageMimeTypeAndEncoding(const char** href, imageMimeTypeEncoding* encoding) {
if (strncmp(*href, "image/", sizeof("image/") - 1)) return false; //not allowed mime type
*href += sizeof("image/") - 1;
//RFC2397 data:[<mediatype>][;base64],<data>
//mediatype := [ type "/" subtype ] *( ";" parameter )
//parameter := attribute "=" value
for (unsigned int i = 0; i < sizeof(imageMimeTypes) / sizeof(imageMimeTypes[0]); i++) {
if (!strncmp(*href, imageMimeTypes[i].name, imageMimeTypes[i].sz - 1)) {
*href += imageMimeTypes[i].sz - 1;
while (**href && **href != ',') {
while (**href && **href != ';') ++(*href);
if (!**href) return false;
++(*href);
if (imageMimeTypes[i].encoding & imageMimeTypeEncoding::base64) {
if (!strncmp(*href, "base64,", sizeof("base64,") - 1)) {
*href += sizeof("base64,") - 1;
*encoding = imageMimeTypeEncoding::base64;
return true; //valid base64
}
}
if (imageMimeTypes[i].encoding & imageMimeTypeEncoding::utf8) {
if (!strncmp(*href, "utf8,", sizeof("utf8,") - 1)) {
*href += sizeof("utf8,") - 1;
*encoding = imageMimeTypeEncoding::utf8;
return true; //valid utf8
}
}
}
//no encoding defined
if (**href == ',' && (imageMimeTypes[i].encoding & imageMimeTypeEncoding::utf8)) {
++(*href);
*encoding = imageMimeTypeEncoding::utf8;
return true; //allow no encoding defined if utf8 expected
}
return false;
}
}
return false;
}
static unique_ptr<Picture> _imageBuildHelper(SvgNode* node, float vx, float vy, float vw, float vh)
{
if (!node->node.image.href) return nullptr;
auto picture = Picture::gen();
const char* href = (*node->node.image.href).c_str();
if (!strncmp(href, "data:", sizeof("data:") - 1)) {
href += sizeof("data:") - 1;
imageMimeTypeEncoding encoding;
if (!_isValidImageMimeTypeAndEncoding(&href, &encoding)) return nullptr; //not allowed mime type or encoding
if (encoding == imageMimeTypeEncoding::base64) {
string decoded = svgUtilBase64Decode(href);
if (picture->load(decoded.c_str(), decoded.size(), true) != Result::Success) return nullptr;
} else {
string decoded = svgUtilURLDecode(href);
if (picture->load(decoded.c_str(), decoded.size(), true) != Result::Success) return nullptr;
}
} else {
//TODO: protect against recursive svg image loading
if (picture->load(href) != Result::Success) return nullptr;
picture->size(node->node.image.w, node->node.image.h);
}
float x, y, w, h;
if (picture->viewbox(&x, &y, &w, &h) == Result::Success && vw && vh) {
auto sx = node->node.image.w / w;
auto sy = node->node.image.h / h;
auto sxt = x * sx;
auto syt = y * sy;
Matrix m = {sx, 0, node->node.image.x - sxt, 0, sy, node->node.image.y - syt, 0, 0, 1};
picture->transform(m);
}
return picture;
}
static unique_ptr<Scene> _sceneBuildHelper(const SvgNode* node, float vx, float vy, float vw, float vh)
{
@ -358,6 +466,9 @@ static unique_ptr<Scene> _sceneBuildHelper(const SvgNode* node, float vx, float
for (uint32_t i = 0; i < node->child.count; ++i, ++child) {
if (_isGroupType((*child)->type)) {
scene->push(_sceneBuildHelper(*child, vx, vy, vw, vh));
} else if ((*child)->type == SvgNodeType::Image) {
auto image = _imageBuildHelper(*child, vx, vy, vw, vh);
if (image) scene->push(move(image));
} else {
auto shape = _shapeBuildHelper(*child, vx, vy, vw, vh);
if (shape) scene->push(move(shape));

View file

@ -24,6 +24,7 @@
#include <memory.h>
#include <ctype.h>
#include <errno.h>
#include <stdint.h>
#include "tvgSvgUtil.h"
/************************************************************************/
@ -35,6 +36,20 @@ static inline bool _floatExact(float a, float b)
return memcmp(&a, &b, sizeof (float)) == 0;
}
static uint8_t _hexCharToDec(const char c) {
if (c >= 'a') return c - 'a' + 10;
else if (c >= 'A') return c - 'A' + 10;
else return c - '0';
}
static uint8_t _base64Value(const char chr) {
if (chr >= 'A' && chr <= 'Z') return chr - 'A';
else if (chr >= 'a' && chr <= 'z') return chr - 'a' + ('Z' - 'A') + 1;
else if (chr >= '0' && chr <= '9') return chr - '0' + ('Z' - 'A') + ('z' - 'a') + 2;
else if (chr == '+' || chr == '-') return 62;
else return 63;
}
/************************************************************************/
/* External Class Implementation */
@ -242,3 +257,52 @@ on_error:
if (endPtr) *endPtr = (char *)nPtr;
return 0.0f;
}
string svgUtilURLDecode(const char *src) {
unsigned int length = strlen(src);
if (!src || !length) return nullptr;
string decoded;
decoded.reserve(length);
char a, b;
while (*src) {
if (*src == '%' &&
((a = src[1]) && (b = src[2])) &&
(isxdigit(a) && isxdigit(b))) {
decoded += (_hexCharToDec(a) << 4) + _hexCharToDec(b);
src+=3;
} else if (*src == '+') {
decoded += ' ';
src++;
} else {
decoded += *src++;
}
}
return decoded;
}
string svgUtilBase64Decode(const char *src) {
unsigned int length = strlen(src);
if (!src || !length) return nullptr;
string decoded;
decoded.reserve(3*(1+(length >> 2)));
while (*src && *(src+1)) {
uint8_t value1 = _base64Value(src[0]);
uint8_t value2 = _base64Value(src[1]);
decoded += (value1 << 2) + ((value2 & 0x30) >> 4);
if (!src[2] || src[2] == '=' || src[2] == '.') break;
uint8_t value3 = _base64Value(src[2]);
decoded += ((value2 & 0x0f) << 4) + ((value3 & 0x3c) >> 2);
if (!src[3] || src[3] == '=' || src[3] == '.') break;
uint8_t value4 = _base64Value(src[3]);
decoded += ((value3 & 0x03) << 6) + value4;
src += 4;
}
return decoded;
}

View file

@ -23,6 +23,11 @@
#ifndef _TVG_SVG_UTIL_H_
#define _TVG_SVG_UTIL_H_
#include "tvgCommon.h"
float svgUtilStrtof(const char *nPtr, char **endPtr);
string svgUtilURLDecode(const char *src);
string svgUtilBase64Decode(const char *src);
#endif //_TVG_SVG_UTIL_H_