diff --git a/src/loaders/svg/tvgSvgLoader.cpp b/src/loaders/svg/tvgSvgLoader.cpp index 15ab7fd7..1d8c5508 100644 --- a/src/loaders/svg/tvgSvgLoader.cpp +++ b/src/loaders/svg/tvgSvgLoader.cpp @@ -375,6 +375,27 @@ static char* _idFromUrl(const char* url) } +static size_t _srcFromUrl(const char* url, char*& src) +{ + src = (char*)strchr(url, '('); + auto close = strchr(url, ')'); + if (!src || !close || src >= close) return 0; + + src = strchr(src, '\''); + if (!src || src >= close) return 0; + ++src; + + close = strchr(src, '\''); + if (!close || close == src) return 0; + --close; + + while (src < close && *src == ' ') ++src; + while (src < close && *close == ' ') --close; + + return close - src + 1; +} + + static unsigned char _parseColor(const char* value, char** end) { float r; @@ -2082,6 +2103,42 @@ static SvgNode* _createImageNode(SvgLoaderData* loader, SvgNode* parent, const c } +static char* _unquote(const char* str) +{ + auto len = str ? strlen(str) : 0; + if (len >= 2 && str[0] == '\'' && str[len - 1] == '\'') return duplicate(str + 1, len - 2); + return strdup(str); +} + + +static bool _attrParseFontFace(void* data, const char* key, const char* value) +{ + if (!key || !value) return false; + + key = _skipSpace(key, nullptr); + value = _skipSpace(value, nullptr); + + auto loader = (SvgLoaderData*)data; + auto& font = loader->fonts.last(); + + if (STR_AS(key, "font-family")) { + if (font.name) tvg::free(font.name); + font.name = _unquote(value); + } else if (STR_AS(key, "src")) { + font.srcLen = _srcFromUrl(value, font.src); + } + + return true; +} + + +static void _createFontFace(SvgLoaderData* loader, const char* buf, unsigned bufLength, parseAttributes func) +{ + loader->fonts.push(FontFace()); + func(buf, bufLength, _attrParseFontFace, loader); +} + + static SvgNode* _getDefsNode(SvgNode* node) { if (!node) return nullptr; @@ -3518,6 +3575,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 (STR_AS(tag, "all")) { if ((node = _createCssStyleNode(loader, loader->cssStyle, attrs, attrsLength, simpleXmlParseW3CAttribute))) node->id = _copyId(name); + } else if (STR_AS(tag, "@font-face")) { //css at-rule specifying font + _createFontFace(loader, attrs, attrsLength, simpleXmlParseW3CAttribute); } else if (!isIgnoreUnsupportedLogElements(tag)) { TVGLOG("SVG", "Unsupported elements used in the internal CSS style sheets [Elements: %s]", tag); } @@ -3871,6 +3930,13 @@ void SvgLoader::clear(bool all) ARRAY_FOREACH(p, loaderData.images) tvg::free(*p); loaderData.images.reset(); + ARRAY_FOREACH(p, loaderData.fonts) { + Text::unload(p->name); + tvg::free(p->decoded); + tvg::free(p->name); + } + loaderData.fonts.reset(); + if (copy) tvg::free((char*)content); delete(root); diff --git a/src/loaders/svg/tvgSvgLoaderCommon.h b/src/loaders/svg/tvgSvgLoaderCommon.h index 1e3cd57a..ce461b03 100644 --- a/src/loaders/svg/tvgSvgLoaderCommon.h +++ b/src/loaders/svg/tvgSvgLoaderCommon.h @@ -568,6 +568,14 @@ struct SvgNodeIdPair char *id; }; +struct FontFace +{ + char* name = nullptr; + char* src = nullptr; + size_t srcLen = 0; + char* decoded = nullptr; +}; + enum class OpenedTagType : uint8_t { Other = 0, @@ -587,6 +595,7 @@ struct SvgLoaderData Array cloneNodes; Array nodesToStyle; Array images; //embedded images + Array fonts; int level = 0; bool result = false; OpenedTagType openedTag = OpenedTagType::Other; diff --git a/src/loaders/svg/tvgSvgSceneBuilder.cpp b/src/loaders/svg/tvgSvgSceneBuilder.cpp index 9a5b6854..bd2a4131 100644 --- a/src/loaders/svg/tvgSvgSceneBuilder.cpp +++ b/src/loaders/svg/tvgSvgSceneBuilder.cpp @@ -947,6 +947,43 @@ static void _updateInvalidViewSize(Scene* scene, Box& vBox, float& w, float& h, if (!validHeight) h *= vBox.h; } + +static void _loadFonts(Array& fonts) +{ + if (fonts.empty()) return; + + static constexpr struct { + const char* prefix; + size_t len; + } prefixes[] = { + {"data:font/ttf;base64,", sizeof("data:font/ttf;base64,") - 1}, + {"data:application/font-ttf;base64,", sizeof("data:application/font-ttf;base64,") - 1} + }; + + + ARRAY_FOREACH(p, fonts) { + if (!p->name) continue; + + size_t shift = 0; + for (const auto& prefix : prefixes) { + if (p->srcLen > prefix.len && !memcmp(p->src, prefix.prefix, prefix.len)) { + shift = prefix.len; + break; + } + } + if (shift == 0) { + TVGLOG("SVG", "The embedded font \"%s\" data not loaded properly.", p->name); + continue; + } + + auto size = b64Decode(p->src + shift, p->srcLen - shift, &p->decoded); + + TaskScheduler::async(false); + if (Text::load(p->name, p->decoded, size) != Result::Success) TVGERR("SVG", "Error while loading the ttf font named \"%s\".", p->name); + TaskScheduler::async(true); + } +} + /************************************************************************/ /* External Class Implementation */ /************************************************************************/ @@ -957,6 +994,8 @@ Scene* svgSceneBuild(SvgLoaderData& loaderData, Box vBox, float w, float h, Aspe if (!loaderData.doc || (loaderData.doc->type != SvgNodeType::Doc)) return nullptr; + _loadFonts(loaderData.fonts); + auto docNode = _sceneBuildHelper(loaderData, loaderData.doc, vBox, svgPath, false, 0); if (!(viewFlag & SvgViewFlag::Viewbox)) _updateInvalidViewSize(docNode, vBox, w, h, viewFlag); diff --git a/src/loaders/svg/tvgXmlParser.cpp b/src/loaders/svg/tvgXmlParser.cpp index 3492e39a..ce9902bf 100644 --- a/src/loaders/svg/tvgXmlParser.cpp +++ b/src/loaders/svg/tvgXmlParser.cpp @@ -473,6 +473,14 @@ bool simpleXmlParseW3CAttribute(const char* buf, unsigned bufLength, simpleXMLAt do { char* sep = (char*)strchr(buf, ':'); next = (char*)strchr(buf, ';'); + + if (auto src = strstr(buf, "src")) {//src tag from css font-face contains extra semicolon + if (src < sep) { + if (next + 1 < end) next = (char*)strchr(next + 1, ';'); + else return true; + } + } + if (sep >= end) { next = nullptr; sep = nullptr;