Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

text tag #2246

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
35 changes: 31 additions & 4 deletions inc/thorvg.h
Original file line number Diff line number Diff line change
Expand Up @@ -1529,7 +1529,7 @@ class TVG_API Text final : public Paint
Result fill(std::unique_ptr<Fill> f) noexcept;

/**
* @brief Loads a scalable font data(ttf) from a file.
* @brief Loads a scalable font data (ttf) from a file.
*
* ThorVG efficiently caches the loaded data using the specified @p path as a key.
* This means that loading the same file again will not result in duplicate operations;
Expand All @@ -1544,16 +1544,42 @@ class TVG_API Text final : public Paint
*
* @note Experimental API
*
* @see Text::unload(const std::string& path)
* @see Text::unload(const std::string& fontId)
*/
static Result load(const std::string& path) noexcept;

/**
* @brief Loads a scalable font data (ttf) from a memory block of a given size.
*
* ThorVG efficiently caches the loaded font data using the specified @p name as a key when @p copy
* is set to @c false. This means that loading the same fonts again will not result in duplicate
* operations for the shared @p data. Instead, ThorVG will reuse the previously loaded font data.
*
* @param[in] name The name under which the font will be stored and accessible.
* @param[in] data A pointer to a memory location where the content of the ttf data is stored.
* @param[in] size The size in bytes of the memory occupied by the @p data.
* @param[in] rpath A resource directory path, if the @p data needs to access any external resources.
* @param[in] copy If @c true the data are copied into the engine local buffer, otherwise they are not.
*
* @retval Result::Success When succeed.
* @retval Result::InvalidArguments In case no data are provided or the @p size is zero or less.
* @retval Result::NonSupport When trying to load a file with an unsupported extension.
* @retval Result::Unknown If an error occurs at a later stage.
*
* @warning: It's the user responsibility to release the @p data memory.
*
* @note Experimental API
*
* @see Text::unload(const std::string& fontId)
*/
static Result load(const std::string& name, const char* data, uint32_t size, const std::string& rpath = "", bool copy = false) noexcept;

/**
* @brief Unloads the specified scalable font data (TTF) that was previously loaded.
*
* This function is used to release resources associated with a font file that has been loaded into memory.
*
* @param[in] path The file path of the loaded font.
* @param[in] fontId The file path or the font name of the loaded font depending on which method was used for loading.
*
* @retval Result::Success Successfully unloads the font data.
* @retval Result::InsufficientCondition Fails if the loader is not initialized.
Expand All @@ -1562,8 +1588,9 @@ class TVG_API Text final : public Paint
* @note Experimental API
*
* @see Text::load(const std::string& path)
* @see Text::load(const std::string& name, const char* data, uint32_t size, const std::string& rpath, bool copy)
*/
static Result unload(const std::string& path) noexcept;
static Result unload(const std::string& fontId) noexcept;

/**
* @brief Creates a new Text object.
Expand Down
173 changes: 165 additions & 8 deletions src/loaders/svg/tvgSvgLoader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,28 @@ static char* _idFromUrl(const char* url)
}


static char* _srcFromUrl(const char* url)
{
auto open = strchr(url, '(');
auto close = strchr(url, ')');
if (!open || !close || open >= close) return nullptr;

open = strchr(open, '\'');
if (!open || open >= close) return nullptr;
++open;

close = strchr(open, '\'');
if (!close || close == open) return nullptr;
--close;

//trim the rest of the spaces if any
while (open < close && *open == ' ') ++open;
while (open < close && *close == ' ') --close;

return strDuplicate(open, (close - open + 1));
}


static unsigned char _parseColor(const char* value, char** end)
{
float r;
Expand Down Expand Up @@ -1999,6 +2021,36 @@ static SvgNode* _createImageNode(SvgLoaderData* loader, SvgNode* parent, const c
}


static bool _attrParseFontFace(void* data, const char* key, const char* value)
{
if (!key || !value) return false;

//Trim the white space
key = _skipSpace(key, nullptr);
value = _skipSpace(value, nullptr);

auto loader = (SvgLoaderData*)data;
auto& embeddedFont = loader->fonts.last();

if (!strcmp(key, "font-family")) {
if (embeddedFont.name) free(embeddedFont.name);
embeddedFont.name = strdup(value);
} else if (!strcmp(key, "src")) {
if (embeddedFont.src) free(embeddedFont.src);
embeddedFont.src = _srcFromUrl(value);
}

return true;
}


static void _createEmbeddedFont(SvgLoaderData* loader, const char* buf, unsigned bufLength, parseAttributes func)
{
loader->fonts.push({});
func(buf, bufLength, _attrParseFontFace, loader);
}


static SvgNode* _getDefsNode(SvgNode* node)
{
if (!node) return nullptr;
Expand Down Expand Up @@ -2119,7 +2171,71 @@ 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;

loader->svgParse->node->node.text.fontSize = 10.0f;
//TODO: support the def font: loader->svgParse->node->node.text.fontFamily = ...

func(buf, bufLength, _attrParseTextNode, loader);

return loader->svgParse->node;
}


static constexpr struct
{
const char* tag;
Expand All @@ -2134,7 +2250,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}
};


Expand Down Expand Up @@ -3122,6 +3239,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;
}
Expand Down Expand Up @@ -3244,7 +3375,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);
Expand All @@ -3260,9 +3391,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;
Expand Down Expand Up @@ -3295,6 +3429,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;
Expand All @@ -3317,6 +3460,8 @@ static void _svgLoaderParserXmlCssStyle(SvgLoaderData* loader, const char* conte
TVGLOG("SVG", "Unsupported elements used in the internal CSS style sheets [Elements: %s]", tag);
} else if (!strcmp(tag, "all")) {
if ((node = _createCssStyleNode(loader, loader->cssStyle, attrs, attrsLength, simpleXmlParseW3CAttribute))) node->id = _copyId(name);
} else if (!strcmp(tag, "@font-face")) { //css at-rule specifying font
_createEmbeddedFont(loader, attrs, attrsLength, simpleXmlParseW3CAttribute);
} else if (!isIgnoreUnsupportedLogElements(tag)) {
TVGLOG("SVG", "Unsupported elements used in the internal CSS style sheets [Elements: %s]", tag);
}
Expand All @@ -3327,7 +3472,7 @@ static void _svgLoaderParserXmlCssStyle(SvgLoaderData* loader, const char* conte
free(tag);
free(name);
}
loader->style = false;
loader->openedTag = OpenedTagType::Other;
}


Expand All @@ -3350,7 +3495,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: {
Expand Down Expand Up @@ -3572,6 +3718,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;
}
Expand Down Expand Up @@ -3663,6 +3814,12 @@ void SvgLoader::clear(bool all)
}
loaderData.images.reset();

for (auto font = loaderData.fonts.begin(); font < loaderData.fonts.end(); ++font) {
Text::unload(font->name);
free(font->name);
}
loaderData.fonts.reset();

if (copy) free((char*)content);

delete(root);
Expand Down
27 changes: 25 additions & 2 deletions src/loaders/svg/tvgSvgLoaderCommon.h
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,14 @@ struct SvgCssStyleNode
{
};

struct SvgTextNode
{
char* text;
float x, y;
float fontSize;
char* fontFamily;
};

struct SvgLinearGradient
{
float x1;
Expand Down Expand Up @@ -518,6 +526,7 @@ struct SvgNode
SvgClipNode clip;
SvgCssStyleNode cssStyle;
SvgSymbolNode symbol;
SvgTextNode text;
} node;
~SvgNode();
};
Expand Down Expand Up @@ -545,6 +554,19 @@ struct SvgNodeIdPair
char *id;
};

struct EmbeddedFont
{
char* name;
char* src;
};

enum class OpenedTagType : uint8_t
{
Other = 0,
Style,
Text
};

struct SvgLoaderData
{
Array<SvgNode*> stack;
Expand All @@ -556,10 +578,11 @@ struct SvgLoaderData
SvgParser* svgParse = nullptr;
Array<SvgNodeIdPair> cloneNodes;
Array<SvgNodeIdPair> nodesToStyle;
Array<char*> images; //embedded images
Array<char*> images; //embedded images
Array<EmbeddedFont> fonts;
int level = 0;
bool result = false;
bool style = false;
OpenedTagType openedTag = OpenedTagType::Other;
SvgNode* currentGraphicsNode = nullptr;
};

Expand Down