소스 검색

Implement SVG in OT support.

bruvzg 3 년 전
부모
커밋
d77902f33a

+ 47 - 22
core/io/xml_parser.cpp

@@ -41,11 +41,11 @@ static inline bool _is_white_space(char c) {
 }
 
 //! sets the state that text was found. Returns true if set should be set
-bool XMLParser::_set_text(char *start, char *end) {
+bool XMLParser::_set_text(const char *start, const char *end) {
 	// check if text is more than 2 characters, and if not, check if there is
 	// only white space, so that this text won't be reported
 	if (end - start < 3) {
-		char *p = start;
+		const char *p = start;
 		for (; p != end; ++p) {
 			if (!_is_white_space(*p)) {
 				break;
@@ -92,7 +92,7 @@ void XMLParser::_parse_closing_xml_element() {
 void XMLParser::_ignore_definition() {
 	node_type = NODE_UNKNOWN;
 
-	char *F = P;
+	const char *F = P;
 	// move until end marked with '>' reached
 	while (*P && *P != '>') {
 		next_char();
@@ -123,8 +123,8 @@ bool XMLParser::_parse_cdata() {
 		return true;
 	}
 
-	char *cDataBegin = P;
-	char *cDataEnd = nullptr;
+	const char *cDataBegin = P;
+	const char *cDataEnd = nullptr;
 
 	// find end of CDATA
 	while (*P && !cDataEnd) {
@@ -152,9 +152,9 @@ void XMLParser::_parse_comment() {
 	node_type = NODE_COMMENT;
 	P += 1;
 
-	char *pEndOfInput = data + length;
-	char *pCommentBegin;
-	char *pCommentEnd;
+	const char *pEndOfInput = data + length;
+	const char *pCommentBegin;
+	const char *pCommentEnd;
 
 	if (P + 1 < pEndOfInput && P[0] == '-' && P[1] == '-') {
 		// Comment, use '-->' as end.
@@ -293,7 +293,7 @@ void XMLParser::_parse_opening_xml_element() {
 }
 
 void XMLParser::_parse_current_node() {
-	char *start = P;
+	const char *start = P;
 	node_offset = P - data;
 
 	// more forward until '<' found
@@ -458,15 +458,36 @@ bool XMLParser::is_empty() const {
 Error XMLParser::open_buffer(const Vector<uint8_t> &p_buffer) {
 	ERR_FAIL_COND_V(p_buffer.size() == 0, ERR_INVALID_DATA);
 
-	if (data) {
-		memdelete_arr(data);
+	if (data_copy) {
+		memdelete_arr(data_copy);
+		data_copy = nullptr;
 	}
 
 	length = p_buffer.size();
-	data = memnew_arr(char, length + 1);
-	memcpy(data, p_buffer.ptr(), length);
-	data[length] = 0;
+	data_copy = memnew_arr(char, length + 1);
+	memcpy(data_copy, p_buffer.ptr(), length);
+	data_copy[length] = 0;
+	data = data_copy;
+	P = data;
+	current_line = 0;
+
+	return OK;
+}
+
+Error XMLParser::_open_buffer(const uint8_t *p_buffer, size_t p_size) {
+	ERR_FAIL_COND_V(p_size == 0, ERR_INVALID_DATA);
+	ERR_FAIL_COND_V(!p_buffer, ERR_INVALID_DATA);
+
+	if (data_copy) {
+		memdelete_arr(data_copy);
+		data_copy = nullptr;
+	}
+
+	length = p_size;
+	data = (const char *)p_buffer;
 	P = data;
+	current_line = 0;
+
 	return OK;
 }
 
@@ -479,13 +500,15 @@ Error XMLParser::open(const String &p_path) {
 	length = file->get_length();
 	ERR_FAIL_COND_V(length < 1, ERR_FILE_CORRUPT);
 
-	if (data) {
-		memdelete_arr(data);
+	if (data_copy) {
+		memdelete_arr(data_copy);
+		data_copy = nullptr;
 	}
 
-	data = memnew_arr(char, length + 1);
-	file->get_buffer((uint8_t *)data, length);
-	data[length] = 0;
+	data_copy = memnew_arr(char, length + 1);
+	file->get_buffer((uint8_t *)data_copy, length);
+	data_copy[length] = 0;
+	data = data_copy;
 	P = data;
 	current_line = 0;
 
@@ -512,8 +535,9 @@ void XMLParser::skip_section() {
 }
 
 void XMLParser::close() {
-	if (data) {
+	if (data_copy) {
 		memdelete_arr(data);
+		data_copy = nullptr;
 	}
 	data = nullptr;
 	length = 0;
@@ -528,7 +552,8 @@ int XMLParser::get_current_line() const {
 }
 
 XMLParser::~XMLParser() {
-	if (data) {
-		memdelete_arr(data);
+	if (data_copy) {
+		memdelete_arr(data_copy);
+		data_copy = nullptr;
 	}
 }

+ 5 - 3
core/io/xml_parser.h

@@ -65,8 +65,9 @@ public:
 	};
 
 private:
-	char *data = nullptr;
-	char *P = nullptr;
+	char *data_copy = nullptr;
+	const char *data = nullptr;
+	const char *P = nullptr;
 	uint64_t length = 0;
 	uint64_t current_line = 0;
 	String node_name;
@@ -81,7 +82,7 @@ private:
 
 	Vector<Attribute> attributes;
 
-	bool _set_text(char *start, char *end);
+	bool _set_text(const char *start, const char *end);
 	void _parse_closing_xml_element();
 	void _ignore_definition();
 	bool _parse_cdata();
@@ -118,6 +119,7 @@ public:
 
 	Error open(const String &p_path);
 	Error open_buffer(const Vector<uint8_t> &p_buffer);
+	Error _open_buffer(const uint8_t *p_buffer, size_t p_size);
 
 	void close();
 

+ 3 - 0
modules/text_server_adv/SCsub

@@ -39,6 +39,9 @@ thirdparty_obj = []
 freetype_enabled = "freetype" in env.module_list
 msdfgen_enabled = "msdfgen" in env.module_list
 
+if "svg" in env.module_list:
+    env_text_server_adv.Prepend(CPPPATH=["#thirdparty/thorvg/inc", "#thirdparty/thorvg/src/lib"])
+
 if env["builtin_harfbuzz"]:
     env_harfbuzz = env_modules.Clone()
     env_harfbuzz.disable_warnings()

+ 74 - 0
modules/text_server_adv/gdextension_build/SConstruct

@@ -23,6 +23,7 @@ opts.Add(BoolVariable("brotli_enabled", "Use Brotli library", True))
 opts.Add(BoolVariable("freetype_enabled", "Use FreeType library", True))
 opts.Add(BoolVariable("msdfgen_enabled", "Use MSDFgen library (require FreeType)", True))
 opts.Add(BoolVariable("graphite_enabled", "Use Graphite library (require FreeType)", True))
+opts.Add(BoolVariable("thorvg_enabled", "Use ThorVG library (require FreeType)", True))
 opts.Add(BoolVariable("static_icu_data", "Use built-in ICU data", True))
 opts.Add(BoolVariable("verbose", "Enable verbose output for the compilation", False))
 
@@ -34,6 +35,79 @@ if not env["verbose"]:
 if env["platform"] == "windows" and not env["use_mingw"]:
     env.AppendUnique(CCFLAGS=["/utf-8"])  # Force to use Unicode encoding.
 
+# ThorVG
+if env["thorvg_enabled"] and env["freetype_enabled"]:
+    env_tvg = env.Clone()
+    env_tvg.disable_warnings()
+
+    thirdparty_tvg_dir = "../../../thirdparty/thorvg/"
+    thirdparty_tvg_sources = [
+        "src/lib/sw_engine/tvgSwFill.cpp",
+        "src/lib/sw_engine/tvgSwImage.cpp",
+        "src/lib/sw_engine/tvgSwMath.cpp",
+        "src/lib/sw_engine/tvgSwMemPool.cpp",
+        "src/lib/sw_engine/tvgSwRaster.cpp",
+        "src/lib/sw_engine/tvgSwRenderer.cpp",
+        "src/lib/sw_engine/tvgSwRle.cpp",
+        "src/lib/sw_engine/tvgSwShape.cpp",
+        "src/lib/sw_engine/tvgSwStroke.cpp",
+        "src/lib/tvgAccessor.cpp",
+        "src/lib/tvgBezier.cpp",
+        "src/lib/tvgCanvas.cpp",
+        "src/lib/tvgFill.cpp",
+        "src/lib/tvgGlCanvas.cpp",
+        "src/lib/tvgInitializer.cpp",
+        "src/lib/tvgLinearGradient.cpp",
+        "src/lib/tvgLoader.cpp",
+        "src/lib/tvgLzw.cpp",
+        "src/lib/tvgPaint.cpp",
+        "src/lib/tvgPicture.cpp",
+        "src/lib/tvgRadialGradient.cpp",
+        "src/lib/tvgRender.cpp",
+        "src/lib/tvgSaver.cpp",
+        "src/lib/tvgScene.cpp",
+        "src/lib/tvgShape.cpp",
+        "src/lib/tvgSwCanvas.cpp",
+        "src/lib/tvgTaskScheduler.cpp",
+        "src/loaders/external_png/tvgPngLoader.cpp",
+        "src/loaders/jpg/tvgJpgd.cpp",
+        "src/loaders/jpg/tvgJpgLoader.cpp",
+        "src/loaders/raw/tvgRawLoader.cpp",
+        "src/loaders/svg/tvgSvgCssStyle.cpp",
+        "src/loaders/svg/tvgSvgLoader.cpp",
+        "src/loaders/svg/tvgSvgPath.cpp",
+        "src/loaders/svg/tvgSvgSceneBuilder.cpp",
+        "src/loaders/svg/tvgSvgUtil.cpp",
+        "src/loaders/svg/tvgXmlParser.cpp",
+        "src/loaders/tvg/tvgTvgBinInterpreter.cpp",
+        "src/loaders/tvg/tvgTvgLoader.cpp",
+        "src/savers/tvg/tvgTvgSaver.cpp",
+    ]
+    thirdparty_tvg_sources = [thirdparty_tvg_dir + file for file in thirdparty_tvg_sources]
+
+    env_tvg.Append(
+        CPPPATH=[
+            "../../../thirdparty/thorvg/inc",
+            "../../../thirdparty/thorvg/src/lib",
+            "../../../thirdparty/thorvg/src/lib/sw_engine",
+            "../../../thirdparty/thorvg/src/loaders/external_png",
+            "../../../thirdparty/thorvg/src/loaders/jpg",
+            "../../../thirdparty/thorvg/src/loaders/raw",
+            "../../../thirdparty/thorvg/src/loaders/svg",
+            "../../../thirdparty/thorvg/src/loaders/tvg",
+            "../../../thirdparty/thorvg/src/savers/tvg",
+            "../../../thirdparty/libpng",
+        ]
+    )
+    env.Append(CPPPATH=["../../../thirdparty/thorvg/inc", "../../../thirdparty/thorvg/src/lib"])
+    env.Append(CPPDEFINES=["MODULE_SVG_ENABLED"])
+
+    lib = env_tvg.Library(
+        f'tvg_builtin{env["suffix"]}{env["LIBSUFFIX"]}',
+        thirdparty_tvg_sources,
+    )
+    env.Append(LIBS=[lib])
+
 # MSDFGEN
 if env["msdfgen_enabled"] and env["freetype_enabled"]:
     env_msdfgen = env.Clone()

+ 13 - 1
modules/text_server_adv/text_server_adv.cpp

@@ -41,6 +41,8 @@
 
 using namespace godot;
 
+#define GLOBAL_GET(m_var) ProjectSettings::get_singleton()->get(m_var)
+
 #else
 // Headers for building as built-in module.
 
@@ -50,7 +52,7 @@ using namespace godot;
 #include "core/string/print_string.h"
 #include "core/string/translation.h"
 
-#include "modules/modules_enabled.gen.h" // For freetype, msdfgen.
+#include "modules/modules_enabled.gen.h" // For freetype, msdfgen, svg.
 
 #endif
 
@@ -69,6 +71,10 @@ using namespace godot;
 #include "msdfgen.h"
 #endif
 
+#ifdef MODULE_SVG_ENABLED
+#include "thorvg_svg_in_ot.h"
+#endif
+
 /*************************************************************************/
 /*  bmp_font_t HarfBuzz Bitmap font interface                            */
 /*************************************************************************/
@@ -1346,6 +1352,9 @@ _FORCE_INLINE_ bool TextServerAdvanced::_ensure_cache_for_size(FontAdvanced *p_f
 				memdelete(fd);
 				ERR_FAIL_V_MSG(false, "FreeType: Error initializing library: '" + String(FT_Error_String(error)) + "'.");
 			}
+#ifdef MODULE_SVG_ENABLED
+			FT_Property_Set(ft_library, "ot-svg", "svg-hooks", get_tvg_svg_in_ot_hooks());
+#endif
 		}
 
 		memset(&fd->stream, 0, sizeof(FT_StreamRec));
@@ -1888,6 +1897,9 @@ int64_t TextServerAdvanced::_font_get_face_count(const RID &p_font_rid) const {
 		if (!ft_library) {
 			error = FT_Init_FreeType(&ft_library);
 			ERR_FAIL_COND_V_MSG(error != 0, false, "FreeType: Error initializing library: '" + String(FT_Error_String(error)) + "'.");
+#ifdef MODULE_SVG_ENABLED
+			FT_Property_Set(ft_library, "ot-svg", "svg-hooks", get_tvg_svg_in_ot_hooks());
+#endif
 		}
 
 		FT_StreamRec stream;

+ 2 - 1
modules/text_server_adv/text_server_adv.h

@@ -88,7 +88,7 @@ using namespace godot;
 #include "core/templates/rid_owner.h"
 #include "scene/resources/texture.h"
 
-#include "modules/modules_enabled.gen.h" // For freetype, msdfgen.
+#include "modules/modules_enabled.gen.h" // For freetype, msdfgen, svg.
 
 #endif
 
@@ -117,6 +117,7 @@ using namespace godot;
 #include FT_ADVANCES_H
 #include FT_MULTIPLE_MASTERS_H
 #include FT_BBOX_H
+#include FT_MODULE_H
 #include FT_CONFIG_OPTIONS_H
 #if !defined(FT_CONFIG_OPTION_USE_BROTLI) && !defined(_MSC_VER)
 #warning FreeType is configured without Brotli support, built-in fonts will not be available.

+ 70 - 0
modules/text_server_adv/thorvg_bounds_iterator.cpp

@@ -0,0 +1,70 @@
+/*************************************************************************/
+/*  thorvg_bounds_iterator.cpp                                           */
+/*************************************************************************/
+/*                       This file is part of:                           */
+/*                           GODOT ENGINE                                */
+/*                      https://godotengine.org                          */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur.                 */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md).   */
+/*                                                                       */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the       */
+/* "Software"), to deal in the Software without restriction, including   */
+/* without limitation the rights to use, copy, modify, merge, publish,   */
+/* distribute, sublicense, and/or sell copies of the Software, and to    */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions:                                             */
+/*                                                                       */
+/* The above copyright notice and this permission notice shall be        */
+/* included in all copies or substantial portions of the Software.       */
+/*                                                                       */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
+/*************************************************************************/
+
+#ifdef GDEXTENSION
+// Headers for building as GDExtension plug-in.
+
+#include <godot_cpp/godot.hpp>
+
+using namespace godot;
+
+#else
+// Headers for building as built-in module.
+
+#include "core/typedefs.h"
+
+#include "modules/modules_enabled.gen.h" // For svg.
+#endif
+
+#ifdef MODULE_SVG_ENABLED
+
+#include "thorvg_bounds_iterator.h"
+
+#include <tvgIteratorAccessor.h>
+#include <tvgPaint.h>
+
+// This function uses private ThorVG API to get bounding box of top level children elements.
+
+void tvg_get_bounds(tvg::Picture *p_picture, float &r_min_x, float &r_min_y, float &r_max_x, float &r_max_y) {
+	tvg::IteratorAccessor itrAccessor;
+	if (tvg::Iterator *it = itrAccessor.iterator(p_picture)) {
+		while (const tvg::Paint *child = it->next()) {
+			float x = 0, y = 0, w = 0, h = 0;
+			child->bounds(&x, &y, &w, &h, true);
+			r_min_x = MIN(x, r_min_x);
+			r_min_y = MIN(y, r_min_y);
+			r_max_x = MAX(x + w, r_max_x);
+			r_max_y = MAX(y + h, r_max_y);
+		}
+		delete (it);
+	}
+}
+
+#endif // MODULE_SVG_ENABLED

+ 58 - 0
modules/text_server_adv/thorvg_bounds_iterator.h

@@ -0,0 +1,58 @@
+/*************************************************************************/
+/*  thorvg_bounds_iterator.h                                             */
+/*************************************************************************/
+/*                       This file is part of:                           */
+/*                           GODOT ENGINE                                */
+/*                      https://godotengine.org                          */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur.                 */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md).   */
+/*                                                                       */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the       */
+/* "Software"), to deal in the Software without restriction, including   */
+/* without limitation the rights to use, copy, modify, merge, publish,   */
+/* distribute, sublicense, and/or sell copies of the Software, and to    */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions:                                             */
+/*                                                                       */
+/* The above copyright notice and this permission notice shall be        */
+/* included in all copies or substantial portions of the Software.       */
+/*                                                                       */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
+/*************************************************************************/
+
+#ifndef THORVG_BOUNDS_ITERATOR_H
+#define THORVG_BOUNDS_ITERATOR_H
+
+#ifdef GDEXTENSION
+// Headers for building as GDExtension plug-in.
+
+#include <godot_cpp/core/mutex_lock.hpp>
+#include <godot_cpp/godot.hpp>
+
+using namespace godot;
+
+#else
+// Headers for building as built-in module.
+
+#include "core/typedefs.h"
+
+#include "modules/modules_enabled.gen.h" // For svg.
+#endif
+
+#ifdef MODULE_SVG_ENABLED
+
+#include <thorvg.h>
+
+void tvg_get_bounds(tvg::Picture *p_picture, float &r_min_x, float &r_min_y, float &r_max_x, float &r_max_y);
+
+#endif // MODULE_SVG_ENABLED
+
+#endif // THORVG_BOUNDS_ITERATOR_H

+ 320 - 0
modules/text_server_adv/thorvg_svg_in_ot.cpp

@@ -0,0 +1,320 @@
+/*************************************************************************/
+/*  thorvg_svg_in_ot.cpp                                                 */
+/*************************************************************************/
+/*                       This file is part of:                           */
+/*                           GODOT ENGINE                                */
+/*                      https://godotengine.org                          */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur.                 */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md).   */
+/*                                                                       */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the       */
+/* "Software"), to deal in the Software without restriction, including   */
+/* without limitation the rights to use, copy, modify, merge, publish,   */
+/* distribute, sublicense, and/or sell copies of the Software, and to    */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions:                                             */
+/*                                                                       */
+/* The above copyright notice and this permission notice shall be        */
+/* included in all copies or substantial portions of the Software.       */
+/*                                                                       */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
+/*************************************************************************/
+
+#ifdef GDEXTENSION
+// Headers for building as GDExtension plug-in.
+
+#include <godot_cpp/classes/xml_parser.hpp>
+#include <godot_cpp/core/mutex_lock.hpp>
+#include <godot_cpp/godot.hpp>
+#include <godot_cpp/templates/vector.hpp>
+
+using namespace godot;
+
+#else
+// Headers for building as built-in module.
+
+#include "core/error/error_macros.h"
+#include "core/io/xml_parser.h"
+#include "core/os/memory.h"
+#include "core/os/os.h"
+#include "core/string/ustring.h"
+#include "core/typedefs.h"
+#include "core/variant/variant.h"
+
+#include "modules/modules_enabled.gen.h" // For svg.
+#endif
+
+#ifdef MODULE_SVG_ENABLED
+
+#include "thorvg_bounds_iterator.h"
+#include "thorvg_svg_in_ot.h"
+
+#include <freetype/otsvg.h>
+#include <ft2build.h>
+
+#include <math.h>
+#include <stdlib.h>
+
+FT_Error tvg_svg_in_ot_init(FT_Pointer *p_state) {
+	*p_state = memnew(TVG_State);
+
+	return FT_Err_Ok;
+}
+
+void tvg_svg_in_ot_free(FT_Pointer *p_state) {
+	TVG_State *state = *reinterpret_cast<TVG_State **>(p_state);
+	memdelete(state);
+}
+
+FT_Error tvg_svg_in_ot_preset_slot(FT_GlyphSlot p_slot, FT_Bool p_cache, FT_Pointer *p_state) {
+	TVG_State *state = *reinterpret_cast<TVG_State **>(p_state);
+	if (!state) {
+		ERR_FAIL_V_MSG(FT_Err_Invalid_SVG_Document, "SVG in OT state not initialized.");
+	}
+	MutexLock lock(state->mutex);
+
+	FT_SVG_Document document = (FT_SVG_Document)p_slot->other;
+	FT_Size_Metrics metrics = document->metrics;
+
+	GL_State &gl_state = state->glyph_map[p_slot->glyph_index];
+	if (!gl_state.ready) {
+		Ref<XMLParser> parser;
+		parser.instantiate();
+#ifdef GDEXTENSION
+		PackedByteArray data;
+		data.resize(document->svg_document_length);
+		memcpy(data.ptrw(), document->svg_document, document->svg_document_length);
+		parser->open_buffer(data);
+#else
+		parser->_open_buffer((const uint8_t *)document->svg_document, document->svg_document_length);
+#endif
+
+		float aspect = 1.0f;
+		String xml_body;
+		while (parser->read() == OK) {
+			if (parser->has_attribute("id")) {
+#ifdef GDEXTENSION
+				const String &gl_name = parser->get_named_attribute_value("id");
+#else
+				const String &gl_name = parser->get_attribute_value("id");
+#endif
+				if (gl_name.begins_with("glyph")) {
+					int dot_pos = gl_name.find(".");
+					int64_t gl_idx = gl_name.substr(5, (dot_pos > 0) ? dot_pos - 5 : -1).to_int();
+					if (p_slot->glyph_index != gl_idx) {
+						parser->skip_section();
+						continue;
+					}
+				}
+			}
+			if (parser->get_node_type() == XMLParser::NODE_ELEMENT && parser->get_node_name() == "svg") {
+				if (parser->has_attribute("viewBox")) {
+#ifdef GDEXTENSION
+					PackedStringArray vb = parser->get_named_attribute_value("viewBox").split(" ");
+#else
+					Vector<String> vb = parser->get_attribute_value("viewBox").split(" ");
+#endif
+
+					if (vb.size() == 4) {
+						aspect = vb[2].to_float() / vb[3].to_float();
+					}
+				}
+				continue;
+			}
+#ifdef GDEXTENSION
+			if (parser->get_node_type() == XMLParser::NODE_ELEMENT) {
+				xml_body = xml_body + "<" + parser->get_node_name();
+				for (int i = 0; i < parser->get_attribute_count(); i++) {
+					xml_body = xml_body + " " + parser->get_attribute_name(i) + "=\"" + parser->get_attribute_value(i) + "\"";
+				}
+				xml_body = xml_body + ">";
+			} else if (parser->get_node_type() == XMLParser::NODE_TEXT) {
+				xml_body = xml_body + parser->get_node_data();
+			} else if (parser->get_node_type() == XMLParser::NODE_ELEMENT_END) {
+				xml_body = xml_body + "</" + parser->get_node_name() + ">";
+			}
+#else
+			if (parser->get_node_type() == XMLParser::NODE_ELEMENT) {
+				xml_body += vformat("<%s", parser->get_node_name());
+				for (int i = 0; i < parser->get_attribute_count(); i++) {
+					xml_body += vformat(" %s=\"%s\"", parser->get_attribute_name(i), parser->get_attribute_value(i));
+				}
+				xml_body += ">";
+			} else if (parser->get_node_type() == XMLParser::NODE_TEXT) {
+				xml_body += parser->get_node_data();
+			} else if (parser->get_node_type() == XMLParser::NODE_ELEMENT_END) {
+				xml_body += vformat("</%s>", parser->get_node_name());
+			}
+#endif
+		}
+		String temp_xml = "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 0 0\">" + xml_body;
+
+		std::unique_ptr<tvg::Picture> picture = tvg::Picture::gen();
+		tvg::Result result = picture->load(temp_xml.utf8().get_data(), temp_xml.utf8().length(), "svg+xml", false);
+		if (result != tvg::Result::Success) {
+			ERR_FAIL_V_MSG(FT_Err_Invalid_SVG_Document, "Failed to load SVG document (bounds detection).");
+		}
+
+		float min_x = INFINITY, min_y = INFINITY, max_x = -INFINITY, max_y = -INFINITY;
+		tvg_get_bounds(picture.get(), min_x, min_y, max_x, max_y);
+
+		float new_h = (max_y - min_y);
+		float new_w = (max_x - min_x);
+
+		if (new_h * aspect >= new_w) {
+			new_w = (new_h * aspect);
+		} else {
+			new_h = (new_w / aspect);
+		}
+
+#ifdef GDEXTENSION
+		gl_state.xml_code = "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"" + rtos(min_x) + " " + rtos(min_y) + " " + rtos(new_w) + " " + rtos(new_h) + "\">" + xml_body;
+#else
+		gl_state.xml_code = vformat("<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"%f %f %f %f\">", min_x, min_y, new_w, new_h) + xml_body;
+#endif
+
+		picture = tvg::Picture::gen();
+		result = picture->load(gl_state.xml_code.utf8().get_data(), gl_state.xml_code.utf8().length(), "svg+xml", false);
+		if (result != tvg::Result::Success) {
+			ERR_FAIL_V_MSG(FT_Err_Invalid_SVG_Document, "Failed to load SVG document (glyph metrics).");
+		}
+
+		float x_svg_to_out, y_svg_to_out;
+		x_svg_to_out = (float)metrics.x_ppem / new_w;
+		y_svg_to_out = (float)metrics.y_ppem / new_h;
+
+		gl_state.m.e11 = (double)document->transform.xx / (1 << 16) * x_svg_to_out;
+		gl_state.m.e12 = -(double)document->transform.xy / (1 << 16) * x_svg_to_out;
+		gl_state.m.e21 = -(double)document->transform.yx / (1 << 16) * y_svg_to_out;
+		gl_state.m.e22 = (double)document->transform.yy / (1 << 16) * y_svg_to_out;
+		gl_state.m.e13 = (double)document->delta.x / 64 * new_w / metrics.x_ppem;
+		gl_state.m.e23 = -(double)document->delta.y / 64 * new_h / metrics.y_ppem;
+		gl_state.m.e31 = 0;
+		gl_state.m.e32 = 0;
+		gl_state.m.e33 = 1;
+
+		result = picture->transform(gl_state.m);
+		if (result != tvg::Result::Success) {
+			ERR_FAIL_V_MSG(FT_Err_Invalid_SVG_Document, "Failed to apply transform to SVG document.");
+		}
+
+		result = picture->bounds(&gl_state.x, &gl_state.y, &gl_state.w, &gl_state.h, true);
+		if (result != tvg::Result::Success) {
+			ERR_FAIL_V_MSG(FT_Err_Invalid_SVG_Document, "Failed to get SVG bounds.");
+		}
+
+		gl_state.bmp_y = -min_y * gl_state.h / new_h;
+		gl_state.bmp_x = min_x * gl_state.w / new_w;
+
+		gl_state.ready = true;
+	}
+
+	p_slot->bitmap_left = (FT_Int)gl_state.bmp_x;
+	p_slot->bitmap_top = (FT_Int)gl_state.bmp_y;
+
+	float tmp = ceil(gl_state.h);
+	p_slot->bitmap.rows = (unsigned int)tmp;
+	tmp = ceil(gl_state.w);
+	p_slot->bitmap.width = (unsigned int)tmp;
+	p_slot->bitmap.pitch = (int)p_slot->bitmap.width * 4;
+	p_slot->bitmap.pixel_mode = FT_PIXEL_MODE_BGRA;
+
+	float metrics_width, metrics_height;
+	float horiBearingX, horiBearingY;
+	float vertBearingX, vertBearingY;
+
+	metrics_width = (float)gl_state.w;
+	metrics_height = (float)gl_state.h;
+	horiBearingX = (float)gl_state.x;
+	horiBearingY = (float)-gl_state.y;
+	vertBearingX = p_slot->metrics.horiBearingX / 64.0f - p_slot->metrics.horiAdvance / 64.0f / 2;
+	vertBearingY = (p_slot->metrics.vertAdvance / 64.0f - p_slot->metrics.height / 64.0f) / 2;
+
+	tmp = roundf(metrics_width * 64);
+	p_slot->metrics.width = (FT_Pos)tmp;
+	tmp = roundf(metrics_height * 64);
+	p_slot->metrics.height = (FT_Pos)tmp;
+
+	p_slot->metrics.horiBearingX = (FT_Pos)(horiBearingX * 64);
+	p_slot->metrics.horiBearingY = (FT_Pos)(horiBearingY * 64);
+	p_slot->metrics.vertBearingX = (FT_Pos)(vertBearingX * 64);
+	p_slot->metrics.vertBearingY = (FT_Pos)(vertBearingY * 64);
+
+	if (p_slot->metrics.vertAdvance == 0) {
+		p_slot->metrics.vertAdvance = (FT_Pos)(metrics_height * 1.2f * 64);
+	}
+
+	return FT_Err_Ok;
+}
+
+FT_Error tvg_svg_in_ot_render(FT_GlyphSlot p_slot, FT_Pointer *p_state) {
+	TVG_State *state = *reinterpret_cast<TVG_State **>(p_state);
+	if (!state) {
+		ERR_FAIL_V_MSG(FT_Err_Invalid_SVG_Document, "SVG in OT state not initialized.");
+	}
+	MutexLock lock(state->mutex);
+
+	if (!state->glyph_map.has(p_slot->glyph_index)) {
+		ERR_FAIL_V_MSG(FT_Err_Invalid_SVG_Document, "SVG glyph not loaded.");
+	}
+
+	GL_State &gl_state = state->glyph_map[p_slot->glyph_index];
+	ERR_FAIL_COND_V_MSG(!gl_state.ready, FT_Err_Invalid_SVG_Document, "SVG glyph not ready.");
+
+	std::unique_ptr<tvg::Picture> picture = tvg::Picture::gen();
+	tvg::Result res = picture->load(gl_state.xml_code.utf8().get_data(), gl_state.xml_code.utf8().length(), "svg+xml", false);
+	if (res != tvg::Result::Success) {
+		ERR_FAIL_V_MSG(FT_Err_Invalid_SVG_Document, "Failed to load SVG document (glyph rendering).");
+	}
+	res = picture->transform(gl_state.m);
+	if (res != tvg::Result::Success) {
+		ERR_FAIL_V_MSG(FT_Err_Invalid_SVG_Document, "Failed to apply transform to SVG document.");
+	}
+
+	std::unique_ptr<tvg::SwCanvas> sw_canvas = tvg::SwCanvas::gen();
+	res = sw_canvas->target((uint32_t *)p_slot->bitmap.buffer, (int)p_slot->bitmap.width, (int)p_slot->bitmap.width, (int)p_slot->bitmap.rows, tvg::SwCanvas::ARGB8888_STRAIGHT);
+	if (res != tvg::Result::Success) {
+		ERR_FAIL_V_MSG(FT_Err_Invalid_Outline, "Failed to create SVG canvas.");
+	}
+	res = sw_canvas->push(std::move(picture));
+	if (res != tvg::Result::Success) {
+		ERR_FAIL_V_MSG(FT_Err_Invalid_Outline, "Failed to set SVG canvas source.");
+	}
+	res = sw_canvas->draw();
+	if (res != tvg::Result::Success) {
+		ERR_FAIL_V_MSG(FT_Err_Invalid_Outline, "Failed to draw to SVG canvas.");
+	}
+	res = sw_canvas->sync();
+	if (res != tvg::Result::Success) {
+		ERR_FAIL_V_MSG(FT_Err_Invalid_Outline, "Failed to sync SVG canvas.");
+	}
+
+	state->glyph_map.erase(p_slot->glyph_index);
+
+	p_slot->bitmap.pixel_mode = FT_PIXEL_MODE_BGRA;
+	p_slot->bitmap.num_grays = 256;
+	p_slot->format = FT_GLYPH_FORMAT_BITMAP;
+
+	return FT_Err_Ok;
+}
+
+SVG_RendererHooks tvg_svg_in_ot_hooks = {
+	(SVG_Lib_Init_Func)tvg_svg_in_ot_init,
+	(SVG_Lib_Free_Func)tvg_svg_in_ot_free,
+	(SVG_Lib_Render_Func)tvg_svg_in_ot_render,
+	(SVG_Lib_Preset_Slot_Func)tvg_svg_in_ot_preset_slot,
+};
+
+SVG_RendererHooks *get_tvg_svg_in_ot_hooks() {
+	return &tvg_svg_in_ot_hooks;
+}
+
+#endif // MODULE_SVG_ENABLED

+ 86 - 0
modules/text_server_adv/thorvg_svg_in_ot.h

@@ -0,0 +1,86 @@
+/*************************************************************************/
+/*  thorvg_svg_in_ot.h                                                   */
+/*************************************************************************/
+/*                       This file is part of:                           */
+/*                           GODOT ENGINE                                */
+/*                      https://godotengine.org                          */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur.                 */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md).   */
+/*                                                                       */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the       */
+/* "Software"), to deal in the Software without restriction, including   */
+/* without limitation the rights to use, copy, modify, merge, publish,   */
+/* distribute, sublicense, and/or sell copies of the Software, and to    */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions:                                             */
+/*                                                                       */
+/* The above copyright notice and this permission notice shall be        */
+/* included in all copies or substantial portions of the Software.       */
+/*                                                                       */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
+/*************************************************************************/
+
+#ifndef THORVG_SVG_IN_OT_H
+#define THORVG_SVG_IN_OT_H
+
+#ifdef GDEXTENSION
+// Headers for building as GDExtension plug-in.
+
+#include <godot_cpp/core/mutex_lock.hpp>
+#include <godot_cpp/godot.hpp>
+#include <godot_cpp/templates/hash_map.hpp>
+
+using namespace godot;
+
+#else
+// Headers for building as built-in module.
+
+#include "core/os/mutex.h"
+#include "core/templates/hash_map.h"
+#include "core/typedefs.h"
+
+#include "modules/modules_enabled.gen.h" // For svg.
+#endif
+
+#ifdef MODULE_SVG_ENABLED
+
+#include <freetype/freetype.h>
+#include <freetype/otsvg.h>
+#include <ft2build.h>
+#include <thorvg.h>
+
+struct GL_State {
+	bool ready = false;
+	float bmp_x = 0;
+	float bmp_y = 0;
+	float x = 0;
+	float y = 0;
+	float w = 0;
+	float h = 0;
+	String xml_code;
+	tvg::Matrix m;
+};
+
+struct TVG_State {
+	Mutex mutex;
+	HashMap<uint32_t, GL_State> glyph_map;
+};
+
+FT_Error tvg_svg_in_ot_init(FT_Pointer *p_state);
+void tvg_svg_in_ot_free(FT_Pointer *p_state);
+FT_Error tvg_svg_in_ot_preset_slot(FT_GlyphSlot p_slot, FT_Bool p_cache, FT_Pointer *p_state);
+FT_Error tvg_svg_in_ot_render(FT_GlyphSlot p_slot, FT_Pointer *p_state);
+
+SVG_RendererHooks *get_tvg_svg_in_ot_hooks();
+
+#endif // MODULE_SVG_ENABLED
+
+#endif // THORVG_SVG_IN_OT_H

+ 3 - 0
modules/text_server_fb/SCsub

@@ -8,6 +8,9 @@ msdfgen_enabled = "msdfgen" in env.module_list
 
 env_text_server_fb = env_modules.Clone()
 
+if "svg" in env.module_list:
+    env_text_server_fb.Prepend(CPPPATH=["#thirdparty/thorvg/inc", "#thirdparty/thorvg/src/lib"])
+
 if env["builtin_msdfgen"] and msdfgen_enabled:
     env_text_server_fb.Prepend(CPPPATH=["#thirdparty/msdfgen"])
 

+ 74 - 0
modules/text_server_fb/gdextension_build/SConstruct

@@ -22,6 +22,7 @@ opts = Variables([], ARGUMENTS)
 opts.Add(BoolVariable("brotli_enabled", "Use Brotli library", True))
 opts.Add(BoolVariable("freetype_enabled", "Use FreeType library", True))
 opts.Add(BoolVariable("msdfgen_enabled", "Use MSDFgen library (require FreeType)", True))
+opts.Add(BoolVariable("thorvg_enabled", "Use ThorVG library (require FreeType)", True))
 opts.Add(BoolVariable("verbose", "Enable verbose output for the compilation", False))
 
 opts.Update(env)
@@ -29,6 +30,79 @@ opts.Update(env)
 if not env["verbose"]:
     methods.no_verbose(sys, env)
 
+# ThorVG
+if env["thorvg_enabled"] and env["freetype_enabled"]:
+    env_tvg = env.Clone()
+    env_tvg.disable_warnings()
+
+    thirdparty_tvg_dir = "../../../thirdparty/thorvg/"
+    thirdparty_tvg_sources = [
+        "src/lib/sw_engine/tvgSwFill.cpp",
+        "src/lib/sw_engine/tvgSwImage.cpp",
+        "src/lib/sw_engine/tvgSwMath.cpp",
+        "src/lib/sw_engine/tvgSwMemPool.cpp",
+        "src/lib/sw_engine/tvgSwRaster.cpp",
+        "src/lib/sw_engine/tvgSwRenderer.cpp",
+        "src/lib/sw_engine/tvgSwRle.cpp",
+        "src/lib/sw_engine/tvgSwShape.cpp",
+        "src/lib/sw_engine/tvgSwStroke.cpp",
+        "src/lib/tvgAccessor.cpp",
+        "src/lib/tvgBezier.cpp",
+        "src/lib/tvgCanvas.cpp",
+        "src/lib/tvgFill.cpp",
+        "src/lib/tvgGlCanvas.cpp",
+        "src/lib/tvgInitializer.cpp",
+        "src/lib/tvgLinearGradient.cpp",
+        "src/lib/tvgLoader.cpp",
+        "src/lib/tvgLzw.cpp",
+        "src/lib/tvgPaint.cpp",
+        "src/lib/tvgPicture.cpp",
+        "src/lib/tvgRadialGradient.cpp",
+        "src/lib/tvgRender.cpp",
+        "src/lib/tvgSaver.cpp",
+        "src/lib/tvgScene.cpp",
+        "src/lib/tvgShape.cpp",
+        "src/lib/tvgSwCanvas.cpp",
+        "src/lib/tvgTaskScheduler.cpp",
+        "src/loaders/external_png/tvgPngLoader.cpp",
+        "src/loaders/jpg/tvgJpgd.cpp",
+        "src/loaders/jpg/tvgJpgLoader.cpp",
+        "src/loaders/raw/tvgRawLoader.cpp",
+        "src/loaders/svg/tvgSvgCssStyle.cpp",
+        "src/loaders/svg/tvgSvgLoader.cpp",
+        "src/loaders/svg/tvgSvgPath.cpp",
+        "src/loaders/svg/tvgSvgSceneBuilder.cpp",
+        "src/loaders/svg/tvgSvgUtil.cpp",
+        "src/loaders/svg/tvgXmlParser.cpp",
+        "src/loaders/tvg/tvgTvgBinInterpreter.cpp",
+        "src/loaders/tvg/tvgTvgLoader.cpp",
+        "src/savers/tvg/tvgTvgSaver.cpp",
+    ]
+    thirdparty_tvg_sources = [thirdparty_tvg_dir + file for file in thirdparty_tvg_sources]
+
+    env_tvg.Append(
+        CPPPATH=[
+            "../../../thirdparty/thorvg/inc",
+            "../../../thirdparty/thorvg/src/lib",
+            "../../../thirdparty/thorvg/src/lib/sw_engine",
+            "../../../thirdparty/thorvg/src/loaders/external_png",
+            "../../../thirdparty/thorvg/src/loaders/jpg",
+            "../../../thirdparty/thorvg/src/loaders/raw",
+            "../../../thirdparty/thorvg/src/loaders/svg",
+            "../../../thirdparty/thorvg/src/loaders/tvg",
+            "../../../thirdparty/thorvg/src/savers/tvg",
+            "../../../thirdparty/libpng",
+        ]
+    )
+    env.Append(CPPPATH=["../../../thirdparty/thorvg/inc", "../../../thirdparty/thorvg/src/lib"])
+    env.Append(CPPDEFINES=["MODULE_SVG_ENABLED"])
+
+    lib = env_tvg.Library(
+        f'tvg_builtin{env["suffix"]}{env["LIBSUFFIX"]}',
+        thirdparty_tvg_sources,
+    )
+    env.Append(LIBS=[lib])
+
 # MSDFGEN
 if env["msdfgen_enabled"] and env["freetype_enabled"]:
     env_msdfgen = env.Clone()

+ 13 - 1
modules/text_server_fb/text_server_fb.cpp

@@ -41,6 +41,8 @@
 
 using namespace godot;
 
+#define GLOBAL_GET(m_var) ProjectSettings::get_singleton()->get(m_var)
+
 #else
 // Headers for building as built-in module.
 
@@ -49,7 +51,7 @@ using namespace godot;
 #include "core/string/print_string.h"
 #include "core/string/ucaps.h"
 
-#include "modules/modules_enabled.gen.h" // For freetype, msdfgen.
+#include "modules/modules_enabled.gen.h" // For freetype, msdfgen, svg.
 
 #endif
 
@@ -62,6 +64,10 @@ using namespace godot;
 #include "msdfgen.h"
 #endif
 
+#ifdef MODULE_SVG_ENABLED
+#include "thorvg_svg_in_ot.h"
+#endif
+
 /*************************************************************************/
 
 #define OT_TAG(c1, c2, c3, c4) ((int32_t)((((uint32_t)(c1)&0xff) << 24) | (((uint32_t)(c2)&0xff) << 16) | (((uint32_t)(c3)&0xff) << 8) | ((uint32_t)(c4)&0xff)))
@@ -771,6 +777,9 @@ _FORCE_INLINE_ bool TextServerFallback::_ensure_cache_for_size(FontFallback *p_f
 				memdelete(fd);
 				ERR_FAIL_V_MSG(false, "FreeType: Error initializing library: '" + String(FT_Error_String(error)) + "'.");
 			}
+#ifdef MODULE_SVG_ENABLED
+			FT_Property_Set(ft_library, "ot-svg", "svg-hooks", get_tvg_svg_in_ot_hooks());
+#endif
 		}
 
 		memset(&fd->stream, 0, sizeof(FT_StreamRec));
@@ -992,6 +1001,9 @@ int64_t TextServerFallback::_font_get_face_count(const RID &p_font_rid) const {
 		if (!ft_library) {
 			error = FT_Init_FreeType(&ft_library);
 			ERR_FAIL_COND_V_MSG(error != 0, false, "FreeType: Error initializing library: '" + String(FT_Error_String(error)) + "'.");
+#ifdef MODULE_SVG_ENABLED
+			FT_Property_Set(ft_library, "ot-svg", "svg-hooks", get_tvg_svg_in_ot_hooks());
+#endif
 		}
 
 		FT_StreamRec stream;

+ 2 - 1
modules/text_server_fb/text_server_fb.h

@@ -87,7 +87,7 @@ using namespace godot;
 #include "core/templates/rid_owner.h"
 #include "scene/resources/texture.h"
 
-#include "modules/modules_enabled.gen.h" // For freetype, msdfgen.
+#include "modules/modules_enabled.gen.h" // For freetype, msdfgen, svg.
 
 #endif
 
@@ -101,6 +101,7 @@ using namespace godot;
 #include FT_ADVANCES_H
 #include FT_MULTIPLE_MASTERS_H
 #include FT_BBOX_H
+#include FT_MODULE_H
 #include FT_CONFIG_OPTIONS_H
 #if !defined(FT_CONFIG_OPTION_USE_BROTLI) && !defined(_MSC_VER)
 #warning FreeType is configured without Brotli support, built-in fonts will not be available.

+ 70 - 0
modules/text_server_fb/thorvg_bounds_iterator.cpp

@@ -0,0 +1,70 @@
+/*************************************************************************/
+/*  thorvg_bounds_iterator.cpp                                           */
+/*************************************************************************/
+/*                       This file is part of:                           */
+/*                           GODOT ENGINE                                */
+/*                      https://godotengine.org                          */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur.                 */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md).   */
+/*                                                                       */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the       */
+/* "Software"), to deal in the Software without restriction, including   */
+/* without limitation the rights to use, copy, modify, merge, publish,   */
+/* distribute, sublicense, and/or sell copies of the Software, and to    */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions:                                             */
+/*                                                                       */
+/* The above copyright notice and this permission notice shall be        */
+/* included in all copies or substantial portions of the Software.       */
+/*                                                                       */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
+/*************************************************************************/
+
+#ifdef GDEXTENSION
+// Headers for building as GDExtension plug-in.
+
+#include <godot_cpp/godot.hpp>
+
+using namespace godot;
+
+#else
+// Headers for building as built-in module.
+
+#include "core/typedefs.h"
+
+#include "modules/modules_enabled.gen.h" // For svg.
+#endif
+
+#ifdef MODULE_SVG_ENABLED
+
+#include "thorvg_bounds_iterator.h"
+
+#include <tvgIteratorAccessor.h>
+#include <tvgPaint.h>
+
+// This function uses private ThorVG API to get bounding box of top level children elements.
+
+void tvg_get_bounds(tvg::Picture *p_picture, float &r_min_x, float &r_min_y, float &r_max_x, float &r_max_y) {
+	tvg::IteratorAccessor itrAccessor;
+	if (tvg::Iterator *it = itrAccessor.iterator(p_picture)) {
+		while (const tvg::Paint *child = it->next()) {
+			float x = 0, y = 0, w = 0, h = 0;
+			child->bounds(&x, &y, &w, &h, true);
+			r_min_x = MIN(x, r_min_x);
+			r_min_y = MIN(y, r_min_y);
+			r_max_x = MAX(x + w, r_max_x);
+			r_max_y = MAX(y + h, r_max_y);
+		}
+		delete (it);
+	}
+}
+
+#endif // MODULE_SVG_ENABLED

+ 58 - 0
modules/text_server_fb/thorvg_bounds_iterator.h

@@ -0,0 +1,58 @@
+/*************************************************************************/
+/*  thorvg_bounds_iterator.h                                             */
+/*************************************************************************/
+/*                       This file is part of:                           */
+/*                           GODOT ENGINE                                */
+/*                      https://godotengine.org                          */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur.                 */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md).   */
+/*                                                                       */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the       */
+/* "Software"), to deal in the Software without restriction, including   */
+/* without limitation the rights to use, copy, modify, merge, publish,   */
+/* distribute, sublicense, and/or sell copies of the Software, and to    */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions:                                             */
+/*                                                                       */
+/* The above copyright notice and this permission notice shall be        */
+/* included in all copies or substantial portions of the Software.       */
+/*                                                                       */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
+/*************************************************************************/
+
+#ifndef THORVG_BOUNDS_ITERATOR_H
+#define THORVG_BOUNDS_ITERATOR_H
+
+#ifdef GDEXTENSION
+// Headers for building as GDExtension plug-in.
+
+#include <godot_cpp/core/mutex_lock.hpp>
+#include <godot_cpp/godot.hpp>
+
+using namespace godot;
+
+#else
+// Headers for building as built-in module.
+
+#include "core/typedefs.h"
+
+#include "modules/modules_enabled.gen.h" // For svg.
+#endif
+
+#ifdef MODULE_SVG_ENABLED
+
+#include <thorvg.h>
+
+void tvg_get_bounds(tvg::Picture *p_picture, float &r_min_x, float &r_min_y, float &r_max_x, float &r_max_y);
+
+#endif // MODULE_SVG_ENABLED
+
+#endif // THORVG_BOUNDS_ITERATOR_H

+ 320 - 0
modules/text_server_fb/thorvg_svg_in_ot.cpp

@@ -0,0 +1,320 @@
+/*************************************************************************/
+/*  thorvg_svg_in_ot.cpp                                                 */
+/*************************************************************************/
+/*                       This file is part of:                           */
+/*                           GODOT ENGINE                                */
+/*                      https://godotengine.org                          */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur.                 */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md).   */
+/*                                                                       */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the       */
+/* "Software"), to deal in the Software without restriction, including   */
+/* without limitation the rights to use, copy, modify, merge, publish,   */
+/* distribute, sublicense, and/or sell copies of the Software, and to    */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions:                                             */
+/*                                                                       */
+/* The above copyright notice and this permission notice shall be        */
+/* included in all copies or substantial portions of the Software.       */
+/*                                                                       */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
+/*************************************************************************/
+
+#ifdef GDEXTENSION
+// Headers for building as GDExtension plug-in.
+
+#include <godot_cpp/classes/xml_parser.hpp>
+#include <godot_cpp/core/mutex_lock.hpp>
+#include <godot_cpp/godot.hpp>
+#include <godot_cpp/templates/vector.hpp>
+
+using namespace godot;
+
+#else
+// Headers for building as built-in module.
+
+#include "core/error/error_macros.h"
+#include "core/io/xml_parser.h"
+#include "core/os/memory.h"
+#include "core/os/os.h"
+#include "core/string/ustring.h"
+#include "core/typedefs.h"
+#include "core/variant/variant.h"
+
+#include "modules/modules_enabled.gen.h" // For svg.
+#endif
+
+#ifdef MODULE_SVG_ENABLED
+
+#include "thorvg_bounds_iterator.h"
+#include "thorvg_svg_in_ot.h"
+
+#include <freetype/otsvg.h>
+#include <ft2build.h>
+
+#include <math.h>
+#include <stdlib.h>
+
+FT_Error tvg_svg_in_ot_init(FT_Pointer *p_state) {
+	*p_state = memnew(TVG_State);
+
+	return FT_Err_Ok;
+}
+
+void tvg_svg_in_ot_free(FT_Pointer *p_state) {
+	TVG_State *state = *reinterpret_cast<TVG_State **>(p_state);
+	memdelete(state);
+}
+
+FT_Error tvg_svg_in_ot_preset_slot(FT_GlyphSlot p_slot, FT_Bool p_cache, FT_Pointer *p_state) {
+	TVG_State *state = *reinterpret_cast<TVG_State **>(p_state);
+	if (!state) {
+		ERR_FAIL_V_MSG(FT_Err_Invalid_SVG_Document, "SVG in OT state not initialized.");
+	}
+	MutexLock lock(state->mutex);
+
+	FT_SVG_Document document = (FT_SVG_Document)p_slot->other;
+	FT_Size_Metrics metrics = document->metrics;
+
+	GL_State &gl_state = state->glyph_map[p_slot->glyph_index];
+	if (!gl_state.ready) {
+		Ref<XMLParser> parser;
+		parser.instantiate();
+#ifdef GDEXTENSION
+		PackedByteArray data;
+		data.resize(document->svg_document_length);
+		memcpy(data.ptrw(), document->svg_document, document->svg_document_length);
+		parser->open_buffer(data);
+#else
+		parser->_open_buffer((const uint8_t *)document->svg_document, document->svg_document_length);
+#endif
+
+		float aspect = 1.0f;
+		String xml_body;
+		while (parser->read() == OK) {
+			if (parser->has_attribute("id")) {
+#ifdef GDEXTENSION
+				const String &gl_name = parser->get_named_attribute_value("id");
+#else
+				const String &gl_name = parser->get_attribute_value("id");
+#endif
+				if (gl_name.begins_with("glyph")) {
+					int dot_pos = gl_name.find(".");
+					int64_t gl_idx = gl_name.substr(5, (dot_pos > 0) ? dot_pos - 5 : -1).to_int();
+					if (p_slot->glyph_index != gl_idx) {
+						parser->skip_section();
+						continue;
+					}
+				}
+			}
+			if (parser->get_node_type() == XMLParser::NODE_ELEMENT && parser->get_node_name() == "svg") {
+				if (parser->has_attribute("viewBox")) {
+#ifdef GDEXTENSION
+					PackedStringArray vb = parser->get_named_attribute_value("viewBox").split(" ");
+#else
+					Vector<String> vb = parser->get_attribute_value("viewBox").split(" ");
+#endif
+
+					if (vb.size() == 4) {
+						aspect = vb[2].to_float() / vb[3].to_float();
+					}
+				}
+				continue;
+			}
+#ifdef GDEXTENSION
+			if (parser->get_node_type() == XMLParser::NODE_ELEMENT) {
+				xml_body = xml_body + "<" + parser->get_node_name();
+				for (int i = 0; i < parser->get_attribute_count(); i++) {
+					xml_body = xml_body + " " + parser->get_attribute_name(i) + "=\"" + parser->get_attribute_value(i) + "\"";
+				}
+				xml_body = xml_body + ">";
+			} else if (parser->get_node_type() == XMLParser::NODE_TEXT) {
+				xml_body = xml_body + parser->get_node_data();
+			} else if (parser->get_node_type() == XMLParser::NODE_ELEMENT_END) {
+				xml_body = xml_body + "</" + parser->get_node_name() + ">";
+			}
+#else
+			if (parser->get_node_type() == XMLParser::NODE_ELEMENT) {
+				xml_body += vformat("<%s", parser->get_node_name());
+				for (int i = 0; i < parser->get_attribute_count(); i++) {
+					xml_body += vformat(" %s=\"%s\"", parser->get_attribute_name(i), parser->get_attribute_value(i));
+				}
+				xml_body += ">";
+			} else if (parser->get_node_type() == XMLParser::NODE_TEXT) {
+				xml_body += parser->get_node_data();
+			} else if (parser->get_node_type() == XMLParser::NODE_ELEMENT_END) {
+				xml_body += vformat("</%s>", parser->get_node_name());
+			}
+#endif
+		}
+		String temp_xml = "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 0 0\">" + xml_body;
+
+		std::unique_ptr<tvg::Picture> picture = tvg::Picture::gen();
+		tvg::Result result = picture->load(temp_xml.utf8().get_data(), temp_xml.utf8().length(), "svg+xml", false);
+		if (result != tvg::Result::Success) {
+			ERR_FAIL_V_MSG(FT_Err_Invalid_SVG_Document, "Failed to load SVG document (bounds detection).");
+		}
+
+		float min_x = INFINITY, min_y = INFINITY, max_x = -INFINITY, max_y = -INFINITY;
+		tvg_get_bounds(picture.get(), min_x, min_y, max_x, max_y);
+
+		float new_h = (max_y - min_y);
+		float new_w = (max_x - min_x);
+
+		if (new_h * aspect >= new_w) {
+			new_w = (new_h * aspect);
+		} else {
+			new_h = (new_w / aspect);
+		}
+
+#ifdef GDEXTENSION
+		gl_state.xml_code = "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"" + rtos(min_x) + " " + rtos(min_y) + " " + rtos(new_w) + " " + rtos(new_h) + "\">" + xml_body;
+#else
+		gl_state.xml_code = vformat("<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"%f %f %f %f\">", min_x, min_y, new_w, new_h) + xml_body;
+#endif
+
+		picture = tvg::Picture::gen();
+		result = picture->load(gl_state.xml_code.utf8().get_data(), gl_state.xml_code.utf8().length(), "svg+xml", false);
+		if (result != tvg::Result::Success) {
+			ERR_FAIL_V_MSG(FT_Err_Invalid_SVG_Document, "Failed to load SVG document (glyph metrics).");
+		}
+
+		float x_svg_to_out, y_svg_to_out;
+		x_svg_to_out = (float)metrics.x_ppem / new_w;
+		y_svg_to_out = (float)metrics.y_ppem / new_h;
+
+		gl_state.m.e11 = (double)document->transform.xx / (1 << 16) * x_svg_to_out;
+		gl_state.m.e12 = -(double)document->transform.xy / (1 << 16) * x_svg_to_out;
+		gl_state.m.e21 = -(double)document->transform.yx / (1 << 16) * y_svg_to_out;
+		gl_state.m.e22 = (double)document->transform.yy / (1 << 16) * y_svg_to_out;
+		gl_state.m.e13 = (double)document->delta.x / 64 * new_w / metrics.x_ppem;
+		gl_state.m.e23 = -(double)document->delta.y / 64 * new_h / metrics.y_ppem;
+		gl_state.m.e31 = 0;
+		gl_state.m.e32 = 0;
+		gl_state.m.e33 = 1;
+
+		result = picture->transform(gl_state.m);
+		if (result != tvg::Result::Success) {
+			ERR_FAIL_V_MSG(FT_Err_Invalid_SVG_Document, "Failed to apply transform to SVG document.");
+		}
+
+		result = picture->bounds(&gl_state.x, &gl_state.y, &gl_state.w, &gl_state.h, true);
+		if (result != tvg::Result::Success) {
+			ERR_FAIL_V_MSG(FT_Err_Invalid_SVG_Document, "Failed to get SVG bounds.");
+		}
+
+		gl_state.bmp_y = -min_y * gl_state.h / new_h;
+		gl_state.bmp_x = min_x * gl_state.w / new_w;
+
+		gl_state.ready = true;
+	}
+
+	p_slot->bitmap_left = (FT_Int)gl_state.bmp_x;
+	p_slot->bitmap_top = (FT_Int)gl_state.bmp_y;
+
+	float tmp = ceil(gl_state.h);
+	p_slot->bitmap.rows = (unsigned int)tmp;
+	tmp = ceil(gl_state.w);
+	p_slot->bitmap.width = (unsigned int)tmp;
+	p_slot->bitmap.pitch = (int)p_slot->bitmap.width * 4;
+	p_slot->bitmap.pixel_mode = FT_PIXEL_MODE_BGRA;
+
+	float metrics_width, metrics_height;
+	float horiBearingX, horiBearingY;
+	float vertBearingX, vertBearingY;
+
+	metrics_width = (float)gl_state.w;
+	metrics_height = (float)gl_state.h;
+	horiBearingX = (float)gl_state.x;
+	horiBearingY = (float)-gl_state.y;
+	vertBearingX = p_slot->metrics.horiBearingX / 64.0f - p_slot->metrics.horiAdvance / 64.0f / 2;
+	vertBearingY = (p_slot->metrics.vertAdvance / 64.0f - p_slot->metrics.height / 64.0f) / 2;
+
+	tmp = roundf(metrics_width * 64);
+	p_slot->metrics.width = (FT_Pos)tmp;
+	tmp = roundf(metrics_height * 64);
+	p_slot->metrics.height = (FT_Pos)tmp;
+
+	p_slot->metrics.horiBearingX = (FT_Pos)(horiBearingX * 64);
+	p_slot->metrics.horiBearingY = (FT_Pos)(horiBearingY * 64);
+	p_slot->metrics.vertBearingX = (FT_Pos)(vertBearingX * 64);
+	p_slot->metrics.vertBearingY = (FT_Pos)(vertBearingY * 64);
+
+	if (p_slot->metrics.vertAdvance == 0) {
+		p_slot->metrics.vertAdvance = (FT_Pos)(metrics_height * 1.2f * 64);
+	}
+
+	return FT_Err_Ok;
+}
+
+FT_Error tvg_svg_in_ot_render(FT_GlyphSlot p_slot, FT_Pointer *p_state) {
+	TVG_State *state = *reinterpret_cast<TVG_State **>(p_state);
+	if (!state) {
+		ERR_FAIL_V_MSG(FT_Err_Invalid_SVG_Document, "SVG in OT state not initialized.");
+	}
+	MutexLock lock(state->mutex);
+
+	if (!state->glyph_map.has(p_slot->glyph_index)) {
+		ERR_FAIL_V_MSG(FT_Err_Invalid_SVG_Document, "SVG glyph not loaded.");
+	}
+
+	GL_State &gl_state = state->glyph_map[p_slot->glyph_index];
+	ERR_FAIL_COND_V_MSG(!gl_state.ready, FT_Err_Invalid_SVG_Document, "SVG glyph not ready.");
+
+	std::unique_ptr<tvg::Picture> picture = tvg::Picture::gen();
+	tvg::Result res = picture->load(gl_state.xml_code.utf8().get_data(), gl_state.xml_code.utf8().length(), "svg+xml", false);
+	if (res != tvg::Result::Success) {
+		ERR_FAIL_V_MSG(FT_Err_Invalid_SVG_Document, "Failed to load SVG document (glyph rendering).");
+	}
+	res = picture->transform(gl_state.m);
+	if (res != tvg::Result::Success) {
+		ERR_FAIL_V_MSG(FT_Err_Invalid_SVG_Document, "Failed to apply transform to SVG document.");
+	}
+
+	std::unique_ptr<tvg::SwCanvas> sw_canvas = tvg::SwCanvas::gen();
+	res = sw_canvas->target((uint32_t *)p_slot->bitmap.buffer, (int)p_slot->bitmap.width, (int)p_slot->bitmap.width, (int)p_slot->bitmap.rows, tvg::SwCanvas::ARGB8888_STRAIGHT);
+	if (res != tvg::Result::Success) {
+		ERR_FAIL_V_MSG(FT_Err_Invalid_Outline, "Failed to create SVG canvas.");
+	}
+	res = sw_canvas->push(std::move(picture));
+	if (res != tvg::Result::Success) {
+		ERR_FAIL_V_MSG(FT_Err_Invalid_Outline, "Failed to set SVG canvas source.");
+	}
+	res = sw_canvas->draw();
+	if (res != tvg::Result::Success) {
+		ERR_FAIL_V_MSG(FT_Err_Invalid_Outline, "Failed to draw to SVG canvas.");
+	}
+	res = sw_canvas->sync();
+	if (res != tvg::Result::Success) {
+		ERR_FAIL_V_MSG(FT_Err_Invalid_Outline, "Failed to sync SVG canvas.");
+	}
+
+	state->glyph_map.erase(p_slot->glyph_index);
+
+	p_slot->bitmap.pixel_mode = FT_PIXEL_MODE_BGRA;
+	p_slot->bitmap.num_grays = 256;
+	p_slot->format = FT_GLYPH_FORMAT_BITMAP;
+
+	return FT_Err_Ok;
+}
+
+SVG_RendererHooks tvg_svg_in_ot_hooks = {
+	(SVG_Lib_Init_Func)tvg_svg_in_ot_init,
+	(SVG_Lib_Free_Func)tvg_svg_in_ot_free,
+	(SVG_Lib_Render_Func)tvg_svg_in_ot_render,
+	(SVG_Lib_Preset_Slot_Func)tvg_svg_in_ot_preset_slot,
+};
+
+SVG_RendererHooks *get_tvg_svg_in_ot_hooks() {
+	return &tvg_svg_in_ot_hooks;
+}
+
+#endif // MODULE_SVG_ENABLED

+ 86 - 0
modules/text_server_fb/thorvg_svg_in_ot.h

@@ -0,0 +1,86 @@
+/*************************************************************************/
+/*  thorvg_svg_in_ot.h                                                   */
+/*************************************************************************/
+/*                       This file is part of:                           */
+/*                           GODOT ENGINE                                */
+/*                      https://godotengine.org                          */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur.                 */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md).   */
+/*                                                                       */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the       */
+/* "Software"), to deal in the Software without restriction, including   */
+/* without limitation the rights to use, copy, modify, merge, publish,   */
+/* distribute, sublicense, and/or sell copies of the Software, and to    */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions:                                             */
+/*                                                                       */
+/* The above copyright notice and this permission notice shall be        */
+/* included in all copies or substantial portions of the Software.       */
+/*                                                                       */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
+/*************************************************************************/
+
+#ifndef THORVG_SVG_IN_OT_H
+#define THORVG_SVG_IN_OT_H
+
+#ifdef GDEXTENSION
+// Headers for building as GDExtension plug-in.
+
+#include <godot_cpp/core/mutex_lock.hpp>
+#include <godot_cpp/godot.hpp>
+#include <godot_cpp/templates/hash_map.hpp>
+
+using namespace godot;
+
+#else
+// Headers for building as built-in module.
+
+#include "core/os/mutex.h"
+#include "core/templates/hash_map.h"
+#include "core/typedefs.h"
+
+#include "modules/modules_enabled.gen.h" // For svg.
+#endif
+
+#ifdef MODULE_SVG_ENABLED
+
+#include <freetype/freetype.h>
+#include <freetype/otsvg.h>
+#include <ft2build.h>
+#include <thorvg.h>
+
+struct GL_State {
+	bool ready = false;
+	float bmp_x = 0;
+	float bmp_y = 0;
+	float x = 0;
+	float y = 0;
+	float w = 0;
+	float h = 0;
+	String xml_code;
+	tvg::Matrix m;
+};
+
+struct TVG_State {
+	Mutex mutex;
+	HashMap<uint32_t, GL_State> glyph_map;
+};
+
+FT_Error tvg_svg_in_ot_init(FT_Pointer *p_state);
+void tvg_svg_in_ot_free(FT_Pointer *p_state);
+FT_Error tvg_svg_in_ot_preset_slot(FT_GlyphSlot p_slot, FT_Bool p_cache, FT_Pointer *p_state);
+FT_Error tvg_svg_in_ot_render(FT_GlyphSlot p_slot, FT_Pointer *p_state);
+
+SVG_RendererHooks *get_tvg_svg_in_ot_hooks();
+
+#endif // MODULE_SVG_ENABLED
+
+#endif // THORVG_SVG_IN_OT_H