mirror of
https://github.com/thorvg/thorvg.git
synced 2025-06-14 12:04:29 +00:00
svg_loader: handle text node
The text node is handled, but default values of the font-family and font-size as used in the user's system are not. For now font has to be loaded by the user. @Issue: https://github.com/thorvg/thorvg/issues/2350
This commit is contained in:
parent
7b5de2fdb3
commit
8afb7f7ca8
3 changed files with 195 additions and 13 deletions
|
@ -2119,7 +2119,72 @@ static SvgNode* _createUseNode(SvgLoaderData* loader, SvgNode* parent, const cha
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//TODO: Implement 'text' primitive
|
static constexpr struct
|
||||||
|
{
|
||||||
|
const char* tag;
|
||||||
|
SvgParserLengthType type;
|
||||||
|
int sz;
|
||||||
|
size_t offset;
|
||||||
|
} textTags[] = {
|
||||||
|
{"x", SvgParserLengthType::Horizontal, sizeof("x"), offsetof(SvgTextNode, x)},
|
||||||
|
{"y", SvgParserLengthType::Vertical, sizeof("y"), offsetof(SvgTextNode, y)},
|
||||||
|
{"font-size", SvgParserLengthType::Vertical, sizeof("font-size"), offsetof(SvgTextNode, fontSize)}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
static bool _attrParseTextNode(void* data, const char* key, const char* value)
|
||||||
|
{
|
||||||
|
SvgLoaderData* loader = (SvgLoaderData*)data;
|
||||||
|
SvgNode* node = loader->svgParse->node;
|
||||||
|
SvgTextNode* text = &(node->node.text);
|
||||||
|
|
||||||
|
unsigned char* array;
|
||||||
|
int sz = strlen(key);
|
||||||
|
|
||||||
|
array = (unsigned char*)text;
|
||||||
|
for (unsigned int i = 0; i < sizeof(textTags) / sizeof(textTags[0]); i++) {
|
||||||
|
if (textTags[i].sz - 1 == sz && !strncmp(textTags[i].tag, key, sz)) {
|
||||||
|
*((float*)(array + textTags[i].offset)) = _toFloat(loader->svgParse, value, textTags[i].type);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!strcmp(key, "font-family")) {
|
||||||
|
if (text->fontFamily && value) free(text->fontFamily);
|
||||||
|
text->fontFamily = strdup(value);
|
||||||
|
} else if (!strcmp(key, "style")) {
|
||||||
|
return simpleXmlParseW3CAttribute(value, strlen(value), _parseStyleAttr, loader);
|
||||||
|
} else if (!strcmp(key, "clip-path")) {
|
||||||
|
_handleClipPathAttr(loader, node, value);
|
||||||
|
} else if (!strcmp(key, "mask")) {
|
||||||
|
_handleMaskAttr(loader, node, value);
|
||||||
|
} else if (!strcmp(key, "id")) {
|
||||||
|
if (node->id && value) free(node->id);
|
||||||
|
node->id = _copyId(value);
|
||||||
|
} else if (!strcmp(key, "class")) {
|
||||||
|
_handleCssClassAttr(loader, node, value);
|
||||||
|
} else {
|
||||||
|
return _parseStyleAttr(loader, key, value, false);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static SvgNode* _createTextNode(SvgLoaderData* loader, SvgNode* parent, const char* buf, unsigned bufLength, parseAttributes func)
|
||||||
|
{
|
||||||
|
loader->svgParse->node = _createNode(parent, SvgNodeType::Text);
|
||||||
|
if (!loader->svgParse->node) return nullptr;
|
||||||
|
|
||||||
|
//TODO: support the def font and size as used in a system?
|
||||||
|
loader->svgParse->node->node.text.fontSize = 10.0f;
|
||||||
|
loader->svgParse->node->node.text.fontFamily = nullptr;
|
||||||
|
|
||||||
|
func(buf, bufLength, _attrParseTextNode, loader);
|
||||||
|
|
||||||
|
return loader->svgParse->node;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static constexpr struct
|
static constexpr struct
|
||||||
{
|
{
|
||||||
const char* tag;
|
const char* tag;
|
||||||
|
@ -2134,7 +2199,8 @@ static constexpr struct
|
||||||
{"rect", sizeof("rect"), _createRectNode},
|
{"rect", sizeof("rect"), _createRectNode},
|
||||||
{"polyline", sizeof("polyline"), _createPolylineNode},
|
{"polyline", sizeof("polyline"), _createPolylineNode},
|
||||||
{"line", sizeof("line"), _createLineNode},
|
{"line", sizeof("line"), _createLineNode},
|
||||||
{"image", sizeof("image"), _createImageNode}
|
{"image", sizeof("image"), _createImageNode},
|
||||||
|
{"text", sizeof("text"), _createTextNode}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -3122,6 +3188,20 @@ static void _copyAttr(SvgNode* to, const SvgNode* from)
|
||||||
to->node.use.symbol = from->node.use.symbol;
|
to->node.use.symbol = from->node.use.symbol;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case SvgNodeType::Text: {
|
||||||
|
to->node.text.x = from->node.text.x;
|
||||||
|
to->node.text.y = from->node.text.y;
|
||||||
|
to->node.text.fontSize = from->node.text.fontSize;
|
||||||
|
if (from->node.text.text) {
|
||||||
|
if (to->node.text.text) free(to->node.text.text);
|
||||||
|
to->node.text.text = strdup(from->node.text.text);
|
||||||
|
}
|
||||||
|
if (from->node.text.fontFamily) {
|
||||||
|
if (to->node.text.fontFamily) free(to->node.text.fontFamily);
|
||||||
|
to->node.text.fontFamily = strdup(from->node.text.fontFamily);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
default: {
|
default: {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -3244,7 +3324,7 @@ static void _svgLoaderParserXmlOpen(SvgLoaderData* loader, const char* content,
|
||||||
node = method(loader, nullptr, attrs, attrsLength, simpleXmlParseAttributes);
|
node = method(loader, nullptr, attrs, attrsLength, simpleXmlParseAttributes);
|
||||||
loader->cssStyle = node;
|
loader->cssStyle = node;
|
||||||
loader->doc->node.doc.style = node;
|
loader->doc->node.doc.style = node;
|
||||||
loader->style = true;
|
loader->openedTag = OpenedTagType::Style;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
node = method(loader, parent, attrs, attrsLength, simpleXmlParseAttributes);
|
node = method(loader, parent, attrs, attrsLength, simpleXmlParseAttributes);
|
||||||
|
@ -3260,9 +3340,12 @@ static void _svgLoaderParserXmlOpen(SvgLoaderData* loader, const char* content,
|
||||||
else parent = loader->doc;
|
else parent = loader->doc;
|
||||||
node = method(loader, parent, attrs, attrsLength, simpleXmlParseAttributes);
|
node = method(loader, parent, attrs, attrsLength, simpleXmlParseAttributes);
|
||||||
if (node && !empty) {
|
if (node && !empty) {
|
||||||
auto defs = _createDefsNode(loader, nullptr, nullptr, 0, nullptr);
|
if (!strcmp(tagName, "text")) loader->openedTag = OpenedTagType::Text;
|
||||||
loader->stack.push(defs);
|
else {
|
||||||
loader->currentGraphicsNode = node;
|
auto defs = _createDefsNode(loader, nullptr, nullptr, 0, nullptr);
|
||||||
|
loader->stack.push(defs);
|
||||||
|
loader->currentGraphicsNode = node;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if ((gradientMethod = _findGradientFactory(tagName))) {
|
} else if ((gradientMethod = _findGradientFactory(tagName))) {
|
||||||
SvgStyleGradient* gradient;
|
SvgStyleGradient* gradient;
|
||||||
|
@ -3295,6 +3378,15 @@ static void _svgLoaderParserXmlOpen(SvgLoaderData* loader, const char* content,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void _svgLoaderParserText(SvgLoaderData* loader, const char* content, unsigned int length)
|
||||||
|
{
|
||||||
|
auto text = &loader->svgParse->node->node.text;
|
||||||
|
if (text->text) free(text->text);
|
||||||
|
text->text = strDuplicate(content, length);
|
||||||
|
loader->openedTag = OpenedTagType::Other;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static void _svgLoaderParserXmlCssStyle(SvgLoaderData* loader, const char* content, unsigned int length)
|
static void _svgLoaderParserXmlCssStyle(SvgLoaderData* loader, const char* content, unsigned int length)
|
||||||
{
|
{
|
||||||
char* tag;
|
char* tag;
|
||||||
|
@ -3327,7 +3419,7 @@ static void _svgLoaderParserXmlCssStyle(SvgLoaderData* loader, const char* conte
|
||||||
free(tag);
|
free(tag);
|
||||||
free(name);
|
free(name);
|
||||||
}
|
}
|
||||||
loader->style = false;
|
loader->openedTag = OpenedTagType::Other;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -3350,7 +3442,8 @@ static bool _svgLoaderParser(void* data, SimpleXMLType type, const char* content
|
||||||
}
|
}
|
||||||
case SimpleXMLType::Data:
|
case SimpleXMLType::Data:
|
||||||
case SimpleXMLType::CData: {
|
case SimpleXMLType::CData: {
|
||||||
if (loader->style) _svgLoaderParserXmlCssStyle(loader, content, length);
|
if (loader->openedTag == OpenedTagType::Style) _svgLoaderParserXmlCssStyle(loader, content, length);
|
||||||
|
else if (loader->openedTag == OpenedTagType::Text) _svgLoaderParserText(loader, content, length);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case SimpleXMLType::DoctypeChild: {
|
case SimpleXMLType::DoctypeChild: {
|
||||||
|
@ -3572,6 +3665,11 @@ static void _freeNode(SvgNode* node)
|
||||||
free(node->node.image.href);
|
free(node->node.image.href);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case SvgNodeType::Text: {
|
||||||
|
free(node->node.text.text);
|
||||||
|
free(node->node.text.fontFamily);
|
||||||
|
break;
|
||||||
|
}
|
||||||
default: {
|
default: {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -374,6 +374,14 @@ struct SvgCssStyleNode
|
||||||
{
|
{
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct SvgTextNode
|
||||||
|
{
|
||||||
|
char* text;
|
||||||
|
char* fontFamily;
|
||||||
|
float x, y;
|
||||||
|
float fontSize;
|
||||||
|
};
|
||||||
|
|
||||||
struct SvgLinearGradient
|
struct SvgLinearGradient
|
||||||
{
|
{
|
||||||
float x1;
|
float x1;
|
||||||
|
@ -518,6 +526,7 @@ struct SvgNode
|
||||||
SvgClipNode clip;
|
SvgClipNode clip;
|
||||||
SvgCssStyleNode cssStyle;
|
SvgCssStyleNode cssStyle;
|
||||||
SvgSymbolNode symbol;
|
SvgSymbolNode symbol;
|
||||||
|
SvgTextNode text;
|
||||||
} node;
|
} node;
|
||||||
~SvgNode();
|
~SvgNode();
|
||||||
};
|
};
|
||||||
|
@ -545,6 +554,13 @@ struct SvgNodeIdPair
|
||||||
char *id;
|
char *id;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum class OpenedTagType : uint8_t
|
||||||
|
{
|
||||||
|
Other = 0,
|
||||||
|
Style,
|
||||||
|
Text
|
||||||
|
};
|
||||||
|
|
||||||
struct SvgLoaderData
|
struct SvgLoaderData
|
||||||
{
|
{
|
||||||
Array<SvgNode*> stack;
|
Array<SvgNode*> stack;
|
||||||
|
@ -559,7 +575,7 @@ struct SvgLoaderData
|
||||||
Array<char*> images; //embedded images
|
Array<char*> images; //embedded images
|
||||||
int level = 0;
|
int level = 0;
|
||||||
bool result = false;
|
bool result = false;
|
||||||
bool style = false;
|
OpenedTagType openedTag = OpenedTagType::Other;
|
||||||
SvgNode* currentGraphicsNode = nullptr;
|
SvgNode* currentGraphicsNode = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -67,6 +67,14 @@ static Box _boundingBox(const Shape* shape)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static Box _boundingBox(const Text* text)
|
||||||
|
{
|
||||||
|
float x, y, w, h;
|
||||||
|
text->bounds(&x, &y, &w, &h, false);
|
||||||
|
return {x, y, w, h};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static void _transformMultiply(const Matrix* mBBox, Matrix* gradTransf)
|
static void _transformMultiply(const Matrix* mBBox, Matrix* gradTransf)
|
||||||
{
|
{
|
||||||
gradTransf->e13 = gradTransf->e13 * mBBox->e11 + mBBox->e13;
|
gradTransf->e13 = gradTransf->e13 * mBBox->e11 + mBBox->e13;
|
||||||
|
@ -320,14 +328,15 @@ static void _applyProperty(SvgLoaderData& loaderData, SvgNode* node, Shape* vg,
|
||||||
if (!style->fill.paint.gradient->userSpace) bBox = _boundingBox(vg);
|
if (!style->fill.paint.gradient->userSpace) bBox = _boundingBox(vg);
|
||||||
|
|
||||||
if (style->fill.paint.gradient->type == SvgGradientType::Linear) {
|
if (style->fill.paint.gradient->type == SvgGradientType::Linear) {
|
||||||
auto linear = _applyLinearGradientProperty(style->fill.paint.gradient, bBox, style->fill.opacity);
|
auto linear = _applyLinearGradientProperty(style->fill.paint.gradient, bBox, style->fill.opacity);
|
||||||
vg->fill(std::move(linear));
|
vg->fill(std::move(linear));
|
||||||
} else if (style->fill.paint.gradient->type == SvgGradientType::Radial) {
|
} else if (style->fill.paint.gradient->type == SvgGradientType::Radial) {
|
||||||
auto radial = _applyRadialGradientProperty(style->fill.paint.gradient, bBox, style->fill.opacity);
|
auto radial = _applyRadialGradientProperty(style->fill.paint.gradient, bBox, style->fill.opacity);
|
||||||
vg->fill(std::move(radial));
|
vg->fill(std::move(radial));
|
||||||
}
|
}
|
||||||
} else if (style->fill.paint.url) {
|
} else if (style->fill.paint.url) {
|
||||||
//TODO: Apply the color pointed by url
|
//TODO: Apply the color pointed by url
|
||||||
|
TVGLOG("SVG", "The fill's url not supported.");
|
||||||
} else if (style->fill.paint.curColor) {
|
} else if (style->fill.paint.curColor) {
|
||||||
//Apply the current style color
|
//Apply the current style color
|
||||||
vg->fill(style->color.r, style->color.g, style->color.b, style->fill.opacity);
|
vg->fill(style->color.r, style->color.g, style->color.b, style->fill.opacity);
|
||||||
|
@ -371,6 +380,7 @@ static void _applyProperty(SvgLoaderData& loaderData, SvgNode* node, Shape* vg,
|
||||||
}
|
}
|
||||||
} else if (style->stroke.paint.url) {
|
} else if (style->stroke.paint.url) {
|
||||||
//TODO: Apply the color pointed by url
|
//TODO: Apply the color pointed by url
|
||||||
|
TVGLOG("SVG", "The stroke's url not supported.");
|
||||||
} else if (style->stroke.paint.curColor) {
|
} else if (style->stroke.paint.curColor) {
|
||||||
//Apply the current style color
|
//Apply the current style color
|
||||||
vg->strokeFill(style->color.r, style->color.g, style->color.b, style->stroke.opacity);
|
vg->strokeFill(style->color.r, style->color.g, style->color.b, style->stroke.opacity);
|
||||||
|
@ -772,6 +782,61 @@ static unique_ptr<Scene> _useBuildHelper(SvgLoaderData& loaderData, const SvgNod
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void _applyTextFill(SvgStyleProperty* style, Text* text, const Box& vBox)
|
||||||
|
{
|
||||||
|
//If fill property is nullptr then do nothing
|
||||||
|
if (style->fill.paint.none) {
|
||||||
|
//Do nothing
|
||||||
|
} else if (style->fill.paint.gradient) {
|
||||||
|
Box bBox = vBox;
|
||||||
|
if (!style->fill.paint.gradient->userSpace) bBox = _boundingBox(text);
|
||||||
|
|
||||||
|
if (style->fill.paint.gradient->type == SvgGradientType::Linear) {
|
||||||
|
auto linear = _applyLinearGradientProperty(style->fill.paint.gradient, bBox, style->fill.opacity);
|
||||||
|
text->fill(std::move(linear));
|
||||||
|
} else if (style->fill.paint.gradient->type == SvgGradientType::Radial) {
|
||||||
|
auto radial = _applyRadialGradientProperty(style->fill.paint.gradient, bBox, style->fill.opacity);
|
||||||
|
text->fill(std::move(radial));
|
||||||
|
}
|
||||||
|
} else if (style->fill.paint.url) {
|
||||||
|
//TODO: Apply the color pointed by url
|
||||||
|
TVGLOG("SVG", "The fill's url not supported.");
|
||||||
|
} else if (style->fill.paint.curColor) {
|
||||||
|
//Apply the current style color
|
||||||
|
text->fill(style->color.r, style->color.g, style->color.b);
|
||||||
|
text->opacity(style->fill.opacity);
|
||||||
|
} else {
|
||||||
|
//Apply the fill color
|
||||||
|
text->fill(style->fill.paint.color.r, style->fill.paint.color.g, style->fill.paint.color.b);
|
||||||
|
text->opacity(style->fill.opacity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static unique_ptr<Text> _textBuildHelper(SvgLoaderData& loaderData, const SvgNode* node, const Box& vBox, const string& svgPath)
|
||||||
|
{
|
||||||
|
auto textNode = &node->node.text;
|
||||||
|
if (!textNode->text) return nullptr;
|
||||||
|
auto text = Text::gen();
|
||||||
|
|
||||||
|
Matrix textTransform = {1, 0, 0, 0, 1, 0, 0, 0, 1};
|
||||||
|
if (node->transform) textTransform = *node->transform;
|
||||||
|
mathTranslateR(&textTransform, node->node.text.x, node->node.text.y - textNode->fontSize);
|
||||||
|
text->transform(textTransform);
|
||||||
|
|
||||||
|
//TODO: handle def values of font and size as used in a system?
|
||||||
|
const float ptPerPx = 0.75f; //1 pt = 1/72; 1 in = 96 px; -> 72/96 = 0.75
|
||||||
|
auto fontSizePt = textNode->fontSize * ptPerPx;
|
||||||
|
if (textNode->fontFamily) text->font(textNode->fontFamily, fontSizePt);
|
||||||
|
text->text(textNode->text);
|
||||||
|
|
||||||
|
_applyTextFill(node->style, text.get(), vBox);
|
||||||
|
_applyComposition(loaderData, text.get(), node, vBox, svgPath);
|
||||||
|
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static unique_ptr<Scene> _sceneBuildHelper(SvgLoaderData& loaderData, const SvgNode* node, const Box& vBox, const string& svgPath, bool mask, int depth, bool* isMaskWhite)
|
static unique_ptr<Scene> _sceneBuildHelper(SvgLoaderData& loaderData, const SvgNode* node, const Box& vBox, const string& svgPath, bool mask, int depth, bool* isMaskWhite)
|
||||||
{
|
{
|
||||||
/* Exception handling: Prevent invalid SVG data input.
|
/* Exception handling: Prevent invalid SVG data input.
|
||||||
|
@ -800,6 +865,9 @@ static unique_ptr<Scene> _sceneBuildHelper(SvgLoaderData& loaderData, const SvgN
|
||||||
scene->push(std::move(image));
|
scene->push(std::move(image));
|
||||||
if (isMaskWhite) *isMaskWhite = false;
|
if (isMaskWhite) *isMaskWhite = false;
|
||||||
}
|
}
|
||||||
|
} else if ((*child)->type == SvgNodeType::Text) {
|
||||||
|
auto text = _textBuildHelper(loaderData, *child, vBox, svgPath);
|
||||||
|
if (text) scene->push(std::move(text));
|
||||||
} else if ((*child)->type != SvgNodeType::Mask) {
|
} else if ((*child)->type != SvgNodeType::Mask) {
|
||||||
auto shape = _shapeBuildHelper(loaderData, *child, vBox, svgPath);
|
auto shape = _shapeBuildHelper(loaderData, *child, vBox, svgPath);
|
||||||
if (shape) {
|
if (shape) {
|
||||||
|
|
Loading…
Add table
Reference in a new issue