mirror of
https://github.com/thorvg/thorvg.git
synced 2025-06-07 21:23:32 +00:00
svg_loader: handle embedded fonts
It is possible to embed fonts directly into an SVG file. Support for parsing and loading embedded fonts has been added. @Issue: https://github.com/thorvg/thorvg/issues/1897
This commit is contained in:
parent
f1e9ce0460
commit
468a1db1fa
4 changed files with 122 additions and 0 deletions
|
@ -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);
|
||||
|
|
|
@ -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<SvgNodeIdPair> cloneNodes;
|
||||
Array<SvgNodeIdPair> nodesToStyle;
|
||||
Array<char*> images; //embedded images
|
||||
Array<FontFace> fonts;
|
||||
int level = 0;
|
||||
bool result = false;
|
||||
OpenedTagType openedTag = OpenedTagType::Other;
|
||||
|
|
|
@ -947,6 +947,43 @@ static void _updateInvalidViewSize(Scene* scene, Box& vBox, float& w, float& h,
|
|||
if (!validHeight) h *= vBox.h;
|
||||
}
|
||||
|
||||
|
||||
static void _loadFonts(Array<FontFace>& 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);
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Add table
Reference in a new issue