Selaa lähdekoodia

[Complex Text Layouts] Implement TextServer interface. Implement Fallback TextServer.

bruvzg 5 vuotta sitten
vanhempi
commit
493da99269

+ 17 - 0
COPYRIGHT.txt

@@ -157,6 +157,11 @@ Copyright: 2018, Source Foundry Authors
   2003, Bitstream Inc.
 License: Expat and Bitstream Vera Fonts Copyright
 
+Files: ./thirdparty/fonts/Tamsyn*.png
+Comment: Tamsyn font
+Copyright: 2015, Scott Fial
+License: Tamsyn
+
 Files: ./thirdparty/freetype/
 Comment: The FreeType Project
 Copyright: 1996-2020, David Turner, Robert Wilhelm, and Werner Lemberg.
@@ -1633,6 +1638,18 @@ License: OFL-1.1
  DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE.
 
+License: Tamsyn
+ Tamsyn font is free.  You are hereby granted permission to use, copy, modify,
+ and distribute it as you see fit.
+ .
+ Tamsyn font is provided "as is" without any express or implied warranty.
+ .
+ The author makes no representations about the suitability of this font for
+ a particular purpose.
+ .
+ In no event will the author be held liable for damages arising from the use
+ of this font.
+
 License: Zlib
  This software is provided 'as-is', without any express or implied
  warranty.  In no event will the authors be held liable for any damages

+ 1 - 0
core/os/main_loop.cpp

@@ -47,6 +47,7 @@ void MainLoop::_bind_methods() {
 	BIND_CONSTANT(NOTIFICATION_APPLICATION_PAUSED);
 	BIND_CONSTANT(NOTIFICATION_APPLICATION_FOCUS_IN);
 	BIND_CONSTANT(NOTIFICATION_APPLICATION_FOCUS_OUT);
+	BIND_CONSTANT(NOTIFICATION_TEXT_SERVER_CHANGED);
 
 	ADD_SIGNAL(MethodInfo("on_request_permissions_result", PropertyInfo(Variant::STRING, "permission"), PropertyInfo(Variant::BOOL, "granted")));
 };

+ 1 - 0
core/os/main_loop.h

@@ -56,6 +56,7 @@ public:
 		NOTIFICATION_APPLICATION_PAUSED = 2015,
 		NOTIFICATION_APPLICATION_FOCUS_IN = 2016,
 		NOTIFICATION_APPLICATION_FOCUS_OUT = 2017,
+		NOTIFICATION_TEXT_SERVER_CHANGED = 2018,
 	};
 
 	virtual void init();

+ 2 - 3
core/string/ustring.cpp

@@ -209,7 +209,6 @@ void CharString::copy_from(const char *p_cstr) {
 /*  String                                                               */
 /*************************************************************************/
 
-//TODO: move to TextServer
 //kind of poor should be rewritten properly
 String String::word_wrap(int p_chars_per_line) const {
 	int from = 0;
@@ -4796,7 +4795,7 @@ Vector<uint8_t> String::to_utf16_buffer() const {
 	Char16String charstr = s->utf16();
 
 	Vector<uint8_t> retval;
-	size_t len = charstr.length() * 2;
+	size_t len = charstr.length() * sizeof(char16_t);
 	retval.resize(len);
 	uint8_t *w = retval.ptrw();
 	copymem(w, (const void *)charstr.ptr(), len);
@@ -4811,7 +4810,7 @@ Vector<uint8_t> String::to_utf32_buffer() const {
 	}
 
 	Vector<uint8_t> retval;
-	size_t len = s->length() * 4;
+	size_t len = s->length() * sizeof(char32_t);
 	retval.resize(len);
 	uint8_t *w = retval.ptrw();
 	copymem(w, (const void *)s->ptr(), len);

+ 4 - 3
core/string/ustring.h

@@ -28,8 +28,9 @@
 /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
 /*************************************************************************/
 
-#ifndef USTRING_H
-#define USTRING_H
+#ifndef USTRING_GODOT_H
+#define USTRING_GODOT_H
+// Note: Renamed to avoid conflict with ICU header with the same name.
 
 #include "core/templates/cowdata.h"
 #include "core/templates/vector.h"
@@ -555,4 +556,4 @@ _FORCE_INLINE_ Vector<String> sarray(P... p_args) {
 	return arr;
 }
 
-#endif // USTRING_H
+#endif // USTRING_GODOT_H

+ 126 - 0
core/templates/lru.h

@@ -0,0 +1,126 @@
+/*************************************************************************/
+/*  lru.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 LRU_H
+#define LRU_H
+
+#include "core/math/math_funcs.h"
+#include "hash_map.h"
+#include "list.h"
+
+template <class TKey, class TData>
+class LRUCache {
+private:
+	struct Pair {
+		TKey key;
+		TData data;
+
+		Pair() {}
+		Pair(const TKey &p_key, const TData &p_data) :
+				key(p_key),
+				data(p_data) {
+		}
+	};
+
+	typedef typename List<Pair>::Element *Element;
+
+	List<Pair> _list;
+	HashMap<TKey, Element> _map;
+	size_t capacity;
+
+public:
+	const TData *insert(const TKey &p_key, const TData &p_value) {
+		Element *e = _map.getptr(p_key);
+		Element n = _list.push_front(Pair(p_key, p_value));
+
+		if (e) {
+			_list.erase(*e);
+			_map.erase(p_key);
+		}
+		_map[p_key] = _list.front();
+
+		while (_map.size() > capacity) {
+			Element d = _list.back();
+			_map.erase(d->get().key);
+			_list.pop_back();
+		}
+
+		return &n->get().data;
+	}
+
+	void clear() {
+		_map.clear();
+		_list.clear();
+	}
+
+	bool has(const TKey &p_key) const {
+		return _map.getptr(p_key);
+	}
+
+	const TData &get(const TKey &p_key) {
+		Element *e = _map.getptr(p_key);
+		CRASH_COND(!e);
+		_list.move_to_front(*e);
+		return (*e)->get().data;
+	};
+
+	const TData *getptr(const TKey &p_key) {
+		Element *e = _map.getptr(p_key);
+		if (!e) {
+			return nullptr;
+		} else {
+			_list.move_to_front(*e);
+			return &(*e)->get().data;
+		}
+	}
+
+	_FORCE_INLINE_ size_t get_capacity() const { return capacity; }
+
+	void set_capacity(size_t p_capacity) {
+		if (capacity > 0) {
+			capacity = p_capacity;
+			while (_map.size() > capacity) {
+				Element d = _list.back();
+				_map.erase(d->get().key);
+				_list.pop_back();
+			}
+		}
+	}
+
+	LRUCache() {
+		capacity = 64;
+	}
+
+	LRUCache(int p_capacity) {
+		capacity = p_capacity;
+	}
+};
+
+#endif

+ 2 - 2
core/variant/variant_call.cpp

@@ -418,7 +418,7 @@ struct _VariantCall {
 		String s;
 		if (p_instance->size() > 0) {
 			const uint8_t *r = p_instance->ptr();
-			s.parse_utf16((const char16_t *)r, p_instance->size() / 2);
+			s.parse_utf16((const char16_t *)r, floor((double)p_instance->size() / (double)sizeof(char16_t)));
 		}
 		return s;
 	}
@@ -427,7 +427,7 @@ struct _VariantCall {
 		String s;
 		if (p_instance->size() > 0) {
 			const uint8_t *r = p_instance->ptr();
-			s = String((const char32_t *)r, p_instance->size() / 4);
+			s = String((const char32_t *)r, floor((double)p_instance->size() / (double)sizeof(char32_t)));
 		}
 		return s;
 	}

+ 117 - 7
main/main.cpp

@@ -72,6 +72,7 @@
 #include "servers/register_server_types.h"
 #include "servers/rendering/rendering_server_raster.h"
 #include "servers/rendering/rendering_server_wrap_mt.h"
+#include "servers/text_server.h"
 #include "servers/xr_server.h"
 
 #ifdef TESTS_ENABLED
@@ -113,6 +114,7 @@ static DisplayServer *display_server = nullptr;
 static RenderingServer *rendering_server = nullptr;
 static CameraServer *camera_server = nullptr;
 static XRServer *xr_server = nullptr;
+static TextServerManager *tsman = nullptr;
 static PhysicsServer3D *physics_server = nullptr;
 static PhysicsServer2D *physics_2d_server = nullptr;
 static NavigationServer3D *navigation_server = nullptr;
@@ -122,6 +124,7 @@ static bool _start_success = false;
 
 // Drivers
 
+static int text_driver_idx = -1;
 static int display_driver_idx = -1;
 static int audio_driver_idx = -1;
 
@@ -304,7 +307,18 @@ void Main::print_help(const char *p_binary) {
 		OS::get_singleton()->print(")");
 	}
 	OS::get_singleton()->print("].\n");
+
 	OS::get_singleton()->print("  --rendering-driver <driver>      Rendering driver (depends on display driver).\n");
+
+	OS::get_singleton()->print("  --text-driver <driver>           Text driver (Fonts, BiDi, shaping) [");
+	for (int i = 0; i < TextServerManager::get_interface_count(); i++) {
+		if (i > 0) {
+			OS::get_singleton()->print(", ");
+		}
+		OS::get_singleton()->print("'%s'", TextServerManager::get_interface_name(i).utf8().get_data());
+	}
+	OS::get_singleton()->print("].\n");
+
 	OS::get_singleton()->print("\n");
 
 #ifndef SERVER_ENABLED
@@ -544,6 +558,7 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
 
 	I = args.front();
 
+	String text_driver = "";
 	String display_driver = "";
 	String audio_driver = "";
 	String tablet_driver = "";
@@ -649,6 +664,40 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
 				OS::get_singleton()->print("Missing audio driver argument, aborting.\n");
 				goto error;
 			}
+		} else if (I->get() == "--text-driver") {
+			if (I->next()) {
+				text_driver = I->next()->get();
+				bool found = false;
+				for (int i = 0; i < TextServerManager::get_interface_count(); i++) {
+					if (text_driver == TextServerManager::get_interface_name(i)) {
+						found = true;
+					}
+				}
+
+				if (!found) {
+					OS::get_singleton()->print("Unknown text driver '%s', aborting.\nValid options are ",
+							text_driver.utf8().get_data());
+
+					for (int i = 0; i < TextServerManager::get_interface_count(); i++) {
+						if (i == TextServerManager::get_interface_count() - 1) {
+							OS::get_singleton()->print(" and ");
+						} else if (i != 0) {
+							OS::get_singleton()->print(", ");
+						}
+
+						OS::get_singleton()->print("'%s'", TextServerManager::get_interface_name(i).utf8().get_data());
+					}
+
+					OS::get_singleton()->print(".\n");
+
+					goto error;
+				}
+
+				N = I->next()->next();
+			} else {
+				OS::get_singleton()->print("Missing text driver argument, aborting.\n");
+				goto error;
+			}
 
 		} else if (I->get() == "--display-driver") { // force video driver
 
@@ -1159,6 +1208,11 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
 
 	OS::get_singleton()->set_cmdline(execpath, main_args);
 
+	GLOBAL_DEF("display/window/text_name", "");
+	if (text_driver == "") {
+		text_driver = GLOBAL_GET("display/window/text_name");
+	}
+
 	GLOBAL_DEF("rendering/quality/driver/driver_name", "Vulkan");
 	ProjectSettings::get_singleton()->set_custom_property_info("rendering/quality/driver/driver_name",
 			PropertyInfo(Variant::STRING,
@@ -1289,6 +1343,35 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
 		OS::get_singleton()->_render_thread_mode = OS::RenderThreadMode(rtm);
 	}
 
+	/* Determine text driver */
+
+	if (text_driver != "") {
+		/* Load user selected text server. */
+		for (int i = 0; i < TextServerManager::get_interface_count(); i++) {
+			if (text_driver == TextServerManager::get_interface_name(i)) {
+				text_driver_idx = i;
+				break;
+			}
+		}
+	}
+
+	if (text_driver_idx < 0) {
+		/* If not selected, use one with the most features available. */
+		int max_features = 0;
+		for (int i = 0; i < TextServerManager::get_interface_count(); i++) {
+			uint32_t ftrs = TextServerManager::get_interface_features(i);
+			int features = 0;
+			while (ftrs) {
+				features += ftrs & 1;
+				ftrs >>= 1;
+			}
+			if (features >= max_features) {
+				max_features = features;
+				text_driver_idx = i;
+			}
+		}
+	}
+
 	/* Determine audio and video drivers */
 
 	for (int i = 0; i < DisplayServer::get_create_function_count(); i++) {
@@ -1388,6 +1471,7 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
 
 error:
 
+	text_driver = "";
 	display_driver = "";
 	audio_driver = "";
 	tablet_driver = "";
@@ -1449,6 +1533,30 @@ Error Main::setup2(Thread::ID p_main_tid_override) {
 		Thread::_main_thread_id = p_main_tid_override;
 	}
 
+	/* Initialize Text Server */
+
+	{
+		tsman = memnew(TextServerManager);
+		Error err;
+		TextServer *text_server = TextServerManager::initialize(text_driver_idx, err);
+		if (err != OK || text_server == nullptr) {
+			for (int i = 0; i < TextServerManager::get_interface_count(); i++) {
+				if (i == text_driver_idx) {
+					continue; //don't try the same twice
+				}
+				text_server = TextServerManager::initialize(i, err);
+				if (err == OK && text_server != nullptr) {
+					break;
+				}
+			}
+		}
+
+		if (err != OK || text_server == nullptr) {
+			ERR_PRINT("Unable to create TextServer, all text drivers failed.");
+			return err;
+		}
+	}
+
 	/* Initialize Input */
 
 	input = memnew(Input);
@@ -1459,23 +1567,21 @@ Error Main::setup2(Thread::ID p_main_tid_override) {
 		String rendering_driver; // temp broken
 
 		Error err;
-		display_server = DisplayServer::create(display_driver_idx, rendering_driver, window_mode, window_flags,
-				window_size, err);
-		if (err != OK) {
+		display_server = DisplayServer::create(display_driver_idx, rendering_driver, window_mode, window_flags, window_size, err);
+		if (err != OK || display_server == nullptr) {
 			//ok i guess we can't use this display server, try other ones
 			for (int i = 0; i < DisplayServer::get_create_function_count(); i++) {
 				if (i == display_driver_idx) {
 					continue; //don't try the same twice
 				}
-				display_server = DisplayServer::create(display_driver_idx, rendering_driver, window_mode, window_flags,
-						window_size, err);
-				if (err == OK) {
+				display_server = DisplayServer::create(i, rendering_driver, window_mode, window_flags, window_size, err);
+				if (err == OK && display_server != nullptr) {
 					break;
 				}
 			}
 		}
 
-		if (!display_server || err != OK) {
+		if (err != OK || display_server == nullptr) {
 			ERR_PRINT("Unable to create DisplayServer, all display drivers failed.");
 			return err;
 		}
@@ -2572,6 +2678,10 @@ void Main::cleanup() {
 	finalize_navigation_server();
 	finalize_display();
 
+	if (tsman) {
+		memdelete(tsman);
+	}
+
 	if (input) {
 		memdelete(input);
 	}

+ 12 - 0
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")

+ 357 - 0
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));
+}

+ 107 - 0
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

+ 11 - 0
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

+ 731 - 0
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);
+	}
+}

+ 172 - 0
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

+ 80 - 0
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

+ 43 - 0
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() {
+}

+ 40 - 0
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

+ 1362 - 0
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);
+}

+ 193 - 0
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

+ 8 - 1
servers/register_server_types.cpp

@@ -65,9 +65,9 @@
 #include "rendering/rasterizer.h"
 #include "rendering/rendering_device.h"
 #include "rendering/rendering_device_binds.h"
-
 #include "rendering_server.h"
 #include "servers/rendering/shader_types.h"
+#include "text_server.h"
 #include "xr/xr_interface.h"
 #include "xr/xr_positional_tracker.h"
 #include "xr_server.h"
@@ -102,6 +102,11 @@ void register_server_types() {
 	ClassDB::register_virtual_class<DisplayServer>();
 	ClassDB::register_virtual_class<RenderingServer>();
 	ClassDB::register_class<AudioServer>();
+
+	ClassDB::register_class<TextServerManager>();
+	ClassDB::register_virtual_class<TextServer>();
+	TextServer::initialize_hex_code_box_fonts();
+
 	ClassDB::register_virtual_class<PhysicsServer2D>();
 	ClassDB::register_virtual_class<PhysicsServer3D>();
 	ClassDB::register_virtual_class<NavigationServer2D>();
@@ -209,6 +214,7 @@ void register_server_types() {
 
 void unregister_server_types() {
 	memdelete(shader_types);
+	TextServer::finish_hex_code_box_fonts();
 }
 
 void register_server_singletons() {
@@ -219,6 +225,7 @@ void register_server_singletons() {
 	Engine::get_singleton()->add_singleton(Engine::Singleton("PhysicsServer3D", PhysicsServer3D::get_singleton()));
 	Engine::get_singleton()->add_singleton(Engine::Singleton("NavigationServer2D", NavigationServer2D::get_singleton_mut()));
 	Engine::get_singleton()->add_singleton(Engine::Singleton("NavigationServer3D", NavigationServer3D::get_singleton_mut()));
+	Engine::get_singleton()->add_singleton(Engine::Singleton("TextServerManager", TextServerManager::get_singleton()));
 	Engine::get_singleton()->add_singleton(Engine::Singleton("XRServer", XRServer::get_singleton()));
 	Engine::get_singleton()->add_singleton(Engine::Singleton("CameraServer", CameraServer::get_singleton()));
 }

+ 1246 - 0
servers/text_server.cpp

@@ -0,0 +1,1246 @@
+/*************************************************************************/
+/*  text_server.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 "servers/text_server.h"
+#include "scene/main/canvas_item.h"
+
+TextServerManager *TextServerManager::singleton = nullptr;
+TextServer *TextServerManager::server = nullptr;
+TextServerManager::TextServerCreate TextServerManager::server_create_functions[TextServerManager::MAX_SERVERS];
+int TextServerManager::server_create_count = 0;
+
+void TextServerManager::_bind_methods() {
+	ClassDB::bind_method(D_METHOD("get_interface_count"), &TextServerManager::_get_interface_count);
+	ClassDB::bind_method(D_METHOD("get_interface_name", "index"), &TextServerManager::_get_interface_name);
+	ClassDB::bind_method(D_METHOD("get_interface_features", "index"), &TextServerManager::_get_interface_features);
+	ClassDB::bind_method(D_METHOD("get_interface", "index"), &TextServerManager::_get_interface);
+	ClassDB::bind_method(D_METHOD("get_interfaces"), &TextServerManager::_get_interfaces);
+	ClassDB::bind_method(D_METHOD("find_interface", "name"), &TextServerManager::_find_interface);
+
+	ClassDB::bind_method(D_METHOD("set_primary_interface", "index"), &TextServerManager::_set_primary_interface);
+	ClassDB::bind_method(D_METHOD("get_primary_interface"), &TextServerManager::_get_primary_interface);
+}
+
+void TextServerManager::register_create_function(const String &p_name, uint32_t p_features, TextServerManager::CreateFunction p_function, void *p_user_data) {
+	ERR_FAIL_COND(server_create_count == MAX_SERVERS);
+	server_create_functions[server_create_count].name = p_name;
+	server_create_functions[server_create_count].create_function = p_function;
+	server_create_functions[server_create_count].user_data = p_user_data;
+	server_create_functions[server_create_count].features = p_features;
+	server_create_count++;
+}
+
+int TextServerManager::get_interface_count() {
+	return server_create_count;
+}
+
+String TextServerManager::get_interface_name(int p_index) {
+	ERR_FAIL_INDEX_V(p_index, server_create_count, String());
+	return server_create_functions[p_index].name;
+}
+
+uint32_t TextServerManager::get_interface_features(int p_index) {
+	ERR_FAIL_INDEX_V(p_index, server_create_count, 0);
+	return server_create_functions[p_index].features;
+}
+
+TextServer *TextServerManager::initialize(int p_index, Error &r_error) {
+	ERR_FAIL_INDEX_V(p_index, server_create_count, nullptr);
+	if (server_create_functions[p_index].instance == nullptr) {
+		server_create_functions[p_index].instance = server_create_functions[p_index].create_function(r_error, server_create_functions[p_index].user_data);
+		if (server_create_functions[p_index].instance != nullptr) {
+			server_create_functions[p_index].instance->load_support_data(""); // Try loading default data.
+		}
+	}
+	if (server_create_functions[p_index].instance != nullptr) {
+		server = server_create_functions[p_index].instance;
+		if (OS::get_singleton()->get_main_loop()) {
+			OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_TEXT_SERVER_CHANGED);
+		}
+	}
+	return server_create_functions[p_index].instance;
+}
+
+TextServer *TextServerManager::get_primary_interface() {
+	return server;
+}
+
+int TextServerManager::_get_interface_count() const {
+	return server_create_count;
+}
+
+String TextServerManager::_get_interface_name(int p_index) const {
+	return get_interface_name(p_index);
+}
+
+uint32_t TextServerManager::_get_interface_features(int p_index) const {
+	return get_interface_features(p_index);
+}
+
+TextServer *TextServerManager::_get_interface(int p_index) const {
+	ERR_FAIL_INDEX_V(p_index, server_create_count, nullptr);
+	if (server_create_functions[p_index].instance == nullptr) {
+		Error error;
+		server_create_functions[p_index].instance = server_create_functions[p_index].create_function(error, server_create_functions[p_index].user_data);
+		if (server_create_functions[p_index].instance != nullptr) {
+			server_create_functions[p_index].instance->load_support_data(""); // Try loading default data.
+		}
+	}
+	return server_create_functions[p_index].instance;
+}
+
+TextServer *TextServerManager::_find_interface(const String &p_name) const {
+	for (int i = 0; i < server_create_count; i++) {
+		if (server_create_functions[i].name == p_name) {
+			return _get_interface(i);
+		}
+	}
+	return nullptr;
+}
+
+Array TextServerManager::_get_interfaces() const {
+	Array ret;
+
+	for (int i = 0; i < server_create_count; i++) {
+		Dictionary iface_info;
+
+		iface_info["id"] = i;
+		iface_info["name"] = server_create_functions[i].name;
+
+		ret.push_back(iface_info);
+	};
+
+	return ret;
+};
+
+bool TextServerManager::_set_primary_interface(int p_index) {
+	Error error;
+	TextServerManager::initialize(p_index, error);
+	return (error == OK);
+}
+
+TextServer *TextServerManager::_get_primary_interface() const {
+	return server;
+}
+
+TextServerManager::TextServerManager() {
+	singleton = this;
+}
+
+TextServerManager::~TextServerManager() {
+	singleton = nullptr;
+	for (int i = 0; i < server_create_count; i++) {
+		if (server_create_functions[i].instance != nullptr) {
+			memdelete(server_create_functions[i].instance);
+			server_create_functions[i].instance = nullptr;
+		}
+	}
+}
+
+/*************************************************************************/
+
+bool TextServer::Glyph::operator==(const Glyph &p_a) const {
+	return (p_a.index == index) && (p_a.font_rid == font_rid) && (p_a.font_size == font_size) && (p_a.start == start);
+}
+
+bool TextServer::Glyph::operator!=(const Glyph &p_a) const {
+	return (p_a.index != index) || (p_a.font_rid != font_rid) || (p_a.font_size != font_size) || (p_a.start != start);
+}
+
+bool TextServer::Glyph::operator<(const Glyph &p_a) const {
+	if (p_a.start == start) {
+		if (p_a.count == count) {
+			if ((p_a.flags & GRAPHEME_IS_VIRTUAL) == GRAPHEME_IS_VIRTUAL) {
+				return true;
+			} else {
+				return false;
+			}
+		}
+		return p_a.count > count;
+	}
+	return p_a.start < start;
+}
+
+bool TextServer::Glyph::operator>(const Glyph &p_a) const {
+	if (p_a.start == start) {
+		if (p_a.count == count) {
+			if ((p_a.flags & GRAPHEME_IS_VIRTUAL) == GRAPHEME_IS_VIRTUAL) {
+				return false;
+			} else {
+				return true;
+			}
+		}
+		return p_a.count < count;
+	}
+	return p_a.start > start;
+}
+
+void TextServer::_bind_methods() {
+	ClassDB::bind_method(D_METHOD("has_feature", "feature"), &TextServer::has_feature);
+	ClassDB::bind_method(D_METHOD("get_name"), &TextServer::get_name);
+	ClassDB::bind_method(D_METHOD("load_support_data", "filename"), &TextServer::load_support_data);
+
+	ClassDB::bind_method(D_METHOD("is_locale_right_to_left", "locale"), &TextServer::is_locale_right_to_left);
+
+	ClassDB::bind_method(D_METHOD("name_to_tag", "name"), &TextServer::name_to_tag);
+	ClassDB::bind_method(D_METHOD("tag_to_name", "tag"), &TextServer::tag_to_name);
+
+	ClassDB::bind_method(D_METHOD("has", "rid"), &TextServer::has);
+	ClassDB::bind_method(D_METHOD("free_rid", "rid"), &TextServer::free); // shouldn't conflict with Object::free()
+
+	/* Font Interface */
+	ClassDB::bind_method(D_METHOD("create_font_system", "name", "base_size"), &TextServer::create_font_system, DEFVAL(16));
+	ClassDB::bind_method(D_METHOD("create_font_resource", "filename", "base_size"), &TextServer::create_font_resource, DEFVAL(16));
+	ClassDB::bind_method(D_METHOD("create_font_memory", "data", "type", "base_size"), &TextServer::_create_font_memory, DEFVAL(16));
+
+	ClassDB::bind_method(D_METHOD("font_get_height", "font", "size"), &TextServer::font_get_height);
+	ClassDB::bind_method(D_METHOD("font_get_ascent", "font", "size"), &TextServer::font_get_ascent);
+	ClassDB::bind_method(D_METHOD("font_get_descent", "font", "size"), &TextServer::font_get_descent);
+
+	ClassDB::bind_method(D_METHOD("font_get_underline_position", "font", "size"), &TextServer::font_get_underline_position);
+	ClassDB::bind_method(D_METHOD("font_get_underline_thickness", "font", "size"), &TextServer::font_get_underline_thickness);
+
+	ClassDB::bind_method(D_METHOD("font_set_antialiased", "font", "antialiased"), &TextServer::font_set_antialiased);
+	ClassDB::bind_method(D_METHOD("font_get_antialiased", "font"), &TextServer::font_get_antialiased);
+
+	ClassDB::bind_method(D_METHOD("font_get_feature_list", "font"), &TextServer::font_get_feature_list);
+
+	ClassDB::bind_method(D_METHOD("font_set_hinting", "font", "hinting"), &TextServer::font_set_hinting);
+	ClassDB::bind_method(D_METHOD("font_get_hinting", "font"), &TextServer::font_get_hinting);
+
+	ClassDB::bind_method(D_METHOD("font_set_distance_field_hint", "font", "distance_field"), &TextServer::font_set_distance_field_hint);
+	ClassDB::bind_method(D_METHOD("font_get_distance_field_hint", "font"), &TextServer::font_get_distance_field_hint);
+
+	ClassDB::bind_method(D_METHOD("font_set_force_autohinter", "font", "enabeld"), &TextServer::font_set_force_autohinter);
+	ClassDB::bind_method(D_METHOD("font_get_force_autohinter", "font"), &TextServer::font_get_force_autohinter);
+
+	ClassDB::bind_method(D_METHOD("font_has_char", "font", "char"), &TextServer::font_has_char);
+	ClassDB::bind_method(D_METHOD("font_get_supported_chars", "font"), &TextServer::font_get_supported_chars);
+
+	ClassDB::bind_method(D_METHOD("font_has_outline", "font"), &TextServer::font_has_outline);
+	ClassDB::bind_method(D_METHOD("font_get_base_size", "font"), &TextServer::font_get_base_size);
+
+	ClassDB::bind_method(D_METHOD("font_is_language_supported", "font", "language"), &TextServer::font_is_language_supported);
+	ClassDB::bind_method(D_METHOD("font_set_language_support_override", "font", "language", "supported"), &TextServer::font_set_language_support_override);
+
+	ClassDB::bind_method(D_METHOD("font_get_language_support_override", "font", "language"), &TextServer::font_get_language_support_override);
+	ClassDB::bind_method(D_METHOD("font_remove_language_support_override", "font", "language"), &TextServer::font_remove_language_support_override);
+	ClassDB::bind_method(D_METHOD("font_get_language_support_overrides", "font"), &TextServer::font_get_language_support_overrides);
+
+	ClassDB::bind_method(D_METHOD("font_is_script_supported", "font", "script"), &TextServer::font_is_script_supported);
+	ClassDB::bind_method(D_METHOD("font_set_script_support_override", "font", "script", "supported"), &TextServer::font_set_script_support_override);
+
+	ClassDB::bind_method(D_METHOD("font_get_script_support_override", "font", "script"), &TextServer::font_get_script_support_override);
+	ClassDB::bind_method(D_METHOD("font_remove_script_support_override", "font", "script"), &TextServer::font_remove_script_support_override);
+	ClassDB::bind_method(D_METHOD("font_get_script_support_overrides", "font"), &TextServer::font_get_script_support_overrides);
+
+	ClassDB::bind_method(D_METHOD("font_get_glyph_index", "font", "char", "variation_selector"), &TextServer::font_get_glyph_index, DEFVAL(0x0000));
+	ClassDB::bind_method(D_METHOD("font_get_glyph_advance", "font", "index", "size"), &TextServer::font_get_glyph_advance);
+	ClassDB::bind_method(D_METHOD("font_get_glyph_kerning", "font", "index_a", "index_b", "size"), &TextServer::font_get_glyph_kerning);
+
+	ClassDB::bind_method(D_METHOD("font_draw_glyph", "font", "canvas", "size", "pos", "index", "color"), &TextServer::font_draw_glyph, DEFVAL(Color(1, 1, 1)));
+	ClassDB::bind_method(D_METHOD("font_draw_glyph_outline", "font", "canvas", "size", "outline_size", "pos", "index", "color"), &TextServer::font_draw_glyph_outline, DEFVAL(Color(1, 1, 1)));
+
+	ClassDB::bind_method(D_METHOD("font_get_oversampling"), &TextServer::font_get_oversampling);
+	ClassDB::bind_method(D_METHOD("font_set_oversampling", "oversampling"), &TextServer::font_set_oversampling);
+
+	ClassDB::bind_method(D_METHOD("get_system_fonts"), &TextServer::get_system_fonts);
+
+	ClassDB::bind_method(D_METHOD("get_hex_code_box_size", "size", "index"), &TextServer::get_hex_code_box_size);
+	ClassDB::bind_method(D_METHOD("draw_hex_code_box", "canvas", "size", "pos", "index", "color"), &TextServer::draw_hex_code_box);
+
+	/* Shaped text buffer interface */
+
+	ClassDB::bind_method(D_METHOD("create_shaped_text", "direction", "orientation"), &TextServer::create_shaped_text, DEFVAL(DIRECTION_AUTO), DEFVAL(ORIENTATION_HORIZONTAL));
+
+	ClassDB::bind_method(D_METHOD("shaped_text_clear"), &TextServer::shaped_text_clear);
+
+	ClassDB::bind_method(D_METHOD("shaped_text_set_direction", "shaped", "direction"), &TextServer::shaped_text_set_direction, DEFVAL(DIRECTION_AUTO));
+	ClassDB::bind_method(D_METHOD("shaped_text_get_direction", "shaped"), &TextServer::shaped_text_get_direction);
+
+	ClassDB::bind_method(D_METHOD("shaped_text_set_bidi_override", "shaped", "override"), &TextServer::_shaped_text_set_bidi_override);
+
+	ClassDB::bind_method(D_METHOD("shaped_text_set_orientation", "shaped", "orientation"), &TextServer::shaped_text_set_orientation, DEFVAL(ORIENTATION_HORIZONTAL));
+	ClassDB::bind_method(D_METHOD("shaped_text_get_orientation", "shaped"), &TextServer::shaped_text_get_orientation);
+
+	ClassDB::bind_method(D_METHOD("shaped_text_set_preserve_invalid", "shaped", "enabled"), &TextServer::shaped_text_set_preserve_invalid);
+	ClassDB::bind_method(D_METHOD("shaped_text_get_preserve_invalid", "shaped"), &TextServer::shaped_text_get_preserve_invalid);
+
+	ClassDB::bind_method(D_METHOD("shaped_text_set_preserve_control", "shaped", "enabled"), &TextServer::shaped_text_set_preserve_control);
+	ClassDB::bind_method(D_METHOD("shaped_text_get_preserve_control", "shaped"), &TextServer::shaped_text_get_preserve_control);
+
+	ClassDB::bind_method(D_METHOD("shaped_text_add_string", "shaped", "text", "fonts", "size", "opentype_features", "language"), &TextServer::shaped_text_add_string, DEFVAL(Dictionary()), DEFVAL(""));
+	ClassDB::bind_method(D_METHOD("shaped_text_add_object", "shaped", "key", "size", "inline_align", "length"), &TextServer::shaped_text_add_object, DEFVAL(VALIGN_CENTER), DEFVAL(1));
+	ClassDB::bind_method(D_METHOD("shaped_text_resize_object", "shaped", "key", "size", "inline_align"), &TextServer::shaped_text_resize_object, DEFVAL(VALIGN_CENTER));
+
+	ClassDB::bind_method(D_METHOD("shaped_text_substr", "shaped", "start", "length"), &TextServer::shaped_text_substr);
+	ClassDB::bind_method(D_METHOD("shaped_text_get_parent", "shaped"), &TextServer::shaped_text_get_parent);
+	ClassDB::bind_method(D_METHOD("shaped_text_fit_to_width", "shaped", "width", "jst_flags"), &TextServer::shaped_text_fit_to_width, DEFVAL(JUSTIFICATION_WORD_BOUND | JUSTIFICATION_KASHIDA));
+	ClassDB::bind_method(D_METHOD("shaped_text_tab_align", "shaped", "tab_stops"), &TextServer::shaped_text_tab_align);
+
+	ClassDB::bind_method(D_METHOD("shaped_text_shape", "shaped"), &TextServer::shaped_text_shape);
+	ClassDB::bind_method(D_METHOD("shaped_text_is_ready", "shaped"), &TextServer::shaped_text_is_ready);
+
+	ClassDB::bind_method(D_METHOD("shaped_text_get_glyphs", "shaped"), &TextServer::_shaped_text_get_glyphs);
+
+	ClassDB::bind_method(D_METHOD("shaped_text_get_range", "shaped"), &TextServer::shaped_text_get_range);
+	ClassDB::bind_method(D_METHOD("shaped_text_get_line_breaks_adv", "shaped", "width", "start", "once", "break_flags"), &TextServer::_shaped_text_get_line_breaks_adv, DEFVAL(0), DEFVAL(true), DEFVAL(BREAK_MANDATORY | BREAK_WORD_BOUND));
+	ClassDB::bind_method(D_METHOD("shaped_text_get_line_breaks", "shaped", "width", "start", "break_flags"), &TextServer::_shaped_text_get_line_breaks, DEFVAL(0), DEFVAL(BREAK_MANDATORY | BREAK_WORD_BOUND));
+	ClassDB::bind_method(D_METHOD("shaped_text_get_word_breaks", "shaped"), &TextServer::_shaped_text_get_word_breaks);
+	ClassDB::bind_method(D_METHOD("shaped_text_get_objects", "shaped"), &TextServer::shaped_text_get_objects);
+	ClassDB::bind_method(D_METHOD("shaped_text_get_object_rect", "shaped", "key"), &TextServer::shaped_text_get_object_rect);
+
+	ClassDB::bind_method(D_METHOD("shaped_text_get_size", "shaped"), &TextServer::shaped_text_get_size);
+	ClassDB::bind_method(D_METHOD("shaped_text_get_ascent", "shaped"), &TextServer::shaped_text_get_ascent);
+	ClassDB::bind_method(D_METHOD("shaped_text_get_descent", "shaped"), &TextServer::shaped_text_get_descent);
+	ClassDB::bind_method(D_METHOD("shaped_text_get_width", "shaped"), &TextServer::shaped_text_get_width);
+	ClassDB::bind_method(D_METHOD("shaped_text_get_underline_position", "shaped"), &TextServer::shaped_text_get_underline_position);
+	ClassDB::bind_method(D_METHOD("shaped_text_get_underline_thickness", "shaped"), &TextServer::shaped_text_get_underline_thickness);
+
+	ClassDB::bind_method(D_METHOD("shaped_text_get_carets", "shaped", "position"), &TextServer::_shaped_text_get_carets);
+	ClassDB::bind_method(D_METHOD("shaped_text_get_selection", "shaped", "start", "end"), &TextServer::_shaped_text_get_selection);
+
+	ClassDB::bind_method(D_METHOD("shaped_text_hit_test_grapheme", "shaped", "coords"), &TextServer::shaped_text_hit_test_grapheme);
+	ClassDB::bind_method(D_METHOD("shaped_text_hit_test_position", "shaped", "coords"), &TextServer::shaped_text_hit_test_position);
+
+	ClassDB::bind_method(D_METHOD("shaped_text_next_grapheme_pos", "shaped", "pos"), &TextServer::shaped_text_next_grapheme_pos);
+	ClassDB::bind_method(D_METHOD("shaped_text_prev_grapheme_pos", "shaped", "pos"), &TextServer::shaped_text_prev_grapheme_pos);
+
+	ClassDB::bind_method(D_METHOD("shaped_text_draw", "shaped", "canvas", "pos", "clip_l", "clip_r", "color"), &TextServer::shaped_text_draw, DEFVAL(-1), DEFVAL(-1), DEFVAL(Color(1, 1, 1)));
+	ClassDB::bind_method(D_METHOD("shaped_text_draw_outline", "shaped", "canvas", "pos", "clip_l", "clip_r", "outline_size", "color"), &TextServer::shaped_text_draw_outline, DEFVAL(-1), DEFVAL(-1), DEFVAL(1), DEFVAL(Color(1, 1, 1)));
+
+	ClassDB::bind_method(D_METHOD("shaped_text_get_dominant_direciton_in_range", "shaped", "start", "end"), &TextServer::shaped_text_get_dominant_direciton_in_range);
+
+	ClassDB::bind_method(D_METHOD("format_number", "number", "language"), &TextServer::format_number, DEFVAL(""));
+	ClassDB::bind_method(D_METHOD("parse_number", "number", "language"), &TextServer::parse_number, DEFVAL(""));
+	ClassDB::bind_method(D_METHOD("percent_sign", "language"), &TextServer::percent_sign, DEFVAL(""));
+
+	/* Direction */
+	BIND_ENUM_CONSTANT(DIRECTION_AUTO);
+	BIND_ENUM_CONSTANT(DIRECTION_LTR);
+	BIND_ENUM_CONSTANT(DIRECTION_RTL);
+
+	/* Orientation */
+	BIND_ENUM_CONSTANT(ORIENTATION_HORIZONTAL);
+	BIND_ENUM_CONSTANT(ORIENTATION_VERTICAL);
+
+	/* JustificationFlag */
+	BIND_ENUM_CONSTANT(JUSTIFICATION_NONE);
+	BIND_ENUM_CONSTANT(JUSTIFICATION_KASHIDA);
+	BIND_ENUM_CONSTANT(JUSTIFICATION_WORD_BOUND);
+	BIND_ENUM_CONSTANT(JUSTIFICATION_TRIM_EDGE_SPACES);
+	BIND_ENUM_CONSTANT(JUSTIFICATION_AFTER_LAST_TAB);
+
+	/* LineBreakFlag */
+	BIND_ENUM_CONSTANT(BREAK_NONE);
+	BIND_ENUM_CONSTANT(BREAK_MANDATORY);
+	BIND_ENUM_CONSTANT(BREAK_WORD_BOUND);
+	BIND_ENUM_CONSTANT(BREAK_GRAPHEME_BOUND);
+
+	/* GraphemeFlag */
+	BIND_ENUM_CONSTANT(GRAPHEME_IS_RTL);
+	BIND_ENUM_CONSTANT(GRAPHEME_IS_VIRTUAL);
+	BIND_ENUM_CONSTANT(GRAPHEME_IS_SPACE);
+	BIND_ENUM_CONSTANT(GRAPHEME_IS_BREAK_HARD);
+	BIND_ENUM_CONSTANT(GRAPHEME_IS_BREAK_SOFT);
+	BIND_ENUM_CONSTANT(GRAPHEME_IS_TAB);
+	BIND_ENUM_CONSTANT(GRAPHEME_IS_ELONGATION);
+
+	/* Hinting */
+	BIND_ENUM_CONSTANT(HINTING_NONE);
+	BIND_ENUM_CONSTANT(HINTING_LIGHT);
+	BIND_ENUM_CONSTANT(HINTING_NORMAL);
+
+	/* Feature */
+	BIND_ENUM_CONSTANT(FEATURE_BIDI_LAYOUT);
+	BIND_ENUM_CONSTANT(FEATURE_VERTICAL_LAYOUT);
+	BIND_ENUM_CONSTANT(FEATURE_SHAPING);
+	BIND_ENUM_CONSTANT(FEATURE_KASHIDA_JUSTIFICATION);
+	BIND_ENUM_CONSTANT(FEATURE_BREAK_ITERATORS);
+	BIND_ENUM_CONSTANT(FEATURE_FONT_SYSTEM);
+	BIND_ENUM_CONSTANT(FEATURE_USE_SUPPORT_DATA);
+}
+
+Vector3 TextServer::hex_code_box_font_size[2] = { Vector3(5, 5, 1), Vector3(10, 10, 2) };
+Ref<CanvasTexture> TextServer::hex_code_box_font_tex[2] = { nullptr, nullptr };
+
+void TextServer::initialize_hex_code_box_fonts() {
+	static unsigned int tamsyn5x9_png_len = 175;
+	static unsigned char tamsyn5x9_png[] = {
+		0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d,
+		0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0x05,
+		0x04, 0x03, 0x00, 0x00, 0x00, 0x20, 0x7c, 0x76, 0xda, 0x00, 0x00, 0x00,
+		0x0f, 0x50, 0x4c, 0x54, 0x45, 0xfd, 0x07, 0x00, 0x00, 0x00, 0x00, 0x06,
+		0x7e, 0x74, 0x00, 0x40, 0xc6, 0xff, 0xff, 0xff, 0x47, 0x9a, 0xd4, 0xc7,
+		0x00, 0x00, 0x00, 0x01, 0x74, 0x52, 0x4e, 0x53, 0x00, 0x40, 0xe6, 0xd8,
+		0x66, 0x00, 0x00, 0x00, 0x4e, 0x49, 0x44, 0x41, 0x54, 0x08, 0x1d, 0x05,
+		0xc1, 0x21, 0x01, 0x00, 0x00, 0x00, 0x83, 0x30, 0x04, 0xc1, 0x10, 0xef,
+		0x9f, 0xe9, 0x1b, 0x86, 0x2c, 0x17, 0xb9, 0xcc, 0x65, 0x0c, 0x73, 0x38,
+		0xc7, 0xe6, 0x22, 0x19, 0x88, 0x98, 0x10, 0x48, 0x4a, 0x29, 0x85, 0x14,
+		0x02, 0x89, 0x10, 0xa3, 0x1c, 0x0b, 0x31, 0xd6, 0xe6, 0x08, 0x69, 0x39,
+		0x48, 0x44, 0xa0, 0x0d, 0x4a, 0x22, 0xa1, 0x94, 0x42, 0x0a, 0x01, 0x63,
+		0x6d, 0x0e, 0x72, 0x18, 0x61, 0x8c, 0x74, 0x38, 0xc7, 0x26, 0x1c, 0xf3,
+		0x71, 0x16, 0x15, 0x27, 0x6a, 0xc2, 0x2f, 0x00, 0x00, 0x00, 0x00, 0x49,
+		0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82
+	};
+
+	static unsigned int tamsyn10x20_png_len = 270;
+	static unsigned char tamsyn10x20_png[] = {
+		0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d,
+		0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0xa0, 0x00, 0x00, 0x00, 0x0a,
+		0x04, 0x03, 0x00, 0x00, 0x00, 0xc1, 0x66, 0x48, 0x96, 0x00, 0x00, 0x00,
+		0x0f, 0x50, 0x4c, 0x54, 0x45, 0x00, 0x00, 0x00, 0xf9, 0x07, 0x00, 0x5d,
+		0x71, 0xa5, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x49, 0xdb, 0xcb, 0x7f,
+		0x00, 0x00, 0x00, 0x01, 0x74, 0x52, 0x4e, 0x53, 0x00, 0x40, 0xe6, 0xd8,
+		0x66, 0x00, 0x00, 0x00, 0xad, 0x49, 0x44, 0x41, 0x54, 0x28, 0xcf, 0xa5,
+		0x92, 0x4b, 0x0e, 0x03, 0x31, 0x08, 0x43, 0xdf, 0x82, 0x83, 0x79, 0xe1,
+		0xfb, 0x9f, 0xa9, 0x0b, 0x3e, 0x61, 0xa6, 0x1f, 0x55, 0xad, 0x14, 0x31,
+		0x66, 0x42, 0x1c, 0x70, 0x0c, 0xb6, 0x00, 0x01, 0xb6, 0x08, 0xdb, 0x00,
+		0x8d, 0xc2, 0x14, 0xb2, 0x55, 0xa1, 0xfe, 0x09, 0xc2, 0x26, 0xdc, 0x25,
+		0x75, 0x22, 0x97, 0x1a, 0x25, 0x77, 0x28, 0x31, 0x02, 0x80, 0xc8, 0xdd,
+		0x2c, 0x11, 0x1a, 0x54, 0x9f, 0xc8, 0xa2, 0x8a, 0x06, 0xa9, 0x93, 0x22,
+		0xbd, 0xd4, 0xd0, 0x0c, 0xcf, 0x81, 0x2b, 0xca, 0xbb, 0x83, 0xe0, 0x10,
+		0xe6, 0xad, 0xff, 0x10, 0x2a, 0x66, 0x34, 0x41, 0x58, 0x35, 0x54, 0x49,
+		0x5a, 0x63, 0xa5, 0xc2, 0x87, 0xab, 0x52, 0x76, 0x9a, 0xba, 0xc6, 0xf4,
+		0x75, 0x7a, 0x9e, 0x3c, 0x46, 0x86, 0x5c, 0xa3, 0xfd, 0x87, 0x0e, 0x75,
+		0x08, 0x7b, 0xee, 0x7e, 0xea, 0x21, 0x5c, 0x4f, 0xf6, 0xc5, 0xc8, 0x4b,
+		0xb9, 0x11, 0xf2, 0xd6, 0xe1, 0x8f, 0x84, 0x62, 0x7b, 0x67, 0xf9, 0x24,
+		0xde, 0x6d, 0xbc, 0xb2, 0xcd, 0xb1, 0xf3, 0xf2, 0x2f, 0xe8, 0xe2, 0xe4,
+		0xae, 0x4b, 0x4f, 0xcf, 0x2b, 0xdc, 0x8d, 0x0d, 0xf0, 0x00, 0x8f, 0x22,
+		0x26, 0x65, 0x75, 0x8a, 0xe6, 0x84, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45,
+		0x4e, 0x44, 0xae, 0x42, 0x60, 0x82
+	};
+
+	if (RenderingServer::get_singleton() != nullptr) {
+		Vector<uint8_t> hex_box_data;
+
+		Ref<Image> image;
+		image.instance();
+
+		Ref<ImageTexture> hex_code_image_tex[2];
+
+		hex_box_data.resize(tamsyn5x9_png_len);
+		memcpy(hex_box_data.ptrw(), tamsyn5x9_png, tamsyn5x9_png_len);
+		image->load_png_from_buffer(hex_box_data);
+		hex_code_image_tex[0].instance();
+		hex_code_image_tex[0]->create_from_image(image);
+		hex_code_box_font_tex[0].instance();
+		hex_code_box_font_tex[0]->set_diffuse_texture(hex_code_image_tex[0]);
+		hex_code_box_font_tex[0]->set_texture_filter(CanvasItem::TEXTURE_FILTER_NEAREST);
+		hex_box_data.clear();
+
+		hex_box_data.resize(tamsyn10x20_png_len);
+		memcpy(hex_box_data.ptrw(), tamsyn10x20_png, tamsyn10x20_png_len);
+		image->load_png_from_buffer(hex_box_data);
+		hex_code_image_tex[1].instance();
+		hex_code_image_tex[1]->create_from_image(image);
+		hex_code_box_font_tex[1].instance();
+		hex_code_box_font_tex[1]->set_diffuse_texture(hex_code_image_tex[1]);
+		hex_code_box_font_tex[1]->set_texture_filter(CanvasItem::TEXTURE_FILTER_NEAREST);
+		hex_box_data.clear();
+	}
+}
+
+void TextServer::finish_hex_code_box_fonts() {
+	if (hex_code_box_font_tex[0].is_valid()) {
+		hex_code_box_font_tex[0].unref();
+	}
+	if (hex_code_box_font_tex[1].is_valid()) {
+		hex_code_box_font_tex[1].unref();
+	}
+}
+
+Vector2 TextServer::get_hex_code_box_size(int p_size, char32_t p_index) const {
+	int fnt = (p_size < 20) ? 0 : 1;
+
+	float w = ((p_index <= 0xFF) ? 1 : ((p_index <= 0xFFFF) ? 2 : 3)) * hex_code_box_font_size[fnt].x;
+	float h = 2 * hex_code_box_font_size[fnt].y;
+	return Vector2(w + 4, h + 3 + 2 * hex_code_box_font_size[fnt].z);
+}
+
+void TextServer::draw_hex_code_box(RID p_canvas, int p_size, const Vector2 &p_pos, char32_t p_index, const Color &p_color) const {
+	int fnt = (p_size < 20) ? 0 : 1;
+
+	ERR_FAIL_COND(hex_code_box_font_tex[fnt].is_null());
+
+	uint8_t a = p_index & 0x0F;
+	uint8_t b = (p_index >> 4) & 0x0F;
+	uint8_t c = (p_index >> 8) & 0x0F;
+	uint8_t d = (p_index >> 12) & 0x0F;
+	uint8_t e = (p_index >> 16) & 0x0F;
+	uint8_t f = (p_index >> 20) & 0x0F;
+
+	Vector2 pos = p_pos;
+	Rect2 dest = Rect2(Vector2(), Vector2(hex_code_box_font_size[fnt].x, hex_code_box_font_size[fnt].y));
+
+	float w = ((p_index <= 0xFF) ? 1 : ((p_index <= 0xFFFF) ? 2 : 3)) * hex_code_box_font_size[fnt].x;
+	float h = 2 * hex_code_box_font_size[fnt].y;
+
+	pos.y -= Math::floor((h + 3 + hex_code_box_font_size[fnt].z) * 0.75);
+
+	RenderingServer::get_singleton()->canvas_item_add_rect(p_canvas, Rect2(pos + Point2(0, 0), Size2(1, h + 2 + 2 * hex_code_box_font_size[fnt].z)), p_color);
+	RenderingServer::get_singleton()->canvas_item_add_rect(p_canvas, Rect2(pos + Point2(w + 2, 0), Size2(1, h + 2 + 2 * hex_code_box_font_size[fnt].z)), p_color);
+	RenderingServer::get_singleton()->canvas_item_add_rect(p_canvas, Rect2(pos + Point2(0, 0), Size2(w + 2, 1)), p_color);
+	RenderingServer::get_singleton()->canvas_item_add_rect(p_canvas, Rect2(pos + Point2(0, h + 2 + 2 * hex_code_box_font_size[fnt].z), Size2(w + 2, 1)), p_color);
+
+	pos += Point2(2, 2);
+	if (p_index <= 0xFF) {
+		dest.position = pos + Vector2(hex_code_box_font_size[fnt].x, hex_code_box_font_size[fnt].y) * Point2(0, 0);
+		RenderingServer::get_singleton()->canvas_item_add_texture_rect_region(p_canvas, dest, hex_code_box_font_tex[fnt]->get_rid(), Rect2(Point2(b * hex_code_box_font_size[fnt].x, 0), dest.size), p_color, false, false);
+		dest.position = pos + Vector2(hex_code_box_font_size[fnt].x, hex_code_box_font_size[fnt].y) * Point2(0, 1) + Point2(0, hex_code_box_font_size[fnt].z);
+		RenderingServer::get_singleton()->canvas_item_add_texture_rect_region(p_canvas, dest, hex_code_box_font_tex[fnt]->get_rid(), Rect2(Point2(a * hex_code_box_font_size[fnt].x, 0), dest.size), p_color, false, false);
+	} else if (p_index <= 0xFFFF) {
+		dest.position = pos + Vector2(hex_code_box_font_size[fnt].x, hex_code_box_font_size[fnt].y) * Point2(0, 0);
+		RenderingServer::get_singleton()->canvas_item_add_texture_rect_region(p_canvas, dest, hex_code_box_font_tex[fnt]->get_rid(), Rect2(Point2(d * hex_code_box_font_size[fnt].x, 0), dest.size), p_color, false, false);
+		dest.position = pos + Vector2(hex_code_box_font_size[fnt].x, hex_code_box_font_size[fnt].y) * Point2(1, 0);
+		RenderingServer::get_singleton()->canvas_item_add_texture_rect_region(p_canvas, dest, hex_code_box_font_tex[fnt]->get_rid(), Rect2(Point2(c * hex_code_box_font_size[fnt].x, 0), dest.size), p_color, false, false);
+		dest.position = pos + Vector2(hex_code_box_font_size[fnt].x, hex_code_box_font_size[fnt].y) * Point2(0, 1) + Point2(0, hex_code_box_font_size[fnt].z);
+		RenderingServer::get_singleton()->canvas_item_add_texture_rect_region(p_canvas, dest, hex_code_box_font_tex[fnt]->get_rid(), Rect2(Point2(b * hex_code_box_font_size[fnt].x, 0), dest.size), p_color, false, false);
+		dest.position = pos + Vector2(hex_code_box_font_size[fnt].x, hex_code_box_font_size[fnt].y) * Point2(1, 1) + Point2(0, hex_code_box_font_size[fnt].z);
+		RenderingServer::get_singleton()->canvas_item_add_texture_rect_region(p_canvas, dest, hex_code_box_font_tex[fnt]->get_rid(), Rect2(Point2(a * hex_code_box_font_size[fnt].x, 0), dest.size), p_color, false, false);
+	} else {
+		dest.position = pos + Vector2(hex_code_box_font_size[fnt].x, hex_code_box_font_size[fnt].y) * Point2(0, 0);
+		RenderingServer::get_singleton()->canvas_item_add_texture_rect_region(p_canvas, dest, hex_code_box_font_tex[fnt]->get_rid(), Rect2(Point2(f * hex_code_box_font_size[fnt].x, 0), dest.size), p_color, false, false);
+		dest.position = pos + Vector2(hex_code_box_font_size[fnt].x, hex_code_box_font_size[fnt].y) * Point2(1, 0);
+		RenderingServer::get_singleton()->canvas_item_add_texture_rect_region(p_canvas, dest, hex_code_box_font_tex[fnt]->get_rid(), Rect2(Point2(e * hex_code_box_font_size[fnt].x, 0), dest.size), p_color, false, false);
+		dest.position = pos + Vector2(hex_code_box_font_size[fnt].x, hex_code_box_font_size[fnt].y) * Point2(2, 0);
+		RenderingServer::get_singleton()->canvas_item_add_texture_rect_region(p_canvas, dest, hex_code_box_font_tex[fnt]->get_rid(), Rect2(Point2(d * hex_code_box_font_size[fnt].x, 0), dest.size), p_color, false, false);
+		dest.position = pos + Vector2(hex_code_box_font_size[fnt].x, hex_code_box_font_size[fnt].y) * Point2(0, 1) + Point2(0, hex_code_box_font_size[fnt].z);
+		RenderingServer::get_singleton()->canvas_item_add_texture_rect_region(p_canvas, dest, hex_code_box_font_tex[fnt]->get_rid(), Rect2(Point2(c * hex_code_box_font_size[fnt].x, 0), dest.size), p_color, false, false);
+		dest.position = pos + Vector2(hex_code_box_font_size[fnt].x, hex_code_box_font_size[fnt].y) * Point2(1, 1) + Point2(0, hex_code_box_font_size[fnt].z);
+		RenderingServer::get_singleton()->canvas_item_add_texture_rect_region(p_canvas, dest, hex_code_box_font_tex[fnt]->get_rid(), Rect2(Point2(b * hex_code_box_font_size[fnt].x, 0), dest.size), p_color, false, false);
+		dest.position = pos + Vector2(hex_code_box_font_size[fnt].x, hex_code_box_font_size[fnt].y) * Point2(2, 1) + Point2(0, hex_code_box_font_size[fnt].z);
+		RenderingServer::get_singleton()->canvas_item_add_texture_rect_region(p_canvas, dest, hex_code_box_font_tex[fnt]->get_rid(), Rect2(Point2(a * hex_code_box_font_size[fnt].x, 0), dest.size), p_color, false, false);
+	}
+}
+
+Vector<Vector2i> TextServer::shaped_text_get_line_breaks_adv(RID p_shaped, const Vector<float> &p_width, int p_start, bool p_once, uint8_t /*TextBreakFlag*/ p_break_flags) const {
+	Vector<Vector2i> lines;
+
+	ERR_FAIL_COND_V(p_width.empty(), lines);
+
+	const_cast<TextServer *>(this)->shaped_text_update_breaks(p_shaped);
+	const Vector<Glyph> &logical = const_cast<TextServer *>(this)->shaped_text_sort_logical(p_shaped);
+	const Vector2i &range = shaped_text_get_range(p_shaped);
+
+	float width = 0.f;
+	int line_start = MAX(p_start, range.x);
+	int last_safe_break = -1;
+	int chunk = 0;
+	for (int i = 0; i < logical.size(); i++) {
+		if (logical[i].start < p_start) {
+			continue;
+		}
+		if (logical[i].count > 0) {
+			if ((p_width[chunk] > 0) && (width + logical[i].advance > p_width[chunk]) && (last_safe_break >= 0)) {
+				lines.push_back(Vector2i(line_start, logical[last_safe_break].end));
+				line_start = logical[last_safe_break].end;
+				i = last_safe_break;
+				last_safe_break = -1;
+				width = 0;
+				chunk++;
+				if (chunk >= p_width.size()) {
+					chunk = 0;
+					if (p_once) {
+						return lines;
+					}
+				}
+				continue;
+			}
+			if ((p_break_flags & BREAK_MANDATORY) == BREAK_MANDATORY) {
+				if ((logical[i].flags & GRAPHEME_IS_BREAK_HARD) == GRAPHEME_IS_BREAK_HARD) {
+					lines.push_back(Vector2i(line_start, logical[i].end));
+					line_start = logical[i].end;
+					last_safe_break = -1;
+					width = 0;
+					chunk = 0;
+					if (p_once) {
+						return lines;
+					}
+					continue;
+				}
+			}
+			if ((p_break_flags & BREAK_WORD_BOUND) == BREAK_WORD_BOUND) {
+				if ((logical[i].flags & GRAPHEME_IS_BREAK_SOFT) == GRAPHEME_IS_BREAK_SOFT) {
+					last_safe_break = i;
+				}
+			}
+			if ((p_break_flags & BREAK_GRAPHEME_BOUND) == BREAK_GRAPHEME_BOUND) {
+				last_safe_break = i;
+			}
+		}
+		width += logical[i].advance;
+	}
+
+	if (logical.size() > 0) {
+		lines.push_back(Vector2i(line_start, range.y));
+	} else {
+		lines.push_back(Vector2i(0, 0));
+	}
+
+	return lines;
+}
+
+Vector<Vector2i> TextServer::shaped_text_get_line_breaks(RID p_shaped, float p_width, int p_start, uint8_t /*TextBreakFlag*/ p_break_flags) const {
+	Vector<Vector2i> lines;
+
+	const_cast<TextServer *>(this)->shaped_text_update_breaks(p_shaped);
+	const Vector<Glyph> &logical = const_cast<TextServer *>(this)->shaped_text_sort_logical(p_shaped);
+	const Vector2i &range = shaped_text_get_range(p_shaped);
+
+	float width = 0.f;
+	int line_start = MAX(p_start, range.x);
+	int last_safe_break = -1;
+	for (int i = 0; i < logical.size(); i++) {
+		if (logical[i].start < p_start) {
+			continue;
+		}
+		if (logical[i].count > 0) {
+			if ((p_width > 0) && (width + logical[i].advance > p_width) && (last_safe_break >= 0)) {
+				lines.push_back(Vector2i(line_start, logical[last_safe_break].end));
+				line_start = logical[last_safe_break].end;
+				i = last_safe_break;
+				last_safe_break = -1;
+				width = 0;
+				continue;
+			}
+			if ((p_break_flags & BREAK_MANDATORY) == BREAK_MANDATORY) {
+				if ((logical[i].flags & GRAPHEME_IS_BREAK_HARD) == GRAPHEME_IS_BREAK_HARD) {
+					lines.push_back(Vector2i(line_start, logical[i].end));
+					line_start = logical[i].end;
+					last_safe_break = -1;
+					width = 0;
+					continue;
+				}
+			}
+			if ((p_break_flags & BREAK_WORD_BOUND) == BREAK_WORD_BOUND) {
+				if ((logical[i].flags & GRAPHEME_IS_BREAK_SOFT) == GRAPHEME_IS_BREAK_SOFT) {
+					last_safe_break = i;
+				}
+			}
+			if ((p_break_flags & BREAK_GRAPHEME_BOUND) == BREAK_GRAPHEME_BOUND) {
+				last_safe_break = i;
+			}
+		}
+		width += logical[i].advance;
+	}
+
+	if (logical.size() > 0) {
+		if (lines.size() == 0 || lines[lines.size() - 1].y < range.y) {
+			lines.push_back(Vector2i(line_start, range.y));
+		}
+	} else {
+		lines.push_back(Vector2i(0, 0));
+	}
+
+	return lines;
+}
+
+Vector<Vector2i> TextServer::shaped_text_get_word_breaks(RID p_shaped) const {
+	Vector<Vector2i> words;
+
+	const_cast<TextServer *>(this)->shaped_text_update_breaks(p_shaped);
+	const Vector<Glyph> &logical = const_cast<TextServer *>(this)->shaped_text_sort_logical(p_shaped);
+	const Vector2i &range = shaped_text_get_range(p_shaped);
+
+	int word_start = range.x;
+	for (int i = 0; i < logical.size(); i++) {
+		if (logical[i].count > 0) {
+			if ((logical[i].flags & GRAPHEME_IS_SPACE) == GRAPHEME_IS_SPACE) {
+				words.push_back(Vector2i(word_start, logical[i].end - 1));
+				word_start = logical[i].end;
+			}
+		}
+	}
+	if (logical.size() > 0) {
+		words.push_back(Vector2i(word_start, range.y));
+	}
+
+	return words;
+}
+
+void TextServer::shaped_text_get_carets(RID p_shaped, int p_position, Rect2 &p_leading_caret, Direction &p_leading_dir, Rect2 &p_trailing_caret, Direction &p_trailing_dir) const {
+	Vector<Rect2> carets;
+	const Vector<TextServer::Glyph> glyphs = shaped_text_get_glyphs(p_shaped);
+	TextServer::Orientation orientation = shaped_text_get_orientation(p_shaped);
+	const Vector2 &range = shaped_text_get_range(p_shaped);
+	float ascent = shaped_text_get_ascent(p_shaped);
+	float descent = shaped_text_get_descent(p_shaped);
+	float height = (ascent + descent) / 2;
+
+	float off = 0.0f;
+	p_leading_dir = DIRECTION_AUTO;
+	p_trailing_dir = DIRECTION_AUTO;
+	for (int i = 0; i < glyphs.size(); i++) {
+		if (glyphs[i].count > 0) {
+			// Caret before grapheme (top / left).
+			if (p_position == glyphs[i].start && ((glyphs[i].flags & GRAPHEME_IS_VIRTUAL) != GRAPHEME_IS_VIRTUAL)) {
+				Rect2 cr;
+				if (orientation == ORIENTATION_HORIZONTAL) {
+					if (glyphs[i].start == range.x) {
+						cr.size.y = height * 2;
+					} else {
+						cr.size.y = height;
+					}
+					cr.position.y = -ascent;
+					cr.position.x = off;
+					if ((glyphs[i].flags & GRAPHEME_IS_RTL) == GRAPHEME_IS_RTL) {
+						p_trailing_dir = DIRECTION_RTL;
+						for (int j = 0; j < glyphs[i].count; j++) {
+							cr.position.x += glyphs[i + j].advance * glyphs[i + j].repeat;
+							cr.size.x -= glyphs[i + j].advance * glyphs[i + j].repeat;
+						}
+					} else {
+						p_trailing_dir = DIRECTION_LTR;
+						for (int j = 0; j < glyphs[i].count; j++) {
+							cr.size.x += glyphs[i + j].advance * glyphs[i + j].repeat;
+						}
+					}
+				} else {
+					if (glyphs[i].start == range.x) {
+						cr.size.x = height * 2;
+					} else {
+						cr.size.x = height;
+					}
+					cr.position.x = -ascent;
+					cr.position.y = off;
+					if ((glyphs[i].flags & GRAPHEME_IS_RTL) == GRAPHEME_IS_RTL) {
+						p_trailing_dir = DIRECTION_RTL;
+						for (int j = 0; j < glyphs[i].count; j++) {
+							cr.position.y += glyphs[i + j].advance * glyphs[i + j].repeat;
+							cr.size.y -= glyphs[i + j].advance * glyphs[i + j].repeat;
+						}
+					} else {
+						p_trailing_dir = DIRECTION_LTR;
+						for (int j = 0; j < glyphs[i].count; j++) {
+							cr.size.y += glyphs[i + j].advance * glyphs[i + j].repeat;
+						}
+					}
+				}
+				p_trailing_caret = cr;
+			}
+			// Caret after grapheme (bottom / right).
+			if (p_position == glyphs[i].end && ((glyphs[i].flags & GRAPHEME_IS_VIRTUAL) != GRAPHEME_IS_VIRTUAL)) {
+				Rect2 cr;
+				if (orientation == ORIENTATION_HORIZONTAL) {
+					if (glyphs[i].end == range.y) {
+						cr.size.y = height * 2;
+						cr.position.y = -ascent;
+					} else {
+						cr.size.y = height;
+						cr.position.y = -ascent + height;
+					}
+					cr.position.x = off;
+					if ((glyphs[i].flags & GRAPHEME_IS_RTL) != GRAPHEME_IS_RTL) {
+						p_leading_dir = DIRECTION_LTR;
+						for (int j = 0; j < glyphs[i].count; j++) {
+							cr.position.x += glyphs[i + j].advance * glyphs[i + j].repeat;
+							cr.size.x -= glyphs[i + j].advance * glyphs[i + j].repeat;
+						}
+					} else {
+						p_leading_dir = DIRECTION_RTL;
+						for (int j = 0; j < glyphs[i].count; j++) {
+							cr.size.x += glyphs[i + j].advance * glyphs[i + j].repeat;
+						}
+					}
+				} else {
+					cr.size.y = 1.0f;
+					if (glyphs[i].end == range.y) {
+						cr.size.x = height * 2;
+						cr.position.x = -ascent;
+					} else {
+						cr.size.x = height;
+						cr.position.x = -ascent + height;
+					}
+					cr.position.y = off;
+					if ((glyphs[i].flags & GRAPHEME_IS_RTL) != GRAPHEME_IS_RTL) {
+						p_leading_dir = DIRECTION_LTR;
+						for (int j = 0; j < glyphs[i].count; j++) {
+							cr.position.y += glyphs[i + j].advance * glyphs[i + j].repeat;
+							cr.size.y -= glyphs[i + j].advance * glyphs[i + j].repeat;
+						}
+					} else {
+						p_leading_dir = DIRECTION_RTL;
+						for (int j = 0; j < glyphs[i].count; j++) {
+							cr.size.y += glyphs[i + j].advance * glyphs[i + j].repeat;
+						}
+					}
+				}
+				p_leading_caret = cr;
+			}
+			// Caret inside grapheme (middle).
+			if (p_position > glyphs[i].start && p_position < glyphs[i].end && (glyphs[i].flags & GRAPHEME_IS_VIRTUAL) != GRAPHEME_IS_VIRTUAL) {
+				float advance = 0.f;
+				for (int j = 0; j < glyphs[i].count; j++) {
+					advance += glyphs[i + j].advance * glyphs[i + j].repeat;
+				}
+				float char_adv = advance / (float)(glyphs[i].end - glyphs[i].start);
+				Rect2 cr;
+				if (orientation == ORIENTATION_HORIZONTAL) {
+					cr.size.x = 1.0f;
+					cr.size.y = height * 2;
+					cr.position.y = -ascent;
+					if ((glyphs[i].flags & GRAPHEME_IS_RTL) == GRAPHEME_IS_RTL) {
+						cr.position.x = off + char_adv * (glyphs[i].end - p_position);
+					} else {
+						cr.position.x = off + char_adv * (p_position - glyphs[i].start);
+					}
+				} else {
+					cr.size.y = 1.0f;
+					cr.size.x = height * 2;
+					cr.position.x = -ascent;
+					if ((glyphs[i].flags & GRAPHEME_IS_RTL) == GRAPHEME_IS_RTL) {
+						cr.position.y = off + char_adv * (glyphs[i].end - p_position);
+					} else {
+						cr.position.y = off + char_adv * (p_position - glyphs[i].start);
+					}
+				}
+				p_trailing_caret = cr;
+				p_leading_caret = cr;
+			}
+		}
+		off += glyphs[i].advance * glyphs[i].repeat;
+	}
+}
+
+TextServer::Direction TextServer::shaped_text_get_dominant_direciton_in_range(RID p_shaped, int p_start, int p_end) const {
+	const Vector<TextServer::Glyph> glyphs = shaped_text_get_glyphs(p_shaped);
+
+	if (p_start == p_end) {
+		return DIRECTION_AUTO;
+	}
+
+	int start = MIN(p_start, p_end);
+	int end = MAX(p_start, p_end);
+
+	int rtl = 0;
+	int ltr = 0;
+
+	for (int i = 0; i < glyphs.size(); i++) {
+		if ((glyphs[i].end > start) && (glyphs[i].start < end)) {
+			if (glyphs[i].count > 0) {
+				if ((glyphs[i].flags & GRAPHEME_IS_RTL) == GRAPHEME_IS_RTL) {
+					rtl++;
+				} else {
+					ltr++;
+				}
+			}
+		}
+	}
+	if (ltr == rtl) {
+		return DIRECTION_AUTO;
+	} else if (ltr > rtl) {
+		return DIRECTION_LTR;
+	} else {
+		return DIRECTION_RTL;
+	}
+}
+
+Vector<Vector2> TextServer::shaped_text_get_selection(RID p_shaped, int p_start, int p_end) const {
+	Vector<Vector2> ranges;
+	const Vector<TextServer::Glyph> glyphs = shaped_text_get_glyphs(p_shaped);
+
+	if (p_start == p_end) {
+		return ranges;
+	}
+
+	int start = MIN(p_start, p_end);
+	int end = MAX(p_start, p_end);
+
+	float off = 0.0f;
+	for (int i = 0; i < glyphs.size(); i++) {
+		for (int k = 0; k < glyphs[i].repeat; k++) {
+			if (glyphs[i].count > 0 && glyphs[i].index != 0) {
+				if (glyphs[i].start < end && glyphs[i].end > start) {
+					// Grapheme fully in selection range.
+					if (glyphs[i].start >= start && glyphs[i].end <= end) {
+						float advance = 0.f;
+						for (int j = 0; j < glyphs[i].count; j++) {
+							advance += glyphs[i + j].advance;
+						}
+						ranges.push_back(Vector2(off, off + advance));
+					}
+					// Only start of grapheme is in selection range.
+					if (glyphs[i].start >= start && glyphs[i].end > end) {
+						float advance = 0.f;
+						for (int j = 0; j < glyphs[i].count; j++) {
+							advance += glyphs[i + j].advance;
+						}
+						float char_adv = advance / (float)(glyphs[i].end - glyphs[i].start);
+						if ((glyphs[i].flags & GRAPHEME_IS_RTL) == GRAPHEME_IS_RTL) {
+							ranges.push_back(Vector2(off + char_adv * (glyphs[i].end - end), off + advance));
+						} else {
+							ranges.push_back(Vector2(off, off + char_adv * (end - glyphs[i].start)));
+						}
+					}
+					// Only end of grapheme is in selection range.
+					if (glyphs[i].start < start && glyphs[i].end <= end) {
+						float advance = 0.f;
+						for (int j = 0; j < glyphs[i].count; j++) {
+							advance += glyphs[i + j].advance;
+						}
+						float char_adv = advance / (float)(glyphs[i].end - glyphs[i].start);
+						if ((glyphs[i].flags & GRAPHEME_IS_RTL) == GRAPHEME_IS_RTL) {
+							ranges.push_back(Vector2(off, off + char_adv * (start - glyphs[i].start)));
+						} else {
+							ranges.push_back(Vector2(off + char_adv * (glyphs[i].end - start), off + advance));
+						}
+					}
+					// Selection range is within grapheme
+					if (glyphs[i].start < start && glyphs[i].end > end) {
+						float advance = 0.f;
+						for (int j = 0; j < glyphs[i].count; j++) {
+							advance += glyphs[i + j].advance;
+						}
+						float char_adv = advance / (float)(glyphs[i].end - glyphs[i].start);
+						if ((glyphs[i].flags & GRAPHEME_IS_RTL) == GRAPHEME_IS_RTL) {
+							ranges.push_back(Vector2(off + char_adv * (glyphs[i].end - end), off + char_adv * (glyphs[i].end - start)));
+						} else {
+							ranges.push_back(Vector2(off + char_adv * (start - glyphs[i].start), off + char_adv * (end - glyphs[i].start)));
+						}
+					}
+				}
+			}
+			off += glyphs[i].advance;
+		}
+	}
+
+	// Merge intersecting ranges.
+	int i = 0;
+	while (i < ranges.size()) {
+		int j = i + 1;
+		while (j < ranges.size()) {
+			if (Math::is_equal_approx(ranges[i].y, ranges[j].x, UNIT_EPSILON)) {
+				ranges.write[i].y = ranges[j].y;
+				ranges.remove(j);
+				continue;
+			}
+			j++;
+		}
+		i++;
+	}
+
+	return ranges;
+}
+
+int TextServer::shaped_text_hit_test_grapheme(RID p_shaped, float p_coords) const {
+	const Vector<TextServer::Glyph> glyphs = shaped_text_get_glyphs(p_shaped);
+
+	// Exact grapheme hit test, return -1 if missed.
+	float off = 0.0f;
+	for (int i = 0; i < glyphs.size(); i++) {
+		for (int j = 0; j < glyphs[i].repeat; j++) {
+			if (p_coords >= off && p_coords < off + glyphs[i].advance) {
+				return i;
+			}
+			off += glyphs[i].advance;
+		}
+	}
+	return -1;
+}
+
+int TextServer::shaped_text_hit_test_position(RID p_shaped, float p_coords) const {
+	const Vector<TextServer::Glyph> glyphs = shaped_text_get_glyphs(p_shaped);
+
+	// Cursor placement hit test.
+
+	// Place caret to the left of the leftmost grapheme, or to position 0 if string is empty.
+	if (p_coords <= 0) {
+		if (glyphs.size() > 0) {
+			if ((glyphs[0].flags & GRAPHEME_IS_RTL) == GRAPHEME_IS_RTL) {
+				return glyphs[0].end;
+			} else {
+				return glyphs[0].start;
+			}
+		} else {
+			return 0;
+		}
+	}
+
+	// Place caret to the right of the rightmost grapheme, or to position 0 if string is empty.
+	if (p_coords >= shaped_text_get_width(p_shaped)) {
+		if (glyphs.size() > 0) {
+			if ((glyphs[glyphs.size() - 1].flags & GRAPHEME_IS_RTL) == GRAPHEME_IS_RTL) {
+				return glyphs[glyphs.size() - 1].start;
+			} else {
+				return glyphs[glyphs.size() - 1].end;
+			}
+		} else {
+			return 0;
+		}
+	}
+
+	float off = 0.0f;
+	for (int i = 0; i < glyphs.size(); i++) {
+		for (int k = 0; k < glyphs[i].repeat; k++) {
+			if (glyphs[i].count > 0) {
+				float advance = 0.f;
+				for (int j = 0; j < glyphs[i].count; j++) {
+					advance += glyphs[i + j].advance;
+				}
+				// Place caret to the left of clicked grapheme.
+				if (p_coords >= off && p_coords < off + advance / 2) {
+					if ((glyphs[i].flags & GRAPHEME_IS_RTL) == GRAPHEME_IS_RTL) {
+						return glyphs[i].end;
+					} else {
+						return glyphs[i].start;
+					}
+				}
+				// Place caret to the right of clicked grapheme.
+				if (p_coords >= off + advance / 2 && p_coords < off + advance) {
+					if ((glyphs[i].flags & GRAPHEME_IS_RTL) == GRAPHEME_IS_RTL) {
+						return glyphs[i].start;
+					} else {
+						return glyphs[i].end;
+					}
+				}
+			}
+			off += glyphs[i].advance;
+		}
+	}
+	return 0;
+}
+
+int TextServer::shaped_text_next_grapheme_pos(RID p_shaped, int p_pos) {
+	const Vector<TextServer::Glyph> glyphs = shaped_text_get_glyphs(p_shaped);
+	for (int i = 0; i < glyphs.size(); i++) {
+		if (p_pos >= glyphs[i].start && p_pos < glyphs[i].end) {
+			return glyphs[i].end;
+		}
+	}
+	return p_pos;
+}
+
+int TextServer::shaped_text_prev_grapheme_pos(RID p_shaped, int p_pos) {
+	const Vector<TextServer::Glyph> glyphs = shaped_text_get_glyphs(p_shaped);
+	for (int i = 0; i < glyphs.size(); i++) {
+		if (p_pos > glyphs[i].start && p_pos <= glyphs[i].end) {
+			return glyphs[i].start;
+		}
+	}
+
+	return p_pos;
+}
+
+void TextServer::shaped_text_draw(RID p_shaped, RID p_canvas, const Vector2 &p_pos, float p_clip_l, float p_clip_r, const Color &p_color) const {
+	const Vector<TextServer::Glyph> glyphs = shaped_text_get_glyphs(p_shaped);
+	TextServer::Orientation orientation = shaped_text_get_orientation(p_shaped);
+	bool hex_codes = shaped_text_get_preserve_control(p_shaped) || shaped_text_get_preserve_invalid(p_shaped);
+
+	Vector2 ofs = p_pos;
+	// Draw at the baseline.
+	for (int i = 0; i < glyphs.size(); i++) {
+		for (int j = 0; j < glyphs[i].repeat; j++) {
+			if (p_clip_r > 0) {
+				// Clip right / bottom.
+				if (orientation == ORIENTATION_HORIZONTAL) {
+					if (ofs.x - p_pos.x > p_clip_r) {
+						return;
+					}
+				} else {
+					if (ofs.y - p_pos.y > p_clip_r) {
+						return;
+					}
+				}
+			}
+			if (p_clip_l > 0) {
+				// Clip left / top.
+				if (orientation == ORIENTATION_HORIZONTAL) {
+					if (ofs.x - p_pos.x < p_clip_l) {
+						ofs.x += glyphs[i].advance;
+						continue;
+					}
+				} else {
+					if (ofs.y - p_pos.y < p_clip_l) {
+						ofs.y += glyphs[i].advance;
+						continue;
+					}
+				}
+			}
+			if (glyphs[i].font_rid != RID()) {
+				font_draw_glyph(glyphs[i].font_rid, p_canvas, glyphs[i].font_size, ofs + Vector2(glyphs[i].x_off, glyphs[i].y_off), glyphs[i].index, p_color);
+			} else if (hex_codes && ((glyphs[i].flags & GRAPHEME_IS_VIRTUAL) != GRAPHEME_IS_VIRTUAL)) {
+				TextServer::draw_hex_code_box(p_canvas, glyphs[i].font_size, ofs + Vector2(glyphs[i].x_off, glyphs[i].y_off), glyphs[i].index, p_color);
+			}
+			if (orientation == ORIENTATION_HORIZONTAL) {
+				ofs.x += glyphs[i].advance;
+			} else {
+				ofs.y += glyphs[i].advance;
+			}
+		}
+	}
+}
+
+void TextServer::shaped_text_draw_outline(RID p_shaped, RID p_canvas, const Vector2 &p_pos, float p_clip_l, float p_clip_r, int p_outline_size, const Color &p_color) const {
+	const Vector<TextServer::Glyph> glyphs = shaped_text_get_glyphs(p_shaped);
+	TextServer::Orientation orientation = shaped_text_get_orientation(p_shaped);
+
+	Vector2 ofs = p_pos;
+	// Draw at the baseline.
+	for (int i = 0; i < glyphs.size(); i++) {
+		for (int j = 0; j < glyphs[i].repeat; j++) {
+			if (p_clip_r > 0) {
+				// Clip right / bottom.
+				if (orientation == ORIENTATION_HORIZONTAL) {
+					if (ofs.x - p_pos.x > p_clip_r) {
+						return;
+					}
+				} else {
+					if (ofs.y - p_pos.y > p_clip_r) {
+						return;
+					}
+				}
+			}
+			if (p_clip_l > 0) {
+				// Clip left / top.
+				if (orientation == ORIENTATION_HORIZONTAL) {
+					if (ofs.x - p_pos.x < p_clip_l) {
+						ofs.x += glyphs[i].advance;
+						continue;
+					}
+				} else {
+					if (ofs.y - p_pos.y < p_clip_l) {
+						ofs.y += glyphs[i].advance;
+						continue;
+					}
+				}
+			}
+			if (glyphs[i].font_rid != RID()) {
+				font_draw_glyph_outline(glyphs[i].font_rid, p_canvas, glyphs[i].font_size, p_outline_size, ofs + Vector2(glyphs[i].x_off, glyphs[i].y_off), glyphs[i].index, p_color);
+			}
+			if (orientation == ORIENTATION_HORIZONTAL) {
+				ofs.x += glyphs[i].advance;
+			} else {
+				ofs.y += glyphs[i].advance;
+			}
+		}
+	}
+}
+
+RID TextServer::_create_font_memory(const PackedByteArray &p_data, const String &p_type, int p_base_size) {
+	return create_font_memory(p_data.ptr(), p_data.size(), p_type, p_base_size);
+}
+
+void TextServer::_shaped_text_set_bidi_override(RID p_shaped, const Array &p_override) {
+	Vector<Vector2i> overrides;
+	for (int i = 0; i < p_override.size(); i++) {
+		overrides.push_back(p_override[i]);
+	}
+	shaped_text_set_bidi_override(p_shaped, overrides);
+}
+
+Array TextServer::_shaped_text_get_glyphs(RID p_shaped) const {
+	Array ret;
+
+	Vector<Glyph> glyphs = shaped_text_get_glyphs(p_shaped);
+	for (int i = 0; i < glyphs.size(); i++) {
+		Dictionary glyph;
+
+		glyph["start"] = glyphs[i].start;
+		glyph["end"] = glyphs[i].end;
+		glyph["repeat"] = glyphs[i].repeat;
+		glyph["count"] = glyphs[i].count;
+		glyph["flags"] = glyphs[i].flags;
+		glyph["offset"] = Vector2(glyphs[i].x_off, glyphs[i].y_off);
+		glyph["advance"] = glyphs[i].advance;
+		glyph["font_rid"] = glyphs[i].font_rid;
+		glyph["font_size"] = glyphs[i].font_size;
+		glyph["index"] = glyphs[i].index;
+
+		ret.push_back(glyph);
+	}
+
+	return ret;
+}
+
+Array TextServer::_shaped_text_get_line_breaks_adv(RID p_shaped, const PackedFloat32Array &p_width, int p_start, bool p_once, uint8_t p_break_flags) const {
+	Array ret;
+
+	Vector<Vector2i> lines = shaped_text_get_line_breaks_adv(p_shaped, p_width, p_start, p_once, p_break_flags);
+	for (int i = 0; i < lines.size(); i++) {
+		ret.push_back(lines[i]);
+	}
+
+	return ret;
+}
+
+Array TextServer::_shaped_text_get_line_breaks(RID p_shaped, float p_width, int p_start, uint8_t p_break_flags) const {
+	Array ret;
+
+	Vector<Vector2i> lines = shaped_text_get_line_breaks(p_shaped, p_width, p_start, p_break_flags);
+	for (int i = 0; i < lines.size(); i++) {
+		ret.push_back(lines[i]);
+	}
+
+	return ret;
+}
+
+Array TextServer::_shaped_text_get_word_breaks(RID p_shaped) const {
+	Array ret;
+
+	Vector<Vector2i> words = shaped_text_get_word_breaks(p_shaped);
+	for (int i = 0; i < words.size(); i++) {
+		ret.push_back(words[i]);
+	}
+
+	return ret;
+}
+
+Dictionary TextServer::_shaped_text_get_carets(RID p_shaped, int p_position) const {
+	Dictionary ret;
+
+	Rect2 l_caret, t_caret;
+	Direction l_dir, t_dir;
+	shaped_text_get_carets(p_shaped, p_position, l_caret, l_dir, t_caret, t_dir);
+
+	ret["leading_rect"] = l_caret;
+	ret["leading_direction"] = l_dir;
+	ret["trailing_rect"] = t_caret;
+	ret["trailing_direction"] = t_dir;
+
+	return ret;
+}
+
+Array TextServer::_shaped_text_get_selection(RID p_shaped, int p_start, int p_end) const {
+	Array ret;
+
+	Vector<Vector2> ranges = shaped_text_get_selection(p_shaped, p_start, p_end);
+	for (int i = 0; i < ranges.size(); i++) {
+		ret.push_back(ranges[i]);
+	}
+
+	return ret;
+}
+
+TextServer::TextServer() {
+}
+
+TextServer::~TextServer() {
+}

+ 453 - 0
servers/text_server.h

@@ -0,0 +1,453 @@
+/*************************************************************************/
+/*  text_server.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_H
+#define TEXT_SERVER_H
+
+#include "core/object/reference.h"
+#include "core/os/os.h"
+#include "core/templates/rid.h"
+#include "core/variant/variant.h"
+#include "scene/resources/texture.h"
+
+class CanvasTexture;
+
+class TextServer : public Object {
+	GDCLASS(TextServer, Object);
+
+public:
+	enum Direction {
+		DIRECTION_AUTO,
+		DIRECTION_LTR,
+		DIRECTION_RTL
+	};
+
+	enum Orientation {
+		ORIENTATION_HORIZONTAL,
+		ORIENTATION_VERTICAL
+	};
+
+	enum JustificationFlag {
+		JUSTIFICATION_NONE = 0,
+		JUSTIFICATION_KASHIDA = 1 << 0,
+		JUSTIFICATION_WORD_BOUND = 1 << 1,
+		JUSTIFICATION_TRIM_EDGE_SPACES = 1 << 2,
+		JUSTIFICATION_AFTER_LAST_TAB = 1 << 3
+	};
+
+	enum LineBreakFlag {
+		BREAK_NONE = 0,
+		BREAK_MANDATORY = 1 << 4,
+		BREAK_WORD_BOUND = 1 << 5,
+		BREAK_GRAPHEME_BOUND = 1 << 6
+		//RESERVED = 1 << 7
+	};
+
+	enum GraphemeFlag {
+		GRAPHEME_IS_VALID = 1 << 0, // Glyph is valid.
+		GRAPHEME_IS_RTL = 1 << 1, // Glyph is right-to-left.
+		GRAPHEME_IS_VIRTUAL = 1 << 2, // Glyph is not part of source string (added by fit_to_width function, do not affect caret movement).
+		GRAPHEME_IS_SPACE = 1 << 3, // Is whitespace (for justification).
+		GRAPHEME_IS_BREAK_HARD = 1 << 4, // Is line break (mandatory break, e.g "\n")
+		GRAPHEME_IS_BREAK_SOFT = 1 << 5, // Is line break (optional break, e.g space)
+		GRAPHEME_IS_TAB = 1 << 6, // Is tab or vertical tab
+		GRAPHEME_IS_ELONGATION = 1 << 7 // Elongation (e.g kashida), glyph can be duplicated or truncated to fit line to width.
+	};
+
+	enum Hinting {
+		HINTING_NONE,
+		HINTING_LIGHT,
+		HINTING_NORMAL
+	};
+
+	enum Feature {
+		FEATURE_BIDI_LAYOUT = 1 << 0,
+		FEATURE_VERTICAL_LAYOUT = 1 << 1,
+		FEATURE_SHAPING = 1 << 2,
+		FEATURE_KASHIDA_JUSTIFICATION = 1 << 3,
+		FEATURE_BREAK_ITERATORS = 1 << 4,
+		FEATURE_FONT_SYSTEM = 1 << 5,
+		FEATURE_USE_SUPPORT_DATA = 1 << 6
+	};
+
+	struct Glyph {
+		int start = -1; // Start offset in the source string.
+		int end = -1; // End offset in the source string.
+
+		uint8_t count = 0; // Number of glyphs in the grapheme, set in the first glyph only.
+		uint8_t repeat = 1; // Draw multiple times in the row.
+		uint8_t flags = 0; // Grapheme flags (valid, rtl, virtual), set in the first glyph only.
+
+		float x_off = 0.f; // Offset from the origin of the glyph on baseline.
+		float y_off = 0.f;
+		float advance = 0.f; // Advance to the next glyph along baseline(x for horizontal layout, y for vertical).
+
+		RID font_rid; // Font resource.
+		int font_size = 0; // Font size;
+		uint32_t index = 0; // Glyph index (font specific) or UTF-32 codepoint (for the invalid glyphs).
+
+		bool operator==(const Glyph &p_a) const;
+		bool operator!=(const Glyph &p_a) const;
+
+		bool operator<(const Glyph &p_a) const;
+		bool operator>(const Glyph &p_a) const;
+	};
+
+	struct GlyphCompare { // For line breaking reordering.
+		_FORCE_INLINE_ bool operator()(const Glyph &l, const Glyph &r) const {
+			if (l.start == r.start) {
+				if (l.count == r.count) {
+					if ((l.flags & GRAPHEME_IS_VIRTUAL) == GRAPHEME_IS_VIRTUAL) {
+						return false;
+					} else {
+						return true;
+					}
+				}
+				return l.count > r.count; // Sort first glyoh with count & flags, order of the rest are irrelevant.
+			} else {
+				return l.start < r.start;
+			}
+		}
+	};
+
+	struct ShapedTextData {
+		/* Source data */
+		RID parent; // Substring parent ShapedTextData.
+
+		int start = 0; // Substring start offset in the parent string.
+		int end = 0; // Substring end offset in the parent string.
+
+		String text;
+		TextServer::Direction direction = DIRECTION_LTR; // Desired text direction.
+		TextServer::Orientation orientation = ORIENTATION_HORIZONTAL;
+
+		struct Span {
+			int start = -1;
+			int end = -1;
+
+			Vector<RID> fonts;
+			int font_size = 0;
+
+			Variant embedded_key;
+
+			String language;
+			Dictionary features;
+		};
+		Vector<Span> spans;
+
+		struct EmbeddedObject {
+			int pos = 0;
+			VAlign inline_align = VALIGN_TOP;
+			Rect2 rect;
+		};
+		Map<Variant, EmbeddedObject> objects;
+
+		/* Shaped data */
+		TextServer::Direction para_direction = DIRECTION_LTR; // Detected text direction.
+		bool valid = false; // String is shaped.
+		bool line_breaks_valid = false; // Line and word break flags are populated (and virtual zero width spaces inserted).
+		bool justification_ops_valid = false; // Virtual elongation glyphs are added to the string.
+		bool sort_valid = false;
+
+		bool preserve_invalid = true; // Draw hex code box instead of missing characters.
+		bool preserve_control = false; // Draw control characters.
+
+		float ascent = 0.f; // Ascent for horizontal layout, 1/2 of width for vertical.
+		float descent = 0.f; // Descent for horizontal layout, 1/2 of width for vertical.
+		float width = 0.f; // Width for horizontal layout, height for vertical.
+
+		float upos = 0.f;
+		float uthk = 0.f;
+
+		Vector<TextServer::Glyph> glyphs;
+		Vector<TextServer::Glyph> glyphs_logical;
+	};
+
+	struct BitmapFontData {
+		int height = 0;
+		int ascent = 0;
+		int charcount = 0;
+		const int *char_rects = nullptr;
+		int kerning_count = 0;
+		const int *kernings = nullptr;
+		int w = 0;
+		int h = 0;
+		const unsigned char *img = nullptr;
+	};
+
+protected:
+	static void _bind_methods();
+
+	static Vector3 hex_code_box_font_size[2];
+	static Ref<CanvasTexture> hex_code_box_font_tex[2];
+
+public:
+	static void initialize_hex_code_box_fonts();
+	static void finish_hex_code_box_fonts();
+
+	virtual bool has_feature(Feature p_feature) = 0;
+	virtual String get_name() const = 0;
+
+	virtual void free(RID p_rid) = 0;
+	virtual bool has(RID p_rid) = 0;
+	virtual bool load_support_data(const String &p_filename) = 0;
+
+#ifdef TOOLS_ENABLED
+	virtual String get_support_data_filename() = 0;
+	virtual String get_support_data_info() = 0;
+	virtual bool save_support_data(const String &p_filename) = 0;
+#endif
+
+	virtual bool is_locale_right_to_left(const String &p_locale) = 0;
+
+	virtual int32_t name_to_tag(const String &p_name) { return 0; };
+	virtual String tag_to_name(int32_t p_tag) { return ""; };
+
+	/* Font interface */
+	virtual RID create_font_system(const String &p_name, int p_base_size = 16) = 0;
+	virtual RID create_font_resource(const String &p_filename, int p_base_size = 16) = 0;
+	virtual RID create_font_memory(const uint8_t *p_data, size_t p_size, const String &p_type, int p_base_size = 16) = 0;
+
+	virtual float font_get_height(RID p_font, int p_size) const = 0;
+	virtual float font_get_ascent(RID p_font, int p_size) const = 0;
+	virtual float font_get_descent(RID p_font, int p_size) const = 0;
+
+	virtual float font_get_underline_position(RID p_font, int p_size) const = 0;
+	virtual float font_get_underline_thickness(RID p_font, int p_size) const = 0;
+
+	virtual void font_set_antialiased(RID p_font, bool p_antialiased) = 0;
+	virtual bool font_get_antialiased(RID p_font) const = 0;
+
+	virtual Dictionary font_get_feature_list(RID p_font) const { return Dictionary(); };
+
+	virtual void font_set_distance_field_hint(RID p_font, bool p_distance_field) = 0;
+	virtual bool font_get_distance_field_hint(RID p_font) const = 0;
+
+	virtual void font_set_hinting(RID p_font, Hinting p_hinting) = 0;
+	virtual Hinting font_get_hinting(RID p_font) const = 0;
+
+	virtual void font_set_force_autohinter(RID p_font, bool p_enabeld) = 0;
+	virtual bool font_get_force_autohinter(RID p_font) const = 0;
+
+	virtual bool font_has_char(RID p_font, char32_t p_char) const = 0;
+	virtual String font_get_supported_chars(RID p_font) const = 0;
+
+	virtual bool font_has_outline(RID p_font) const = 0;
+	virtual float font_get_base_size(RID p_font) const = 0;
+
+	virtual bool font_is_language_supported(RID p_font, const String &p_language) const = 0;
+	virtual void font_set_language_support_override(RID p_font, const String &p_language, bool p_supported) = 0;
+	virtual bool font_get_language_support_override(RID p_font, const String &p_language) = 0;
+	virtual void font_remove_language_support_override(RID p_font, const String &p_language) = 0;
+	virtual Vector<String> font_get_language_support_overrides(RID p_font) = 0;
+
+	virtual bool font_is_script_supported(RID p_font, const String &p_script) const = 0;
+	virtual void font_set_script_support_override(RID p_font, const String &p_script, bool p_supported) = 0;
+	virtual bool font_get_script_support_override(RID p_font, const String &p_script) = 0;
+	virtual void font_remove_script_support_override(RID p_font, const String &p_script) = 0;
+	virtual Vector<String> font_get_script_support_overrides(RID p_font) = 0;
+
+	virtual uint32_t font_get_glyph_index(RID p_font, char32_t p_char, char32_t p_variation_selector = 0x0000) const = 0;
+	virtual Vector2 font_get_glyph_advance(RID p_font, uint32_t p_index, int p_size) const = 0;
+	virtual Vector2 font_get_glyph_kerning(RID p_font, uint32_t p_index_a, uint32_t p_index_b, int p_size) const = 0;
+
+	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 = 0;
+	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 = 0;
+
+	virtual float font_get_oversampling() const = 0;
+	virtual void font_set_oversampling(float p_oversampling) = 0;
+
+	Vector2 get_hex_code_box_size(int p_size, char32_t p_index) const;
+	void draw_hex_code_box(RID p_canvas, int p_size, const Vector2 &p_pos, char32_t p_index, const Color &p_color) const;
+
+	virtual Vector<String> get_system_fonts() const = 0;
+
+	/* Shaped text buffer interface */
+
+	virtual RID create_shaped_text(Direction p_direction = DIRECTION_AUTO, Orientation p_orientation = ORIENTATION_HORIZONTAL) = 0;
+
+	virtual void shaped_text_clear(RID p_shaped) = 0;
+
+	virtual void shaped_text_set_direction(RID p_shaped, Direction p_direction = DIRECTION_AUTO) = 0;
+	virtual Direction shaped_text_get_direction(RID p_shaped) const = 0;
+
+	virtual void shaped_text_set_bidi_override(RID p_shaped, const Vector<Vector2i> &p_override) = 0;
+
+	virtual void shaped_text_set_orientation(RID p_shaped, Orientation p_orientation = ORIENTATION_HORIZONTAL) = 0;
+	virtual Orientation shaped_text_get_orientation(RID p_shaped) const = 0;
+
+	virtual void shaped_text_set_preserve_invalid(RID p_shaped, bool p_enabled) = 0;
+	virtual bool shaped_text_get_preserve_invalid(RID p_shaped) const = 0;
+
+	virtual void shaped_text_set_preserve_control(RID p_shaped, bool p_enabled) = 0;
+	virtual bool shaped_text_get_preserve_control(RID p_shaped) const = 0;
+
+	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 = "") = 0;
+	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) = 0;
+	virtual bool shaped_text_resize_object(RID p_shaped, Variant p_key, const Size2 &p_size, VAlign p_inline_align = VALIGN_CENTER) = 0;
+
+	virtual RID shaped_text_substr(RID p_shaped, int p_start, int p_length) const = 0; // Copy shaped substring (e.g. line break) without reshaping, but correctly reordered, preservers range.
+	virtual RID shaped_text_get_parent(RID p_shaped) const = 0;
+
+	virtual float shaped_text_fit_to_width(RID p_shaped, float p_width, uint8_t /*JustificationFlag*/ p_jst_flags = JUSTIFICATION_WORD_BOUND | JUSTIFICATION_KASHIDA) = 0;
+	virtual float shaped_text_tab_align(RID p_shaped, const Vector<float> &p_tab_stops) = 0;
+
+	virtual bool shaped_text_shape(RID p_shaped) = 0;
+	virtual bool shaped_text_update_breaks(RID p_shaped) = 0;
+	virtual bool shaped_text_update_justification_ops(RID p_shaped) = 0;
+
+	virtual bool shaped_text_is_ready(RID p_shaped) const = 0;
+
+	virtual Vector<Glyph> shaped_text_get_glyphs(RID p_shaped) const = 0;
+
+	virtual Vector2i shaped_text_get_range(RID p_shaped) const = 0;
+
+	virtual Vector<Glyph> shaped_text_sort_logical(RID p_shaped) = 0;
+
+	virtual Vector<Vector2i> shaped_text_get_line_breaks_adv(RID p_shaped, const Vector<float> &p_width, int p_start = 0, bool p_once = true, uint8_t /*TextBreakFlag*/ p_break_flags = BREAK_MANDATORY | BREAK_WORD_BOUND) const;
+	virtual Vector<Vector2i> shaped_text_get_line_breaks(RID p_shaped, float p_width, int p_start = 0, uint8_t /*TextBreakFlag*/ p_break_flags = BREAK_MANDATORY | BREAK_WORD_BOUND) const;
+	virtual Vector<Vector2i> shaped_text_get_word_breaks(RID p_shaped) const;
+	virtual Array shaped_text_get_objects(RID p_shaped) const = 0;
+	virtual Rect2 shaped_text_get_object_rect(RID p_shaped, Variant p_key) const = 0;
+
+	virtual Size2 shaped_text_get_size(RID p_shaped) const = 0;
+	virtual float shaped_text_get_ascent(RID p_shaped) const = 0;
+	virtual float shaped_text_get_descent(RID p_shaped) const = 0;
+	virtual float shaped_text_get_width(RID p_shaped) const = 0;
+	virtual float shaped_text_get_underline_position(RID p_shaped) const = 0;
+	virtual float shaped_text_get_underline_thickness(RID p_shaped) const = 0;
+
+	virtual Direction shaped_text_get_dominant_direciton_in_range(RID p_shaped, int p_start, int p_end) const;
+
+	virtual void shaped_text_get_carets(RID p_shaped, int p_position, Rect2 &p_leading_caret, Direction &p_leading_dir, Rect2 &p_trailing_caret, Direction &p_trailing_dir) const;
+	virtual Vector<Vector2> shaped_text_get_selection(RID p_shaped, int p_start, int p_end) const;
+
+	virtual int shaped_text_hit_test_grapheme(RID p_shaped, float p_coords) const; // Return grapheme index.
+	virtual int shaped_text_hit_test_position(RID p_shaped, float p_coords) const; // Return caret/selection position.
+
+	virtual int shaped_text_next_grapheme_pos(RID p_shaped, int p_pos);
+	virtual int shaped_text_prev_grapheme_pos(RID p_shaped, int p_pos);
+
+	// The pen position is always placed on the baseline and moveing left to right.
+	virtual void shaped_text_draw(RID p_shaped, RID p_canvas, const Vector2 &p_pos, float p_clip_l = -1.f, float p_clip_r = -1.f, const Color &p_color = Color(1, 1, 1)) const;
+	virtual void shaped_text_draw_outline(RID p_shaped, RID p_canvas, const Vector2 &p_pos, float p_clip_l = -1.f, float p_clip_r = -1.f, int p_outline_size = 1, const Color &p_color = Color(1, 1, 1)) const;
+
+	// Number conversion.
+	virtual String format_number(const String &p_string, const String &p_language = "") const { return p_string; };
+	virtual String parse_number(const String &p_string, const String &p_language = "") const { return p_string; };
+	virtual String percent_sign(const String &p_language = "") const { return "%"; };
+
+	/* GDScript wrappers */
+	RID _create_font_memory(const PackedByteArray &p_data, const String &p_type, int p_base_size = 16);
+
+	Array _shaped_text_get_glyphs(RID p_shaped) const;
+	Dictionary _shaped_text_get_carets(RID p_shaped, int p_position) const;
+
+	void _shaped_text_set_bidi_override(RID p_shaped, const Array &p_override);
+
+	Array _shaped_text_get_line_breaks_adv(RID p_shaped, const PackedFloat32Array &p_width, int p_start, bool p_once, uint8_t p_break_flags) const;
+	Array _shaped_text_get_line_breaks(RID p_shaped, float p_width, int p_start, uint8_t p_break_flags) const;
+	Array _shaped_text_get_word_breaks(RID p_shaped) const;
+
+	Array _shaped_text_get_selection(RID p_shaped, int p_start, int p_end) const;
+
+	TextServer();
+	~TextServer();
+};
+
+/*************************************************************************/
+
+class TextServerManager : public Object {
+	GDCLASS(TextServerManager, Object);
+
+public:
+	typedef TextServer *(*CreateFunction)(Error &r_error, void *p_user_data);
+
+protected:
+	static void _bind_methods();
+
+private:
+	static TextServerManager *singleton;
+	static TextServer *server;
+	enum {
+		MAX_SERVERS = 64
+	};
+
+	struct TextServerCreate {
+		String name;
+		CreateFunction create_function = nullptr;
+		uint32_t features = 0;
+		TextServer *instance = nullptr;
+		void *user_data = nullptr;
+	};
+
+	static TextServerCreate server_create_functions[MAX_SERVERS];
+	static int server_create_count;
+
+public:
+	_FORCE_INLINE_ static TextServerManager *get_singleton() {
+		return singleton;
+	}
+
+	static void register_create_function(const String &p_name, uint32_t p_features, CreateFunction p_function, void *p_user_data);
+	static int get_interface_count();
+	static String get_interface_name(int p_index);
+	static uint32_t get_interface_features(int p_index);
+	static TextServer *initialize(int p_index, Error &r_error);
+	static TextServer *get_primary_interface();
+
+	/* GDScript wrappers */
+	int _get_interface_count() const;
+	String _get_interface_name(int p_index) const;
+	uint32_t _get_interface_features(int p_index) const;
+	TextServer *_get_interface(int p_index) const;
+	Array _get_interfaces() const;
+	TextServer *_find_interface(const String &p_name) const;
+
+	bool _set_primary_interface(int p_index);
+	TextServer *_get_primary_interface() const;
+
+	TextServerManager();
+	~TextServerManager();
+};
+
+/*************************************************************************/
+
+#define TS TextServerManager::get_primary_interface()
+
+VARIANT_ENUM_CAST(TextServer::Direction);
+VARIANT_ENUM_CAST(TextServer::Orientation);
+VARIANT_ENUM_CAST(TextServer::JustificationFlag);
+VARIANT_ENUM_CAST(TextServer::LineBreakFlag);
+VARIANT_ENUM_CAST(TextServer::GraphemeFlag);
+VARIANT_ENUM_CAST(TextServer::Hinting);
+VARIANT_ENUM_CAST(TextServer::Feature);
+
+#endif // TEXT_SERVER_H

+ 100 - 0
tests/test_lru.h

@@ -0,0 +1,100 @@
+/*************************************************************************/
+/*  test_lru.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 TEST_LRU_H
+#define TEST_LRU_H
+
+#include "core/templates/lru.h"
+#include "core/templates/vector.h"
+
+#include "tests/test_macros.h"
+
+namespace TestLRU {
+
+TEST_CASE("[LRU] Store and read") {
+	LRUCache<int, int> lru;
+
+	lru.set_capacity(3);
+	lru.insert(1, 1);
+	lru.insert(50, 2);
+	lru.insert(100, 5);
+
+	CHECK(lru.has(1));
+	CHECK(lru.has(50));
+	CHECK(lru.has(100));
+	CHECK(!lru.has(200));
+
+	CHECK(lru.get(1) == 1);
+	CHECK(lru.get(50) == 2);
+	CHECK(lru.get(100) == 5);
+
+	CHECK(lru.getptr(1) != nullptr);
+	CHECK(lru.getptr(1000) == nullptr);
+
+	lru.insert(600, 600); // Erase <50>
+	CHECK(lru.has(600));
+	CHECK(!lru.has(50));
+}
+
+TEST_CASE("[LRU] Resize and clear") {
+	LRUCache<int, int> lru;
+
+	lru.set_capacity(3);
+	lru.insert(1, 1);
+	lru.insert(2, 2);
+	lru.insert(3, 3);
+
+	CHECK(lru.get_capacity() == 3);
+
+	lru.set_capacity(5);
+	CHECK(lru.get_capacity() == 5);
+
+	CHECK(lru.has(1));
+	CHECK(lru.has(2));
+	CHECK(lru.has(3));
+	CHECK(!lru.has(4));
+
+	lru.set_capacity(2);
+	CHECK(lru.get_capacity() == 2);
+
+	CHECK(!lru.has(1));
+	CHECK(lru.has(2));
+	CHECK(lru.has(3));
+	CHECK(!lru.has(4));
+
+	lru.clear();
+	CHECK(!lru.has(1));
+	CHECK(!lru.has(2));
+	CHECK(!lru.has(3));
+	CHECK(!lru.has(4));
+}
+} // namespace TestLRU
+
+#endif // TEST_LRU_H

+ 2 - 0
tests/test_main.cpp

@@ -45,6 +45,7 @@
 #include "test_gui.h"
 #include "test_json.h"
 #include "test_list.h"
+#include "test_lru.h"
 #include "test_math.h"
 #include "test_method_bind.h"
 #include "test_node_path.h"
@@ -58,6 +59,7 @@
 #include "test_render.h"
 #include "test_shader_lang.h"
 #include "test_string.h"
+#include "test_text_server.h"
 #include "test_validate_testing.h"
 #include "test_variant.h"
 

+ 249 - 0
tests/test_text_server.h

@@ -0,0 +1,249 @@
+/*************************************************************************/
+/*  test_text_server.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 TEST_TEXT_SERVER_H
+#define TEST_TEXT_SERVER_H
+
+#include "editor/builtin_fonts.gen.h"
+#include "servers/text_server.h"
+#include "tests/test_macros.h"
+
+namespace TestTextServer {
+
+TEST_SUITE("[[TextServer]") {
+	TEST_CASE("[TextServer] Init, font loading and shaping") {
+		TextServerManager *tsman = memnew(TextServerManager);
+		Error err = OK;
+
+		SUBCASE("[TextServer] Init") {
+			for (int i = 0; i < TextServerManager::get_interface_count(); i++) {
+				TextServer *ts = TextServerManager::initialize(i, err);
+				TEST_FAIL_COND((err != OK || ts == nullptr), "Text server " + TextServerManager::get_interface_name(i) + " init failed.");
+			}
+		}
+
+		SUBCASE("[TextServer] Loading fonts") {
+			for (int i = 0; i < TextServerManager::get_interface_count(); i++) {
+				TextServer *ts = TextServerManager::initialize(i, err);
+
+				RID font = ts->create_font_memory(_font_NotoSansUI_Regular, _font_NotoSansUI_Regular_size, "ttf");
+				TEST_FAIL_COND(font == RID(), "Loading font failed.");
+				ts->free(font);
+			}
+		}
+
+		SUBCASE("[TextServer] Text layout: Font fallback") {
+			for (int i = 0; i < TextServerManager::get_interface_count(); i++) {
+				TextServer *ts = TextServerManager::initialize(i, err);
+
+				Vector<RID> font;
+				font.push_back(ts->create_font_memory(_font_NotoSansUI_Regular, _font_NotoSansUI_Regular_size, "ttf"));
+				font.push_back(ts->create_font_memory(_font_NotoSansThaiUI_Regular, _font_NotoSansThaiUI_Regular_size, "ttf"));
+
+				String test = U"คนอ้วน khon uan ראה";
+				//                 6^       17^
+
+				RID ctx = ts->create_shaped_text();
+				TEST_FAIL_COND(ctx == RID(), "Creating text buffer failed.");
+				bool ok = ts->shaped_text_add_string(ctx, test, font, 16);
+				TEST_FAIL_COND(!ok, "Adding text to the buffer failed.");
+
+				Vector<TextServer::Glyph> glyphs = ts->shaped_text_get_glyphs(ctx);
+				TEST_FAIL_COND(glyphs.size() == 0, "Shaping failed");
+				for (int j = 0; j < glyphs.size(); j++) {
+					if (glyphs[j].start < 6) {
+						TEST_FAIL_COND(glyphs[j].font_rid != font[1], "Incorrect font selected.");
+					}
+					if ((glyphs[j].start > 6) && (glyphs[j].start < 16)) {
+						TEST_FAIL_COND(glyphs[j].font_rid != font[0], "Incorrect font selected.");
+					}
+					if (glyphs[j].start > 16) {
+						TEST_FAIL_COND(glyphs[j].font_rid != RID(), "Incorrect font selected.");
+						TEST_FAIL_COND(glyphs[j].index != test[glyphs[j].start], "Incorrect glyph index.");
+					}
+					TEST_FAIL_COND((glyphs[j].start < 0 || glyphs[j].end > test.length()), "Incorrect glyph range.");
+					TEST_FAIL_COND(glyphs[j].font_size != 16, "Incorrect glyph font size.");
+				}
+
+				ts->free(ctx);
+
+				for (int j = 0; j < font.size(); j++) {
+					ts->free(font[j]);
+				}
+				font.clear();
+			}
+		}
+
+		SUBCASE("[TextServer] Text layout: BiDi") {
+			for (int i = 0; i < TextServerManager::get_interface_count(); i++) {
+				TextServer *ts = TextServerManager::initialize(i, err);
+
+				if (!ts->has_feature(TextServer::FEATURE_BIDI_LAYOUT)) {
+					continue;
+				}
+
+				Vector<RID> font;
+				font.push_back(ts->create_font_memory(_font_NotoSansUI_Regular, _font_NotoSansUI_Regular_size, "ttf"));
+				font.push_back(ts->create_font_memory(_font_NotoNaskhArabicUI_Regular, _font_NotoNaskhArabicUI_Regular_size, "ttf"));
+
+				String test = U"Arabic (اَلْعَرَبِيَّةُ, al-ʿarabiyyah)";
+				//                    7^      26^
+
+				RID ctx = ts->create_shaped_text();
+				TEST_FAIL_COND(ctx == RID(), "Creating text buffer failed.");
+				bool ok = ts->shaped_text_add_string(ctx, test, font, 16);
+				TEST_FAIL_COND(!ok, "Adding text to the buffer failed.");
+
+				Vector<TextServer::Glyph> glyphs = ts->shaped_text_get_glyphs(ctx);
+				TEST_FAIL_COND(glyphs.size() == 0, "Shaping failed");
+				for (int j = 0; j < glyphs.size(); j++) {
+					if (glyphs[j].count > 0) {
+						if (glyphs[j].start < 7) {
+							TEST_FAIL_COND(((glyphs[j].flags & TextServer::GRAPHEME_IS_RTL) == TextServer::GRAPHEME_IS_RTL), "Incorrect direction.");
+						}
+						if ((glyphs[j].start > 8) && (glyphs[j].start < 23)) {
+							TEST_FAIL_COND(((glyphs[j].flags & TextServer::GRAPHEME_IS_RTL) != TextServer::GRAPHEME_IS_RTL), "Incorrect direction.");
+						}
+						if (glyphs[j].start > 26) {
+							TEST_FAIL_COND(((glyphs[j].flags & TextServer::GRAPHEME_IS_RTL) == TextServer::GRAPHEME_IS_RTL), "Incorrect direction.");
+						}
+					}
+				}
+
+				ts->free(ctx);
+
+				for (int j = 0; j < font.size(); j++) {
+					ts->free(font[j]);
+				}
+				font.clear();
+			}
+		}
+
+		SUBCASE("[TextServer] Text layout: Line breaking") {
+			for (int i = 0; i < TextServerManager::get_interface_count(); i++) {
+				TextServer *ts = TextServerManager::initialize(i, err);
+
+				String test_1 = U"test test test";
+				//                   5^  10^
+
+				Vector<RID> font;
+				font.push_back(ts->create_font_memory(_font_NotoSansUI_Regular, _font_NotoSansUI_Regular_size, "ttf"));
+				font.push_back(ts->create_font_memory(_font_NotoSansThaiUI_Regular, _font_NotoSansThaiUI_Regular_size, "ttf"));
+
+				RID ctx = ts->create_shaped_text();
+				TEST_FAIL_COND(ctx == RID(), "Creating text buffer failed.");
+				bool ok = ts->shaped_text_add_string(ctx, test_1, font, 16);
+				TEST_FAIL_COND(!ok, "Adding text to the buffer failed.");
+
+				Vector<Vector2i> brks = ts->shaped_text_get_line_breaks(ctx, 1);
+				TEST_FAIL_COND(brks.size() != 3, "Invalid line breaks number.");
+				if (brks.size() == 3) {
+					TEST_FAIL_COND(brks[0] != Vector2i(0, 5), "Invalid line break position.");
+					TEST_FAIL_COND(brks[1] != Vector2i(5, 10), "Invalid line break position.");
+					TEST_FAIL_COND(brks[2] != Vector2i(10, 14), "Invalid line break position.");
+				}
+
+				ts->free(ctx);
+
+				for (int j = 0; j < font.size(); j++) {
+					ts->free(font[j]);
+				}
+				font.clear();
+			}
+		}
+
+		SUBCASE("[TextServer] Text layout: Justification") {
+			for (int i = 0; i < TextServerManager::get_interface_count(); i++) {
+				TextServer *ts = TextServerManager::initialize(i, err);
+
+				Vector<RID> font;
+				font.push_back(ts->create_font_memory(_font_NotoSansUI_Regular, _font_NotoSansUI_Regular_size, "ttf"));
+				font.push_back(ts->create_font_memory(_font_NotoNaskhArabicUI_Regular, _font_NotoNaskhArabicUI_Regular_size, "ttf"));
+
+				String test_1 = U"الحمد";
+				String test_2 = U"الحمد test";
+				String test_3 = U"test test";
+				//                    7^      26^
+
+				RID ctx;
+				bool ok;
+				float width_old, width;
+				if (ts->has_feature(TextServer::FEATURE_KASHIDA_JUSTIFICATION)) {
+					ctx = ts->create_shaped_text();
+					TEST_FAIL_COND(ctx == RID(), "Creating text buffer failed.");
+					ok = ts->shaped_text_add_string(ctx, test_1, font, 16);
+					TEST_FAIL_COND(!ok, "Adding text to the buffer failed.");
+
+					width_old = ts->shaped_text_get_width(ctx);
+					width = ts->shaped_text_fit_to_width(ctx, 100, TextServer::JUSTIFICATION_WORD_BOUND);
+					TEST_FAIL_COND((width != width_old), "Invalid fill width.");
+					width = ts->shaped_text_fit_to_width(ctx, 100, TextServer::JUSTIFICATION_WORD_BOUND | TextServer::JUSTIFICATION_KASHIDA);
+					TEST_FAIL_COND((width <= width_old || width > 100), "Invalid fill width.");
+
+					ts->free(ctx);
+
+					ctx = ts->create_shaped_text();
+					TEST_FAIL_COND(ctx == RID(), "Creating text buffer failed.");
+					ok = ts->shaped_text_add_string(ctx, test_2, font, 16);
+					TEST_FAIL_COND(!ok, "Adding text to the buffer failed.");
+
+					width_old = ts->shaped_text_get_width(ctx);
+					width = ts->shaped_text_fit_to_width(ctx, 100, TextServer::JUSTIFICATION_WORD_BOUND);
+					TEST_FAIL_COND((width <= width_old || width > 100), "Invalid fill width.");
+					width = ts->shaped_text_fit_to_width(ctx, 100, TextServer::JUSTIFICATION_WORD_BOUND | TextServer::JUSTIFICATION_KASHIDA);
+					TEST_FAIL_COND((width <= width_old || width > 100), "Invalid fill width.");
+
+					ts->free(ctx);
+				}
+
+				ctx = ts->create_shaped_text();
+				TEST_FAIL_COND(ctx == RID(), "Creating text buffer failed.");
+				ok = ts->shaped_text_add_string(ctx, test_3, font, 16);
+				TEST_FAIL_COND(!ok, "Adding text to the buffer failed.");
+
+				width_old = ts->shaped_text_get_width(ctx);
+				width = ts->shaped_text_fit_to_width(ctx, 100, TextServer::JUSTIFICATION_WORD_BOUND);
+				TEST_FAIL_COND((width <= width_old || width > 100), "Invalid fill width.");
+
+				ts->free(ctx);
+
+				for (int j = 0; j < font.size(); j++) {
+					ts->free(font[j]);
+				}
+				font.clear();
+			}
+		}
+
+		memdelete(tsman);
+	}
+}
+}; // namespace TestTextServer
+
+#endif // TEST_TEXT_SERVER_H

+ 6 - 0
thirdparty/README.md

@@ -143,6 +143,12 @@ Use UI font variant if available, because it has tight vertical metrics and good
 - Version: ? (pre-2014 commit when DroidSansJapanese.ttf was obsoleted)
 - License: Apache 2.0
 
+### Tamsyn
+- Upstream: http://www.fial.com/~scott/tamsyn-font/
+- Version: 1.11
+- License: Tamsyn
+
+Extracted "0..9,A..F" characters for hex code printing.
 
 ## freetype
 

BIN
thirdparty/fonts/Tamsyn10x20.png


BIN
thirdparty/fonts/Tamsyn5x9.png