summaryrefslogtreecommitdiff
path: root/modules
diff options
context:
space:
mode:
authorbruvzg <7645683+bruvzg@users.noreply.github.com>2020-08-05 09:25:28 +0300
committerbruvzg <7645683+bruvzg@users.noreply.github.com>2020-11-26 13:55:26 +0200
commit493da99269b3a111cf58e0352495e25ee49c5f84 (patch)
treebc34af12b8f3697ee6da79c1c44e2cfcd8d4b90c /modules
parenta8c2cc9028535aa6f474e93b71b09b5b7402c79d (diff)
[Complex Text Layouts] Implement TextServer interface. Implement Fallback TextServer.
Diffstat (limited to 'modules')
-rw-r--r--modules/text_server_fb/SCsub12
-rw-r--r--modules/text_server_fb/bitmap_font_fb.cpp357
-rw-r--r--modules/text_server_fb/bitmap_font_fb.h107
-rw-r--r--modules/text_server_fb/config.py11
-rw-r--r--modules/text_server_fb/dynamic_font_fb.cpp731
-rw-r--r--modules/text_server_fb/dynamic_font_fb.h172
-rw-r--r--modules/text_server_fb/font_fb.h80
-rw-r--r--modules/text_server_fb/register_types.cpp43
-rw-r--r--modules/text_server_fb/register_types.h40
-rw-r--r--modules/text_server_fb/text_server_fb.cpp1362
-rw-r--r--modules/text_server_fb/text_server_fb.h193
11 files changed, 3108 insertions, 0 deletions
diff --git a/modules/text_server_fb/SCsub b/modules/text_server_fb/SCsub
new file mode 100644
index 0000000000..7650e27063
--- /dev/null
+++ b/modules/text_server_fb/SCsub
@@ -0,0 +1,12 @@
+#!/usr/bin/env python
+
+Import("env")
+Import("env_modules")
+
+env_text_server_fb = env_modules.Clone()
+env_text_server_fb.Append(
+ CPPPATH=[
+ "#thirdparty/freetype/include",
+ ]
+)
+env_text_server_fb.add_source_files(env.modules_sources, "*.cpp")
diff --git a/modules/text_server_fb/bitmap_font_fb.cpp b/modules/text_server_fb/bitmap_font_fb.cpp
new file mode 100644
index 0000000000..5ab8edbd71
--- /dev/null
+++ b/modules/text_server_fb/bitmap_font_fb.cpp
@@ -0,0 +1,357 @@
+/*************************************************************************/
+/* bitmap_font_fb.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 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. */
+/*************************************************************************/
+
+#include "bitmap_font_fb.h"
+
+Error BitmapFontDataFallback::load_from_file(const String &p_filename, int p_base_size) {
+ _THREAD_SAFE_METHOD_
+ //fnt format used by angelcode bmfont
+ //http://www.angelcode.com/products/bmfont/
+
+ FileAccess *f = FileAccess::open(p_filename, FileAccess::READ);
+ ERR_FAIL_COND_V_MSG(!f, ERR_FILE_NOT_FOUND, "Can't open font: " + p_filename + ".");
+
+ while (true) {
+ String line = f->get_line();
+
+ int delimiter = line.find(" ");
+ String type = line.substr(0, delimiter);
+ int pos = delimiter + 1;
+ Map<String, String> keys;
+
+ while (pos < line.size() && line[pos] == ' ') {
+ pos++;
+ }
+
+ while (pos < line.size()) {
+ int eq = line.find("=", pos);
+ if (eq == -1) {
+ break;
+ }
+ String key = line.substr(pos, eq - pos);
+ int end = -1;
+ String value;
+ if (line[eq + 1] == '"') {
+ end = line.find("\"", eq + 2);
+ if (end == -1) {
+ break;
+ }
+ value = line.substr(eq + 2, end - 1 - eq - 1);
+ pos = end + 1;
+ } else {
+ end = line.find(" ", eq + 1);
+ if (end == -1) {
+ end = line.size();
+ }
+ value = line.substr(eq + 1, end - eq);
+ pos = end;
+ }
+
+ while (pos < line.size() && line[pos] == ' ') {
+ pos++;
+ }
+
+ keys[key] = value;
+ }
+
+ if (type == "info") {
+ if (keys.has("size")) {
+ base_size = keys["size"].to_int();
+ }
+ } else if (type == "common") {
+ if (keys.has("lineHeight")) {
+ height = keys["lineHeight"].to_int();
+ }
+ if (keys.has("base")) {
+ ascent = keys["base"].to_int();
+ }
+ } else if (type == "page") {
+ if (keys.has("file")) {
+ String base_dir = p_filename.get_base_dir();
+ String file = base_dir.plus_file(keys["file"]);
+ if (RenderingServer::get_singleton() != nullptr) {
+ Ref<Texture2D> tex = ResourceLoader::load(file);
+ if (tex.is_null()) {
+ ERR_PRINT("Can't load font texture!");
+ } else {
+ ERR_FAIL_COND_V_MSG(tex.is_null(), ERR_FILE_CANT_READ, "It's not a reference to a valid Texture object.");
+ textures.push_back(tex);
+ }
+ }
+ }
+ } else if (type == "char") {
+ Character c;
+ char32_t idx = 0;
+ if (keys.has("id")) {
+ idx = keys["id"].to_int();
+ }
+ if (keys.has("x")) {
+ c.rect.position.x = keys["x"].to_int();
+ }
+ if (keys.has("y")) {
+ c.rect.position.y = keys["y"].to_int();
+ }
+ if (keys.has("width")) {
+ c.rect.size.width = keys["width"].to_int();
+ }
+ if (keys.has("height")) {
+ c.rect.size.height = keys["height"].to_int();
+ }
+ if (keys.has("xoffset")) {
+ c.align.x = keys["xoffset"].to_int();
+ }
+ if (keys.has("yoffset")) {
+ c.align.y = keys["yoffset"].to_int();
+ }
+ if (keys.has("page")) {
+ c.texture_idx = keys["page"].to_int();
+ }
+ if (keys.has("xadvance")) {
+ c.advance.x = keys["xadvance"].to_int();
+ }
+ if (keys.has("yadvance")) {
+ c.advance.y = keys["yadvance"].to_int();
+ }
+ if (c.advance.x < 0) {
+ c.advance.x = c.rect.size.width + 1;
+ }
+ if (c.advance.y < 0) {
+ c.advance.y = c.rect.size.height + 1;
+ }
+ char_map[idx] = c;
+ } else if (type == "kerning") {
+ KerningPairKey kpk;
+ float k = 0;
+ if (keys.has("first")) {
+ kpk.A = keys["first"].to_int();
+ }
+ if (keys.has("second")) {
+ kpk.B = keys["second"].to_int();
+ }
+ if (keys.has("amount")) {
+ k = keys["amount"].to_int();
+ }
+ kerning_map[kpk] = k;
+ }
+
+ if (f->eof_reached()) {
+ break;
+ }
+ }
+ if (base_size == 0) {
+ base_size = height;
+ }
+
+ valid = true;
+
+ memdelete(f);
+ return OK;
+}
+
+Error BitmapFontDataFallback::load_from_memory(const uint8_t *p_data, size_t p_size, int p_base_size) {
+ _THREAD_SAFE_METHOD_
+ ERR_FAIL_COND_V(p_data == nullptr, ERR_CANT_CREATE);
+ ERR_FAIL_COND_V(p_size != sizeof(TextServer::BitmapFontData), ERR_CANT_CREATE);
+
+ const TextServer::BitmapFontData *data = (const TextServer::BitmapFontData *)p_data;
+
+ if (RenderingServer::get_singleton() != nullptr) {
+ Ref<Image> image = memnew(Image(data->img));
+ Ref<ImageTexture> tex = memnew(ImageTexture);
+ tex->create_from_image(image);
+
+ textures.push_back(tex);
+ }
+
+ for (int i = 0; i < data->charcount; i++) {
+ const int *c = &data->char_rects[i * 8];
+
+ Character chr;
+ chr.rect.position.x = c[1];
+ chr.rect.position.y = c[2];
+ chr.rect.size.x = c[3];
+ chr.rect.size.y = c[4];
+ chr.texture_idx = 0;
+ if (c[7] < 0) {
+ chr.advance.x = c[3];
+ } else {
+ chr.advance.x = c[7];
+ }
+ chr.align = Vector2(c[6], c[5]);
+ char_map[c[0]] = chr;
+ }
+
+ for (int i = 0; i < data->kerning_count; i++) {
+ KerningPairKey kpk;
+ kpk.A = data->kernings[i * 3 + 0];
+ kpk.B = data->kernings[i * 3 + 1];
+
+ if (data->kernings[i * 3 + 2] == 0 && kerning_map.has(kpk)) {
+ kerning_map.erase(kpk);
+ } else {
+ kerning_map[kpk] = data->kernings[i * 3 + 2];
+ }
+ }
+
+ height = data->height;
+ ascent = data->ascent;
+
+ base_size = p_base_size;
+ if (base_size == 0) {
+ base_size = height;
+ }
+
+ valid = true;
+
+ return OK;
+}
+
+float BitmapFontDataFallback::get_height(int p_size) const {
+ ERR_FAIL_COND_V(!valid, 0.f);
+ return height * (float(p_size) / float(base_size));
+}
+
+float BitmapFontDataFallback::get_ascent(int p_size) const {
+ ERR_FAIL_COND_V(!valid, 0.f);
+ return ascent * (float(p_size) / float(base_size));
+}
+
+float BitmapFontDataFallback::get_descent(int p_size) const {
+ ERR_FAIL_COND_V(!valid, 0.f);
+ return (height - ascent) * (float(p_size) / float(base_size));
+}
+
+float BitmapFontDataFallback::get_underline_position(int p_size) const {
+ ERR_FAIL_COND_V(!valid, 0.f);
+ return 2 * (float(p_size) / float(base_size));
+}
+
+float BitmapFontDataFallback::get_underline_thickness(int p_size) const {
+ ERR_FAIL_COND_V(!valid, 0.f);
+ return 1 * (float(p_size) / float(base_size));
+}
+
+void BitmapFontDataFallback::set_distance_field_hint(bool p_distance_field) {
+ distance_field_hint = p_distance_field;
+}
+
+bool BitmapFontDataFallback::get_distance_field_hint() const {
+ return distance_field_hint;
+}
+
+float BitmapFontDataFallback::get_base_size() const {
+ return base_size;
+}
+
+bool BitmapFontDataFallback::has_char(char32_t p_char) const {
+ _THREAD_SAFE_METHOD_
+ ERR_FAIL_COND_V(!valid, false);
+ return char_map.has(p_char);
+}
+
+String BitmapFontDataFallback::get_supported_chars() const {
+ _THREAD_SAFE_METHOD_
+ ERR_FAIL_COND_V(!valid, String());
+ String chars;
+ const char32_t *k = nullptr;
+ while ((k = char_map.next(k))) {
+ chars += char32_t(*k);
+ }
+ return chars;
+}
+
+Vector2 BitmapFontDataFallback::get_advance(char32_t p_char, int p_size) const {
+ _THREAD_SAFE_METHOD_
+ ERR_FAIL_COND_V(!valid, Vector2());
+ const Character *c = char_map.getptr(p_char);
+ ERR_FAIL_COND_V(c == nullptr, Vector2());
+
+ return c->advance * (float(p_size) / float(base_size));
+}
+
+Vector2 BitmapFontDataFallback::get_kerning(char32_t p_char, char32_t p_next, int p_size) const {
+ _THREAD_SAFE_METHOD_
+ ERR_FAIL_COND_V(!valid, Vector2());
+ KerningPairKey kpk;
+ kpk.A = p_char;
+ kpk.B = p_next;
+
+ const Map<KerningPairKey, int>::Element *E = kerning_map.find(kpk);
+ if (E) {
+ return Vector2(-E->get() * (float(p_size) / float(base_size)), 0);
+ } else {
+ return Vector2();
+ }
+}
+
+Vector2 BitmapFontDataFallback::draw_glyph(RID p_canvas, int p_size, const Vector2 &p_pos, uint32_t p_index, const Color &p_color) const {
+ _THREAD_SAFE_METHOD_
+ if (p_index == 0) {
+ return Vector2();
+ }
+ ERR_FAIL_COND_V(!valid, Vector2());
+ const Character *c = char_map.getptr(p_index);
+
+ ERR_FAIL_COND_V(c == nullptr, Vector2());
+ ERR_FAIL_COND_V(c->texture_idx < -1 || c->texture_idx >= textures.size(), Vector2());
+ if (c->texture_idx != -1) {
+ Point2 cpos = p_pos;
+ cpos += c->align * (float(p_size) / float(base_size));
+ cpos.y -= ascent * (float(p_size) / float(base_size));
+
+ if (RenderingServer::get_singleton() != nullptr) {
+ //if (distance_field_hint) { // Not implemented.
+ // RenderingServer::get_singleton()->canvas_item_set_distance_field_mode(p_canvas, true);
+ //}
+ RenderingServer::get_singleton()->canvas_item_add_texture_rect_region(p_canvas, Rect2(cpos, c->rect.size * (float(p_size) / float(base_size))), textures[c->texture_idx]->get_rid(), c->rect, p_color, false, false);
+ //if (distance_field_hint) {
+ // RenderingServer::get_singleton()->canvas_item_set_distance_field_mode(p_canvas, false);
+ //}
+ }
+ }
+
+ return c->advance * (float(p_size) / float(base_size));
+}
+
+Vector2 BitmapFontDataFallback::draw_glyph_outline(RID p_canvas, int p_size, int p_outline_size, const Vector2 &p_pos, uint32_t p_index, const Color &p_color) const {
+ _THREAD_SAFE_METHOD_
+ if (p_index == 0) {
+ return Vector2();
+ }
+ ERR_FAIL_COND_V(!valid, Vector2());
+ const Character *c = char_map.getptr(p_index);
+
+ ERR_FAIL_COND_V(c == nullptr, Vector2());
+ ERR_FAIL_COND_V(c->texture_idx < -1 || c->texture_idx >= textures.size(), Vector2());
+
+ // Not supported, return advance for compatibility.
+
+ return c->advance * (float(p_size) / float(base_size));
+}
diff --git a/modules/text_server_fb/bitmap_font_fb.h b/modules/text_server_fb/bitmap_font_fb.h
new file mode 100644
index 0000000000..73e6d8f791
--- /dev/null
+++ b/modules/text_server_fb/bitmap_font_fb.h
@@ -0,0 +1,107 @@
+/*************************************************************************/
+/* bitmap_font_fb.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 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 BITMAP_FONT_FALLBACK_H
+#define BITMAP_FONT_FALLBACK_H
+
+#include "font_fb.h"
+
+struct BitmapFontDataFallback : public FontDataFallback {
+ _THREAD_SAFE_CLASS_
+
+private:
+ Vector<Ref<Texture2D>> textures;
+
+ struct Character {
+ int texture_idx = 0;
+ Rect2 rect;
+ Vector2 align;
+ Vector2 advance = Vector2(-1, -1);
+ };
+
+ struct KerningPairKey {
+ union {
+ struct {
+ uint32_t A, B;
+ };
+
+ uint64_t pair = 0;
+ };
+
+ _FORCE_INLINE_ bool operator<(const KerningPairKey &p_r) const { return pair < p_r.pair; }
+ };
+
+ HashMap<char32_t, Character> char_map;
+ Map<KerningPairKey, int> kerning_map;
+
+ float height = 0.f;
+ float ascent = 0.f;
+ int base_size = 0;
+ bool distance_field_hint = false;
+
+public:
+ virtual void clear_cache() override{};
+
+ virtual Error load_from_file(const String &p_filename, int p_base_size) override;
+ virtual Error load_from_memory(const uint8_t *p_data, size_t p_size, int p_base_size) override;
+
+ virtual float get_height(int p_size) const override;
+ virtual float get_ascent(int p_size) const override;
+ virtual float get_descent(int p_size) const override;
+
+ virtual float get_underline_position(int p_size) const override;
+ virtual float get_underline_thickness(int p_size) const override;
+
+ virtual void set_antialiased(bool p_antialiased) override{};
+ virtual bool get_antialiased() const override { return false; };
+
+ virtual void set_hinting(TextServer::Hinting p_hinting) override{};
+ virtual TextServer::Hinting get_hinting() const override { return TextServer::HINTING_NONE; };
+
+ virtual void set_distance_field_hint(bool p_distance_field) override;
+ virtual bool get_distance_field_hint() const override;
+
+ virtual void set_force_autohinter(bool p_enabeld) override{};
+ virtual bool get_force_autohinter() const override { return false; };
+
+ virtual bool has_outline() const override { return false; };
+ virtual float get_base_size() const override;
+
+ virtual bool has_char(char32_t p_char) const override;
+ virtual String get_supported_chars() const override;
+
+ virtual Vector2 get_advance(char32_t p_char, int p_size) const override;
+ virtual Vector2 get_kerning(char32_t p_char, char32_t p_next, int p_size) const override;
+
+ virtual Vector2 draw_glyph(RID p_canvas, int p_size, const Vector2 &p_pos, uint32_t p_index, const Color &p_color) const override;
+ virtual Vector2 draw_glyph_outline(RID p_canvas, int p_size, int p_outline_size, const Vector2 &p_pos, uint32_t p_index, const Color &p_color) const override;
+};
+
+#endif // BITMAP_FONT_FALLBACK_H
diff --git a/modules/text_server_fb/config.py b/modules/text_server_fb/config.py
new file mode 100644
index 0000000000..491377a369
--- /dev/null
+++ b/modules/text_server_fb/config.py
@@ -0,0 +1,11 @@
+def can_build(env, platform):
+ return env.module_check_dependencies("text_server_fb", ["freetype"])
+
+
+def configure(env):
+ pass
+
+
+def is_enabled():
+ # The module is disabled by default. Use module_text_server_fb_enabled=yes to enable it.
+ return False
diff --git a/modules/text_server_fb/dynamic_font_fb.cpp b/modules/text_server_fb/dynamic_font_fb.cpp
new file mode 100644
index 0000000000..6feeaec102
--- /dev/null
+++ b/modules/text_server_fb/dynamic_font_fb.cpp
@@ -0,0 +1,731 @@
+/*************************************************************************/
+/* dynamic_font_fb.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 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. */
+/*************************************************************************/
+
+#include "dynamic_font_fb.h"
+
+#include FT_STROKER_H
+#include FT_ADVANCES_H
+
+HashMap<String, Vector<uint8_t>> DynamicFontDataFallback::font_mem_cache;
+
+DynamicFontDataFallback::DataAtSize *DynamicFontDataFallback::get_data_for_size(int p_size, int p_outline_size) {
+ ERR_FAIL_COND_V(!valid, nullptr);
+ ERR_FAIL_COND_V(p_size < 0 || p_size > UINT16_MAX, nullptr);
+ ERR_FAIL_COND_V(p_outline_size < 0 || p_outline_size > UINT16_MAX, nullptr);
+
+ CacheID id;
+ id.size = p_size;
+ id.outline_size = p_outline_size;
+
+ DataAtSize *fds = nullptr;
+ Map<CacheID, DataAtSize *>::Element *E = nullptr;
+ if (p_outline_size != 0) {
+ E = size_cache_outline.find(id);
+ } else {
+ E = size_cache.find(id);
+ }
+
+ if (E != nullptr) {
+ fds = E->get();
+ } else {
+ // FT_OPEN_STREAM is extremely slow only on Android.
+ if (OS::get_singleton()->get_name() == "Android" && font_mem == nullptr && font_path != String()) {
+ if (font_mem_cache.has(font_path)) {
+ font_mem = font_mem_cache[font_path].ptr();
+ font_mem_size = font_mem_cache[font_path].size();
+ } else {
+ FileAccess *f = FileAccess::open(font_path, FileAccess::READ);
+ if (!f) {
+ ERR_FAIL_V_MSG(nullptr, "Cannot open font file '" + font_path + "'.");
+ }
+
+ size_t len = f->get_len();
+ font_mem_cache[font_path] = Vector<uint8_t>();
+ Vector<uint8_t> &fontdata = font_mem_cache[font_path];
+ fontdata.resize(len);
+ f->get_buffer(fontdata.ptrw(), len);
+ font_mem = fontdata.ptr();
+ font_mem_size = len;
+ f->close();
+ }
+ }
+
+ int error = 0;
+ fds = memnew(DataAtSize);
+ if (font_mem == nullptr && font_path != String()) {
+ FileAccess *f = FileAccess::open(font_path, FileAccess::READ);
+ if (!f) {
+ memdelete(fds);
+ ERR_FAIL_V_MSG(nullptr, "Cannot open font file '" + font_path + "'.");
+ }
+
+ memset(&fds->stream, 0, sizeof(FT_StreamRec));
+ fds->stream.base = nullptr;
+ fds->stream.size = f->get_len();
+ fds->stream.pos = 0;
+ fds->stream.descriptor.pointer = f;
+ fds->stream.read = _ft_stream_io;
+ fds->stream.close = _ft_stream_close;
+
+ FT_Open_Args fargs;
+ memset(&fargs, 0, sizeof(FT_Open_Args));
+ fargs.flags = FT_OPEN_STREAM;
+ fargs.stream = &fds->stream;
+ error = FT_Open_Face(library, &fargs, 0, &fds->face);
+ } else if (font_mem) {
+ memset(&fds->stream, 0, sizeof(FT_StreamRec));
+ fds->stream.base = (unsigned char *)font_mem;
+ fds->stream.size = font_mem_size;
+ fds->stream.pos = 0;
+
+ FT_Open_Args fargs;
+ memset(&fargs, 0, sizeof(FT_Open_Args));
+ fargs.memory_base = (unsigned char *)font_mem;
+ fargs.memory_size = font_mem_size;
+ fargs.flags = FT_OPEN_MEMORY;
+ fargs.stream = &fds->stream;
+ error = FT_Open_Face(library, &fargs, 0, &fds->face);
+
+ } else {
+ memdelete(fds);
+ ERR_FAIL_V_MSG(nullptr, "DynamicFont uninitialized.");
+ }
+
+ if (error == FT_Err_Unknown_File_Format) {
+ memdelete(fds);
+ ERR_FAIL_V_MSG(nullptr, "Unknown font format.");
+
+ } else if (error) {
+ memdelete(fds);
+ ERR_FAIL_V_MSG(nullptr, "Error loading font.");
+ }
+
+ oversampling = TS->font_get_oversampling();
+
+ if (FT_HAS_COLOR(fds->face) && fds->face->num_fixed_sizes > 0) {
+ int best_match = 0;
+ int diff = ABS(p_size - ((int64_t)fds->face->available_sizes[0].width));
+ fds->scale_color_font = float(p_size * oversampling) / fds->face->available_sizes[0].width;
+ for (int i = 1; i < fds->face->num_fixed_sizes; i++) {
+ int ndiff = ABS(p_size - ((int64_t)fds->face->available_sizes[i].width));
+ if (ndiff < diff) {
+ best_match = i;
+ diff = ndiff;
+ fds->scale_color_font = float(p_size * oversampling) / fds->face->available_sizes[i].width;
+ }
+ }
+ FT_Select_Size(fds->face, best_match);
+ } else {
+ FT_Set_Pixel_Sizes(fds->face, 0, p_size * oversampling);
+ }
+
+ fds->size = p_size;
+ fds->ascent = (fds->face->size->metrics.ascender / 64.0) / oversampling * fds->scale_color_font;
+ fds->descent = (-fds->face->size->metrics.descender / 64.0) / oversampling * fds->scale_color_font;
+ fds->underline_position = -fds->face->underline_position / 64.0 / oversampling * fds->scale_color_font;
+ fds->underline_thickness = fds->face->underline_thickness / 64.0 / oversampling * fds->scale_color_font;
+ if (p_outline_size != 0) {
+ size_cache_outline[id] = fds;
+ } else {
+ size_cache[id] = fds;
+ }
+ }
+
+ return fds;
+}
+
+unsigned long DynamicFontDataFallback::_ft_stream_io(FT_Stream stream, unsigned long offset, unsigned char *buffer, unsigned long count) {
+ FileAccess *f = (FileAccess *)stream->descriptor.pointer;
+
+ if (f->get_position() != offset) {
+ f->seek(offset);
+ }
+ if (count == 0) {
+ return 0;
+ }
+
+ return f->get_buffer(buffer, count);
+}
+
+void DynamicFontDataFallback::_ft_stream_close(FT_Stream stream) {
+ FileAccess *f = (FileAccess *)stream->descriptor.pointer;
+ f->close();
+ memdelete(f);
+}
+
+DynamicFontDataFallback::TexturePosition DynamicFontDataFallback::find_texture_pos_for_glyph(DynamicFontDataFallback::DataAtSize *p_data, int p_color_size, Image::Format p_image_format, int p_width, int p_height) {
+ TexturePosition ret;
+ ret.index = -1;
+ ret.x = 0;
+ ret.y = 0;
+
+ int mw = p_width;
+ int mh = p_height;
+
+ for (int i = 0; i < p_data->textures.size(); i++) {
+ const CharTexture &ct = p_data->textures[i];
+
+ if (RenderingServer::get_singleton() != nullptr) {
+ if (ct.texture->get_format() != p_image_format) {
+ continue;
+ }
+ }
+
+ if (mw > ct.texture_size || mh > ct.texture_size) { //too big for this texture
+ continue;
+ }
+
+ ret.y = 0x7FFFFFFF;
+ ret.x = 0;
+
+ for (int j = 0; j < ct.texture_size - mw; j++) {
+ int max_y = 0;
+
+ for (int k = j; k < j + mw; k++) {
+ int y = ct.offsets[k];
+ if (y > max_y) {
+ max_y = y;
+ }
+ }
+
+ if (max_y < ret.y) {
+ ret.y = max_y;
+ ret.x = j;
+ }
+ }
+
+ if (ret.y == 0x7FFFFFFF || ret.y + mh > ct.texture_size) {
+ continue; //fail, could not fit it here
+ }
+
+ ret.index = i;
+ break;
+ }
+
+ if (ret.index == -1) {
+ //could not find texture to fit, create one
+ ret.x = 0;
+ ret.y = 0;
+
+ int texsize = MAX(p_data->size * oversampling * 8, 256);
+ if (mw > texsize) {
+ texsize = mw; //special case, adapt to it?
+ }
+ if (mh > texsize) {
+ texsize = mh; //special case, adapt to it?
+ }
+
+ texsize = next_power_of_2(texsize);
+
+ texsize = MIN(texsize, 4096);
+
+ CharTexture tex;
+ tex.texture_size = texsize;
+ tex.imgdata.resize(texsize * texsize * p_color_size); //grayscale alpha
+
+ {
+ //zero texture
+ uint8_t *w = tex.imgdata.ptrw();
+ ERR_FAIL_COND_V(texsize * texsize * p_color_size > tex.imgdata.size(), ret);
+ // Initialize the texture to all-white pixels to prevent artifacts when the
+ // font is displayed at a non-default scale with filtering enabled.
+ if (p_color_size == 2) {
+ for (int i = 0; i < texsize * texsize * p_color_size; i += 2) { // FORMAT_LA8
+ w[i + 0] = 255;
+ w[i + 1] = 0;
+ }
+ } else if (p_color_size == 4) {
+ for (int i = 0; i < texsize * texsize * p_color_size; i += 4) { // FORMAT_RGBA8
+ w[i + 0] = 255;
+ w[i + 1] = 255;
+ w[i + 2] = 255;
+ w[i + 3] = 0;
+ }
+ } else {
+ ERR_FAIL_V(ret);
+ }
+ }
+ tex.offsets.resize(texsize);
+ for (int i = 0; i < texsize; i++) { //zero offsets
+ tex.offsets.write[i] = 0;
+ }
+
+ p_data->textures.push_back(tex);
+ ret.index = p_data->textures.size() - 1;
+ }
+
+ return ret;
+}
+
+DynamicFontDataFallback::Character DynamicFontDataFallback::Character::not_found() {
+ Character ch;
+ return ch;
+}
+
+DynamicFontDataFallback::Character DynamicFontDataFallback::bitmap_to_character(DynamicFontDataFallback::DataAtSize *p_data, FT_Bitmap bitmap, int yofs, int xofs, const Vector2 &advance) {
+ int w = bitmap.width;
+ int h = bitmap.rows;
+
+ int mw = w + rect_margin * 2;
+ int mh = h + rect_margin * 2;
+
+ ERR_FAIL_COND_V(mw > 4096, Character::not_found());
+ ERR_FAIL_COND_V(mh > 4096, Character::not_found());
+
+ int color_size = bitmap.pixel_mode == FT_PIXEL_MODE_BGRA ? 4 : 2;
+ Image::Format require_format = color_size == 4 ? Image::FORMAT_RGBA8 : Image::FORMAT_LA8;
+
+ TexturePosition tex_pos = find_texture_pos_for_glyph(p_data, color_size, require_format, mw, mh);
+ ERR_FAIL_COND_V(tex_pos.index < 0, Character::not_found());
+
+ //fit character in char texture
+
+ CharTexture &tex = p_data->textures.write[tex_pos.index];
+
+ {
+ uint8_t *wr = tex.imgdata.ptrw();
+
+ for (int i = 0; i < h; i++) {
+ for (int j = 0; j < w; j++) {
+ int ofs = ((i + tex_pos.y + rect_margin) * tex.texture_size + j + tex_pos.x + rect_margin) * color_size;
+ ERR_FAIL_COND_V(ofs >= tex.imgdata.size(), Character::not_found());
+ switch (bitmap.pixel_mode) {
+ case FT_PIXEL_MODE_MONO: {
+ int byte = i * bitmap.pitch + (j >> 3);
+ int bit = 1 << (7 - (j % 8));
+ wr[ofs + 0] = 255; //grayscale as 1
+ wr[ofs + 1] = (bitmap.buffer[byte] & bit) ? 255 : 0;
+ } break;
+ case FT_PIXEL_MODE_GRAY:
+ wr[ofs + 0] = 255; //grayscale as 1
+ wr[ofs + 1] = bitmap.buffer[i * bitmap.pitch + j];
+ break;
+ case FT_PIXEL_MODE_BGRA: {
+ int ofs_color = i * bitmap.pitch + (j << 2);
+ wr[ofs + 2] = bitmap.buffer[ofs_color + 0];
+ wr[ofs + 1] = bitmap.buffer[ofs_color + 1];
+ wr[ofs + 0] = bitmap.buffer[ofs_color + 2];
+ wr[ofs + 3] = bitmap.buffer[ofs_color + 3];
+ } break;
+ // TODO: FT_PIXEL_MODE_LCD
+ default:
+ ERR_FAIL_V_MSG(Character::not_found(), "Font uses unsupported pixel format: " + itos(bitmap.pixel_mode) + ".");
+ break;
+ }
+ }
+ }
+ }
+
+ //blit to image and texture
+ {
+ if (RenderingServer::get_singleton() != nullptr) {
+ Ref<Image> img = memnew(Image(tex.texture_size, tex.texture_size, 0, require_format, tex.imgdata));
+
+ if (tex.texture.is_null()) {
+ tex.texture.instance();
+ tex.texture->create_from_image(img);
+ } else {
+ tex.texture->update(img); //update
+ }
+ }
+ }
+
+ // update height array
+ for (int k = tex_pos.x; k < tex_pos.x + mw; k++) {
+ tex.offsets.write[k] = tex_pos.y + mh;
+ }
+
+ Character chr;
+ chr.align = Vector2(xofs, -yofs) * p_data->scale_color_font / oversampling;
+ chr.advance = advance * p_data->scale_color_font / oversampling;
+ chr.texture_idx = tex_pos.index;
+ chr.found = true;
+
+ chr.rect_uv = Rect2(tex_pos.x + rect_margin, tex_pos.y + rect_margin, w, h);
+ chr.rect = chr.rect_uv;
+ chr.rect.position /= oversampling;
+ chr.rect.size *= (p_data->scale_color_font / oversampling);
+ return chr;
+}
+
+void DynamicFontDataFallback::update_char(int p_size, char32_t p_char) {
+ DataAtSize *fds = get_data_for_size(p_size, false);
+ ERR_FAIL_COND(fds == nullptr);
+
+ if (fds->char_map.has(p_char)) {
+ return;
+ }
+
+ Character character = Character::not_found();
+
+ FT_GlyphSlot slot = fds->face->glyph;
+ FT_UInt gl_index = FT_Get_Char_Index(fds->face, p_char);
+
+ if (gl_index == 0) {
+ fds->char_map[p_char] = character;
+ return;
+ }
+
+ int ft_hinting;
+ switch (hinting) {
+ case TextServer::HINTING_NONE:
+ ft_hinting = FT_LOAD_NO_HINTING;
+ break;
+ case TextServer::HINTING_LIGHT:
+ ft_hinting = FT_LOAD_TARGET_LIGHT;
+ break;
+ default:
+ ft_hinting = FT_LOAD_TARGET_NORMAL;
+ break;
+ }
+
+ FT_Fixed v, h;
+ FT_Get_Advance(fds->face, gl_index, FT_HAS_COLOR(fds->face) ? FT_LOAD_COLOR : FT_LOAD_DEFAULT | (force_autohinter ? FT_LOAD_FORCE_AUTOHINT : 0) | ft_hinting, &h);
+ FT_Get_Advance(fds->face, gl_index, FT_HAS_COLOR(fds->face) ? FT_LOAD_COLOR : FT_LOAD_DEFAULT | (force_autohinter ? FT_LOAD_FORCE_AUTOHINT : 0) | ft_hinting | FT_LOAD_VERTICAL_LAYOUT, &v);
+
+ int error = FT_Load_Glyph(fds->face, gl_index, FT_HAS_COLOR(fds->face) ? FT_LOAD_COLOR : FT_LOAD_DEFAULT | (force_autohinter ? FT_LOAD_FORCE_AUTOHINT : 0) | ft_hinting);
+ if (error) {
+ fds->char_map[p_char] = character;
+ return;
+ }
+
+ error = FT_Render_Glyph(fds->face->glyph, antialiased ? FT_RENDER_MODE_NORMAL : FT_RENDER_MODE_MONO);
+ if (!error) {
+ character = bitmap_to_character(fds, slot->bitmap, slot->bitmap_top, slot->bitmap_left, Vector2((h + (1 << 9)) >> 10, (v + (1 << 9)) >> 10) / 64.0);
+ }
+
+ fds->char_map[p_char] = character;
+}
+
+void DynamicFontDataFallback::update_char_outline(int p_size, int p_outline_size, char32_t p_char) {
+ DataAtSize *fds = get_data_for_size(p_size, p_outline_size);
+ ERR_FAIL_COND(fds == nullptr);
+
+ if (fds->char_map.has(p_char)) {
+ return;
+ }
+
+ Character character = Character::not_found();
+ FT_UInt gl_index = FT_Get_Char_Index(fds->face, p_char);
+
+ if (gl_index == 0) {
+ fds->char_map[p_char] = character;
+ return;
+ }
+
+ int error = FT_Load_Glyph(fds->face, gl_index, FT_LOAD_NO_BITMAP | (force_autohinter ? FT_LOAD_FORCE_AUTOHINT : 0));
+ if (error) {
+ fds->char_map[p_char] = character;
+ return;
+ }
+
+ FT_Stroker stroker;
+ if (FT_Stroker_New(library, &stroker) != 0) {
+ fds->char_map[p_char] = character;
+ return;
+ }
+
+ FT_Stroker_Set(stroker, (int)(p_outline_size * oversampling * 64.0), FT_STROKER_LINECAP_BUTT, FT_STROKER_LINEJOIN_ROUND, 0);
+ FT_Glyph glyph;
+ FT_BitmapGlyph glyph_bitmap;
+
+ if (FT_Get_Glyph(fds->face->glyph, &glyph) != 0) {
+ goto cleanup_stroker;
+ }
+ if (FT_Glyph_Stroke(&glyph, stroker, 1) != 0) {
+ goto cleanup_glyph;
+ }
+ if (FT_Glyph_To_Bitmap(&glyph, antialiased ? FT_RENDER_MODE_NORMAL : FT_RENDER_MODE_MONO, nullptr, 1) != 0) {
+ goto cleanup_glyph;
+ }
+
+ glyph_bitmap = (FT_BitmapGlyph)glyph;
+ character = bitmap_to_character(fds, glyph_bitmap->bitmap, glyph_bitmap->top, glyph_bitmap->left, Vector2());
+
+cleanup_glyph:
+ FT_Done_Glyph(glyph);
+cleanup_stroker:
+ FT_Stroker_Done(stroker);
+
+ fds->char_map[p_char] = character;
+}
+
+void DynamicFontDataFallback::clear_cache() {
+ _THREAD_SAFE_METHOD_
+ for (Map<CacheID, DataAtSize *>::Element *E = size_cache.front(); E; E = E->next()) {
+ memdelete(E->get());
+ }
+ size_cache.clear();
+ for (Map<CacheID, DataAtSize *>::Element *E = size_cache_outline.front(); E; E = E->next()) {
+ memdelete(E->get());
+ }
+ size_cache_outline.clear();
+}
+
+Error DynamicFontDataFallback::load_from_file(const String &p_filename, int p_base_size) {
+ _THREAD_SAFE_METHOD_
+ if (library == nullptr) {
+ int error = FT_Init_FreeType(&library);
+ ERR_FAIL_COND_V_MSG(error != 0, ERR_CANT_CREATE, "Error initializing FreeType.");
+ }
+ clear_cache();
+
+ font_path = p_filename;
+ base_size = p_base_size;
+
+ valid = true;
+ DataAtSize *fds = get_data_for_size(base_size); // load base size.
+ if (fds == nullptr) {
+ valid = false;
+ ERR_FAIL_V(ERR_CANT_CREATE);
+ }
+
+ return OK;
+}
+
+Error DynamicFontDataFallback::load_from_memory(const uint8_t *p_data, size_t p_size, int p_base_size) {
+ _THREAD_SAFE_METHOD_
+ if (library == nullptr) {
+ int error = FT_Init_FreeType(&library);
+ ERR_FAIL_COND_V_MSG(error != 0, ERR_CANT_CREATE, "Error initializing FreeType.");
+ }
+ clear_cache();
+
+ font_mem = p_data;
+ font_mem_size = p_size;
+ base_size = p_base_size;
+
+ valid = true;
+ DataAtSize *fds = get_data_for_size(base_size); // load base size.
+ if (fds == nullptr) {
+ valid = false;
+ ERR_FAIL_V(ERR_CANT_CREATE);
+ }
+
+ return OK;
+}
+
+float DynamicFontDataFallback::get_height(int p_size) const {
+ _THREAD_SAFE_METHOD_
+ DataAtSize *fds = const_cast<DynamicFontDataFallback *>(this)->get_data_for_size(p_size);
+ ERR_FAIL_COND_V(fds == nullptr, 0.f);
+ return fds->ascent + fds->descent;
+}
+
+float DynamicFontDataFallback::get_ascent(int p_size) const {
+ _THREAD_SAFE_METHOD_
+ DataAtSize *fds = const_cast<DynamicFontDataFallback *>(this)->get_data_for_size(p_size);
+ ERR_FAIL_COND_V(fds == nullptr, 0.f);
+ return fds->ascent;
+}
+
+float DynamicFontDataFallback::get_descent(int p_size) const {
+ _THREAD_SAFE_METHOD_
+ DataAtSize *fds = const_cast<DynamicFontDataFallback *>(this)->get_data_for_size(p_size);
+ ERR_FAIL_COND_V(fds == nullptr, 0.f);
+ return fds->descent;
+}
+
+float DynamicFontDataFallback::get_underline_position(int p_size) const {
+ _THREAD_SAFE_METHOD_
+ DataAtSize *fds = const_cast<DynamicFontDataFallback *>(this)->get_data_for_size(p_size);
+ ERR_FAIL_COND_V(fds == nullptr, 0.f);
+ return fds->underline_position;
+}
+
+float DynamicFontDataFallback::get_underline_thickness(int p_size) const {
+ _THREAD_SAFE_METHOD_
+ DataAtSize *fds = const_cast<DynamicFontDataFallback *>(this)->get_data_for_size(p_size);
+ ERR_FAIL_COND_V(fds == nullptr, 0.f);
+ return fds->underline_thickness;
+}
+
+void DynamicFontDataFallback::set_antialiased(bool p_antialiased) {
+ if (antialiased != p_antialiased) {
+ clear_cache();
+ antialiased = p_antialiased;
+ }
+}
+
+bool DynamicFontDataFallback::get_antialiased() const {
+ return antialiased;
+}
+
+void DynamicFontDataFallback::set_force_autohinter(bool p_enabled) {
+ if (force_autohinter != p_enabled) {
+ clear_cache();
+ force_autohinter = p_enabled;
+ }
+}
+
+bool DynamicFontDataFallback::get_force_autohinter() const {
+ return force_autohinter;
+}
+
+void DynamicFontDataFallback::set_hinting(TextServer::Hinting p_hinting) {
+ if (hinting != p_hinting) {
+ clear_cache();
+ hinting = p_hinting;
+ }
+}
+
+TextServer::Hinting DynamicFontDataFallback::get_hinting() const {
+ return hinting;
+}
+
+bool DynamicFontDataFallback::has_outline() const {
+ return true;
+}
+
+float DynamicFontDataFallback::get_base_size() const {
+ return base_size;
+}
+
+bool DynamicFontDataFallback::has_char(char32_t p_char) const {
+ _THREAD_SAFE_METHOD_
+ DataAtSize *fds = const_cast<DynamicFontDataFallback *>(this)->get_data_for_size(base_size);
+ ERR_FAIL_COND_V(fds == nullptr, false);
+
+ const_cast<DynamicFontDataFallback *>(this)->update_char(base_size, p_char);
+ Character ch = fds->char_map[p_char];
+
+ return (ch.found);
+}
+
+String DynamicFontDataFallback::get_supported_chars() const {
+ _THREAD_SAFE_METHOD_
+ DataAtSize *fds = const_cast<DynamicFontDataFallback *>(this)->get_data_for_size(base_size);
+ ERR_FAIL_COND_V(fds == nullptr, String());
+
+ String chars;
+
+ FT_UInt gindex;
+ FT_ULong charcode = FT_Get_First_Char(fds->face, &gindex);
+ while (gindex != 0) {
+ if (charcode != 0) {
+ chars += char32_t(charcode);
+ }
+ charcode = FT_Get_Next_Char(fds->face, charcode, &gindex);
+ }
+
+ return chars;
+}
+
+Vector2 DynamicFontDataFallback::get_advance(char32_t p_char, int p_size) const {
+ _THREAD_SAFE_METHOD_
+ DataAtSize *fds = const_cast<DynamicFontDataFallback *>(this)->get_data_for_size(p_size);
+ ERR_FAIL_COND_V(fds == nullptr, Vector2());
+
+ const_cast<DynamicFontDataFallback *>(this)->update_char(p_size, p_char);
+ Character ch = fds->char_map[p_char];
+
+ return ch.advance;
+}
+
+Vector2 DynamicFontDataFallback::get_kerning(char32_t p_char, char32_t p_next, int p_size) const {
+ _THREAD_SAFE_METHOD_
+ DataAtSize *fds = const_cast<DynamicFontDataFallback *>(this)->get_data_for_size(p_size);
+ ERR_FAIL_COND_V(fds == nullptr, Vector2());
+
+ FT_Vector delta;
+ FT_Get_Kerning(fds->face, FT_Get_Char_Index(fds->face, p_char), FT_Get_Char_Index(fds->face, p_next), FT_KERNING_DEFAULT, &delta);
+ return Vector2(delta.x, delta.y);
+}
+
+Vector2 DynamicFontDataFallback::draw_glyph(RID p_canvas, int p_size, const Vector2 &p_pos, uint32_t p_index, const Color &p_color) const {
+ _THREAD_SAFE_METHOD_
+ DataAtSize *fds = const_cast<DynamicFontDataFallback *>(this)->get_data_for_size(p_size);
+ ERR_FAIL_COND_V(fds == nullptr, Vector2());
+
+ const_cast<DynamicFontDataFallback *>(this)->update_char(p_size, p_index);
+ Character ch = fds->char_map[p_index];
+
+ Vector2 advance;
+ if (ch.found) {
+ ERR_FAIL_COND_V(ch.texture_idx < -1 || ch.texture_idx >= fds->textures.size(), Vector2());
+
+ if (ch.texture_idx != -1) {
+ Point2 cpos = p_pos;
+ cpos += ch.align;
+
+ Color modulate = p_color;
+ if (FT_HAS_COLOR(fds->face)) {
+ modulate.r = modulate.g = modulate.b = 1.0;
+ }
+ if (RenderingServer::get_singleton() != nullptr) {
+ RID texture = fds->textures[ch.texture_idx].texture->get_rid();
+ RenderingServer::get_singleton()->canvas_item_add_texture_rect_region(p_canvas, Rect2(cpos, ch.rect.size), texture, ch.rect_uv, modulate, false, false);
+ }
+ }
+
+ advance = ch.advance;
+ }
+
+ return advance;
+}
+
+Vector2 DynamicFontDataFallback::draw_glyph_outline(RID p_canvas, int p_size, int p_outline_size, const Vector2 &p_pos, uint32_t p_index, const Color &p_color) const {
+ _THREAD_SAFE_METHOD_
+ DataAtSize *fds = const_cast<DynamicFontDataFallback *>(this)->get_data_for_size(p_size, p_outline_size);
+ ERR_FAIL_COND_V(fds == nullptr, Vector2());
+
+ const_cast<DynamicFontDataFallback *>(this)->update_char_outline(p_size, p_outline_size, p_index);
+ Character ch = fds->char_map[p_index];
+
+ Vector2 advance;
+ if (ch.found) {
+ ERR_FAIL_COND_V(ch.texture_idx < -1 || ch.texture_idx >= fds->textures.size(), Vector2());
+
+ if (ch.texture_idx != -1) {
+ Point2 cpos = p_pos;
+ cpos += ch.align;
+
+ Color modulate = p_color;
+ if (FT_HAS_COLOR(fds->face)) {
+ modulate.r = modulate.g = modulate.b = 1.0;
+ }
+ if (RenderingServer::get_singleton() != nullptr) {
+ RID texture = fds->textures[ch.texture_idx].texture->get_rid();
+ RenderingServer::get_singleton()->canvas_item_add_texture_rect_region(p_canvas, Rect2(cpos, ch.rect.size), texture, ch.rect_uv, modulate, false, false);
+ }
+ }
+
+ advance = ch.advance;
+ }
+
+ return advance;
+}
+
+DynamicFontDataFallback::~DynamicFontDataFallback() {
+ clear_cache();
+ if (library != nullptr) {
+ FT_Done_FreeType(library);
+ }
+}
diff --git a/modules/text_server_fb/dynamic_font_fb.h b/modules/text_server_fb/dynamic_font_fb.h
new file mode 100644
index 0000000000..060b8cfbf9
--- /dev/null
+++ b/modules/text_server_fb/dynamic_font_fb.h
@@ -0,0 +1,172 @@
+/*************************************************************************/
+/* dynamic_font_fb.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 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 DYNAMIC_FONT_FALLBACK_H
+#define DYNAMIC_FONT_FALLBACK_H
+
+#include "font_fb.h"
+
+#include <ft2build.h>
+#include FT_FREETYPE_H
+
+struct DynamicFontDataFallback : public FontDataFallback {
+ _THREAD_SAFE_CLASS_
+
+private:
+ struct CharTexture {
+ Vector<uint8_t> imgdata;
+ int texture_size = 0;
+ Vector<int> offsets;
+ Ref<ImageTexture> texture;
+ };
+
+ struct Character {
+ bool found = false;
+ int texture_idx = 0;
+ Rect2 rect;
+ Rect2 rect_uv;
+ Vector2 align;
+ Vector2 advance = Vector2(-1, -1);
+
+ static Character not_found();
+ };
+
+ struct TexturePosition {
+ int index = 0;
+ int x = 0;
+ int y = 0;
+ };
+
+ struct CacheID {
+ union {
+ struct {
+ uint32_t size : 16;
+ uint32_t outline_size : 16;
+ };
+ uint32_t key;
+ };
+ bool operator<(CacheID right) const {
+ return key < right.key;
+ }
+ CacheID() {
+ key = 0;
+ }
+ };
+
+ struct DataAtSize {
+ FT_Face face = nullptr;
+ FT_StreamRec stream;
+
+ int size = 0;
+ float scale_color_font = 1.f;
+ float ascent = 0;
+ float descent = 0;
+ float underline_position = 0;
+ float underline_thickness = 0;
+
+ Vector<CharTexture> textures;
+ HashMap<char32_t, Character> char_map;
+
+ ~DataAtSize() {
+ if (face != nullptr) {
+ FT_Done_Face(face);
+ }
+ }
+ };
+
+ FT_Library library = nullptr;
+
+ // Source data.
+ const uint8_t *font_mem = nullptr;
+ int font_mem_size = 0;
+ String font_path;
+ static HashMap<String, Vector<uint8_t>> font_mem_cache;
+
+ float rect_margin = 1.f;
+ int base_size = 16;
+ float oversampling = 1.f;
+ bool antialiased = true;
+ bool force_autohinter = false;
+ TextServer::Hinting hinting = TextServer::HINTING_LIGHT;
+
+ Map<CacheID, DataAtSize *> size_cache;
+ Map<CacheID, DataAtSize *> size_cache_outline;
+
+ static unsigned long _ft_stream_io(FT_Stream stream, unsigned long offset, unsigned char *buffer, unsigned long count);
+ static void _ft_stream_close(FT_Stream stream);
+
+ DataAtSize *get_data_for_size(int p_size, int p_outline_size = 0);
+
+ TexturePosition find_texture_pos_for_glyph(DataAtSize *p_data, int p_color_size, Image::Format p_image_format, int p_width, int p_height);
+ Character bitmap_to_character(DataAtSize *p_data, FT_Bitmap bitmap, int yofs, int xofs, const Vector2 &advance);
+ _FORCE_INLINE_ void update_char(int p_size, char32_t p_char);
+ _FORCE_INLINE_ void update_char_outline(int p_size, int p_outline_size, char32_t p_char);
+
+public:
+ virtual void clear_cache() override;
+
+ virtual Error load_from_file(const String &p_filename, int p_base_size) override;
+ virtual Error load_from_memory(const uint8_t *p_data, size_t p_size, int p_base_size) override;
+
+ virtual float get_height(int p_size) const override;
+ virtual float get_ascent(int p_size) const override;
+ virtual float get_descent(int p_size) const override;
+
+ virtual float get_underline_position(int p_size) const override;
+ virtual float get_underline_thickness(int p_size) const override;
+
+ virtual void set_antialiased(bool p_antialiased) override;
+ virtual bool get_antialiased() const override;
+
+ virtual void set_hinting(TextServer::Hinting p_hinting) override;
+ virtual TextServer::Hinting get_hinting() const override;
+
+ virtual void set_force_autohinter(bool p_enabeld) override;
+ virtual bool get_force_autohinter() const override;
+
+ virtual void set_distance_field_hint(bool p_distance_field) override{};
+ virtual bool get_distance_field_hint() const override { return false; };
+
+ virtual bool has_outline() const override;
+ virtual float get_base_size() const override;
+
+ virtual bool has_char(char32_t p_char) const override;
+ virtual String get_supported_chars() const override;
+
+ virtual Vector2 get_advance(char32_t p_char, int p_size) const override;
+ virtual Vector2 get_kerning(char32_t p_char, char32_t p_next, int p_size) const override;
+
+ virtual Vector2 draw_glyph(RID p_canvas, int p_size, const Vector2 &p_pos, uint32_t p_index, const Color &p_color) const override;
+ virtual Vector2 draw_glyph_outline(RID p_canvas, int p_size, int p_outline_size, const Vector2 &p_pos, uint32_t p_index, const Color &p_color) const override;
+
+ virtual ~DynamicFontDataFallback() override;
+};
+
+#endif // DYNAMIC_FONT_FALLBACK_H
diff --git a/modules/text_server_fb/font_fb.h b/modules/text_server_fb/font_fb.h
new file mode 100644
index 0000000000..d2ce2661a1
--- /dev/null
+++ b/modules/text_server_fb/font_fb.h
@@ -0,0 +1,80 @@
+/*************************************************************************/
+/* font_fb.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 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 FONT_FALLBACK_H
+#define FONT_FALLBACK_H
+
+#include "servers/text_server.h"
+
+struct FontDataFallback {
+ Map<String, bool> lang_support_overrides;
+ Map<String, bool> script_support_overrides;
+ bool valid = false;
+
+ virtual void clear_cache() = 0;
+
+ virtual Error load_from_file(const String &p_filename, int p_base_size) = 0;
+ virtual Error load_from_memory(const uint8_t *p_data, size_t p_size, int p_base_size) = 0;
+
+ virtual float get_height(int p_size) const = 0;
+ virtual float get_ascent(int p_size) const = 0;
+ virtual float get_descent(int p_size) const = 0;
+
+ virtual float get_underline_position(int p_size) const = 0;
+ virtual float get_underline_thickness(int p_size) const = 0;
+
+ virtual void set_antialiased(bool p_antialiased) = 0;
+ virtual bool get_antialiased() const = 0;
+
+ virtual void set_hinting(TextServer::Hinting p_hinting) = 0;
+ virtual TextServer::Hinting get_hinting() const = 0;
+
+ virtual void set_distance_field_hint(bool p_distance_field) = 0;
+ virtual bool get_distance_field_hint() const = 0;
+
+ virtual void set_force_autohinter(bool p_enabeld) = 0;
+ virtual bool get_force_autohinter() const = 0;
+
+ virtual bool has_outline() const = 0;
+ virtual float get_base_size() const = 0;
+
+ virtual bool has_char(char32_t p_char) const = 0;
+ virtual String get_supported_chars() const = 0;
+
+ virtual Vector2 get_advance(char32_t p_char, int p_size) const = 0;
+ virtual Vector2 get_kerning(char32_t p_char, char32_t p_next, int p_size) const = 0;
+
+ virtual Vector2 draw_glyph(RID p_canvas, int p_size, const Vector2 &p_pos, uint32_t p_index, const Color &p_color) const = 0;
+ virtual Vector2 draw_glyph_outline(RID p_canvas, int p_size, int p_outline_size, const Vector2 &p_pos, uint32_t p_index, const Color &p_color) const = 0;
+
+ virtual ~FontDataFallback(){};
+};
+
+#endif // FONT_FALLBACK_H
diff --git a/modules/text_server_fb/register_types.cpp b/modules/text_server_fb/register_types.cpp
new file mode 100644
index 0000000000..ad4d2d47ab
--- /dev/null
+++ b/modules/text_server_fb/register_types.cpp
@@ -0,0 +1,43 @@
+/*************************************************************************/
+/* register_types.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 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. */
+/*************************************************************************/
+
+#include "register_types.h"
+
+#include "text_server_fb.h"
+
+void preregister_text_server_fb_types() {
+ TextServerFallback::register_server();
+}
+
+void register_text_server_fb_types() {
+}
+
+void unregister_text_server_fb_types() {
+}
diff --git a/modules/text_server_fb/register_types.h b/modules/text_server_fb/register_types.h
new file mode 100644
index 0000000000..58f8436c67
--- /dev/null
+++ b/modules/text_server_fb/register_types.h
@@ -0,0 +1,40 @@
+/*************************************************************************/
+/* register_types.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 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 TEXT_SERVER_FB_REGISTER_TYPES_H
+#define TEXT_SERVER_FB_REGISTER_TYPES_H
+
+#define MODULE_TEXT_SERVER_FB_HAS_PREREGISTER
+
+void preregister_text_server_fb_types();
+void register_text_server_fb_types();
+void unregister_text_server_fb_types();
+
+#endif // TEXT_SERVER_FB_REGISTER_TYPES_H
diff --git a/modules/text_server_fb/text_server_fb.cpp b/modules/text_server_fb/text_server_fb.cpp
new file mode 100644
index 0000000000..e74a1d9ef9
--- /dev/null
+++ b/modules/text_server_fb/text_server_fb.cpp
@@ -0,0 +1,1362 @@
+/*************************************************************************/
+/* text_server_fb.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 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. */
+/*************************************************************************/
+
+#include "text_server_fb.h"
+
+#include "bitmap_font_fb.h"
+#include "dynamic_font_fb.h"
+
+_FORCE_INLINE_ bool is_control(char32_t p_char) {
+ return (p_char <= 0x001f) || (p_char >= 0x007f && p_char <= 0x009F);
+}
+
+_FORCE_INLINE_ bool is_whitespace(char32_t p_char) {
+ return (p_char == 0x0020) || (p_char == 0x00A0) || (p_char == 0x1680) || (p_char >= 0x2000 && p_char <= 0x200a) || (p_char == 0x202f) || (p_char == 0x205f) || (p_char == 0x3000) || (p_char == 0x2028) || (p_char == 0x2029) || (p_char >= 0x0009 && p_char <= 0x000d) || (p_char == 0x0085);
+}
+
+_FORCE_INLINE_ bool is_linebreak(char32_t p_char) {
+ return (p_char >= 0x000a && p_char <= 0x000d) || (p_char == 0x0085) || (p_char == 0x2028) || (p_char == 0x2029);
+}
+
+/*************************************************************************/
+
+String TextServerFallback::interface_name = "Fallback";
+uint32_t TextServerFallback::interface_features = 0; // Nothing is supported.
+
+bool TextServerFallback::has_feature(Feature p_feature) {
+ return (interface_features & p_feature) == p_feature;
+}
+
+String TextServerFallback::get_name() const {
+ return interface_name;
+}
+
+void TextServerFallback::free(RID p_rid) {
+ _THREAD_SAFE_METHOD_
+ if (font_owner.owns(p_rid)) {
+ FontDataFallback *fd = font_owner.getornull(p_rid);
+ font_owner.free(p_rid);
+ memdelete(fd);
+ } else if (shaped_owner.owns(p_rid)) {
+ ShapedTextData *sd = shaped_owner.getornull(p_rid);
+ shaped_owner.free(p_rid);
+ memdelete(sd);
+ }
+}
+
+bool TextServerFallback::has(RID p_rid) {
+ _THREAD_SAFE_METHOD_
+ return font_owner.owns(p_rid) || shaped_owner.owns(p_rid);
+}
+
+bool TextServerFallback::load_support_data(const String &p_filename) {
+ return false; // No extra data used.
+}
+
+#ifdef TOOLS_ENABLED
+
+bool TextServerFallback::save_support_data(const String &p_filename) {
+ return false; // No extra data used.
+}
+
+#endif
+
+bool TextServerFallback::is_locale_right_to_left(const String &p_locale) {
+ return false; // No RTL support.
+}
+
+/*************************************************************************/
+/* Font interface */
+/*************************************************************************/
+
+RID TextServerFallback::create_font_system(const String &p_name, int p_base_size) {
+ ERR_FAIL_V_MSG(RID(), "System fonts are not supported by this text server.");
+}
+
+RID TextServerFallback::create_font_resource(const String &p_filename, int p_base_size) {
+ _THREAD_SAFE_METHOD_
+ FontDataFallback *fd = nullptr;
+ if (p_filename.get_extension() == "ttf" || p_filename.get_extension() == "otf" || p_filename.get_extension() == "woff") {
+ fd = memnew(DynamicFontDataFallback);
+ } else if (p_filename.get_extension() == "fnt" || p_filename.get_extension() == "font") {
+ fd = memnew(BitmapFontDataFallback);
+ } else {
+ return RID();
+ }
+
+ Error err = fd->load_from_file(p_filename, p_base_size);
+ if (err != OK) {
+ memdelete(fd);
+ return RID();
+ }
+
+ return font_owner.make_rid(fd);
+}
+
+RID TextServerFallback::create_font_memory(const uint8_t *p_data, size_t p_size, const String &p_type, int p_base_size) {
+ _THREAD_SAFE_METHOD_
+ FontDataFallback *fd = nullptr;
+ if (p_type == "ttf" || p_type == "otf" || p_type == "woff") {
+ fd = memnew(DynamicFontDataFallback);
+ } else if (p_type == "fnt" || p_type == "font") {
+ fd = memnew(BitmapFontDataFallback);
+ } else {
+ return RID();
+ }
+
+ Error err = fd->load_from_memory(p_data, p_size, p_base_size);
+ if (err != OK) {
+ memdelete(fd);
+ return RID();
+ }
+
+ return font_owner.make_rid(fd);
+}
+
+float TextServerFallback::font_get_height(RID p_font, int p_size) const {
+ _THREAD_SAFE_METHOD_
+ const FontDataFallback *fd = font_owner.getornull(p_font);
+ ERR_FAIL_COND_V(!fd, 0.f);
+ return fd->get_height(p_size);
+}
+
+float TextServerFallback::font_get_ascent(RID p_font, int p_size) const {
+ _THREAD_SAFE_METHOD_
+ const FontDataFallback *fd = font_owner.getornull(p_font);
+ ERR_FAIL_COND_V(!fd, 0.f);
+ return fd->get_ascent(p_size);
+}
+
+float TextServerFallback::font_get_descent(RID p_font, int p_size) const {
+ _THREAD_SAFE_METHOD_
+ const FontDataFallback *fd = font_owner.getornull(p_font);
+ ERR_FAIL_COND_V(!fd, 0.f);
+ return fd->get_descent(p_size);
+}
+
+float TextServerFallback::font_get_underline_position(RID p_font, int p_size) const {
+ _THREAD_SAFE_METHOD_
+ const FontDataFallback *fd = font_owner.getornull(p_font);
+ ERR_FAIL_COND_V(!fd, 0.f);
+ return fd->get_underline_position(p_size);
+}
+
+float TextServerFallback::font_get_underline_thickness(RID p_font, int p_size) const {
+ _THREAD_SAFE_METHOD_
+ const FontDataFallback *fd = font_owner.getornull(p_font);
+ ERR_FAIL_COND_V(!fd, 0.f);
+ return fd->get_underline_thickness(p_size);
+}
+
+void TextServerFallback::font_set_antialiased(RID p_font, bool p_antialiased) {
+ _THREAD_SAFE_METHOD_
+ FontDataFallback *fd = font_owner.getornull(p_font);
+ ERR_FAIL_COND(!fd);
+ fd->set_antialiased(p_antialiased);
+}
+
+bool TextServerFallback::font_get_antialiased(RID p_font) const {
+ _THREAD_SAFE_METHOD_
+ const FontDataFallback *fd = font_owner.getornull(p_font);
+ ERR_FAIL_COND_V(!fd, false);
+ return fd->get_antialiased();
+}
+
+void TextServerFallback::font_set_distance_field_hint(RID p_font, bool p_distance_field) {
+ _THREAD_SAFE_METHOD_
+ FontDataFallback *fd = font_owner.getornull(p_font);
+ ERR_FAIL_COND(!fd);
+ fd->set_distance_field_hint(p_distance_field);
+}
+
+bool TextServerFallback::font_get_distance_field_hint(RID p_font) const {
+ _THREAD_SAFE_METHOD_
+ const FontDataFallback *fd = font_owner.getornull(p_font);
+ ERR_FAIL_COND_V(!fd, false);
+ return fd->get_distance_field_hint();
+}
+
+void TextServerFallback::font_set_hinting(RID p_font, TextServer::Hinting p_hinting) {
+ _THREAD_SAFE_METHOD_
+ FontDataFallback *fd = font_owner.getornull(p_font);
+ ERR_FAIL_COND(!fd);
+ fd->set_hinting(p_hinting);
+}
+
+TextServer::Hinting TextServerFallback::font_get_hinting(RID p_font) const {
+ _THREAD_SAFE_METHOD_
+ const FontDataFallback *fd = font_owner.getornull(p_font);
+ ERR_FAIL_COND_V(!fd, TextServer::HINTING_NONE);
+ return fd->get_hinting();
+}
+
+void TextServerFallback::font_set_force_autohinter(RID p_font, bool p_enabeld) {
+ _THREAD_SAFE_METHOD_
+ FontDataFallback *fd = font_owner.getornull(p_font);
+ ERR_FAIL_COND(!fd);
+ fd->set_force_autohinter(p_enabeld);
+}
+
+bool TextServerFallback::font_get_force_autohinter(RID p_font) const {
+ _THREAD_SAFE_METHOD_
+ const FontDataFallback *fd = font_owner.getornull(p_font);
+ ERR_FAIL_COND_V(!fd, false);
+ return fd->get_force_autohinter();
+}
+
+bool TextServerFallback::font_has_char(RID p_font, char32_t p_char) const {
+ _THREAD_SAFE_METHOD_
+ const FontDataFallback *fd = font_owner.getornull(p_font);
+ ERR_FAIL_COND_V(!fd, false);
+ return fd->has_char(p_char);
+}
+
+String TextServerFallback::font_get_supported_chars(RID p_font) const {
+ _THREAD_SAFE_METHOD_
+ const FontDataFallback *fd = font_owner.getornull(p_font);
+ ERR_FAIL_COND_V(!fd, String());
+ return fd->get_supported_chars();
+}
+
+bool TextServerFallback::font_has_outline(RID p_font) const {
+ _THREAD_SAFE_METHOD_
+ const FontDataFallback *fd = font_owner.getornull(p_font);
+ ERR_FAIL_COND_V(!fd, false);
+ return fd->has_outline();
+}
+
+float TextServerFallback::font_get_base_size(RID p_font) const {
+ _THREAD_SAFE_METHOD_
+ const FontDataFallback *fd = font_owner.getornull(p_font);
+ ERR_FAIL_COND_V(!fd, 0.f);
+ return fd->get_base_size();
+}
+
+bool TextServerFallback::font_is_language_supported(RID p_font, const String &p_language) const {
+ _THREAD_SAFE_METHOD_
+ const FontDataFallback *fd = font_owner.getornull(p_font);
+ ERR_FAIL_COND_V(!fd, false);
+ if (fd->lang_support_overrides.has(p_language)) {
+ return fd->lang_support_overrides[p_language];
+ } else {
+ Vector<String> tags = p_language.replace("-", "_").split("_");
+ if (tags.size() > 0) {
+ if (fd->lang_support_overrides.has(tags[0])) {
+ return fd->lang_support_overrides[tags[0]];
+ }
+ }
+ return false;
+ }
+}
+
+void TextServerFallback::font_set_language_support_override(RID p_font, const String &p_language, bool p_supported) {
+ _THREAD_SAFE_METHOD_
+ FontDataFallback *fd = font_owner.getornull(p_font);
+ ERR_FAIL_COND(!fd);
+ fd->lang_support_overrides[p_language] = p_supported;
+}
+
+bool TextServerFallback::font_get_language_support_override(RID p_font, const String &p_language) {
+ _THREAD_SAFE_METHOD_
+ FontDataFallback *fd = font_owner.getornull(p_font);
+ ERR_FAIL_COND_V(!fd, false);
+ return fd->lang_support_overrides[p_language];
+}
+
+void TextServerFallback::font_remove_language_support_override(RID p_font, const String &p_language) {
+ _THREAD_SAFE_METHOD_
+ FontDataFallback *fd = font_owner.getornull(p_font);
+ ERR_FAIL_COND(!fd);
+ fd->lang_support_overrides.erase(p_language);
+}
+
+Vector<String> TextServerFallback::font_get_language_support_overrides(RID p_font) {
+ _THREAD_SAFE_METHOD_
+ FontDataFallback *fd = font_owner.getornull(p_font);
+ ERR_FAIL_COND_V(!fd, Vector<String>());
+ Vector<String> ret;
+ for (Map<String, bool>::Element *E = fd->lang_support_overrides.front(); E; E = E->next()) {
+ ret.push_back(E->key());
+ }
+ return ret;
+}
+
+bool TextServerFallback::font_is_script_supported(RID p_font, const String &p_script) const {
+ _THREAD_SAFE_METHOD_
+ const FontDataFallback *fd = font_owner.getornull(p_font);
+ ERR_FAIL_COND_V(!fd, false);
+ if (fd->script_support_overrides.has(p_script)) {
+ return fd->script_support_overrides[p_script];
+ } else {
+ return true;
+ }
+}
+
+void TextServerFallback::font_set_script_support_override(RID p_font, const String &p_script, bool p_supported) {
+ _THREAD_SAFE_METHOD_
+ FontDataFallback *fd = font_owner.getornull(p_font);
+ ERR_FAIL_COND(!fd);
+ fd->script_support_overrides[p_script] = p_supported;
+}
+
+bool TextServerFallback::font_get_script_support_override(RID p_font, const String &p_script) {
+ _THREAD_SAFE_METHOD_
+ FontDataFallback *fd = font_owner.getornull(p_font);
+ ERR_FAIL_COND_V(!fd, false);
+ return fd->script_support_overrides[p_script];
+}
+
+void TextServerFallback::font_remove_script_support_override(RID p_font, const String &p_script) {
+ _THREAD_SAFE_METHOD_
+ FontDataFallback *fd = font_owner.getornull(p_font);
+ ERR_FAIL_COND(!fd);
+ fd->script_support_overrides.erase(p_script);
+}
+
+Vector<String> TextServerFallback::font_get_script_support_overrides(RID p_font) {
+ _THREAD_SAFE_METHOD_
+ FontDataFallback *fd = font_owner.getornull(p_font);
+ ERR_FAIL_COND_V(!fd, Vector<String>());
+ Vector<String> ret;
+ for (Map<String, bool>::Element *E = fd->script_support_overrides.front(); E; E = E->next()) {
+ ret.push_back(E->key());
+ }
+ return ret;
+}
+
+uint32_t TextServerFallback::font_get_glyph_index(RID p_font, char32_t p_char, char32_t p_variation_selector) const {
+ return (uint32_t)p_char;
+}
+
+Vector2 TextServerFallback::font_get_glyph_advance(RID p_font, uint32_t p_index, int p_size) const {
+ _THREAD_SAFE_METHOD_
+ const FontDataFallback *fd = font_owner.getornull(p_font);
+ ERR_FAIL_COND_V(!fd, Vector2());
+ return fd->get_advance(p_index, p_size);
+}
+
+Vector2 TextServerFallback::font_get_glyph_kerning(RID p_font, uint32_t p_index_a, uint32_t p_index_b, int p_size) const {
+ _THREAD_SAFE_METHOD_
+ const FontDataFallback *fd = font_owner.getornull(p_font);
+ ERR_FAIL_COND_V(!fd, Vector2());
+ return fd->get_kerning(p_index_a, p_index_b, p_size);
+}
+
+Vector2 TextServerFallback::font_draw_glyph(RID p_font, RID p_canvas, int p_size, const Vector2 &p_pos, uint32_t p_index, const Color &p_color) const {
+ _THREAD_SAFE_METHOD_
+ const FontDataFallback *fd = font_owner.getornull(p_font);
+ ERR_FAIL_COND_V(!fd, Vector2());
+ return fd->draw_glyph(p_canvas, p_size, p_pos, p_index, p_color);
+}
+
+Vector2 TextServerFallback::font_draw_glyph_outline(RID p_font, RID p_canvas, int p_size, int p_outline_size, const Vector2 &p_pos, uint32_t p_index, const Color &p_color) const {
+ _THREAD_SAFE_METHOD_
+ const FontDataFallback *fd = font_owner.getornull(p_font);
+ ERR_FAIL_COND_V(!fd, Vector2());
+ return fd->draw_glyph_outline(p_canvas, p_size, p_outline_size, p_pos, p_index, p_color);
+}
+
+float TextServerFallback::font_get_oversampling() const {
+ return oversampling;
+}
+
+void TextServerFallback::font_set_oversampling(float p_oversampling) {
+ _THREAD_SAFE_METHOD_
+ if (oversampling != p_oversampling) {
+ oversampling = p_oversampling;
+ List<RID> fonts;
+ font_owner.get_owned_list(&fonts);
+ for (List<RID>::Element *E = fonts.front(); E; E = E->next()) {
+ font_owner.getornull(E->get())->clear_cache();
+ }
+ }
+}
+
+Vector<String> TextServerFallback::get_system_fonts() const {
+ return Vector<String>();
+}
+
+/*************************************************************************/
+/* Shaped text buffer interface */
+/*************************************************************************/
+
+void TextServerFallback::invalidate(ShapedTextData *p_shaped) {
+ p_shaped->valid = false;
+ p_shaped->sort_valid = false;
+ p_shaped->line_breaks_valid = false;
+ p_shaped->justification_ops_valid = false;
+ p_shaped->ascent = 0.f;
+ p_shaped->descent = 0.f;
+ p_shaped->width = 0.f;
+ p_shaped->upos = 0.f;
+ p_shaped->uthk = 0.f;
+ p_shaped->glyphs.clear();
+ p_shaped->glyphs_logical.clear();
+}
+
+void TextServerFallback::full_copy(ShapedTextData *p_shaped) {
+ ShapedTextData *parent = shaped_owner.getornull(p_shaped->parent);
+
+ for (Map<Variant, ShapedTextData::EmbeddedObject>::Element *E = parent->objects.front(); E; E = E->next()) {
+ if (E->get().pos >= p_shaped->start && E->get().pos < p_shaped->end) {
+ p_shaped->objects[E->key()] = E->get();
+ }
+ }
+
+ for (int k = 0; k < parent->spans.size(); k++) {
+ ShapedTextData::Span span = parent->spans[k];
+ if (span.start >= p_shaped->end || span.end <= p_shaped->start) {
+ continue;
+ }
+ span.start = MAX(p_shaped->start, span.start);
+ span.end = MIN(p_shaped->end, span.end);
+ p_shaped->spans.push_back(span);
+ }
+
+ p_shaped->parent = RID();
+}
+
+RID TextServerFallback::create_shaped_text(TextServer::Direction p_direction, TextServer::Orientation p_orientation) {
+ _THREAD_SAFE_METHOD_
+ ShapedTextData *sd = memnew(ShapedTextData);
+ sd->direction = p_direction;
+ sd->orientation = p_orientation;
+
+ return shaped_owner.make_rid(sd);
+}
+
+void TextServerFallback::shaped_text_clear(RID p_shaped) {
+ _THREAD_SAFE_METHOD_
+ ShapedTextData *sd = shaped_owner.getornull(p_shaped);
+ ERR_FAIL_COND(!sd);
+
+ sd->parent = RID();
+ sd->start = 0;
+ sd->end = 0;
+ sd->text = String();
+ sd->spans.clear();
+ sd->objects.clear();
+ invalidate(sd);
+}
+
+void TextServerFallback::shaped_text_set_direction(RID p_shaped, TextServer::Direction p_direction) {
+ if (p_direction == DIRECTION_RTL) {
+ ERR_PRINT_ONCE("Right-to-left layout is not supported by this text server.");
+ }
+}
+
+TextServer::Direction TextServerFallback::shaped_text_get_direction(RID p_shaped) const {
+ return TextServer::DIRECTION_LTR;
+}
+
+void TextServerFallback::shaped_text_set_orientation(RID p_shaped, TextServer::Orientation p_orientation) {
+ _THREAD_SAFE_METHOD_
+ ShapedTextData *sd = shaped_owner.getornull(p_shaped);
+ ERR_FAIL_COND(!sd);
+ if (sd->orientation != p_orientation) {
+ if (sd->parent != RID()) {
+ full_copy(sd);
+ }
+ sd->orientation = p_orientation;
+ invalidate(sd);
+ }
+}
+
+void TextServerFallback::shaped_text_set_bidi_override(RID p_shaped, const Vector<Vector2i> &p_override) {
+ //No BiDi support, ignore.
+}
+
+TextServer::Orientation TextServerFallback::shaped_text_get_orientation(RID p_shaped) const {
+ _THREAD_SAFE_METHOD_
+ const ShapedTextData *sd = shaped_owner.getornull(p_shaped);
+ ERR_FAIL_COND_V(!sd, TextServer::ORIENTATION_HORIZONTAL);
+ return sd->orientation;
+}
+
+void TextServerFallback::shaped_text_set_preserve_invalid(RID p_shaped, bool p_enabled) {
+ _THREAD_SAFE_METHOD_
+ ShapedTextData *sd = shaped_owner.getornull(p_shaped);
+ ERR_FAIL_COND(!sd);
+ if (sd->preserve_invalid != p_enabled) {
+ if (sd->parent != RID()) {
+ full_copy(sd);
+ }
+ sd->preserve_invalid = p_enabled;
+ invalidate(sd);
+ }
+}
+
+bool TextServerFallback::shaped_text_get_preserve_invalid(RID p_shaped) const {
+ _THREAD_SAFE_METHOD_
+ const ShapedTextData *sd = shaped_owner.getornull(p_shaped);
+ ERR_FAIL_COND_V(!sd, false);
+ return sd->preserve_invalid;
+}
+
+void TextServerFallback::shaped_text_set_preserve_control(RID p_shaped, bool p_enabled) {
+ _THREAD_SAFE_METHOD_
+ ShapedTextData *sd = shaped_owner.getornull(p_shaped);
+ ERR_FAIL_COND(!sd);
+ if (sd->preserve_control != p_enabled) {
+ if (sd->parent != RID()) {
+ full_copy(sd);
+ }
+ sd->preserve_control = p_enabled;
+ invalidate(sd);
+ }
+}
+
+bool TextServerFallback::shaped_text_get_preserve_control(RID p_shaped) const {
+ _THREAD_SAFE_METHOD_
+ const ShapedTextData *sd = shaped_owner.getornull(p_shaped);
+ ERR_FAIL_COND_V(!sd, false);
+ return sd->preserve_control;
+}
+
+bool TextServerFallback::shaped_text_add_string(RID p_shaped, const String &p_text, const Vector<RID> &p_fonts, int p_size, const Dictionary &p_opentype_features, const String &p_language) {
+ _THREAD_SAFE_METHOD_
+ ShapedTextData *sd = shaped_owner.getornull(p_shaped);
+ ERR_FAIL_COND_V(!sd, false);
+ ERR_FAIL_COND_V(p_size <= 0, false);
+
+ if (p_text.empty()) {
+ return true;
+ }
+
+ if (sd->parent != RID()) {
+ full_copy(sd);
+ }
+
+ ShapedTextData::Span span;
+ span.start = sd->text.length();
+ span.end = span.start + p_text.length();
+ // Pre-sort fonts, push fonts with the language support first.
+ for (int i = 0; i < p_fonts.size(); i++) {
+ if (font_is_language_supported(p_fonts[i], p_language)) {
+ span.fonts.push_back(p_fonts[i]);
+ }
+ }
+ // Push the rest valid fonts.
+ for (int i = 0; i < p_fonts.size(); i++) {
+ if (!font_is_language_supported(p_fonts[i], p_language)) {
+ span.fonts.push_back(p_fonts[i]);
+ }
+ }
+ ERR_FAIL_COND_V(span.fonts.empty(), false);
+ span.font_size = p_size;
+ span.language = p_language;
+
+ sd->spans.push_back(span);
+ sd->text += p_text;
+ sd->end += p_text.length();
+ invalidate(sd);
+
+ return true;
+}
+
+bool TextServerFallback::shaped_text_add_object(RID p_shaped, Variant p_key, const Size2 &p_size, VAlign p_inline_align, int p_length) {
+ _THREAD_SAFE_METHOD_
+ ShapedTextData *sd = shaped_owner.getornull(p_shaped);
+ ERR_FAIL_COND_V(!sd, false);
+ ERR_FAIL_COND_V(p_key == Variant(), false);
+ ERR_FAIL_COND_V(sd->objects.has(p_key), false);
+
+ if (sd->parent != RID()) {
+ full_copy(sd);
+ }
+
+ ShapedTextData::Span span;
+ span.start = sd->text.length();
+ span.end = span.start + p_length;
+ span.embedded_key = p_key;
+
+ ShapedTextData::EmbeddedObject obj;
+ obj.inline_align = p_inline_align;
+ obj.rect.size = p_size;
+ obj.pos = span.start;
+
+ sd->spans.push_back(span);
+ sd->text += String::chr(0xfffc).repeat(p_length);
+ sd->end += p_length;
+ sd->objects[p_key] = obj;
+ invalidate(sd);
+
+ return true;
+}
+
+bool TextServerFallback::shaped_text_resize_object(RID p_shaped, Variant p_key, const Size2 &p_size, VAlign p_inline_align) {
+ _THREAD_SAFE_METHOD_
+ ShapedTextData *sd = shaped_owner.getornull(p_shaped);
+ ERR_FAIL_COND_V(!sd, false);
+ ERR_FAIL_COND_V(!sd->objects.has(p_key), false);
+ sd->objects[p_key].rect.size = p_size;
+ sd->objects[p_key].inline_align = p_inline_align;
+ if (sd->valid) {
+ // Recalc string metrics.
+ sd->ascent = 0;
+ sd->descent = 0;
+ sd->width = 0;
+ sd->upos = 0;
+ sd->uthk = 0;
+ for (int i = 0; i < sd->glyphs.size(); i++) {
+ Glyph gl = sd->glyphs[i];
+ Variant key;
+ if (gl.count == 1) {
+ for (Map<Variant, ShapedTextData::EmbeddedObject>::Element *E = sd->objects.front(); E; E = E->next()) {
+ if (E->get().pos == gl.start) {
+ key = E->key();
+ break;
+ }
+ }
+ }
+ if (key != Variant()) {
+ if (sd->orientation == ORIENTATION_HORIZONTAL) {
+ sd->objects[key].rect.position.x = sd->width;
+ sd->width += sd->objects[key].rect.size.x;
+ switch (sd->objects[key].inline_align) {
+ case VALIGN_TOP: {
+ sd->ascent = MAX(sd->ascent, sd->objects[key].rect.size.y);
+ } break;
+ case VALIGN_CENTER: {
+ sd->ascent = MAX(sd->ascent, sd->objects[key].rect.size.y / 2);
+ sd->descent = MAX(sd->descent, sd->objects[key].rect.size.y / 2);
+ } break;
+ case VALIGN_BOTTOM: {
+ sd->descent = MAX(sd->descent, sd->objects[key].rect.size.y);
+ } break;
+ }
+ sd->glyphs.write[i].advance = sd->objects[key].rect.size.x;
+ } else {
+ sd->objects[key].rect.position.y = sd->width;
+ sd->width += sd->objects[key].rect.size.y;
+ switch (sd->objects[key].inline_align) {
+ case VALIGN_TOP: {
+ sd->ascent = MAX(sd->ascent, sd->objects[key].rect.size.x);
+ } break;
+ case VALIGN_CENTER: {
+ sd->ascent = MAX(sd->ascent, sd->objects[key].rect.size.x / 2);
+ sd->descent = MAX(sd->descent, sd->objects[key].rect.size.x / 2);
+ } break;
+ case VALIGN_BOTTOM: {
+ sd->descent = MAX(sd->descent, sd->objects[key].rect.size.x);
+ } break;
+ }
+ sd->glyphs.write[i].advance = sd->objects[key].rect.size.y;
+ }
+ } else {
+ const FontDataFallback *fd = font_owner.getornull(gl.font_rid);
+ if (fd != nullptr) {
+ if (sd->orientation == ORIENTATION_HORIZONTAL) {
+ sd->ascent = MAX(sd->ascent, fd->get_ascent(gl.font_size));
+ sd->descent = MAX(sd->descent, fd->get_descent(gl.font_size));
+ } else {
+ sd->ascent = MAX(sd->ascent, fd->get_advance(gl.index, gl.font_size).x * 0.5);
+ sd->descent = MAX(sd->descent, fd->get_advance(gl.index, gl.font_size).x * 0.5);
+ }
+ sd->upos = MAX(sd->upos, font_get_underline_position(gl.font_rid, gl.font_size));
+ sd->uthk = MAX(sd->uthk, font_get_underline_thickness(gl.font_rid, gl.font_size));
+ } else if (sd->preserve_invalid || (sd->preserve_control && is_control(gl.index))) {
+ // Glyph not found, replace with hex code box.
+ if (sd->orientation == ORIENTATION_HORIZONTAL) {
+ sd->ascent = MAX(sd->ascent, get_hex_code_box_size(gl.font_size, gl.index).y * 0.75f);
+ sd->descent = MAX(sd->descent, get_hex_code_box_size(gl.font_size, gl.index).y * 0.25f);
+ } else {
+ sd->ascent = MAX(sd->ascent, get_hex_code_box_size(gl.font_size, gl.index).x * 0.5f);
+ sd->descent = MAX(sd->descent, get_hex_code_box_size(gl.font_size, gl.index).x * 0.5f);
+ }
+ }
+ sd->width += gl.advance * gl.repeat;
+ }
+ }
+
+ // Align embedded objects to baseline.
+ for (Map<Variant, ShapedTextData::EmbeddedObject>::Element *E = sd->objects.front(); E; E = E->next()) {
+ if ((E->get().pos >= sd->start) && (E->get().pos < sd->end)) {
+ if (sd->orientation == ORIENTATION_HORIZONTAL) {
+ switch (E->get().inline_align) {
+ case VALIGN_TOP: {
+ E->get().rect.position.y = -sd->ascent;
+ } break;
+ case VALIGN_CENTER: {
+ E->get().rect.position.y = -(E->get().rect.size.y / 2);
+ } break;
+ case VALIGN_BOTTOM: {
+ E->get().rect.position.y = sd->descent - E->get().rect.size.y;
+ } break;
+ }
+ } else {
+ switch (E->get().inline_align) {
+ case VALIGN_TOP: {
+ E->get().rect.position.x = -sd->ascent;
+ } break;
+ case VALIGN_CENTER: {
+ E->get().rect.position.x = -(E->get().rect.size.x / 2);
+ } break;
+ case VALIGN_BOTTOM: {
+ E->get().rect.position.x = sd->descent - E->get().rect.size.x;
+ } break;
+ }
+ }
+ }
+ }
+ }
+ return true;
+}
+
+RID TextServerFallback::shaped_text_substr(RID p_shaped, int p_start, int p_length) const {
+ _THREAD_SAFE_METHOD_
+ const ShapedTextData *sd = shaped_owner.getornull(p_shaped);
+ ERR_FAIL_COND_V(!sd, RID());
+ if (sd->parent != RID()) {
+ return shaped_text_substr(sd->parent, p_start, p_length);
+ }
+ if (!sd->valid) {
+ const_cast<TextServerFallback *>(this)->shaped_text_shape(p_shaped);
+ }
+ ERR_FAIL_COND_V(p_start < 0 || p_length < 0, RID());
+ ERR_FAIL_COND_V(sd->start > p_start || sd->end < p_start, RID());
+ ERR_FAIL_COND_V(sd->end < p_start + p_length, RID());
+
+ ShapedTextData *new_sd = memnew(ShapedTextData);
+ new_sd->parent = p_shaped;
+ new_sd->start = p_start;
+ new_sd->end = p_start + p_length;
+
+ new_sd->orientation = sd->orientation;
+ new_sd->direction = sd->direction;
+ new_sd->para_direction = sd->para_direction;
+ new_sd->line_breaks_valid = sd->line_breaks_valid;
+ new_sd->justification_ops_valid = sd->justification_ops_valid;
+ new_sd->sort_valid = false;
+ new_sd->upos = sd->upos;
+ new_sd->uthk = sd->uthk;
+
+ if (p_length > 0) {
+ new_sd->text = sd->text.substr(p_start, p_length);
+
+ for (int i = 0; i < sd->glyphs.size(); i++) {
+ if ((sd->glyphs[i].start >= new_sd->start) && (sd->glyphs[i].end <= new_sd->end)) {
+ Glyph gl = sd->glyphs[i];
+ Variant key;
+ if (gl.count == 1) {
+ for (Map<Variant, ShapedTextData::EmbeddedObject>::Element *E = sd->objects.front(); E; E = E->next()) {
+ if (E->get().pos == gl.start) {
+ key = E->key();
+ new_sd->objects[key] = E->get();
+ break;
+ }
+ }
+ }
+ if (key != Variant()) {
+ if (new_sd->orientation == ORIENTATION_HORIZONTAL) {
+ new_sd->objects[key].rect.position.x = new_sd->width;
+ new_sd->width += new_sd->objects[key].rect.size.x;
+ switch (new_sd->objects[key].inline_align) {
+ case VALIGN_TOP: {
+ new_sd->ascent = MAX(new_sd->ascent, new_sd->objects[key].rect.size.y);
+ } break;
+ case VALIGN_CENTER: {
+ new_sd->ascent = MAX(new_sd->ascent, new_sd->objects[key].rect.size.y / 2);
+ new_sd->descent = MAX(new_sd->descent, new_sd->objects[key].rect.size.y / 2);
+ } break;
+ case VALIGN_BOTTOM: {
+ new_sd->descent = MAX(new_sd->descent, new_sd->objects[key].rect.size.y);
+ } break;
+ }
+ } else {
+ new_sd->objects[key].rect.position.y = new_sd->width;
+ new_sd->width += new_sd->objects[key].rect.size.y;
+ switch (new_sd->objects[key].inline_align) {
+ case VALIGN_TOP: {
+ new_sd->ascent = MAX(new_sd->ascent, new_sd->objects[key].rect.size.x);
+ } break;
+ case VALIGN_CENTER: {
+ new_sd->ascent = MAX(new_sd->ascent, new_sd->objects[key].rect.size.x / 2);
+ new_sd->descent = MAX(new_sd->descent, new_sd->objects[key].rect.size.x / 2);
+ } break;
+ case VALIGN_BOTTOM: {
+ new_sd->descent = MAX(new_sd->descent, new_sd->objects[key].rect.size.x);
+ } break;
+ }
+ }
+ } else {
+ const FontDataFallback *fd = font_owner.getornull(gl.font_rid);
+ if (fd != nullptr) {
+ if (new_sd->orientation == ORIENTATION_HORIZONTAL) {
+ new_sd->ascent = MAX(new_sd->ascent, fd->get_ascent(gl.font_size));
+ new_sd->descent = MAX(new_sd->descent, fd->get_descent(gl.font_size));
+ } else {
+ new_sd->ascent = MAX(new_sd->ascent, fd->get_advance(gl.index, gl.font_size).x * 0.5);
+ new_sd->descent = MAX(new_sd->descent, fd->get_advance(gl.index, gl.font_size).x * 0.5);
+ }
+ } else if (new_sd->preserve_invalid || (new_sd->preserve_control && is_control(gl.index))) {
+ // Glyph not found, replace with hex code box.
+ if (new_sd->orientation == ORIENTATION_HORIZONTAL) {
+ new_sd->ascent = MAX(new_sd->ascent, get_hex_code_box_size(gl.font_size, gl.index).y * 0.75f);
+ new_sd->descent = MAX(new_sd->descent, get_hex_code_box_size(gl.font_size, gl.index).y * 0.25f);
+ } else {
+ new_sd->ascent = MAX(new_sd->ascent, get_hex_code_box_size(gl.font_size, gl.index).x * 0.5f);
+ new_sd->descent = MAX(new_sd->descent, get_hex_code_box_size(gl.font_size, gl.index).x * 0.5f);
+ }
+ }
+ new_sd->width += gl.advance * gl.repeat;
+ }
+ new_sd->glyphs.push_back(gl);
+ }
+ }
+
+ for (Map<Variant, ShapedTextData::EmbeddedObject>::Element *E = new_sd->objects.front(); E; E = E->next()) {
+ if ((E->get().pos >= new_sd->start) && (E->get().pos < new_sd->end)) {
+ if (sd->orientation == ORIENTATION_HORIZONTAL) {
+ switch (E->get().inline_align) {
+ case VALIGN_TOP: {
+ E->get().rect.position.y = -new_sd->ascent;
+ } break;
+ case VALIGN_CENTER: {
+ E->get().rect.position.y = -(E->get().rect.size.y / 2);
+ } break;
+ case VALIGN_BOTTOM: {
+ E->get().rect.position.y = new_sd->descent - E->get().rect.size.y;
+ } break;
+ }
+ } else {
+ switch (E->get().inline_align) {
+ case VALIGN_TOP: {
+ E->get().rect.position.x = -new_sd->ascent;
+ } break;
+ case VALIGN_CENTER: {
+ E->get().rect.position.x = -(E->get().rect.size.x / 2);
+ } break;
+ case VALIGN_BOTTOM: {
+ E->get().rect.position.x = new_sd->descent - E->get().rect.size.x;
+ } break;
+ }
+ }
+ }
+ }
+ }
+ new_sd->valid = true;
+
+ return shaped_owner.make_rid(new_sd);
+}
+
+RID TextServerFallback::shaped_text_get_parent(RID p_shaped) const {
+ _THREAD_SAFE_METHOD_
+ ShapedTextData *sd = shaped_owner.getornull(p_shaped);
+ ERR_FAIL_COND_V(!sd, RID());
+ return sd->parent;
+}
+
+float TextServerFallback::shaped_text_fit_to_width(RID p_shaped, float p_width, uint8_t /*JustificationFlag*/ p_jst_flags) {
+ _THREAD_SAFE_METHOD_
+ ShapedTextData *sd = shaped_owner.getornull(p_shaped);
+ ERR_FAIL_COND_V(!sd, 0.f);
+ if (!sd->valid) {
+ const_cast<TextServerFallback *>(this)->shaped_text_shape(p_shaped);
+ }
+ if (!sd->justification_ops_valid) {
+ const_cast<TextServerFallback *>(this)->shaped_text_update_justification_ops(p_shaped);
+ }
+
+ int start_pos = 0;
+ int end_pos = sd->glyphs.size() - 1;
+
+ if ((p_jst_flags & JUSTIFICATION_AFTER_LAST_TAB) == JUSTIFICATION_AFTER_LAST_TAB) {
+ int start, end, delta;
+ if (sd->para_direction == DIRECTION_LTR) {
+ start = sd->glyphs.size() - 1;
+ end = -1;
+ delta = -1;
+ } else {
+ start = 0;
+ end = sd->glyphs.size();
+ delta = +1;
+ }
+
+ for (int i = start; i != end; i += delta) {
+ if ((sd->glyphs[i].flags & GRAPHEME_IS_TAB) == GRAPHEME_IS_TAB) {
+ if (sd->para_direction == DIRECTION_LTR) {
+ start_pos = i;
+ break;
+ } else {
+ end_pos = i;
+ break;
+ }
+ }
+ }
+ }
+
+ if ((p_jst_flags & JUSTIFICATION_TRIM_EDGE_SPACES) == JUSTIFICATION_TRIM_EDGE_SPACES) {
+ while ((start_pos < end_pos) && ((sd->glyphs[start_pos].flags & GRAPHEME_IS_SPACE) == GRAPHEME_IS_SPACE || (sd->glyphs[start_pos].flags & GRAPHEME_IS_BREAK_HARD) == GRAPHEME_IS_BREAK_HARD || (sd->glyphs[start_pos].flags & GRAPHEME_IS_BREAK_SOFT) == GRAPHEME_IS_BREAK_SOFT)) {
+ sd->width -= sd->glyphs[start_pos].advance * sd->glyphs[start_pos].repeat;
+ sd->glyphs.write[start_pos].advance = 0;
+ start_pos += sd->glyphs[start_pos].count;
+ }
+ while ((start_pos < end_pos) && ((sd->glyphs[end_pos].flags & GRAPHEME_IS_SPACE) == GRAPHEME_IS_SPACE || (sd->glyphs[end_pos].flags & GRAPHEME_IS_BREAK_HARD) == GRAPHEME_IS_BREAK_HARD || (sd->glyphs[end_pos].flags & GRAPHEME_IS_BREAK_SOFT) == GRAPHEME_IS_BREAK_SOFT)) {
+ sd->width -= sd->glyphs[end_pos].advance * sd->glyphs[end_pos].repeat;
+ sd->glyphs.write[end_pos].advance = 0;
+ end_pos -= sd->glyphs[end_pos].count;
+ }
+ }
+
+ int space_count = 0;
+ for (int i = start_pos; i <= end_pos; i++) {
+ const Glyph &gl = sd->glyphs[i];
+ if (gl.count > 0) {
+ if ((gl.flags & GRAPHEME_IS_SPACE) == GRAPHEME_IS_SPACE) {
+ space_count++;
+ }
+ }
+ }
+
+ if ((space_count > 0) && ((p_jst_flags & JUSTIFICATION_WORD_BOUND) == JUSTIFICATION_WORD_BOUND)) {
+ float delta_width_per_space = (p_width - sd->width) / space_count;
+ for (int i = start_pos; i <= end_pos; i++) {
+ Glyph &gl = sd->glyphs.write[i];
+ if (gl.count > 0) {
+ if ((gl.flags & GRAPHEME_IS_SPACE) == GRAPHEME_IS_SPACE) {
+ float old_adv = gl.advance;
+ gl.advance = MAX(gl.advance + delta_width_per_space, 0.05 * gl.font_size);
+ sd->width += (gl.advance - old_adv);
+ }
+ }
+ }
+ }
+
+ return sd->width;
+}
+
+float TextServerFallback::shaped_text_tab_align(RID p_shaped, const Vector<float> &p_tab_stops) {
+ _THREAD_SAFE_METHOD_
+ ShapedTextData *sd = shaped_owner.getornull(p_shaped);
+ ERR_FAIL_COND_V(!sd, 0.f);
+ if (!sd->valid) {
+ const_cast<TextServerFallback *>(this)->shaped_text_shape(p_shaped);
+ }
+ if (!sd->line_breaks_valid) {
+ const_cast<TextServerFallback *>(this)->shaped_text_update_breaks(p_shaped);
+ }
+
+ int tab_index = 0;
+ float off = 0.f;
+
+ int start, end, delta;
+ if (sd->para_direction == DIRECTION_LTR) {
+ start = 0;
+ end = sd->glyphs.size();
+ delta = +1;
+ } else {
+ start = sd->glyphs.size() - 1;
+ end = -1;
+ delta = -1;
+ }
+
+ for (int i = start; i != end; i += delta) {
+ if ((sd->glyphs[i].flags & GRAPHEME_IS_TAB) == GRAPHEME_IS_TAB) {
+ float tab_off = 0.f;
+ while (tab_off <= off) {
+ tab_off += p_tab_stops[tab_index];
+ tab_index++;
+ if (tab_index >= p_tab_stops.size()) {
+ tab_index = 0;
+ }
+ }
+ float old_adv = sd->glyphs.write[i].advance;
+ sd->glyphs.write[i].advance = (tab_off - off);
+ sd->width += sd->glyphs.write[i].advance - old_adv;
+ off = 0;
+ continue;
+ }
+ off += sd->glyphs[i].advance * sd->glyphs[i].repeat;
+ }
+
+ return 0.f;
+}
+
+bool TextServerFallback::shaped_text_update_breaks(RID p_shaped) {
+ _THREAD_SAFE_METHOD_
+ ShapedTextData *sd = shaped_owner.getornull(p_shaped);
+ ERR_FAIL_COND_V(!sd, false);
+ if (!sd->valid) {
+ shaped_text_shape(p_shaped);
+ }
+
+ if (sd->line_breaks_valid) {
+ return true; // Noting to do.
+ }
+
+ for (int i = 0; i < sd->glyphs.size(); i++) {
+ if (sd->glyphs[i].count > 0) {
+ char32_t c = sd->text[sd->glyphs[i].start];
+ if (is_whitespace(c) && !is_linebreak(c)) {
+ sd->glyphs.write[i].flags |= GRAPHEME_IS_SPACE;
+ sd->glyphs.write[i].flags |= GRAPHEME_IS_BREAK_SOFT;
+ }
+ if (is_linebreak(c)) {
+ sd->glyphs.write[i].flags |= GRAPHEME_IS_BREAK_HARD;
+ }
+ if (c == 0x0009 || c == 0x000b) {
+ sd->glyphs.write[i].flags |= GRAPHEME_IS_TAB;
+ }
+
+ i += (sd->glyphs[i].count - 1);
+ }
+ }
+ sd->line_breaks_valid = true;
+ return sd->line_breaks_valid;
+}
+
+bool TextServerFallback::shaped_text_update_justification_ops(RID p_shaped) {
+ _THREAD_SAFE_METHOD_
+ ShapedTextData *sd = shaped_owner.getornull(p_shaped);
+ ERR_FAIL_COND_V(!sd, false);
+ if (!sd->valid) {
+ shaped_text_shape(p_shaped);
+ }
+ if (!sd->line_breaks_valid) {
+ shaped_text_update_breaks(p_shaped);
+ }
+
+ sd->justification_ops_valid = true; // Not supported by fallback server.
+ return true;
+}
+
+bool TextServerFallback::shaped_text_shape(RID p_shaped) {
+ _THREAD_SAFE_METHOD_
+ ShapedTextData *sd = shaped_owner.getornull(p_shaped);
+ ERR_FAIL_COND_V(!sd, false);
+ if (sd->valid) {
+ return true;
+ }
+
+ if (sd->parent != RID()) {
+ full_copy(sd);
+ }
+
+ // Cleanup.
+ sd->justification_ops_valid = false;
+ sd->line_breaks_valid = false;
+ sd->ascent = 0.f;
+ sd->descent = 0.f;
+ sd->width = 0.f;
+ sd->glyphs.clear();
+
+ if (sd->text.length() == 0) {
+ sd->valid = true;
+ return true;
+ }
+
+ // "Shape" string.
+ for (int i = 0; i < sd->spans.size(); i++) {
+ const ShapedTextData::Span &span = sd->spans[i];
+ if (span.embedded_key != Variant()) {
+ // Embedded object.
+ if (sd->orientation == ORIENTATION_HORIZONTAL) {
+ sd->objects[span.embedded_key].rect.position.x = sd->width;
+ sd->width += sd->objects[span.embedded_key].rect.size.x;
+ switch (sd->objects[span.embedded_key].inline_align) {
+ case VALIGN_TOP: {
+ sd->ascent = MAX(sd->ascent, sd->objects[span.embedded_key].rect.size.y);
+ } break;
+ case VALIGN_CENTER: {
+ sd->ascent = MAX(sd->ascent, sd->objects[span.embedded_key].rect.size.y / 2);
+ sd->descent = MAX(sd->descent, sd->objects[span.embedded_key].rect.size.y / 2);
+ } break;
+ case VALIGN_BOTTOM: {
+ sd->descent = MAX(sd->descent, sd->objects[span.embedded_key].rect.size.y);
+ } break;
+ }
+ } else {
+ sd->objects[span.embedded_key].rect.position.y = sd->width;
+ sd->width += sd->objects[span.embedded_key].rect.size.y;
+ switch (sd->objects[span.embedded_key].inline_align) {
+ case VALIGN_TOP: {
+ sd->ascent = MAX(sd->ascent, sd->objects[span.embedded_key].rect.size.x);
+ } break;
+ case VALIGN_CENTER: {
+ sd->ascent = MAX(sd->ascent, sd->objects[span.embedded_key].rect.size.x / 2);
+ sd->descent = MAX(sd->descent, sd->objects[span.embedded_key].rect.size.x / 2);
+ } break;
+ case VALIGN_BOTTOM: {
+ sd->descent = MAX(sd->descent, sd->objects[span.embedded_key].rect.size.x);
+ } break;
+ }
+ }
+ Glyph gl;
+ gl.start = span.start;
+ gl.end = span.end;
+ gl.count = 1;
+ gl.index = 0;
+ gl.flags = GRAPHEME_IS_VALID | GRAPHEME_IS_VIRTUAL;
+ if (sd->orientation == ORIENTATION_HORIZONTAL) {
+ gl.advance = sd->objects[span.embedded_key].rect.size.x;
+ } else {
+ gl.advance = sd->objects[span.embedded_key].rect.size.y;
+ }
+ sd->glyphs.push_back(gl);
+ } else {
+ // Text span.
+ for (int j = span.start; j < span.end; j++) {
+ const FontDataFallback *fd = nullptr;
+
+ Glyph gl;
+ gl.start = j;
+ gl.end = j + 1;
+ gl.count = 1;
+ gl.font_size = span.font_size;
+ gl.index = (uint32_t)sd->text[j]; // Use codepoint.
+ if (gl.index == 0x0009 || gl.index == 0x000b) {
+ gl.index = 0x0020;
+ }
+ if (!sd->preserve_control && is_control(gl.index)) {
+ gl.index = 0x0020;
+ }
+ // Select first font which has character (font are already sorted by span language).
+ for (int k = 0; k < span.fonts.size(); k++) {
+ fd = font_owner.getornull(span.fonts[k]);
+ if (fd != nullptr && fd->has_char(gl.index)) {
+ gl.font_rid = span.fonts[k];
+ break;
+ }
+ }
+
+ if (gl.font_rid != RID()) {
+ if (sd->text[j] != 0 && !is_linebreak(sd->text[j])) {
+ if (sd->orientation == ORIENTATION_HORIZONTAL) {
+ gl.advance = fd->get_advance(gl.index, gl.font_size).x;
+ gl.x_off = 0;
+ gl.y_off = 0;
+ sd->ascent = MAX(sd->ascent, fd->get_ascent(gl.font_size));
+ sd->descent = MAX(sd->descent, fd->get_descent(gl.font_size));
+ } else {
+ gl.advance = fd->get_advance(gl.index, gl.font_size).y;
+ gl.x_off = -fd->get_advance(gl.index, gl.font_size).x * 0.5;
+ gl.y_off = fd->get_ascent(gl.font_size);
+ sd->ascent = MAX(sd->ascent, fd->get_advance(gl.index, gl.font_size).x * 0.5);
+ sd->descent = MAX(sd->descent, fd->get_advance(gl.index, gl.font_size).x * 0.5);
+ }
+ }
+ sd->upos = MAX(sd->upos, font_get_underline_position(gl.font_rid, gl.font_size));
+ sd->uthk = MAX(sd->uthk, font_get_underline_thickness(gl.font_rid, gl.font_size));
+
+ // Add kerning to previous glyph.
+ if (sd->glyphs.size() > 0) {
+ Glyph &prev_gl = sd->glyphs.write[sd->glyphs.size() - 1];
+ if (prev_gl.font_rid == gl.font_rid && prev_gl.font_size == gl.font_size) {
+ if (sd->orientation == ORIENTATION_HORIZONTAL) {
+ prev_gl.advance += fd->get_kerning(prev_gl.index, gl.index, gl.font_size).x;
+ } else {
+ prev_gl.advance += fd->get_kerning(prev_gl.index, gl.index, gl.font_size).y;
+ }
+ }
+ }
+ } else if (sd->preserve_invalid || (sd->preserve_control && is_control(gl.index))) {
+ // Glyph not found, replace with hex code box.
+ if (sd->orientation == ORIENTATION_HORIZONTAL) {
+ gl.advance = get_hex_code_box_size(gl.font_size, gl.index).x;
+ sd->ascent = MAX(sd->ascent, get_hex_code_box_size(gl.font_size, gl.index).y * 0.75f);
+ sd->descent = MAX(sd->descent, get_hex_code_box_size(gl.font_size, gl.index).y * 0.25f);
+ } else {
+ gl.advance = get_hex_code_box_size(gl.font_size, gl.index).y;
+ sd->ascent = MAX(sd->ascent, get_hex_code_box_size(gl.font_size, gl.index).x * 0.5f);
+ sd->descent = MAX(sd->descent, get_hex_code_box_size(gl.font_size, gl.index).x * 0.5f);
+ }
+ }
+ sd->width += gl.advance;
+ sd->glyphs.push_back(gl);
+ }
+ }
+ }
+
+ // Align embedded objects to baseline.
+ for (Map<Variant, ShapedTextData::EmbeddedObject>::Element *E = sd->objects.front(); E; E = E->next()) {
+ if (sd->orientation == ORIENTATION_HORIZONTAL) {
+ switch (E->get().inline_align) {
+ case VALIGN_TOP: {
+ E->get().rect.position.y = -sd->ascent;
+ } break;
+ case VALIGN_CENTER: {
+ E->get().rect.position.y = -(E->get().rect.size.y / 2);
+ } break;
+ case VALIGN_BOTTOM: {
+ E->get().rect.position.y = sd->descent - E->get().rect.size.y;
+ } break;
+ }
+ } else {
+ switch (E->get().inline_align) {
+ case VALIGN_TOP: {
+ E->get().rect.position.x = -sd->ascent;
+ } break;
+ case VALIGN_CENTER: {
+ E->get().rect.position.x = -(E->get().rect.size.x / 2);
+ } break;
+ case VALIGN_BOTTOM: {
+ E->get().rect.position.x = sd->descent - E->get().rect.size.x;
+ } break;
+ }
+ }
+ }
+
+ sd->valid = true;
+ return sd->valid;
+}
+
+bool TextServerFallback::shaped_text_is_ready(RID p_shaped) const {
+ _THREAD_SAFE_METHOD_
+ const ShapedTextData *sd = shaped_owner.getornull(p_shaped);
+ ERR_FAIL_COND_V(!sd, false);
+ return sd->valid;
+}
+
+Vector<TextServer::Glyph> TextServerFallback::shaped_text_get_glyphs(RID p_shaped) const {
+ _THREAD_SAFE_METHOD_
+ const ShapedTextData *sd = shaped_owner.getornull(p_shaped);
+ ERR_FAIL_COND_V(!sd, Vector<TextServer::Glyph>());
+ if (!sd->valid) {
+ const_cast<TextServerFallback *>(this)->shaped_text_shape(p_shaped);
+ }
+ return sd->glyphs;
+}
+
+Vector2i TextServerFallback::shaped_text_get_range(RID p_shaped) const {
+ _THREAD_SAFE_METHOD_
+ const ShapedTextData *sd = shaped_owner.getornull(p_shaped);
+ ERR_FAIL_COND_V(!sd, Vector2i());
+ return Vector2(sd->start, sd->end);
+}
+
+Vector<TextServer::Glyph> TextServerFallback::shaped_text_sort_logical(RID p_shaped) {
+ _THREAD_SAFE_METHOD_
+ const ShapedTextData *sd = shaped_owner.getornull(p_shaped);
+ ERR_FAIL_COND_V(!sd, Vector<TextServer::Glyph>());
+ if (!sd->valid) {
+ const_cast<TextServerFallback *>(this)->shaped_text_shape(p_shaped);
+ }
+
+ return sd->glyphs; // Already in the logical order, return as is.
+}
+
+Array TextServerFallback::shaped_text_get_objects(RID p_shaped) const {
+ _THREAD_SAFE_METHOD_
+ Array ret;
+ const ShapedTextData *sd = shaped_owner.getornull(p_shaped);
+ ERR_FAIL_COND_V(!sd, ret);
+ for (const Map<Variant, ShapedTextData::EmbeddedObject>::Element *E = sd->objects.front(); E; E = E->next()) {
+ ret.push_back(E->key());
+ }
+
+ return ret;
+}
+
+Rect2 TextServerFallback::shaped_text_get_object_rect(RID p_shaped, Variant p_key) const {
+ _THREAD_SAFE_METHOD_
+ const ShapedTextData *sd = shaped_owner.getornull(p_shaped);
+ ERR_FAIL_COND_V(!sd, Rect2());
+ ERR_FAIL_COND_V(!sd->objects.has(p_key), Rect2());
+ if (!sd->valid) {
+ const_cast<TextServerFallback *>(this)->shaped_text_shape(p_shaped);
+ }
+ return sd->objects[p_key].rect;
+}
+
+Size2 TextServerFallback::shaped_text_get_size(RID p_shaped) const {
+ _THREAD_SAFE_METHOD_
+ const ShapedTextData *sd = shaped_owner.getornull(p_shaped);
+ ERR_FAIL_COND_V(!sd, Size2());
+ if (!sd->valid) {
+ const_cast<TextServerFallback *>(this)->shaped_text_shape(p_shaped);
+ }
+ if (sd->orientation == TextServer::ORIENTATION_HORIZONTAL) {
+ return Size2(sd->width, sd->ascent + sd->descent);
+ } else {
+ return Size2(sd->ascent + sd->descent, sd->width);
+ }
+}
+
+float TextServerFallback::shaped_text_get_ascent(RID p_shaped) const {
+ _THREAD_SAFE_METHOD_
+ const ShapedTextData *sd = shaped_owner.getornull(p_shaped);
+ ERR_FAIL_COND_V(!sd, 0.f);
+ if (!sd->valid) {
+ const_cast<TextServerFallback *>(this)->shaped_text_shape(p_shaped);
+ }
+ return sd->ascent;
+}
+
+float TextServerFallback::shaped_text_get_descent(RID p_shaped) const {
+ _THREAD_SAFE_METHOD_
+ const ShapedTextData *sd = shaped_owner.getornull(p_shaped);
+ ERR_FAIL_COND_V(!sd, 0.f);
+ if (!sd->valid) {
+ const_cast<TextServerFallback *>(this)->shaped_text_shape(p_shaped);
+ }
+ return sd->descent;
+}
+
+float TextServerFallback::shaped_text_get_width(RID p_shaped) const {
+ _THREAD_SAFE_METHOD_
+ const ShapedTextData *sd = shaped_owner.getornull(p_shaped);
+ ERR_FAIL_COND_V(!sd, 0.f);
+ if (!sd->valid) {
+ const_cast<TextServerFallback *>(this)->shaped_text_shape(p_shaped);
+ }
+ return sd->width;
+}
+
+float TextServerFallback::shaped_text_get_underline_position(RID p_shaped) const {
+ _THREAD_SAFE_METHOD_
+ const ShapedTextData *sd = shaped_owner.getornull(p_shaped);
+ ERR_FAIL_COND_V(!sd, 0.f);
+ if (!sd->valid) {
+ const_cast<TextServerFallback *>(this)->shaped_text_shape(p_shaped);
+ }
+
+ return sd->upos;
+}
+
+float TextServerFallback::shaped_text_get_underline_thickness(RID p_shaped) const {
+ _THREAD_SAFE_METHOD_
+ const ShapedTextData *sd = shaped_owner.getornull(p_shaped);
+ ERR_FAIL_COND_V(!sd, 0.f);
+ if (!sd->valid) {
+ const_cast<TextServerFallback *>(this)->shaped_text_shape(p_shaped);
+ }
+
+ return sd->uthk;
+}
+
+TextServer *TextServerFallback::create_func(Error &r_error, void *p_user_data) {
+ r_error = OK;
+ return memnew(TextServerFallback());
+}
+
+void TextServerFallback::register_server() {
+ TextServerManager::register_create_function(interface_name, interface_features, create_func, nullptr);
+}
diff --git a/modules/text_server_fb/text_server_fb.h b/modules/text_server_fb/text_server_fb.h
new file mode 100644
index 0000000000..56bb1f7bf9
--- /dev/null
+++ b/modules/text_server_fb/text_server_fb.h
@@ -0,0 +1,193 @@
+/*************************************************************************/
+/* text_server_fb.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 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 TEXT_SERVER_FALLBACK_H
+#define TEXT_SERVER_FALLBACK_H
+
+/*************************************************************************/
+/* Fallback Text Server provides simplified TS functionality, without */
+/* BiDi, shaping and advanced font features support. */
+/*************************************************************************/
+
+#include "servers/text_server.h"
+
+#include "core/templates/rid_owner.h"
+
+#include "scene/resources/texture.h"
+
+#include "font_fb.h"
+
+class TextServerFallback : public TextServer {
+ GDCLASS(TextServerFallback, TextServer);
+ _THREAD_SAFE_CLASS_
+
+ float oversampling = 1.f;
+ mutable RID_PtrOwner<FontDataFallback> font_owner;
+ mutable RID_PtrOwner<ShapedTextData> shaped_owner;
+
+ static String interface_name;
+ static uint32_t interface_features;
+
+protected:
+ static void _bind_methods(){};
+
+ void full_copy(ShapedTextData *p_shaped);
+ void invalidate(ShapedTextData *p_shaped);
+
+public:
+ virtual bool has_feature(Feature p_feature) override;
+ virtual String get_name() const override;
+
+ virtual void free(RID p_rid) override;
+ virtual bool has(RID p_rid) override;
+ virtual bool load_support_data(const String &p_filename) override;
+
+#ifdef TOOLS_ENABLED
+ virtual String get_support_data_filename() override { return ""; };
+ virtual String get_support_data_info() override { return "Not supported"; };
+ virtual bool save_support_data(const String &p_filename) override;
+#endif
+
+ virtual bool is_locale_right_to_left(const String &p_locale) override;
+
+ /* Font interface */
+ virtual RID create_font_system(const String &p_name, int p_base_size = 16) override;
+ virtual RID create_font_resource(const String &p_filename, int p_base_size = 16) override;
+ virtual RID create_font_memory(const uint8_t *p_data, size_t p_size, const String &p_type, int p_base_size = 16) override;
+
+ virtual float font_get_height(RID p_font, int p_size) const override;
+ virtual float font_get_ascent(RID p_font, int p_size) const override;
+ virtual float font_get_descent(RID p_font, int p_size) const override;
+
+ virtual float font_get_underline_position(RID p_font, int p_size) const override;
+ virtual float font_get_underline_thickness(RID p_font, int p_size) const override;
+
+ virtual void font_set_antialiased(RID p_font, bool p_antialiased) override;
+ virtual bool font_get_antialiased(RID p_font) const override;
+
+ virtual void font_set_hinting(RID p_font, Hinting p_hinting) override;
+ virtual Hinting font_get_hinting(RID p_font) const override;
+
+ virtual void font_set_force_autohinter(RID p_font, bool p_enabeld) override;
+ virtual bool font_get_force_autohinter(RID p_font) const override;
+
+ virtual bool font_has_char(RID p_font, char32_t p_char) const override;
+ virtual String font_get_supported_chars(RID p_font) const override;
+
+ virtual void font_set_distance_field_hint(RID p_font, bool p_distance_field) override;
+ virtual bool font_get_distance_field_hint(RID p_font) const override;
+
+ virtual bool font_has_outline(RID p_font) const override;
+ virtual float font_get_base_size(RID p_font) const override;
+
+ virtual bool font_is_language_supported(RID p_font, const String &p_language) const override;
+ virtual void font_set_language_support_override(RID p_font, const String &p_language, bool p_supported) override;
+ virtual bool font_get_language_support_override(RID p_font, const String &p_language) override;
+ virtual void font_remove_language_support_override(RID p_font, const String &p_language) override;
+ Vector<String> font_get_language_support_overrides(RID p_font) override;
+
+ virtual bool font_is_script_supported(RID p_font, const String &p_script) const override;
+ virtual void font_set_script_support_override(RID p_font, const String &p_script, bool p_supported) override;
+ virtual bool font_get_script_support_override(RID p_font, const String &p_script) override;
+ virtual void font_remove_script_support_override(RID p_font, const String &p_script) override;
+ Vector<String> font_get_script_support_overrides(RID p_font) override;
+
+ virtual uint32_t font_get_glyph_index(RID p_font, char32_t p_char, char32_t p_variation_selector = 0x0000) const override;
+ virtual Vector2 font_get_glyph_advance(RID p_font, uint32_t p_index, int p_size) const override;
+ virtual Vector2 font_get_glyph_kerning(RID p_font, uint32_t p_index_a, uint32_t p_index_b, int p_size) const override;
+
+ virtual Vector2 font_draw_glyph(RID p_font, RID p_canvas, int p_size, const Vector2 &p_pos, uint32_t p_index, const Color &p_color = Color(1, 1, 1)) const override;
+ virtual Vector2 font_draw_glyph_outline(RID p_font, RID p_canvas, int p_size, int p_outline_size, const Vector2 &p_pos, uint32_t p_index, const Color &p_color = Color(1, 1, 1)) const override;
+
+ virtual float font_get_oversampling() const override;
+ virtual void font_set_oversampling(float p_oversampling) override;
+
+ virtual Vector<String> get_system_fonts() const override;
+
+ /* Shaped text buffer interface */
+
+ virtual RID create_shaped_text(Direction p_direction = DIRECTION_AUTO, Orientation p_orientation = ORIENTATION_HORIZONTAL) override;
+
+ virtual void shaped_text_clear(RID p_shaped) override;
+
+ virtual void shaped_text_set_direction(RID p_shaped, Direction p_direction = DIRECTION_AUTO) override;
+ virtual Direction shaped_text_get_direction(RID p_shaped) const override;
+
+ virtual void shaped_text_set_bidi_override(RID p_shaped, const Vector<Vector2i> &p_override) override;
+
+ virtual void shaped_text_set_orientation(RID p_shaped, Orientation p_orientation = ORIENTATION_HORIZONTAL) override;
+ virtual Orientation shaped_text_get_orientation(RID p_shaped) const override;
+
+ virtual void shaped_text_set_preserve_invalid(RID p_shaped, bool p_enabled) override;
+ virtual bool shaped_text_get_preserve_invalid(RID p_shaped) const override;
+
+ virtual void shaped_text_set_preserve_control(RID p_shaped, bool p_enabled) override;
+ virtual bool shaped_text_get_preserve_control(RID p_shaped) const override;
+
+ virtual bool shaped_text_add_string(RID p_shaped, const String &p_text, const Vector<RID> &p_fonts, int p_size, const Dictionary &p_opentype_features = Dictionary(), const String &p_language = "") override;
+ virtual bool shaped_text_add_object(RID p_shaped, Variant p_key, const Size2 &p_size, VAlign p_inline_align = VALIGN_CENTER, int p_length = 1) override;
+ virtual bool shaped_text_resize_object(RID p_shaped, Variant p_key, const Size2 &p_size, VAlign p_inline_align = VALIGN_CENTER) override;
+
+ virtual RID shaped_text_substr(RID p_shaped, int p_start, int p_length) const override;
+ virtual RID shaped_text_get_parent(RID p_shaped) const override;
+
+ virtual float shaped_text_fit_to_width(RID p_shaped, float p_width, uint8_t /*JustificationFlag*/ p_jst_flags = JUSTIFICATION_WORD_BOUND | JUSTIFICATION_KASHIDA) override;
+ virtual float shaped_text_tab_align(RID p_shaped, const Vector<float> &p_tab_stops) override;
+
+ virtual bool shaped_text_shape(RID p_shaped) override;
+ virtual bool shaped_text_update_breaks(RID p_shaped) override;
+ virtual bool shaped_text_update_justification_ops(RID p_shaped) override;
+
+ virtual bool shaped_text_is_ready(RID p_shaped) const override;
+
+ virtual Vector<Glyph> shaped_text_get_glyphs(RID p_shaped) const override;
+
+ virtual Vector2i shaped_text_get_range(RID p_shaped) const override;
+
+ virtual Vector<Glyph> shaped_text_sort_logical(RID p_shaped) override;
+
+ virtual Array shaped_text_get_objects(RID p_shaped) const override;
+ virtual Rect2 shaped_text_get_object_rect(RID p_shaped, Variant p_key) const override;
+
+ virtual Size2 shaped_text_get_size(RID p_shaped) const override;
+ virtual float shaped_text_get_ascent(RID p_shaped) const override;
+ virtual float shaped_text_get_descent(RID p_shaped) const override;
+ virtual float shaped_text_get_width(RID p_shaped) const override;
+ virtual float shaped_text_get_underline_position(RID p_shaped) const override;
+ virtual float shaped_text_get_underline_thickness(RID p_shaped) const override;
+
+ static TextServer *create_func(Error &r_error, void *p_user_data);
+ static void register_server();
+
+ TextServerFallback(){};
+ ~TextServerFallback(){};
+};
+
+#endif // TEXT_SERVER_FALLBACK_H