feat: updated engine version to 4.4-rc1

This commit is contained in:
Sara 2025-02-23 14:38:14 +01:00
parent ee00efde1f
commit 21ba8e33af
5459 changed files with 1128836 additions and 198305 deletions

View file

@ -1,4 +1,5 @@
#!/usr/bin/env python
from misc.utility.scons_hints import *
Import("env")
Import("env_modules")

View file

@ -1,4 +1,5 @@
def can_build(env, platform):
env.module_add_dependencies("text_server_fb", ["freetype", "msdfgen", "svg"], True)
return True

View file

@ -5,7 +5,7 @@
</brief_description>
<description>
A fallback implementation of Godot's text server. This fallback is faster than [TextServerAdvanced] for processing a lot of text, but it does not support BiDi and complex text layout.
[b]Note:[/b] This text server is not part of official Godot binaries. If you want to use it, compile the engine with the option [code]module_text_server_fb_enabled=yes[/code].
[b]Note:[/b] This text server is not part of official Godot binaries. If you want to use it, compile the engine with the option [code]module_text_server_fb_enabled=yes[/code]. When building with [TextServerFallback], consider also disabling [TextServerAdvanced] with [code]module_text_server_adv_enabled=no[/code] to reduce binary size.
</description>
<tutorials>
</tutorials>

View file

@ -1,28 +1,8 @@
#!/usr/bin/env python
import atexit
import sys
import time
# ruff: noqa: F821
import methods
# Enable ANSI escape code support on Windows 10 and later (for colored console output).
# <https://github.com/python/cpython/issues/73245>
if sys.stdout.isatty() and sys.platform == "win32":
try:
from ctypes import WinError, byref, windll # type: ignore
from ctypes.wintypes import DWORD # type: ignore
stdout_handle = windll.kernel32.GetStdHandle(DWORD(-11))
mode = DWORD(0)
if not windll.kernel32.GetConsoleMode(stdout_handle, byref(mode)):
raise WinError()
mode = DWORD(mode.value | 4)
if not windll.kernel32.SetConsoleMode(stdout_handle, mode):
raise WinError()
except Exception as e:
methods._colorize = False
methods.print_error(f"Failed to enable ANSI escape code support, disabling color output.\n{e}")
# For the reference:
# - CCFLAGS are compilation flags shared between C and C++
# - CFLAGS are for C-specific compilation flags
@ -31,8 +11,6 @@ if sys.stdout.isatty() and sys.platform == "win32":
# - CPPDEFINES are for pre-processor defines
# - LINKFLAGS are for linking flags
time_at_start = time.time()
env = SConscript("./godot-cpp/SConstruct")
env.__class__.disable_warnings = methods.disable_warnings
@ -45,9 +23,6 @@ opts.Add(BoolVariable("verbose", "Enable verbose output for the compilation", Fa
opts.Update(env)
if not env["verbose"]:
methods.no_verbose(env)
# ThorVG
if env["thorvg_enabled"] and env["freetype_enabled"]:
env_tvg = env.Clone()
@ -130,7 +105,7 @@ if env["thorvg_enabled"] and env["freetype_enabled"]:
env.Append(CPPDEFINES=["MODULE_SVG_ENABLED"])
lib = env_tvg.Library(
f'tvg_builtin{env["suffix"]}{env["LIBSUFFIX"]}',
f"tvg_builtin{env['suffix']}{env['LIBSUFFIX']}",
thirdparty_tvg_sources,
)
env.Append(LIBS=[lib])
@ -143,6 +118,7 @@ if env["msdfgen_enabled"] and env["freetype_enabled"]:
thirdparty_msdfgen_dir = "../../../thirdparty/msdfgen/"
thirdparty_msdfgen_sources = [
"core/Contour.cpp",
"core/DistanceMapping.cpp",
"core/EdgeHolder.cpp",
"core/MSDFErrorCorrection.cpp",
"core/Projection.cpp",
@ -153,10 +129,15 @@ if env["msdfgen_enabled"] and env["freetype_enabled"]:
"core/edge-segments.cpp",
"core/edge-selectors.cpp",
"core/equation-solver.cpp",
# "core/export-svg.cpp",
"core/msdf-error-correction.cpp",
"core/msdfgen.cpp",
"core/rasterization.cpp",
"core/render-sdf.cpp",
# "core/save-bmp.cpp",
# "core/save-fl32.cpp",
# "core/save-rgba.cpp",
# "core/save-tiff.cpp",
"core/sdf-error-estimation.cpp",
"core/shape-description.cpp",
]
@ -169,7 +150,7 @@ if env["msdfgen_enabled"] and env["freetype_enabled"]:
env.Append(CPPDEFINES=["MODULE_MSDFGEN_ENABLED"])
lib = env_msdfgen.Library(
f'msdfgen_builtin{env["suffix"]}{env["LIBSUFFIX"]}',
f"msdfgen_builtin{env['suffix']}{env['LIBSUFFIX']}",
thirdparty_msdfgen_sources,
)
env.Append(LIBS=[lib])
@ -298,7 +279,7 @@ if env["freetype_enabled"]:
env.Append(CPPDEFINES=["MODULE_FREETYPE_ENABLED"])
lib = env_freetype.Library(
f'freetype_builtin{env["suffix"]}{env["LIBSUFFIX"]}',
f"freetype_builtin{env['suffix']}{env['LIBSUFFIX']}",
thirdparty_freetype_sources,
)
env.Append(LIBS=[lib])
@ -310,35 +291,21 @@ sources = Glob("../*.cpp")
if env["platform"] == "macos":
methods.write_macos_plist(
f'./bin/libtextserver_fallback.macos.{env["target"]}.framework',
f'libtextserver_fallback.macos.{env["target"]}',
f"./bin/libtextserver_fallback.macos.{env['target']}.framework",
f"libtextserver_fallback.macos.{env['target']}",
"org.godotengine.textserver_fallback",
"Fallback Text Server",
)
library = env.SharedLibrary(
f'./bin/libtextserver_fallback.macos.{env["target"]}.framework/libtextserver_fallback.macos.{env["target"]}',
f"./bin/libtextserver_fallback.macos.{env['target']}.framework/libtextserver_fallback.macos.{env['target']}",
source=sources,
)
else:
library = env.SharedLibrary(
f'./bin/libtextserver_fallback{env["suffix"]}{env["SHLIBSUFFIX"]}',
f"./bin/libtextserver_fallback{env['suffix']}{env['SHLIBSUFFIX']}",
source=sources,
)
Default(library)
def print_elapsed_time():
elapsed_time_sec = round(time.time() - time_at_start, 2)
time_centiseconds = round((elapsed_time_sec % 1) * 100)
print(
"{}[Time elapsed: {}.{:02}]{}".format(
methods.ANSI.GRAY,
time.strftime("%H:%M:%S", time.gmtime(elapsed_time_sec)),
time_centiseconds,
methods.ANSI.RESET,
)
)
atexit.register(print_elapsed_time)
methods.prepare_timer()

View file

@ -1,120 +1,33 @@
import os
import sys
from enum import Enum
# Colors are disabled in non-TTY environments such as pipes. This means
# that if output is redirected to a file, it won't contain color codes.
# Colors are always enabled on continuous integration.
_colorize = bool(sys.stdout.isatty() or os.environ.get("CI"))
class ANSI(Enum):
"""
Enum class for adding ansi colorcodes directly into strings.
Automatically converts values to strings representing their
internal value, or an empty string in a non-colorized scope.
"""
RESET = "\x1b[0m"
BOLD = "\x1b[1m"
ITALIC = "\x1b[3m"
UNDERLINE = "\x1b[4m"
STRIKETHROUGH = "\x1b[9m"
REGULAR = "\x1b[22;23;24;29m"
BLACK = "\x1b[30m"
RED = "\x1b[31m"
GREEN = "\x1b[32m"
YELLOW = "\x1b[33m"
BLUE = "\x1b[34m"
MAGENTA = "\x1b[35m"
CYAN = "\x1b[36m"
WHITE = "\x1b[37m"
PURPLE = "\x1b[38;5;93m"
PINK = "\x1b[38;5;206m"
ORANGE = "\x1b[38;5;214m"
GRAY = "\x1b[38;5;244m"
def __str__(self) -> str:
global _colorize
return str(self.value) if _colorize else ""
def no_verbose(env):
colors = [ANSI.BLUE, ANSI.BOLD, ANSI.REGULAR, ANSI.RESET]
# There is a space before "..." to ensure that source file names can be
# Ctrl + clicked in the VS Code terminal.
compile_source_message = "{}Compiling {}$SOURCE{} ...{}".format(*colors)
java_compile_source_message = "{}Compiling {}$SOURCE{} ...{}".format(*colors)
compile_shared_source_message = "{}Compiling shared {}$SOURCE{} ...{}".format(*colors)
link_program_message = "{}Linking Program {}$TARGET{} ...{}".format(*colors)
link_library_message = "{}Linking Static Library {}$TARGET{} ...{}".format(*colors)
ranlib_library_message = "{}Ranlib Library {}$TARGET{} ...{}".format(*colors)
link_shared_library_message = "{}Linking Shared Library {}$TARGET{} ...{}".format(*colors)
java_library_message = "{}Creating Java Archive {}$TARGET{} ...{}".format(*colors)
compiled_resource_message = "{}Creating Compiled Resource {}$TARGET{} ...{}".format(*colors)
generated_file_message = "{}Generating {}$TARGET{} ...{}".format(*colors)
env["CXXCOMSTR"] = compile_source_message
env["CCCOMSTR"] = compile_source_message
env["SHCCCOMSTR"] = compile_shared_source_message
env["SHCXXCOMSTR"] = compile_shared_source_message
env["ARCOMSTR"] = link_library_message
env["RANLIBCOMSTR"] = ranlib_library_message
env["SHLINKCOMSTR"] = link_shared_library_message
env["LINKCOMSTR"] = link_program_message
env["JARCOMSTR"] = java_library_message
env["JAVACCOMSTR"] = java_compile_source_message
env["RCCOMSTR"] = compiled_resource_message
env["GENCOMSTR"] = generated_file_message
def disable_warnings(self):
# 'self' is the environment
if self["platform"] == "windows" and not self["use_mingw"]:
# We have to remove existing warning level defines before appending /w,
# otherwise we get: "warning D9025 : overriding '/W3' with '/w'"
warn_flags = ["/Wall", "/W4", "/W3", "/W2", "/W1", "/WX"]
self.Append(CCFLAGS=["/w"])
self.Append(CFLAGS=["/w"])
self.Append(CXXFLAGS=["/w"])
self["CCFLAGS"] = [x for x in self["CCFLAGS"] if x not in warn_flags]
self["CFLAGS"] = [x for x in self["CFLAGS"] if x not in warn_flags]
self["CXXFLAGS"] = [x for x in self["CXXFLAGS"] if x not in warn_flags]
WARN_FLAGS = ["/Wall", "/W4", "/W3", "/W2", "/W1", "/W0"]
self["CCFLAGS"] = [x for x in self["CCFLAGS"] if x not in WARN_FLAGS]
self["CFLAGS"] = [x for x in self["CFLAGS"] if x not in WARN_FLAGS]
self["CXXFLAGS"] = [x for x in self["CXXFLAGS"] if x not in WARN_FLAGS]
self.AppendUnique(CCFLAGS=["/w"])
else:
self.Append(CCFLAGS=["-w"])
self.Append(CFLAGS=["-w"])
self.Append(CXXFLAGS=["-w"])
self.AppendUnique(CCFLAGS=["-w"])
def make_icu_data(target, source, env):
dst = target[0].srcnode().abspath
with open(dst, "w", encoding="utf-8", newline="\n") as g:
g.write("/* THIS FILE IS GENERATED DO NOT EDIT */\n")
g.write("/* (C) 2016 and later: Unicode, Inc. and others. */\n")
g.write("/* License & terms of use: https://www.unicode.org/copyright.html */\n")
g.write("#ifndef _ICU_DATA_H\n")
g.write("#define _ICU_DATA_H\n")
g.write('#include "unicode/utypes.h"\n')
g.write('#include "unicode/udata.h"\n')
g.write('#include "unicode/uversion.h"\n')
def prepare_timer():
import atexit
import time
with open(source[0].srcnode().abspath, "rb") as f:
buf = f.read()
def print_elapsed_time(time_at_start: float):
time_elapsed = time.time() - time_at_start
time_formatted = time.strftime("%H:%M:%S", time.gmtime(time_elapsed))
time_centiseconds = round((time_elapsed % 1) * 100)
print(f"[Time elapsed: {time_formatted}.{time_centiseconds}]")
g.write('extern "C" U_EXPORT const size_t U_ICUDATA_SIZE = ' + str(len(buf)) + ";\n")
g.write('extern "C" U_EXPORT const unsigned char U_ICUDATA_ENTRY_POINT[] = {\n')
for i in range(len(buf)):
g.write("\t" + str(buf[i]) + ",\n")
g.write("};\n")
g.write("#endif")
atexit.register(print_elapsed_time, time.time())
def write_macos_plist(target, binary_name, identifier, name):
import os
os.makedirs(f"{target}/Resource/", exist_ok=True)
with open(f"{target}/Resource/Info.plist", "w", encoding="utf-8", newline="\n") as f:
f.write(f"""\

File diff suppressed because it is too large Load diff

View file

@ -72,6 +72,7 @@
#include <godot_cpp/templates/hash_map.hpp>
#include <godot_cpp/templates/hash_set.hpp>
#include <godot_cpp/templates/rid_owner.hpp>
#include <godot_cpp/templates/safe_refcount.hpp>
#include <godot_cpp/templates/vector.hpp>
using namespace godot;
@ -83,6 +84,7 @@ using namespace godot;
#include "core/object/worker_thread_pool.h"
#include "core/templates/hash_map.h"
#include "core/templates/rid_owner.h"
#include "core/templates/safe_refcount.h"
#include "scene/resources/image_texture.h"
#include "servers/text/text_server_extension.h"
@ -116,6 +118,9 @@ class TextServerFallback : public TextServerExtension {
HashMap<StringName, int32_t> feature_sets;
HashMap<int32_t, StringName> feature_sets_inv;
SafeNumeric<TextServer::FontLCDSubpixelLayout> lcd_subpixel_layout{ TextServer::FontLCDSubpixelLayout::FONT_LCD_SUBPIXEL_LAYOUT_NONE };
void _update_settings();
void _insert_feature_sets();
_FORCE_INLINE_ void _insert_feature(const StringName &p_name, int32_t p_tag);
@ -265,6 +270,7 @@ class TextServerFallback : public TextServerExtension {
bool allow_system_fallback = true;
TextServer::Hinting hinting = TextServer::HINTING_LIGHT;
TextServer::SubpixelPositioning subpixel_positioning = TextServer::SUBPIXEL_POSITIONING_AUTO;
bool keep_rounding_remainders = true;
Dictionary variation_coordinates;
double oversampling = 0.0;
double embolden = 0.0;
@ -278,7 +284,7 @@ class TextServerFallback : public TextServerExtension {
int extra_spacing[4] = { 0, 0, 0, 0 };
double baseline_offset = 0.0;
HashMap<Vector2i, FontForSizeFallback *, VariantHasher, VariantComparator> cache;
HashMap<Vector2i, FontForSizeFallback *> cache;
bool face_init = false;
Dictionary supported_varaitions;
@ -308,8 +314,9 @@ class TextServerFallback : public TextServerExtension {
#ifdef MODULE_FREETYPE_ENABLED
_FORCE_INLINE_ FontGlyph rasterize_bitmap(FontForSizeFallback *p_data, int p_rect_margin, FT_Bitmap p_bitmap, int p_yofs, int p_xofs, const Vector2 &p_advance, bool p_bgra) const;
#endif
_FORCE_INLINE_ bool _ensure_glyph(FontFallback *p_font_data, const Vector2i &p_size, int32_t p_glyph) const;
_FORCE_INLINE_ bool _ensure_cache_for_size(FontFallback *p_font_data, const Vector2i &p_size) const;
_FORCE_INLINE_ bool _ensure_glyph(FontFallback *p_font_data, const Vector2i &p_size, int32_t p_glyph, FontGlyph &r_glyph) const;
_FORCE_INLINE_ bool _ensure_cache_for_size(FontFallback *p_font_data, const Vector2i &p_size, FontForSizeFallback *&r_cache_for_size, bool p_silent = false) const;
_FORCE_INLINE_ bool _font_validate(const RID &p_font_rid) const;
_FORCE_INLINE_ void _font_clear_cache(FontFallback *p_font_data);
static void _generateMTSDF_threaded(void *p_td, uint32_t p_y);
@ -420,6 +427,8 @@ class TextServerFallback : public TextServerExtension {
Variant meta;
};
Vector<Span> spans;
int first_span = 0; // First span in the parent ShapedTextData.
int last_span = 0;
struct EmbeddedObject {
int start = -1;
@ -432,7 +441,7 @@ class TextServerFallback : public TextServerExtension {
/* Shaped data */
TextServer::Direction para_direction = DIRECTION_LTR; // Detected text direction.
bool valid = false; // String is shaped.
SafeFlag valid{ false }; // String is shaped.
bool line_breaks_valid = false; // Line and word break flags are populated (and virtual zero width spaces inserted).
bool justification_ops_valid = false; // Virtual elongation glyphs are added to the string.
bool sort_valid = false;
@ -489,6 +498,7 @@ class TextServerFallback : public TextServerExtension {
int fixed_size = 0;
TextServer::Hinting hinting = TextServer::HINTING_LIGHT;
TextServer::SubpixelPositioning subpixel_positioning = TextServer::SUBPIXEL_POSITIONING_AUTO;
bool keep_rounding_remainders = true;
Dictionary variation_coordinates;
double oversampling = 0.0;
double embolden = 0.0;
@ -497,7 +507,7 @@ class TextServerFallback : public TextServerExtension {
double baseline_offset = 0.0;
bool operator==(const SystemFontKey &p_b) const {
return (font_name == p_b.font_name) && (antialiasing == p_b.antialiasing) && (italic == p_b.italic) && (disable_embedded_bitmaps == p_b.disable_embedded_bitmaps) && (mipmaps == p_b.mipmaps) && (msdf == p_b.msdf) && (force_autohinter == p_b.force_autohinter) && (weight == p_b.weight) && (stretch == p_b.stretch) && (msdf_range == p_b.msdf_range) && (msdf_source_size == p_b.msdf_source_size) && (fixed_size == p_b.fixed_size) && (hinting == p_b.hinting) && (subpixel_positioning == p_b.subpixel_positioning) && (variation_coordinates == p_b.variation_coordinates) && (oversampling == p_b.oversampling) && (embolden == p_b.embolden) && (transform == p_b.transform) && (extra_spacing[SPACING_TOP] == p_b.extra_spacing[SPACING_TOP]) && (extra_spacing[SPACING_BOTTOM] == p_b.extra_spacing[SPACING_BOTTOM]) && (extra_spacing[SPACING_SPACE] == p_b.extra_spacing[SPACING_SPACE]) && (extra_spacing[SPACING_GLYPH] == p_b.extra_spacing[SPACING_GLYPH]) && (baseline_offset == p_b.baseline_offset);
return (font_name == p_b.font_name) && (antialiasing == p_b.antialiasing) && (italic == p_b.italic) && (disable_embedded_bitmaps == p_b.disable_embedded_bitmaps) && (mipmaps == p_b.mipmaps) && (msdf == p_b.msdf) && (force_autohinter == p_b.force_autohinter) && (weight == p_b.weight) && (stretch == p_b.stretch) && (msdf_range == p_b.msdf_range) && (msdf_source_size == p_b.msdf_source_size) && (fixed_size == p_b.fixed_size) && (hinting == p_b.hinting) && (subpixel_positioning == p_b.subpixel_positioning) && (keep_rounding_remainders == p_b.keep_rounding_remainders) && (variation_coordinates == p_b.variation_coordinates) && (oversampling == p_b.oversampling) && (embolden == p_b.embolden) && (transform == p_b.transform) && (extra_spacing[SPACING_TOP] == p_b.extra_spacing[SPACING_TOP]) && (extra_spacing[SPACING_BOTTOM] == p_b.extra_spacing[SPACING_BOTTOM]) && (extra_spacing[SPACING_SPACE] == p_b.extra_spacing[SPACING_SPACE]) && (extra_spacing[SPACING_GLYPH] == p_b.extra_spacing[SPACING_GLYPH]) && (baseline_offset == p_b.baseline_offset);
}
SystemFontKey(const String &p_font_name, bool p_italic, int p_weight, int p_stretch, RID p_font, const TextServerFallback *p_fb) {
@ -515,6 +525,7 @@ class TextServerFallback : public TextServerExtension {
force_autohinter = p_fb->_font_is_force_autohinter(p_font);
hinting = p_fb->_font_get_hinting(p_font);
subpixel_positioning = p_fb->_font_get_subpixel_positioning(p_font);
keep_rounding_remainders = p_fb->_font_get_keep_rounding_remainders(p_font);
variation_coordinates = p_fb->_font_get_variation_coordinates(p_font);
oversampling = p_fb->_font_get_oversampling(p_font);
embolden = p_fb->_font_get_embolden(p_font);
@ -557,7 +568,7 @@ class TextServerFallback : public TextServerExtension {
hash = hash_murmur3_one_32(p_a.extra_spacing[SPACING_SPACE], hash);
hash = hash_murmur3_one_32(p_a.extra_spacing[SPACING_GLYPH], hash);
hash = hash_murmur3_one_double(p_a.baseline_offset, hash);
return hash_fmix32(hash_murmur3_one_32(((int)p_a.mipmaps) | ((int)p_a.msdf << 1) | ((int)p_a.italic << 2) | ((int)p_a.force_autohinter << 3) | ((int)p_a.hinting << 4) | ((int)p_a.subpixel_positioning << 8) | ((int)p_a.antialiasing << 12) | ((int)p_a.disable_embedded_bitmaps << 14), hash));
return hash_fmix32(hash_murmur3_one_32(((int)p_a.mipmaps) | ((int)p_a.msdf << 1) | ((int)p_a.italic << 2) | ((int)p_a.force_autohinter << 3) | ((int)p_a.hinting << 4) | ((int)p_a.subpixel_positioning << 8) | ((int)p_a.antialiasing << 12) | ((int)p_a.disable_embedded_bitmaps << 14) | ((int)p_a.keep_rounding_remainders << 15), hash));
}
};
mutable HashMap<SystemFontKey, SystemFontCache, SystemFontKeyHasher> system_fonts;
@ -569,7 +580,7 @@ class TextServerFallback : public TextServerExtension {
Mutex ft_mutex;
protected:
static void _bind_methods(){};
static void _bind_methods() {}
void full_copy(ShapedTextDataFallback *p_shaped);
void invalidate(ShapedTextDataFallback *p_shaped);
@ -586,6 +597,7 @@ public:
MODBIND0RC(String, get_support_data_filename);
MODBIND0RC(String, get_support_data_info);
MODBIND1RC(bool, save_support_data, const String &);
MODBIND0RC(PackedByteArray, get_support_data);
MODBIND1RC(bool, is_locale_right_to_left, const String &);
@ -653,6 +665,9 @@ public:
MODBIND2(font_set_subpixel_positioning, const RID &, SubpixelPositioning);
MODBIND1RC(SubpixelPositioning, font_get_subpixel_positioning, const RID &);
MODBIND2(font_set_keep_rounding_remainders, const RID &, bool);
MODBIND1RC(bool, font_get_keep_rounding_remainders, const RID &);
MODBIND2(font_set_embolden, const RID &, double);
MODBIND1RC(double, font_get_embolden, const RID &);
@ -739,6 +754,7 @@ public:
MODBIND2RC(bool, font_has_char, const RID &, int64_t);
MODBIND1RC(String, font_get_supported_chars, const RID &);
MODBIND1RC(PackedInt32Array, font_get_supported_glyphs, const RID &);
MODBIND4(font_render_range, const RID &, const Vector2i &, int64_t, int64_t);
MODBIND3(font_render_glyph, const RID &, const Vector2i &, int64_t);
@ -803,6 +819,7 @@ public:
MODBIND1RC(int64_t, shaped_get_span_count, const RID &);
MODBIND2RC(Variant, shaped_get_span_meta, const RID &, int64_t);
MODBIND2RC(Variant, shaped_get_span_embedded_object, const RID &, int64_t);
MODBIND5(shaped_set_span_update_font, const RID &, int64_t, const TypedArray<RID> &, int64_t, const Dictionary &);
MODBIND3RC(RID, shaped_text_substr, const RID &, int64_t, int64_t);

View file

@ -1,70 +0,0 @@
/**************************************************************************/
/* thorvg_bounds_iterator.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* 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;
#elif defined(GODOT_MODULE)
// 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

@ -1,58 +0,0 @@
/**************************************************************************/
/* thorvg_bounds_iterator.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* 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;
#elif defined(GODOT_MODULE)
// 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

@ -49,7 +49,7 @@ using namespace godot;
#include "core/typedefs.h"
#include "core/variant/variant.h"
#include "modules/modules_enabled.gen.h" // For svg.
#include "modules/modules_enabled.gen.h" // For svg, freetype.
#endif
#ifdef MODULE_SVG_ENABLED
@ -57,8 +57,6 @@ using namespace godot;
#include "thorvg_svg_in_ot.h"
#include "thorvg_bounds_iterator.h"
#include <freetype/otsvg.h>
#include <ft2build.h>
@ -76,6 +74,46 @@ void tvg_svg_in_ot_free(FT_Pointer *p_state) {
memdelete(state);
}
static void construct_xml(Ref<XMLParser> &parser, double &r_embox_x, double &r_embox_y, String *p_xml, int64_t &r_tag_count) {
if (parser->get_node_type() == XMLParser::NODE_ELEMENT) {
*p_xml += vformat("<%s", parser->get_node_name());
bool is_svg_tag = parser->get_node_name() == "svg";
for (int i = 0; i < parser->get_attribute_count(); i++) {
String aname = parser->get_attribute_name(i);
String value = parser->get_attribute_value(i);
if (is_svg_tag && aname == "viewBox") {
PackedStringArray vb = value.split(" ");
if (vb.size() == 4) {
r_embox_x = vb[2].to_float();
r_embox_y = vb[3].to_float();
}
} else if (is_svg_tag && aname == "width") {
r_embox_x = value.to_float();
} else if (is_svg_tag && aname == "height") {
r_embox_y = value.to_float();
} else {
*p_xml += vformat(" %s=\"%s\"", aname, value);
}
}
if (parser->is_empty()) {
*p_xml += "/>";
} else {
*p_xml += ">";
if (r_tag_count >= 0) {
r_tag_count++;
}
}
} else if (parser->get_node_type() == XMLParser::NODE_TEXT) {
*p_xml += parser->get_node_data();
} else if (parser->get_node_type() == XMLParser::NODE_ELEMENT_END) {
*p_xml += vformat("</%s>", parser->get_node_name());
if (r_tag_count > 0) {
r_tag_count--;
}
}
}
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) {
@ -92,132 +130,164 @@ FT_Error tvg_svg_in_ot_preset_slot(FT_GlyphSlot p_slot, FT_Bool p_cache, FT_Poin
parser.instantiate();
parser->_open_buffer((const uint8_t *)document->svg_document, document->svg_document_length);
float aspect = 1.0f;
String xml_body;
while (parser->read() == OK) {
if (parser->has_attribute("id")) {
const String &gl_name = parser->get_named_attribute_value("id");
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;
double embox_x = document->units_per_EM;
double embox_y = document->units_per_EM;
TVG_DocumentCache &cache = state->document_map[document->svg_document];
if (!cache.xml_body.is_empty()) {
// If we have a cached document, that means we have already parsed it.
// All node cache should be available.
xml_body = cache.xml_body;
embox_x = cache.embox_x;
embox_y = cache.embox_y;
ERR_FAIL_COND_V(!cache.node_caches.has(p_slot->glyph_index), FT_Err_Invalid_SVG_Document);
Vector<TVG_NodeCache> &ncs = cache.node_caches[p_slot->glyph_index];
uint64_t offset = 0;
for (TVG_NodeCache &nc : ncs) {
// Seek will call read() internally.
if (parser->seek(nc.document_offset) == OK) {
int64_t tag_count = 0;
String xml_node;
// We only parse the glyph node.
do {
construct_xml(parser, embox_x, embox_y, &xml_node, tag_count);
} while (tag_count != 0 && parser->read() == OK);
xml_body = xml_body.insert(nc.body_offset + offset, xml_node);
offset += xml_node.length();
}
}
} else {
String xml_node;
String xml_body_temp;
String *p_xml = &xml_body_temp;
int64_t tag_count = -1;
while (parser->read() == OK) {
if (parser->has_attribute("id")) {
const String &gl_name = parser->get_named_attribute_value("id");
if (gl_name.begins_with("glyph")) {
#ifdef GDEXTENSION
int dot_pos = gl_name.find(".");
#else
int dot_pos = gl_name.find_char('.');
#endif // GDEXTENSION
int64_t gl_idx = gl_name.substr(5, (dot_pos > 0) ? dot_pos - 5 : -1).to_int();
TVG_NodeCache node_cache = TVG_NodeCache();
node_cache.document_offset = parser->get_node_offset(),
node_cache.body_offset = (uint64_t)cache.xml_body.length();
cache.node_caches[gl_idx].push_back(node_cache);
if (p_slot->glyph_index != gl_idx) {
parser->skip_section();
continue;
}
tag_count = 0;
xml_node = "";
p_xml = &xml_node;
}
}
}
if (parser->get_node_type() == XMLParser::NODE_ELEMENT && parser->get_node_name() == "svg") {
if (parser->has_attribute("viewBox")) {
PackedStringArray vb = parser->get_named_attribute_value("viewBox").split(" ");
if (vb.size() == 4) {
aspect = vb[2].to_float() / vb[3].to_float();
}
}
continue;
}
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_temp = "";
construct_xml(parser, embox_x, embox_y, p_xml, tag_count);
if (xml_body_temp.length() > 0) {
xml_body += xml_body_temp;
cache.xml_body += xml_body_temp;
continue;
}
if (parser->is_empty()) {
xml_body += "/>";
} else {
xml_body += ">";
if (tag_count == 0) {
p_xml = &xml_body_temp;
tag_count = -1;
xml_body += xml_node;
}
} 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());
}
cache.embox_x = embox_x;
cache.embox_y = embox_y;
}
String temp_xml_str = "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 1 1\">" + xml_body;
CharString temp_xml = temp_xml_str.utf8();
std::unique_ptr<tvg::Picture> picture = tvg::Picture::gen();
tvg::Result result = picture->load(temp_xml.get_data(), temp_xml.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).");
}
gl_state.xml_code = xml_body.utf8();
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);
}
String xml_code_str = "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"" + rtos(min_x) + " " + rtos(min_y) + " " + rtos(new_w) + " " + rtos(new_h) + "\">" + xml_body;
gl_state.xml_code = xml_code_str.utf8();
picture = tvg::Picture::gen();
result = picture->load(gl_state.xml_code.get_data(), gl_state.xml_code.length(), "svg+xml", false);
tvg::Result result = picture->load(gl_state.xml_code.get_data(), gl_state.xml_code.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;
float svg_width, svg_height;
picture->size(&svg_width, &svg_height);
double aspect = svg_width / svg_height;
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;
result = picture->size(embox_x * aspect, embox_y);
if (result != tvg::Result::Success) {
ERR_FAIL_V_MSG(FT_Err_Invalid_SVG_Document, "Failed to resize SVG document.");
}
double x_svg_to_out = (double)metrics.x_ppem / embox_x;
double y_svg_to_out = (double)metrics.y_ppem / embox_y;
gl_state.m.e11 = (double)document->transform.xx / (1 << 16);
gl_state.m.e12 = -(double)document->transform.xy / (1 << 16);
gl_state.m.e21 = -(double)document->transform.yx / (1 << 16);
gl_state.m.e22 = (double)document->transform.yy / (1 << 16);
gl_state.m.e13 = (double)document->delta.x / 64 * embox_x / metrics.x_ppem;
gl_state.m.e23 = -(double)document->delta.y / 64 * embox_y / metrics.y_ppem;
gl_state.m.e31 = 0;
gl_state.m.e32 = 0;
gl_state.m.e33 = 1;
result = picture->size(embox_x * aspect * x_svg_to_out, embox_y * y_svg_to_out);
if (result != tvg::Result::Success) {
ERR_FAIL_V_MSG(FT_Err_Invalid_SVG_Document, "Failed to resize SVG document.");
}
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 = gl_state.h + metrics.descender / 64.f;
gl_state.bmp_x = 0;
picture->size(&gl_state.w, &gl_state.h);
gl_state.x = (gl_state.h - gl_state.w) / 2.0;
gl_state.y = -gl_state.h;
gl_state.ready = true;
}
p_slot->bitmap_left = (FT_Int)gl_state.bmp_x;
p_slot->bitmap_top = (FT_Int)gl_state.bmp_y;
p_slot->bitmap_left = (FT_Int)gl_state.x;
p_slot->bitmap_top = (FT_Int)-gl_state.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;
double tmpd = Math::ceil(gl_state.h);
p_slot->bitmap.rows = (unsigned int)tmpd;
tmpd = Math::ceil(gl_state.w);
p_slot->bitmap.width = (unsigned int)tmpd;
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;
float metrics_width = (float)gl_state.w;
float metrics_height = (float)gl_state.h;
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;
float horiBearingX = (float)gl_state.x;
float horiBearingY = (float)-gl_state.y;
tmp = roundf(metrics_width * 64);
p_slot->metrics.width = (FT_Pos)tmp;
tmp = roundf(metrics_height * 64);
p_slot->metrics.height = (FT_Pos)tmp;
float vertBearingX = p_slot->metrics.horiBearingX / 64.0f - p_slot->metrics.horiAdvance / 64.0f / 2;
float vertBearingY = (p_slot->metrics.vertAdvance / 64.0f - p_slot->metrics.height / 64.0f) / 2;
float tmpf = Math::round(metrics_width * 64);
p_slot->metrics.width = (FT_Pos)tmpf;
tmpf = Math::round(metrics_height * 64);
p_slot->metrics.height = (FT_Pos)tmpf;
p_slot->metrics.horiBearingX = (FT_Pos)(horiBearingX * 64);
p_slot->metrics.horiBearingY = (FT_Pos)(horiBearingY * 64);
@ -250,6 +320,10 @@ FT_Error tvg_svg_in_ot_render(FT_GlyphSlot p_slot, FT_Pointer *p_state) {
if (res != tvg::Result::Success) {
ERR_FAIL_V_MSG(FT_Err_Invalid_SVG_Document, "Failed to load SVG document (glyph rendering).");
}
res = picture->size(gl_state.w, gl_state.h);
if (res != tvg::Result::Success) {
ERR_FAIL_V_MSG(FT_Err_Invalid_SVG_Document, "Failed to resize SVG document.");
}
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.");

View file

@ -60,8 +60,6 @@ using namespace godot;
struct GL_State {
bool ready = false;
float bmp_x = 0;
float bmp_y = 0;
float x = 0;
float y = 0;
float w = 0;
@ -70,9 +68,22 @@ struct GL_State {
tvg::Matrix m;
};
struct TVG_NodeCache {
uint64_t document_offset;
uint64_t body_offset;
};
struct TVG_DocumentCache {
String xml_body;
double embox_x;
double embox_y;
HashMap<int64_t, Vector<TVG_NodeCache>> node_caches;
};
struct TVG_State {
Mutex mutex;
HashMap<uint32_t, GL_State> glyph_map;
HashMap<FT_Byte *, TVG_DocumentCache> document_map;
};
FT_Error tvg_svg_in_ot_init(FT_Pointer *p_state);