Forráskód Böngészése

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

bruvzg 5 éve
szülő
commit
493da99269

+ 17 - 0
COPYRIGHT.txt

@@ -157,6 +157,11 @@ Copyright: 2018, Source Foundry Authors
   2003, Bitstream Inc.
   2003, Bitstream Inc.
 License: Expat and Bitstream Vera Fonts Copyright
 License: Expat and Bitstream Vera Fonts Copyright
 
 
+Files: ./thirdparty/fonts/Tamsyn*.png
+Comment: Tamsyn font
+Copyright: 2015, Scott Fial
+License: Tamsyn
+
 Files: ./thirdparty/freetype/
 Files: ./thirdparty/freetype/
 Comment: The FreeType Project
 Comment: The FreeType Project
 Copyright: 1996-2020, David Turner, Robert Wilhelm, and Werner Lemberg.
 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
  DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE.
  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
 License: Zlib
  This software is provided 'as-is', without any express or implied
  This software is provided 'as-is', without any express or implied
  warranty.  In no event will the authors be held liable for any damages
  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_PAUSED);
 	BIND_CONSTANT(NOTIFICATION_APPLICATION_FOCUS_IN);
 	BIND_CONSTANT(NOTIFICATION_APPLICATION_FOCUS_IN);
 	BIND_CONSTANT(NOTIFICATION_APPLICATION_FOCUS_OUT);
 	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")));
 	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_PAUSED = 2015,
 		NOTIFICATION_APPLICATION_FOCUS_IN = 2016,
 		NOTIFICATION_APPLICATION_FOCUS_IN = 2016,
 		NOTIFICATION_APPLICATION_FOCUS_OUT = 2017,
 		NOTIFICATION_APPLICATION_FOCUS_OUT = 2017,
+		NOTIFICATION_TEXT_SERVER_CHANGED = 2018,
 	};
 	};
 
 
 	virtual void init();
 	virtual void init();

+ 2 - 3
core/string/ustring.cpp

@@ -209,7 +209,6 @@ void CharString::copy_from(const char *p_cstr) {
 /*  String                                                               */
 /*  String                                                               */
 /*************************************************************************/
 /*************************************************************************/
 
 
-//TODO: move to TextServer
 //kind of poor should be rewritten properly
 //kind of poor should be rewritten properly
 String String::word_wrap(int p_chars_per_line) const {
 String String::word_wrap(int p_chars_per_line) const {
 	int from = 0;
 	int from = 0;
@@ -4796,7 +4795,7 @@ Vector<uint8_t> String::to_utf16_buffer() const {
 	Char16String charstr = s->utf16();
 	Char16String charstr = s->utf16();
 
 
 	Vector<uint8_t> retval;
 	Vector<uint8_t> retval;
-	size_t len = charstr.length() * 2;
+	size_t len = charstr.length() * sizeof(char16_t);
 	retval.resize(len);
 	retval.resize(len);
 	uint8_t *w = retval.ptrw();
 	uint8_t *w = retval.ptrw();
 	copymem(w, (const void *)charstr.ptr(), len);
 	copymem(w, (const void *)charstr.ptr(), len);
@@ -4811,7 +4810,7 @@ Vector<uint8_t> String::to_utf32_buffer() const {
 	}
 	}
 
 
 	Vector<uint8_t> retval;
 	Vector<uint8_t> retval;
-	size_t len = s->length() * 4;
+	size_t len = s->length() * sizeof(char32_t);
 	retval.resize(len);
 	retval.resize(len);
 	uint8_t *w = retval.ptrw();
 	uint8_t *w = retval.ptrw();
 	copymem(w, (const void *)s->ptr(), len);
 	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.                */
 /* 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/cowdata.h"
 #include "core/templates/vector.h"
 #include "core/templates/vector.h"
@@ -555,4 +556,4 @@ _FORCE_INLINE_ Vector<String> sarray(P... p_args) {
 	return arr;
 	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;
 		String s;
 		if (p_instance->size() > 0) {
 		if (p_instance->size() > 0) {
 			const uint8_t *r = p_instance->ptr();
 			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;
 		return s;
 	}
 	}
@@ -427,7 +427,7 @@ struct _VariantCall {
 		String s;
 		String s;
 		if (p_instance->size() > 0) {
 		if (p_instance->size() > 0) {
 			const uint8_t *r = p_instance->ptr();
 			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;
 		return s;
 	}
 	}

+ 117 - 7
main/main.cpp

@@ -72,6 +72,7 @@
 #include "servers/register_server_types.h"
 #include "servers/register_server_types.h"
 #include "servers/rendering/rendering_server_raster.h"
 #include "servers/rendering/rendering_server_raster.h"
 #include "servers/rendering/rendering_server_wrap_mt.h"
 #include "servers/rendering/rendering_server_wrap_mt.h"
+#include "servers/text_server.h"
 #include "servers/xr_server.h"
 #include "servers/xr_server.h"
 
 
 #ifdef TESTS_ENABLED
 #ifdef TESTS_ENABLED
@@ -113,6 +114,7 @@ static DisplayServer *display_server = nullptr;
 static RenderingServer *rendering_server = nullptr;
 static RenderingServer *rendering_server = nullptr;
 static CameraServer *camera_server = nullptr;
 static CameraServer *camera_server = nullptr;
 static XRServer *xr_server = nullptr;
 static XRServer *xr_server = nullptr;
+static TextServerManager *tsman = nullptr;
 static PhysicsServer3D *physics_server = nullptr;
 static PhysicsServer3D *physics_server = nullptr;
 static PhysicsServer2D *physics_2d_server = nullptr;
 static PhysicsServer2D *physics_2d_server = nullptr;
 static NavigationServer3D *navigation_server = nullptr;
 static NavigationServer3D *navigation_server = nullptr;
@@ -122,6 +124,7 @@ static bool _start_success = false;
 
 
 // Drivers
 // Drivers
 
 
+static int text_driver_idx = -1;
 static int display_driver_idx = -1;
 static int display_driver_idx = -1;
 static int audio_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(")");
 	}
 	}
 	OS::get_singleton()->print("].\n");
 	OS::get_singleton()->print("].\n");
+
 	OS::get_singleton()->print("  --rendering-driver <driver>      Rendering driver (depends on display driver).\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");
 	OS::get_singleton()->print("\n");
 
 
 #ifndef SERVER_ENABLED
 #ifndef SERVER_ENABLED
@@ -544,6 +558,7 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
 
 
 	I = args.front();
 	I = args.front();
 
 
+	String text_driver = "";
 	String display_driver = "";
 	String display_driver = "";
 	String audio_driver = "";
 	String audio_driver = "";
 	String tablet_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");
 				OS::get_singleton()->print("Missing audio driver argument, aborting.\n");
 				goto error;
 				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
 		} 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);
 	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");
 	GLOBAL_DEF("rendering/quality/driver/driver_name", "Vulkan");
 	ProjectSettings::get_singleton()->set_custom_property_info("rendering/quality/driver/driver_name",
 	ProjectSettings::get_singleton()->set_custom_property_info("rendering/quality/driver/driver_name",
 			PropertyInfo(Variant::STRING,
 			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);
 		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 */
 	/* Determine audio and video drivers */
 
 
 	for (int i = 0; i < DisplayServer::get_create_function_count(); i++) {
 	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:
 error:
 
 
+	text_driver = "";
 	display_driver = "";
 	display_driver = "";
 	audio_driver = "";
 	audio_driver = "";
 	tablet_driver = "";
 	tablet_driver = "";
@@ -1449,6 +1533,30 @@ Error Main::setup2(Thread::ID p_main_tid_override) {
 		Thread::_main_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 */
 	/* Initialize Input */
 
 
 	input = memnew(Input);
 	input = memnew(Input);
@@ -1459,23 +1567,21 @@ Error Main::setup2(Thread::ID p_main_tid_override) {
 		String rendering_driver; // temp broken
 		String rendering_driver; // temp broken
 
 
 		Error err;
 		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
 			//ok i guess we can't use this display server, try other ones
 			for (int i = 0; i < DisplayServer::get_create_function_count(); i++) {
 			for (int i = 0; i < DisplayServer::get_create_function_count(); i++) {
 				if (i == display_driver_idx) {
 				if (i == display_driver_idx) {
 					continue; //don't try the same twice
 					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;
 					break;
 				}
 				}
 			}
 			}
 		}
 		}
 
 
-		if (!display_server || err != OK) {
+		if (err != OK || display_server == nullptr) {
 			ERR_PRINT("Unable to create DisplayServer, all display drivers failed.");
 			ERR_PRINT("Unable to create DisplayServer, all display drivers failed.");
 			return err;
 			return err;
 		}
 		}
@@ -2572,6 +2678,10 @@ void Main::cleanup() {
 	finalize_navigation_server();
 	finalize_navigation_server();
 	finalize_display();
 	finalize_display();
 
 
+	if (tsman) {
+		memdelete(tsman);
+	}
+
 	if (input) {
 	if (input) {
 		memdelete(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/rasterizer.h"
 #include "rendering/rendering_device.h"
 #include "rendering/rendering_device.h"
 #include "rendering/rendering_device_binds.h"
 #include "rendering/rendering_device_binds.h"
-
 #include "rendering_server.h"
 #include "rendering_server.h"
 #include "servers/rendering/shader_types.h"
 #include "servers/rendering/shader_types.h"
+#include "text_server.h"
 #include "xr/xr_interface.h"
 #include "xr/xr_interface.h"
 #include "xr/xr_positional_tracker.h"
 #include "xr/xr_positional_tracker.h"
 #include "xr_server.h"
 #include "xr_server.h"
@@ -102,6 +102,11 @@ void register_server_types() {
 	ClassDB::register_virtual_class<DisplayServer>();
 	ClassDB::register_virtual_class<DisplayServer>();
 	ClassDB::register_virtual_class<RenderingServer>();
 	ClassDB::register_virtual_class<RenderingServer>();
 	ClassDB::register_class<AudioServer>();
 	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<PhysicsServer2D>();
 	ClassDB::register_virtual_class<PhysicsServer3D>();
 	ClassDB::register_virtual_class<PhysicsServer3D>();
 	ClassDB::register_virtual_class<NavigationServer2D>();
 	ClassDB::register_virtual_class<NavigationServer2D>();
@@ -209,6 +214,7 @@ void register_server_types() {
 
 
 void unregister_server_types() {
 void unregister_server_types() {
 	memdelete(shader_types);
 	memdelete(shader_types);
+	TextServer::finish_hex_code_box_fonts();
 }
 }
 
 
 void register_server_singletons() {
 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("PhysicsServer3D", PhysicsServer3D::get_singleton()));
 	Engine::get_singleton()->add_singleton(Engine::Singleton("NavigationServer2D", NavigationServer2D::get_singleton_mut()));
 	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("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("XRServer", XRServer::get_singleton()));
 	Engine::get_singleton()->add_singleton(Engine::Singleton("CameraServer", CameraServer::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_gui.h"
 #include "test_json.h"
 #include "test_json.h"
 #include "test_list.h"
 #include "test_list.h"
+#include "test_lru.h"
 #include "test_math.h"
 #include "test_math.h"
 #include "test_method_bind.h"
 #include "test_method_bind.h"
 #include "test_node_path.h"
 #include "test_node_path.h"
@@ -58,6 +59,7 @@
 #include "test_render.h"
 #include "test_render.h"
 #include "test_shader_lang.h"
 #include "test_shader_lang.h"
 #include "test_string.h"
 #include "test_string.h"
+#include "test_text_server.h"
 #include "test_validate_testing.h"
 #include "test_validate_testing.h"
 #include "test_variant.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)
 - Version: ? (pre-2014 commit when DroidSansJapanese.ttf was obsoleted)
 - License: Apache 2.0
 - 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
 ## freetype
 
 

BIN
thirdparty/fonts/Tamsyn10x20.png


BIN
thirdparty/fonts/Tamsyn5x9.png