1
0
mirror of https://github.com/godotengine/godot synced 2024-07-08 18:10:44 +00:00

Implement SVG in OT support.

This commit is contained in:
bruvzg 2022-06-14 10:29:58 +03:00
parent 191c8ed12f
commit d77902f33a
No known key found for this signature in database
GPG Key ID: 7960FCF39844EC38
18 changed files with 1304 additions and 29 deletions

View File

@ -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;
}
}

View File

@ -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();

View File

@ -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()

View File

@ -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()

View File

@ -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;

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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"])

View File

@ -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()

View File

@ -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;

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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