diff --git a/src/loaders/svg/tvgSvgLoader.cpp b/src/loaders/svg/tvgSvgLoader.cpp index 2e7c07b3..f29bc09b 100644 --- a/src/loaders/svg/tvgSvgLoader.cpp +++ b/src/loaders/svg/tvgSvgLoader.cpp @@ -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 { const char* tag; @@ -2134,7 +2199,8 @@ static constexpr struct {"rect", sizeof("rect"), _createRectNode}, {"polyline", sizeof("polyline"), _createPolylineNode}, {"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; 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: { break; } @@ -3244,7 +3324,7 @@ static void _svgLoaderParserXmlOpen(SvgLoaderData* loader, const char* content, node = method(loader, nullptr, attrs, attrsLength, simpleXmlParseAttributes); loader->cssStyle = node; loader->doc->node.doc.style = node; - loader->style = true; + loader->openedTag = OpenedTagType::Style; } } else { node = method(loader, parent, attrs, attrsLength, simpleXmlParseAttributes); @@ -3260,9 +3340,12 @@ static void _svgLoaderParserXmlOpen(SvgLoaderData* loader, const char* content, else parent = loader->doc; node = method(loader, parent, attrs, attrsLength, simpleXmlParseAttributes); if (node && !empty) { - auto defs = _createDefsNode(loader, nullptr, nullptr, 0, nullptr); - loader->stack.push(defs); - loader->currentGraphicsNode = node; + if (!strcmp(tagName, "text")) loader->openedTag = OpenedTagType::Text; + else { + auto defs = _createDefsNode(loader, nullptr, nullptr, 0, nullptr); + loader->stack.push(defs); + loader->currentGraphicsNode = node; + } } } else if ((gradientMethod = _findGradientFactory(tagName))) { 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) { char* tag; @@ -3327,7 +3419,7 @@ static void _svgLoaderParserXmlCssStyle(SvgLoaderData* loader, const char* conte free(tag); 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::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; } case SimpleXMLType::DoctypeChild: { @@ -3572,6 +3665,11 @@ static void _freeNode(SvgNode* node) free(node->node.image.href); break; } + case SvgNodeType::Text: { + free(node->node.text.text); + free(node->node.text.fontFamily); + break; + } default: { break; } diff --git a/src/loaders/svg/tvgSvgLoaderCommon.h b/src/loaders/svg/tvgSvgLoaderCommon.h index 9f257734..5661c8ae 100644 --- a/src/loaders/svg/tvgSvgLoaderCommon.h +++ b/src/loaders/svg/tvgSvgLoaderCommon.h @@ -374,6 +374,14 @@ struct SvgCssStyleNode { }; +struct SvgTextNode +{ + char* text; + char* fontFamily; + float x, y; + float fontSize; +}; + struct SvgLinearGradient { float x1; @@ -518,6 +526,7 @@ struct SvgNode SvgClipNode clip; SvgCssStyleNode cssStyle; SvgSymbolNode symbol; + SvgTextNode text; } node; ~SvgNode(); }; @@ -545,6 +554,13 @@ struct SvgNodeIdPair char *id; }; +enum class OpenedTagType : uint8_t +{ + Other = 0, + Style, + Text +}; + struct SvgLoaderData { Array stack; @@ -559,7 +575,7 @@ struct SvgLoaderData Array images; //embedded images int level = 0; bool result = false; - bool style = false; + OpenedTagType openedTag = OpenedTagType::Other; SvgNode* currentGraphicsNode = nullptr; }; diff --git a/src/loaders/svg/tvgSvgSceneBuilder.cpp b/src/loaders/svg/tvgSvgSceneBuilder.cpp index 149fd6cd..b048695a 100644 --- a/src/loaders/svg/tvgSvgSceneBuilder.cpp +++ b/src/loaders/svg/tvgSvgSceneBuilder.cpp @@ -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) { 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->type == SvgGradientType::Linear) { - auto linear = _applyLinearGradientProperty(style->fill.paint.gradient, bBox, style->fill.opacity); - vg->fill(std::move(linear)); + auto linear = _applyLinearGradientProperty(style->fill.paint.gradient, bBox, style->fill.opacity); + vg->fill(std::move(linear)); } else if (style->fill.paint.gradient->type == SvgGradientType::Radial) { - auto radial = _applyRadialGradientProperty(style->fill.paint.gradient, bBox, style->fill.opacity); - vg->fill(std::move(radial)); + auto radial = _applyRadialGradientProperty(style->fill.paint.gradient, bBox, style->fill.opacity); + vg->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 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) { //TODO: Apply the color pointed by url + TVGLOG("SVG", "The stroke's url not supported."); } else if (style->stroke.paint.curColor) { //Apply the current style color vg->stroke(style->color.r, style->color.g, style->color.b, style->stroke.opacity); @@ -772,6 +782,61 @@ static unique_ptr _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 _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 _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. @@ -800,6 +865,9 @@ static unique_ptr _sceneBuildHelper(SvgLoaderData& loaderData, const SvgN scene->push(std::move(image)); 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) { auto shape = _shapeBuildHelper(loaderData, *child, vBox, svgPath); if (shape) {