mirror of
https://github.com/thorvg/thorvg.git
synced 2025-06-14 12:04:29 +00:00
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:
parent
af140a1fe5
commit
80fa97d366
5 changed files with 257 additions and 4 deletions
|
@ -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}
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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_
|
||||
|
|
Loading…
Add table
Reference in a new issue