text:
/*************************************************************************/
/*  tl_bitmap_font.cpp                                                   */
/*************************************************************************/

/*
This file is based in part on Godot's BitmapFont implementation, licensed under MIT license.
For more information, see https://github.com/godotengine/godot/blob/master/LICENSE.txt
For original source, see https://github.com/godotengine/godot/blob/master/scene/resources/font.cpp
*/

#include "tl_bitmap_font.hpp"

#ifdef GODOT_MODULE
#include "core/bind/core_bind.h"
#include "servers/visual_server.h"
#define File _File
#else
#include <File.hpp>
#include <ResourceLoader.hpp>
#include <Texture.hpp>
#include <VisualServer.hpp>
#endif

/*************************************************************************/
/*  hb_bmp_font_t HarfBuzz Bitmap font interface                         */
/*************************************************************************/

struct hb_bmp_font_t {
	TLBitmapFontFaceAtSize *bm_face;
	bool unref; /* Whether to destroy bm_face when done. */
};

static hb_bmp_font_t *_hb_bmp_font_create(TLBitmapFontFaceAtSize *bm_face, bool unref) {
	hb_bmp_font_t *bm_font = reinterpret_cast<hb_bmp_font_t *>(calloc(1, sizeof(hb_bmp_font_t)));

	if (!bm_font)
		return nullptr;

	bm_font->bm_face = bm_face;
	bm_font->unref = unref;

	return bm_font;
}

static void _hb_bmp_font_destroy(void *data) {
	hb_bmp_font_t *bm_font = reinterpret_cast<hb_bmp_font_t *>(data);
	free(bm_font);
}

static hb_bool_t hb_bmp_get_nominal_glyph(hb_font_t *font, void *font_data, hb_codepoint_t unicode, hb_codepoint_t *glyph, void *user_data) {
	const hb_bmp_font_t *bm_font = reinterpret_cast<const hb_bmp_font_t *>(font_data);

	if (!bm_font->bm_face)
		return false;

	if (!bm_font->bm_face->font->has_glyph(unicode)) {
		if (bm_font->bm_face->font->has_glyph(0xF000u + unicode)) {
			*glyph = 0xF000u + unicode;
			return true;
		} else {
			return false;
		}
	}

	*glyph = unicode;
	return true;
}

static hb_position_t hb_bmp_get_glyph_h_advance(hb_font_t *font, void *font_data, hb_codepoint_t glyph, void *user_data) {
	const hb_bmp_font_t *bm_font = reinterpret_cast<const hb_bmp_font_t *>(font_data);

	if (!bm_font->bm_face)
		return 0;

	if (!bm_font->bm_face->font->has_glyph(glyph))
		return 0;

	return bm_font->bm_face->font->get_glyph_advance(glyph, bm_font->bm_face->size) * 64;
}

static hb_position_t hb_bmp_get_glyph_h_kerning(hb_font_t *font, void *font_data, hb_codepoint_t left_glyph, hb_codepoint_t right_glyph, void *user_data) {
	const hb_bmp_font_t *bm_font = reinterpret_cast<const hb_bmp_font_t *>(font_data);

	if (!bm_font->bm_face)
		return 0;

	if (!bm_font->bm_face->font->has_glyph(left_glyph))
		return 0;

	if (!bm_font->bm_face->font->has_glyph(right_glyph))
		return 0;

	return bm_font->bm_face->font->get_kerning_pair(left_glyph, right_glyph, bm_font->bm_face->size) * 64;
}

static hb_bool_t hb_bmp_get_glyph_v_origin(hb_font_t *font, void *font_data, hb_codepoint_t glyph, hb_position_t *x, hb_position_t *y, void *user_data) {
	const hb_bmp_font_t *bm_font = reinterpret_cast<const hb_bmp_font_t *>(font_data);

	if (!bm_font->bm_face)
		return false;

	if (!bm_font->bm_face->font->has_glyph(glyph))
		return false;

	*x = bm_font->bm_face->font->get_glyph_align(glyph, bm_font->bm_face->size).x * 64;
	*y = bm_font->bm_face->font->get_glyph_align(glyph, bm_font->bm_face->size).y * 64;

	return true;
}

static hb_bool_t hb_bmp_get_glyph_extents(hb_font_t *font, void *font_data, hb_codepoint_t glyph, hb_glyph_extents_t *extents, void *user_data) {
	const hb_bmp_font_t *bm_font = reinterpret_cast<const hb_bmp_font_t *>(font_data);

	if (!bm_font->bm_face)
		return false;

	if (!bm_font->bm_face->font->has_glyph(glyph))
		return false;

	extents->x_bearing = 0;
	extents->y_bearing = 0;
	extents->width = bm_font->bm_face->font->get_glyph_size(glyph, bm_font->bm_face->size).x * 64;
	extents->height = bm_font->bm_face->font->get_glyph_size(glyph, bm_font->bm_face->size).y * 64;

	return true;
}

static hb_bool_t hb_bmp_get_font_h_extents(hb_font_t *font, void *font_data, hb_font_extents_t *metrics, void *user_data) {
	const hb_bmp_font_t *bm_font = reinterpret_cast<const hb_bmp_font_t *>(font_data);

	if (!bm_font->bm_face)
		return false;

	metrics->ascender = bm_font->bm_face->font->get_ascent(bm_font->bm_face->size);
	metrics->descender = bm_font->bm_face->font->get_descent(bm_font->bm_face->size);
	metrics->line_gap = 0;

	return true;
}

static hb_font_funcs_t *_hb_bmp_get_font_funcs(void) {

	hb_font_funcs_t *funcs = hb_font_funcs_create();

	hb_font_funcs_set_font_h_extents_func(funcs, hb_bmp_get_font_h_extents, nullptr, nullptr);
	//hb_font_funcs_set_font_v_extents_func (funcs, hb_bmp_get_font_v_extents, nullptr, nullptr);
	hb_font_funcs_set_nominal_glyph_func(funcs, hb_bmp_get_nominal_glyph, nullptr, nullptr);
	//hb_font_funcs_set_variation_glyph_func (funcs, hb_bmp_get_variation_glyph, nullptr, nullptr);
	hb_font_funcs_set_glyph_h_advance_func(funcs, hb_bmp_get_glyph_h_advance, nullptr, nullptr);
	//hb_font_funcs_set_glyph_v_advance_func (funcs, hb_bmp_get_glyph_v_advance, nullptr, nullptr);
	//hb_font_funcs_set_glyph_h_origin_func (funcs, hb_bmp_get_glyph_h_origin, nullptr, nullptr);
	hb_font_funcs_set_glyph_v_origin_func(funcs, hb_bmp_get_glyph_v_origin, nullptr, nullptr);
	hb_font_funcs_set_glyph_h_kerning_func(funcs, hb_bmp_get_glyph_h_kerning, nullptr, nullptr);
	//hb_font_funcs_set_glyph_v_kerning_func (funcs, hb_bmp_get_glyph_v_kerning, nullptr, nullptr);
	hb_font_funcs_set_glyph_extents_func(funcs, hb_bmp_get_glyph_extents, nullptr, nullptr);
	//hb_font_funcs_set_glyph_contour_point_func (funcs, hb_bmp_get_glyph_contour_point, nullptr, nullptr);
	//hb_font_funcs_set_glyph_name_func (funcs, hb_bmp_get_glyph_name, nullptr, nullptr);
	//hb_font_funcs_set_glyph_from_name_func (funcs, hb_bmp_get_glyph_from_name, nullptr, nullptr);

	hb_font_funcs_make_immutable(funcs);

	return funcs;
}

static void _hb_bmp_font_set_funcs(hb_font_t *font, TLBitmapFontFaceAtSize *bm_face, bool unref) {
	hb_font_set_funcs(font, _hb_bmp_get_font_funcs(), _hb_bmp_font_create(bm_face, unref), _hb_bmp_font_destroy);
}

hb_font_t *hb_bmp_font_create(TLBitmapFontFaceAtSize *bm_face, hb_destroy_func_t destroy) {
	hb_font_t *font;
	hb_face_t *face = hb_face_create(NULL, 0);

	font = hb_font_create(face);
	hb_face_destroy(face);
	_hb_bmp_font_set_funcs(font, bm_face, false);
	return font;
}

/*************************************************************************/
/*  TLBitmapFontFaceAtSize                                               */
/*************************************************************************/

TLBitmapFontFaceAtSize::TLBitmapFontFaceAtSize() {

	hb_font = NULL;
	font = NULL;
	size = 0;
}

TLBitmapFontFaceAtSize::~TLBitmapFontFaceAtSize() {

	if (hb_font) {
		hb_font_destroy(hb_font);
		hb_font = NULL;
	}
}

/*************************************************************************/
/*  TLBitmapFontFace                                                     */
/*************************************************************************/

TLBitmapFontFace::TLBitmapFontFace() {

#ifdef GODOT_MODULE
	_init();
#endif
}

void TLBitmapFontFace::_init() {

	txt_flags = Texture::FLAG_VIDEO_SURFACE;
	bmp_size = 0;
	ascent = 0.0f;
	descent = 0.0f;
	height = 0.0f;
	loaded = false;
}

TLBitmapFontFace::~TLBitmapFontFace() {

	clear_cache();
	loaded = false;
}

float TLBitmapFontFace::get_glyph_advance(uint32_t p_codepoint, int p_size) const {

	if (!loaded)
		const_cast<TLBitmapFontFace *>(this)->load(path);
	float scale = (float)p_size / bmp_size;
	if (loaded && (glyph_cache.count(p_codepoint) > 0)) {
		return glyph_cache.at(p_codepoint).advance * scale;
	} else {
		return 0.0f;
	}
}

Point2 TLBitmapFontFace::get_glyph_align(uint32_t p_codepoint, int p_size) const {

	if (!loaded)
		const_cast<TLBitmapFontFace *>(this)->load(path);
	float scale = (float)p_size / bmp_size;
	if (loaded && (glyph_cache.count(p_codepoint) > 0)) {
		return glyph_cache.at(p_codepoint).align * scale;
	} else {
		return Point2();
	}
}

Size2 TLBitmapFontFace::get_glyph_size(uint32_t p_codepoint, int p_size) const {

	if (!loaded)
		const_cast<TLBitmapFontFace *>(this)->load(path);
	float scale = (float)p_size / bmp_size;
	if (loaded && (glyph_cache.count(p_codepoint) > 0)) {
		return glyph_cache.at(p_codepoint).uv.size * scale;
	} else {
		return Size2();
	}
}

float TLBitmapFontFace::get_kerning_pair(uint32_t p_codepoint_l, uint32_t p_codepoint_r, int p_size) const {

	if (!loaded)
		const_cast<TLBitmapFontFace *>(this)->load(path);

	float scale = (float)p_size / bmp_size;
	if (loaded) {
		KerningPairKey kpk;
		kpk.A = p_codepoint_l;
		kpk.B = p_codepoint_r;

		auto search = kerning_map.find(kpk);
		if (search != kerning_map.end()) {
			return search->second * scale;
		}
	}
	return 0.0f;
}

bool TLBitmapFontFace::has_glyph(uint32_t p_codepoint) const {

	if (!loaded)
		const_cast<TLBitmapFontFace *>(this)->load(path);

	return (loaded && (glyph_cache.count(p_codepoint) > 0));
}

void TLBitmapFontFace::_draw_char(RID p_canvas_item, const Point2 p_pos, uint32_t p_codepoint, const Color p_modulate, int p_size) const {

	draw_glyph(p_canvas_item, p_pos, p_codepoint, p_modulate, p_size);
}

void TLBitmapFontFace::draw_glyph(RID p_canvas_item, const Point2 p_pos, uint32_t p_codepoint, const Color p_modulate, int p_size) const {

	if (p_size < 1) return;

	if (!loaded)
		const_cast<TLBitmapFontFace *>(this)->load(path);

	float scale = (float)p_size / bmp_size;
	if (loaded && (glyph_cache.count(p_codepoint) > 0)) {

		const Glyph &gl = glyph_cache.at(p_codepoint);

		ERR_FAIL_COND(gl.id >= texture_cache.size());

		VisualServer::get_singleton()->canvas_item_add_texture_rect_region(p_canvas_item, Rect2(p_pos + gl.align * scale, gl.uv.size * scale), texture_cache[gl.id]->get_rid(), gl.uv, p_modulate, false, RID(), false);
	} else {
		WARN_PRINTS("Font not loaded or glyph not found!");
	}
}

hb_font_t *TLBitmapFontFace::get_hb_font(int p_size) const {

	if (p_size < 1) return NULL;

	if (!loaded)
		const_cast<TLBitmapFontFace *>(this)->load(path);

	if (sizes.count(p_size) > 0) {
		return sizes.at(p_size)->hb_font;
	} else {
		TLBitmapFontFaceAtSize *f_at_s = new TLBitmapFontFaceAtSize();

		f_at_s->font = this;
		f_at_s->size = p_size;
		f_at_s->hb_font = hb_bmp_font_create(f_at_s, NULL);
		if (f_at_s->hb_font) {
			sizes[p_size] = f_at_s;
			return f_at_s->hb_font;
		}
	}
	return NULL;
}

void TLBitmapFontFace::clear_cache() {

	texture_cache.clear();
	glyph_cache.clear();
	kerning_map.clear();
	for (auto it = sizes.begin(); it != sizes.end(); ++it) {
		delete it->second;
	}
	sizes.clear();
}

void TLBitmapFontFace::set_font_path(String p_resource_path) {

	if (path != p_resource_path) {
		load(p_resource_path);
	}
}

bool TLBitmapFontFace::load(String p_resource_path) {

	path = p_resource_path;
	bool was_loaded = loaded;
	if (loaded) {
		//unload existing
		clear_cache();
		loaded = false;
	}

	Ref<File> file;
	file.instance();
	if (file->open(p_resource_path, File::READ) != Error::OK) {
		ERR_PRINTS("Can't open bitmap font file: \"" + p_resource_path + "\"");
		if (was_loaded) emit_signal(_CHANGED); //Only emit when old valid font is unloaded during this call, to prevent cyclic calls
		return false;
	}

	while (true) {

		String line = file->get_line();

		int delimiter = line.find(" ", 0);
		String type = line.substr(0, delimiter);
		int pos = delimiter + 1;
		std::map<String, String> keys;

		while (pos < line.length() && line[pos] == ' ')
			pos++;

		while (pos < line.length()) {

			int eq = line.find("=", pos);
			if (eq == -1)
				break;
			String key = line.substr(pos, eq - pos);
			int end;
			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.length();

				value = line.substr(eq + 1, end - eq);

				pos = end;
			}

			while (pos < line.length() && line[pos] == ' ')
				pos++;

			keys[key] = value;
		}

		if (type == "info") {
			//if (keys.count("face") > 0)
			//	font_name = keys["face"];
			if (keys.count("size") > 0) {
				bmp_size = keys["size"].to_int();
			}
		} else if (type == "common") {
			if (keys.count("lineHeight") > 0)
				height = keys["lineHeight"].to_int();
			if (keys.count("base") > 0)
				ascent = keys["base"].to_int();
		} else if (type == "page") {
			if (keys.count("file") > 0) {
				String base_dir = p_resource_path.get_base_dir();
				String file_name = base_dir.plus_file(keys["file"]);
#ifdef GODOT_MODULE
				Ref<Texture> tex = ResourceLoader::load(file_name);
#else
				Ref<Texture> tex = cast_to<Texture>(*ResourceLoader::get_singleton()->load(file_name));
#endif
				if (tex.is_null()) {
					ERR_PRINTS("Can't load bitmap font texture: \"" + file_name + "\"");
					clear_cache();
					file->close();
					if (was_loaded) emit_signal(_CHANGED);
					return false;
				} else {
					tex->set_flags(txt_flags);
					texture_cache.push_back(tex);
				}
			}
		} else if (type == "char") {
			uint32_t idx = 0;
			if (keys.count("id") > 0)
				idx = keys["id"].to_int();

			Rect2 rect;
			if (keys.count("x") > 0)
				rect.position.x = keys["x"].to_int();
			if (keys.count("y"))
				rect.position.y = keys["y"].to_int();
			if (keys.count("width") > 0)
				rect.size.width = keys["width"].to_int();
			if (keys.count("height") > 0)
				rect.size.height = keys["height"].to_int();

			Point2 ofs;
			if (keys.count("xoffset") > 0)
				ofs.x = keys["xoffset"].to_int();
			if (keys.count("yoffset") > 0)
				ofs.y = keys["yoffset"].to_int();

			int texture = 0;
			if (keys.count("page") > 0)
				texture = keys["page"].to_int();
			int advance = -1;
			if (keys.count("xadvance") > 0)
				advance = keys["xadvance"].to_int();
			if (advance < 0)
				advance = rect.size.width;

			Glyph gl;
			gl.uv = rect;
			gl.id = texture;
			gl.align = ofs;
			gl.advance = advance;

			glyph_cache[idx] = gl;
		} else if (type == "kerning") {
			uint32_t first = 0, second = 0;
			int k = 0;

			if (keys.count("first") > 0)
				first = keys["first"].to_int();
			if (keys.count("second") > 0)
				second = keys["second"].to_int();
			if (keys.count("amount") > 0)
				k = keys["amount"].to_int();

			KerningPairKey kpk;
			kpk.A = first;
			kpk.B = second;

			if ((k == 0) && (kerning_map.count(kpk) > 0)) {
				kerning_map.erase(kpk);
			} else {
				kerning_map[kpk] = -k;
			}
		}

		if (file->eof_reached())
			break;
	}

	file->close();
	loaded = true;
	emit_signal(_CHANGED);
	return loaded;
}

std::vector<hb_script_t> TLBitmapFontFace::unicode_scripts_supported() const {

	std::vector<hb_script_t> ret;
	//TODO: detect supported scripts!
	ret.push_back(HB_SCRIPT_COMMON);
	return ret;
}

double TLBitmapFontFace::get_ascent(int p_size) const {

	if (p_size < 1) return 0.0f;

	float scale = (float)p_size / bmp_size;
	if (!loaded)
		const_cast<TLBitmapFontFace *>(this)->load(path);
	return loaded ? ascent * scale : 0.0f;
}

double TLBitmapFontFace::get_descent(int p_size) const {

	if (p_size < 1) return 0.0f;

	float scale = (float)p_size / bmp_size;
	if (!loaded)
		const_cast<TLBitmapFontFace *>(this)->load(path);
	return loaded ? descent * scale : 0.0f;
}

double TLBitmapFontFace::get_height(int p_size) const {

	if (p_size < 1) return 0.0f;

	float scale = (float)p_size / bmp_size;
	if (!loaded)
		const_cast<TLBitmapFontFace *>(this)->load(path);
	return loaded ? height * scale : 0.0f;
}

int TLBitmapFontFace::get_base_size() const {

	if (!loaded)
		const_cast<TLBitmapFontFace *>(this)->load(path);
	return loaded ? bmp_size : 0;
}

void TLBitmapFontFace::set_texture_flags(int p_flags) {

	if (txt_flags != p_flags) {
		txt_flags = p_flags;
		if (!loaded)
			load(path);
		if (loaded) {
			for (size_t i = 0; i < texture_cache.size(); i++) {
				Ref<Texture> &tex = texture_cache[i];
				if (!tex.is_null())
					tex->set_flags(txt_flags);
			}
			emit_signal(_CHANGED);
		}
	}
}

int TLBitmapFontFace::get_texture_flags() const {

	return txt_flags;
}

repo_language: C

mime_type: text/x-c


text:
/*************************************************************************/
/*  tl_gd_font_wrapper.hpp                                               */
/*************************************************************************/

#ifndef TL_GD_FONT_WRAPPER_HPP
#define TL_GD_FONT_WRAPPER_HPP

#ifdef GODOT_MODULE
#include "scene/resources/font.h"
#else
#include <Font.hpp>
#endif

#include <list>
#include <map>

#include "resources/tl_font_family.hpp"
#include "resources/tl_shaped_string.hpp"

using namespace godot;

class TLGDFontWrapper : public Font {

	GODOT_CLASS(TLGDFontWrapper, Font);

	mutable std::map<int64_t, Ref<TLShapedString> > sstr;
	mutable std::list<int64_t> sids;

	int scdepth;

	Ref<TLFontFamily> family;
	String fstyle;
	int fsize;

public:
#ifdef GODOT_MODULE
	static void _bind_methods();
#else
	static void _register_methods();
#endif
	void _init();

	Ref<TLFontFamily> get_base_font() const;
	void set_base_font(const Ref<TLFontFamily> p_font);

	String get_base_font_style() const;
	void set_base_font_style(const String p_style);

	int get_base_font_size() const;
	void set_base_font_size(int p_size);

	int get_cache_depth() const;
	void set_cache_depth(int p_cache_depth);

	virtual float get_height() const;

	virtual float get_ascent() const;
	virtual float get_descent() const;

	virtual Size2 get_char_size(CharType p_char, CharType p_next = 0) const;
	virtual Size2 get_string_size(const String &p_text) const;

	virtual bool is_distance_field_hint() const;

	virtual void draw(RID p_canvas_item, const Point2 &p_pos, const String &p_text, const Color &p_modulate = Color(1, 1, 1), int p_clip_w = -1, const Color &p_outline_modulate = Color(1, 1, 1)) const;

	virtual float draw_char(RID p_canvas_item, const Point2 &p_pos, CharType p_char, CharType p_next = 0, const Color &p_modulate = Color(1, 1, 1), bool p_outline = false) const;

	TLGDFontWrapper();
	virtual ~TLGDFontWrapper();
};

#endif /*TL_GD_FONT_WRAPPER_HPP*/


text:
/*************************************************************************/
/*  tl_icu_data_loader.hpp                                               */
/*************************************************************************/

#ifndef TL_ICU_DATA_LOADER_H
#define TL_ICU_DATA_LOADER_H

#include "tl_core.hpp"

#ifdef GODOT_MODULE
#include "core/resource.h"
#endif

#include <unicode/uclean.h>
#include <unicode/udata.h>

using namespace godot;

class TLICUDataLoader : public Resource {
	GODOT_CLASS(TLICUDataLoader, Resource);

	static uint8_t *icu_data;
	String path;

public:
	TLICUDataLoader();
	~TLICUDataLoader();

	void _init();

	void set_data_path(String p_resource_path);
	String get_data_path() const;

	bool load(String p_path);
	static void unload();

#ifdef GODOT_MODULE
	static void _bind_methods();
#else
	static void _register_methods();
#endif
};

#endif /*TL_ICU_DATA_LOADER*/


text:
/*************************************************************************/
/*  tl_shaped_string.hpp                                                 */
/*************************************************************************/

#ifndef TL_SHAPED_STRING_HPP
#define TL_SHAPED_STRING_HPP

#include "tl_font.hpp"
#include "tl_font_family.hpp"

#ifdef GODOT_MODULE

#else
#include <Array.hpp>
#endif

#include <algorithm>
#include <cstdlib>
#include <vector>

#include <unicode/ubidi.h>
#include <unicode/ubrk.h>
#include <unicode/uchar.h>
#include <unicode/uclean.h>
#include <unicode/udata.h>
#include <unicode/uiter.h>
#include <unicode/uloc.h>
#include <unicode/uscript.h>
#include <unicode/ustring.h>
#include <unicode/utypes.h>

#include <hb-icu.h>
#include <hb.h>

using namespace godot;

enum TextDirection {

	TEXT_DIRECTION_LTR = 0,
	TEXT_DIRECTION_RTL = 1,
	TEXT_DIRECTION_LOCALE = 2,
	TEXT_DIRECTION_AUTO = 3,
	TEXT_DIRECTION_INVALID = 4
};

enum TextJustification {

	TEXT_JUSTIFICATION_NONE = 0,
	TEXT_JUSTIFICATION_KASHIDA_AND_WHITESPACE = 1,
	TEXT_JUSTIFICATION_KASHIDA_ONLY = 2,
	TEXT_JUSTIFICATION_WHITESPACE_ONLY = 3,
	TEXT_JUSTIFICATION_KASHIDA_AND_WHITESPACE_AND_INTERCHAR = 4,
	TEXT_JUSTIFICATION_KASHIDA_AND_INTERCHAR = 5,
	TEXT_JUSTIFICATION_WHITESPACE_AND_INTERCHAR = 6,
	TEXT_JUSTIFICATION_INTERCHAR_ONLY = 7
};

enum TextBreak {

	TEXT_BREAK_NONE = 0,
	TEXT_BREAK_MANDATORY = 1,
	TEXT_BREAK_MANDATORY_AND_WORD_BOUND = 2,
	TEXT_BREAK_MANDATORY_AND_ANYWHERE = 3
};

enum TextTrimMode {

	TEXT_TRIM_NONE = 0,
	TEXT_TRIM_BREAK = 1,
	TEXT_TRIM_BREAK_AND_WHITESPACE = 2
};

class TLShapedString : public Resource {
	GODOT_CLASS(TLShapedString, Resource);

protected:
	enum {
		_CLUSTER_TYPE_INVALID = 0,
		_CLUSTER_TYPE_HEX_BOX = 1, //Fallback hex box font codepoint
		_CLUSTER_TYPE_TEXT = 2, //Normal glyphs
		_CLUSTER_TYPE_SKIP = 3 //Justification spaces
	};

	struct Glyph {

		UChar32 codepoint;
		Point2 offset;
		Point2 advance;

		Glyph();
		Glyph(const Glyph &p_a);
		Glyph(uint32_t p_codepoint, Point2 p_offset, Point2 p_advance);
	};

	struct Cluster {

		int cl_type;

		int64_t start;
		int64_t end;

		bool valid;
		bool is_rtl;
		bool ignore_on_input;

		TLFontFace *font_face;

		float offset;
		float ascent;
		float descent;
		float width;

		//debug info
		hb_script_t script;
		hb_direction_t dir;
		hb_language_t lang;
		//debug info

		std::vector<Glyph> glyphs;

		Cluster(const Cluster &p_a);
		Cluster();
	};

	static bool ClusterCompare(const Cluster &p_a, const Cluster &p_b) {
		return p_a.start < p_b.start;
	};

	class ScriptIterator {

		struct ScriptRange {

			int32_t start;
			int32_t end;
			hb_script_t script;
		};

		static bool same_script(int32_t p_script_one, int32_t p_script_two);

		_ALWAYS_INLINE_ int32_t next_bound(const UChar *p_chars, int32_t p_offset, int32_t p_length) const {

			if (p_offset < 0)
				p_offset = 0;

			if (p_offset >= p_length)
				return p_length;

			return MIN((U16_IS_SURROGATE(p_chars[p_offset]) && U16_IS_SURROGATE_TRAIL(p_chars[p_offset])) ? p_offset + 1 : p_offset, p_length);
		}

		int64_t cur;
		bool is_rtl;
		std::vector<ScriptRange> script_ranges;

	public:
		bool next();

		int32_t get_start() const;
		int32_t get_end() const;
		hb_script_t get_script() const;

		void reset(hb_direction_t p_run_direction);

		ScriptIterator(const UChar *p_chars, int32_t p_start, int32_t p_length);
	};

	struct BreakOpportunity {

		int64_t position;
		bool hard;
	};

	struct JustificationOpportunity {

		int64_t position;
		bool kashida;
	};

	bool valid;

	TextDirection base_direction;
	TextDirection para_direction;
	Ref<TLFontFamily> base_font;
	String base_style;
	int base_size;

	UChar *data;
	std::vector<hb_feature_t> font_features;
	hb_language_t language;

	UBiDi *bidi_iter;
	ScriptIterator *script_iter;

	hb_buffer_t *hb_buffer;
	bool preserve_control;

	int64_t data_size;
	int64_t char_size;

	std::vector<Cluster> visual;

	float ascent;
	float descent;
	float width;

	_ALWAYS_INLINE_ bool is_ain(uint32_t p_chr) const { return u_getIntPropertyValue(p_chr, UCHAR_JOINING_GROUP) == U_JG_AIN; }
	_ALWAYS_INLINE_ bool is_alef(uint32_t p_chr) const { return u_getIntPropertyValue(p_chr, UCHAR_JOINING_GROUP) == U_JG_ALEF; }
	_ALWAYS_INLINE_ bool is_beh(uint32_t p_chr) const {
		int32_t prop = u_getIntPropertyValue(p_chr, UCHAR_JOINING_GROUP);
		return (prop == U_JG_BEH) || (prop == U_JG_NOON) || (prop == U_JG_AFRICAN_NOON) || (prop == U_JG_NYA) || (prop == U_JG_YEH) || (prop == U_JG_FARSI_YEH);
	}
	_ALWAYS_INLINE_ bool is_dal(uint32_t p_chr) const { return u_getIntPropertyValue(p_chr, UCHAR_JOINING_GROUP) == U_JG_DAL; }
	_ALWAYS_INLINE_ bool is_feh(uint32_t p_chr) const { return (u_getIntPropertyValue(p_chr, UCHAR_JOINING_GROUP) == U_JG_FEH) || (u_getIntPropertyValue(p_chr, UCHAR_JOINING_GROUP) == U_JG_AFRICAN_FEH); }
	_ALWAYS_INLINE_ bool is_gaf(uint32_t p_chr) const { return u_getIntPropertyValue(p_chr, UCHAR_JOINING_GROUP) == U_JG_GAF; }
	_ALWAYS_INLINE_ bool is_heh(uint32_t p_chr) const { return u_getIntPropertyValue(p_chr, UCHAR_JOINING_GROUP) == U_JG_HEH; }
	_ALWAYS_INLINE_ bool is_kaf(uint32_t p_chr) const { return u_getIntPropertyValue(p_chr, UCHAR_JOINING_GROUP) == U_JG_KAF; }
	_ALWAYS_INLINE_ bool is_lam(uint32_t p_chr) const { return u_getIntPropertyValue(p_chr, UCHAR_JOINING_GROUP) == U_JG_LAM; }
	_ALWAYS_INLINE_ bool is_qaf(uint32_t p_chr) const { return (u_getIntPropertyValue(p_chr, UCHAR_JOINING_GROUP) == U_JG_QAF) || (u_getIntPropertyValue(p_chr, UCHAR_JOINING_GROUP) == U_JG_AFRICAN_QAF); }
	_ALWAYS_INLINE_ bool is_reh(uint32_t p_chr) const { return u_getIntPropertyValue(p_chr, UCHAR_JOINING_GROUP) == U_JG_REH; }
	_ALWAYS_INLINE_ bool is_seen_sad(uint32_t p_chr) const { return (u_getIntPropertyValue(p_chr, UCHAR_JOINING_GROUP) == U_JG_SAD) || (u_getIntPropertyValue(p_chr, UCHAR_JOINING_GROUP) == U_JG_SEEN); }
	_ALWAYS_INLINE_ bool is_tah(uint32_t p_chr) const { return u_getIntPropertyValue(p_chr, UCHAR_JOINING_GROUP) == U_JG_TAH; }
	_ALWAYS_INLINE_ bool is_teh_marbuta(uint32_t p_chr) const { return u_getIntPropertyValue(p_chr, UCHAR_JOINING_GROUP) == U_JG_TEH_MARBUTA; }
	_ALWAYS_INLINE_ bool is_yeh(uint32_t p_chr) const {
		int32_t prop = u_getIntPropertyValue(p_chr, UCHAR_JOINING_GROUP);
		return (prop == U_JG_YEH) || (prop == U_JG_FARSI_YEH) || (prop == U_JG_YEH_BARREE) || (prop == U_JG_BURUSHASKI_YEH_BARREE) || (prop == U_JG_YEH_WITH_TAIL);
	}
	_ALWAYS_INLINE_ bool is_waw(uint32_t p_chr) const { return u_getIntPropertyValue(p_chr, UCHAR_JOINING_GROUP) == U_JG_WAW; }
	_ALWAYS_INLINE_ bool is_transparent(uint32_t p_chr) const { return u_getIntPropertyValue(p_chr, UCHAR_JOINING_TYPE) == U_JT_TRANSPARENT; }
	_ALWAYS_INLINE_ bool is_ligature(uint32_t p_chr, uint32_t p_nchr) const { return (is_lam(p_chr) && is_alef(p_nchr)); }
	_ALWAYS_INLINE_ bool is_connected_to_prev(uint32_t p_chr, uint32_t p_pchr) const {
		int32_t prop = u_getIntPropertyValue(p_pchr, UCHAR_JOINING_TYPE);
		return (prop != U_JT_RIGHT_JOINING) && (prop != U_JT_NON_JOINING) ? !is_ligature(p_pchr, p_chr) : false;
	}
	_ALWAYS_INLINE_ bool is_ws(UChar32 p_char) const {
		return (p_char == 0x000A) || (p_char == 0x000B) || (p_char == 0x000C) || (p_char == 0x000D) || (p_char == 0x0085) || (p_char == 0x2028) || (p_char == 0x2029) || (p_char == 0x0009) || (p_char == 0x0020) || (p_char == 0x1680) || (p_char == 0x205F) || (p_char == 0x3000) || (p_char == 0x180E) || (p_char == 0x2060) || ((p_char >= 0x2000) && (p_char <= 0x200D));
	}
	_ALWAYS_INLINE_ bool is_break(UChar32 p_char) const {
		return (p_char == 0x000A) || (p_char == 0x000B) || (p_char == 0x000C) || (p_char == 0x000D) || (p_char == 0x0085) || (p_char == 0x2028) || (p_char == 0x2029);
	}
	_ALWAYS_INLINE_ UChar32 get_char(int64_t p_offset) const {
		UChar32 ret;
		U16_GET(data, 0, p_offset, data_size, ret);
		return ret;
	}

	virtual void _clear_props();
	virtual void _clear_visual();

	virtual void _generate_kashida_justification_opportunies(int64_t p_start, int64_t p_end, /*out*/ std::vector<JustificationOpportunity> &p_ops) const;
	virtual void _generate_justification_opportunies(int32_t p_start, int32_t p_end, const char *p_lang, /*out*/ std::vector<JustificationOpportunity> &p_ops) const;
	virtual void _generate_break_opportunies(int32_t p_start, int32_t p_end, const char *p_lang, /*out*/ std::vector<BreakOpportunity> &p_ops) const;

	virtual void _generate_justification_opportunies_fallback(int32_t p_start, int32_t p_end, /*out*/ std::vector<JustificationOpportunity> &p_ops) const;
	virtual void _generate_break_opportunies_fallback(int32_t p_start, int32_t p_end, /*out*/ std::vector<BreakOpportunity> &p_ops) const;

	void _shape_substring(TLShapedString *p_ref, int64_t p_start, int64_t p_end, int p_trim) const;

	virtual void _shape_single_cluster(int64_t p_start, int64_t p_end, hb_direction_t p_run_direction, hb_script_t p_run_script, UChar32 p_codepoint, TLFontFallbackIterator p_font, /*out*/ Cluster &p_cluster, bool p_font_override = false) const;
	virtual void _shape_bidi_script_run(hb_direction_t p_run_direction, hb_script_t p_run_script, int32_t p_run_start, int32_t p_run_end, TLFontFallbackIterator p_font);
	virtual void _shape_bidi_run(hb_direction_t p_run_direction, int32_t p_run_start, int32_t p_run_end);
	virtual void _shape_hex_run(hb_direction_t p_run_direction, int32_t p_run_start, int32_t p_run_end);
	virtual void _shape_full_string();

public:
	TLShapedString();
	virtual ~TLShapedString();

	void _init();

	void copy_properties(Ref<TLShapedString> p_source);

	//Input
	int get_base_direction() const;
	void set_base_direction(int p_base_direction);

	String get_text() const;
	void set_text(const String p_text);
	virtual void add_text(const String p_text);
	virtual void replace_text(int64_t p_start, int64_t p_end, const String p_text);

	PoolByteArray get_utf8() const;
	void set_utf8(const PoolByteArray p_text);
	virtual void add_utf8(const PoolByteArray p_text);
	virtual void replace_utf8(int64_t p_start, int64_t p_end, const PoolByteArray p_text);

	PoolByteArray get_utf16() const;
	void set_utf16(const PoolByteArray p_text);
	virtual void add_utf16(const PoolByteArray p_text);
	virtual void replace_utf16(int64_t p_start, int64_t p_end, const PoolByteArray p_text);

	PoolByteArray get_utf32() const;
	void set_utf32(const PoolByteArray p_text);
	virtual void add_utf32(const PoolByteArray p_text);
	virtual void replace_utf32(int64_t p_start, int64_t p_end, const PoolByteArray p_text);

	virtual void add_sstring(Ref<TLShapedString> p_text);
	virtual void replace_sstring(int64_t p_start, int64_t p_end, Ref<TLShapedString> p_text);

	Ref<TLFontFamily> get_base_font() const;
	void set_base_font(const Ref<TLFontFamily> p_font);

	String get_base_font_style() const;
	void set_base_font_style(const String p_style);

	int get_base_font_size() const;
	void set_base_font_size(int p_size);

	String get_features() const;
	void set_features(const String p_features);

	String get_language() const;
	void set_language(const String p_language);

	bool get_preserve_control() const;
	void set_preserve_control(bool p_enable);

	//Line data
	virtual bool shape();
	virtual bool is_valid() const;
	virtual bool empty() const;
	virtual int64_t length() const;
	virtual int64_t char_count() const;

	virtual float get_ascent() const;
	virtual float get_descent() const;
	virtual float get_width() const;
	virtual float get_height() const;

	//Line modification
	virtual std::vector<int> break_words() const;
	virtual std::vector<int> break_jst() const;
	virtual std::vector<int> break_lines(float p_width, TextBreak p_flags) const;
	virtual Ref<TLShapedString> substr(int64_t p_start, int64_t p_end, int p_trim) const;
	virtual float extend_to_width(float p_width, TextJustification p_flags);

	//Cluster data
	virtual int64_t clusters() const;
	virtual int64_t get_cluster_index(int64_t p_position) const;
	virtual Ref<TLFontFace> get_cluster_face(int64_t p_index) const;
	virtual float get_cluster_face_size(int64_t p_index) const;
	virtual int64_t get_cluster_glyphs(int64_t p_index) const;
	virtual uint32_t get_cluster_glyph(int64_t p_index, int64_t p_glyph) const;
	virtual Point2 get_cluster_glyph_offset(int64_t p_index, int64_t p_glyph) const;
	virtual Point2 get_cluster_glyph_advance(int64_t p_index, int64_t p_glyph) const;
	virtual float get_cluster_trailing_edge(int64_t p_index) const;
	virtual float get_cluster_leading_edge(int64_t p_index) const;
	virtual int64_t get_cluster_start(int64_t p_index) const;
	virtual int64_t get_cluster_end(int64_t p_index) const;
	virtual float get_cluster_ascent(int64_t p_index) const;
	virtual float get_cluster_descent(int64_t p_index) const;
	virtual float get_cluster_width(int64_t p_index) const;
	virtual float get_cluster_height(int64_t p_index) const;
	virtual Rect2 get_cluster_rect(int64_t p_index) const;
	virtual String get_cluster_debug_info(int64_t p_index) const;

	//Output
	virtual std::vector<Rect2> get_highlight_shapes(int64_t p_start, int64_t p_end) const;
	virtual std::vector<float> get_cursor_positions(int64_t p_position, TextDirection p_primary_dir) const;
	virtual TextDirection get_char_direction(int64_t p_position) const;
	virtual int64_t hit_test(float p_position) const;
	virtual int64_t hit_test_cluster(float p_position) const;

	int get_para_direction() const;

	virtual Vector2 draw_cluster(RID p_canvas_item, const Point2 p_position, int64_t p_index, const Color p_modulate = Color(1, 1, 1));
	virtual void draw(RID p_canvas_item, const Point2 p_position, const Color p_modulate = Color(1, 1, 1));

	//Extra
	virtual void draw_dbg(RID p_canvas_item, const Point2 p_position, const Color p_modulate = Color(1, 1, 1), bool p_draw_brk_ops = false, bool p_draw_jst_ops = false);
	virtual void draw_as_hex(RID p_canvas_item, const Point2 p_position, const Color p_modulate = Color(1, 1, 1), bool p_draw_brk_ops = false, bool p_draw_jst_ops = false);
	virtual void draw_logical_as_hex(RID p_canvas_item, const Point2 p_position, const Color p_modulate = Color(1, 1, 1), bool p_draw_brk_ops = false, bool p_draw_jst_ops = false);

	//GDNative wrappers
	Array _break_words() const;
	Array _break_jst() const;
	Array _break_lines(float p_width, int64_t p_flags) const;
	Array _get_highlight_shapes(int64_t p_start, int64_t p_end) const;
	Array _get_cursor_positions(int64_t p_position, int64_t p_primary_dir) const;
	float _extend_to_width(float p_width, int64_t p_flags);
	float _collapse_to_width(float p_width, int64_t p_flags);

	//Helpers
	int64_t pos_u16_to_wcs(int64_t p_position) const;
	int64_t pos_wcs_to_u16(int64_t p_position) const;

	int64_t next_safe_bound(int64_t p_offset) const;
	int64_t prev_safe_bound(int64_t p_offset) const;

	void _font_changed();

#ifdef GODOT_MODULE
	static void _bind_methods();
#else
	bool _set(String p_name, Variant p_value);
	Variant _get(String p_name) const;
	Array _get_property_list() const;
	static void _register_methods();
#endif
};

#ifdef GODOT_MODULE
VARIANT_ENUM_CAST(TextDirection);
VARIANT_ENUM_CAST(TextJustification);
VARIANT_ENUM_CAST(TextBreak);
VARIANT_ENUM_CAST(TextTrimMode);
#endif

#endif /*TL_SHAPED_STRING_HPP*/


text:
/*************************************************************************/
/*  tl_dynamic_font.hpp                                                  */
/*************************************************************************/

#ifndef TL_DYNAMIC_FONT_FACE_HPP
#define TL_DYNAMIC_FONT_FACE_HPP

#include "tl_font.hpp"

#include <cmath>
#include <map>
#include <vector>

#include <ft2build.h>
#include <hb-ft.h>

#include FT_TRUETYPE_TABLES_H

using namespace godot;

enum DynamicFaceHinting {
	DF_HINTING_NONE = 0,
	DF_HINTING_LIGHT = 1,
	DF_HINTING_NORMAL = 2
};

class TLDynamicFontFaceAtSize {

protected:
	//Common FreeType data
	bool loaded;
	FT_Library ft_library;
	FT_StreamRec ft_stream;

	float oversampling;
	DynamicFaceHinting hinting;
	bool force_autohinter;
	int txt_flags;

	static unsigned long ft_stream_io(FT_Stream p_stream, unsigned long p_offset, unsigned char *p_buffer, unsigned long p_count);
	static void ft_stream_close(FT_Stream p_stream);

	//Texture cache
	struct GlyphTexture {
		PoolByteArray imgdata;
		//Image::Format format;
		//RID rid;
		Ref<ImageTexture> image;
		std::vector<int> offsets;
		int texture_size;

		GlyphTexture() {
			image.instance();
			texture_size = 0;
		};
	};

	struct TexturePosition {
		int index;
		int x;
		int y;
	};

	struct Glyph {
		bool valid;

		Point2 align;
		Size2 size;
		Rect2 uv;
		size_t id;

		Glyph() {
			valid = false;
			id = 0;
		}
	};

	TexturePosition find_texture_pos_for_glyph(int p_color_size, Image::Format p_image_format, int p_width, int p_height);
	Glyph bitmap_to_glyph(FT_Bitmap p_bitmap, int p_yofs, int p_xofs);
	void clear_cache();

	//FreeType data for size
	FT_Face ft_face;
	hb_font_t *hb_font;

	int ft_size;
	float scale_color_font;

	TT_OS2 *os2;

	float ascent;
	float descent;
	float height;

	std::vector<GlyphTexture> texture_cache;
	std::map<uint32_t, Glyph> glyph_cache;
	std::map<uint32_t, Glyph> glyph_outline_cache;

	void update_glyph(uint32_t p_codepoint);
	void update_outline_glyph(uint32_t p_codepoint);

	_ALWAYS_INLINE_ unsigned int _next_power_of_2(unsigned int x);

public:
	TLDynamicFontFaceAtSize();
	~TLDynamicFontFaceAtSize();

	hb_font_t *get_hb_font() const;
	float get_glyph_scale() const;

	void draw_glyph(RID p_canvas_item, const Point2 p_pos, uint32_t p_codepoint, const Color p_modulate) const;
	void _draw_char(RID p_canvas_item, const Point2 p_pos, uint32_t p_codepoint, const Color p_modulate) const; //raw char for debug only, do not use
	void draw_glyph_outline(RID p_canvas_item, const Point2 p_pos, uint32_t p_codepoint, const Color p_modulate) const;
	Array get_glyph_outline(const Point2 p_pos, uint32_t p_codepoint) const;

	std::vector<hb_script_t> unicode_scripts_supported() const;
	//Array unicode_scripts_supported() const;
	//bool unicode_range_supported(uint8_t p_bank, uint32_t p_range) const;

	bool load(String p_resource_path, int p_size, const uint8_t *p_font_mem, int p_font_mem_size);

	double get_ascent() const;
	double get_descent() const;
	double get_height() const;

	void set_texture_flags(int p_flags);
	void set_hinting(int p_hinting);
	void set_force_autohinter(bool p_force);
	void set_oversampling(float p_oversampling);
};

class TLDynamicFontFace : public TLFontFace {
	GODOT_SUBCLASS(TLDynamicFontFace, TLFontFace);

protected:
	float oversampling;
	DynamicFaceHinting hinting;
	bool force_autohinter;
	int txt_flags;

	const uint8_t *font_mem;
	int font_mem_size;

	mutable std::map<int, TLDynamicFontFaceAtSize *> sizes;

public:
	TLDynamicFontFace();
	virtual ~TLDynamicFontFace();

	void _init();

	virtual hb_font_t *get_hb_font(int p_size) const;
	virtual float get_glyph_scale(int p_size) const;

	virtual void draw_glyph(RID p_canvas_item, const Point2 p_pos, uint32_t p_codepoint, const Color p_modulate, int p_size) const;
	virtual void _draw_char(RID p_canvas_item, const Point2 p_pos, uint32_t p_codepoint, const Color p_modulate, int p_size) const; //raw char for debug only, do not use
	virtual void draw_glyph_outline(RID p_canvas_item, const Point2 p_pos, uint32_t p_codepoint, const Color p_modulate, int p_size) const;
	virtual Array get_glyph_outline(const Point2 p_pos, uint32_t p_codepoint, int p_size) const;

	virtual std::vector<hb_script_t> unicode_scripts_supported() const;

	//GDNative methods
	virtual bool load(String p_resource_path);

	virtual void set_font_ptr(const uint8_t *p_font_mem, int p_font_mem_size);
	virtual void set_font_path(String p_resource_path);

	virtual double get_ascent(int p_size) const;
	virtual double get_descent(int p_size) const;
	virtual double get_height(int p_size) const;

	virtual void set_texture_flags(int p_flags);
	virtual int get_texture_flags() const;

	void set_hinting(int p_hinting);
	int get_hinting() const;

	void set_force_autohinter(bool p_force);
	bool get_force_autohinter() const;

	void set_oversampling(float p_oversampling);
	float get_oversampling() const;

	bool has_graphite() const;

#ifdef GODOT_MODULE
	static void _bind_methods();
#else
	static void _register_methods();
#endif
};

#ifdef GODOT_MODULE
VARIANT_ENUM_CAST(DynamicFaceHinting);
#endif

#endif /*TL_DYNAMIC_FONT_FACE_HPP*/


text:
/*************************************************************************/
/*  tl_shaped_paragraph.cpp                                              */
/*************************************************************************/

#include "resources/tl_shaped_paragraph.hpp"

static float min_paragraph_width = 20.0f;

TLShapedParagraph::TLShapedParagraph() {

#ifdef GODOT_MODULE
	_init();
#endif
}

TLShapedParagraph::~TLShapedParagraph() {

	for (int64_t i = 0; i < (int64_t)line_ctx.size(); i++) {
		line_ctx[i]->disconnect("string_changed", this, "_line_change_warning");
	}
}

void TLShapedParagraph::_init() {

	ctx.instance();
	ctx->connect("string_changed", this, "_update_paragraph");

	width = -1.0f;
	max_line_width = 0.0f;
	height = 0.0f;
	brk_flags = TEXT_BREAK_MANDATORY_AND_WORD_BOUND;
	jst_flags = TEXT_JUSTIFICATION_KASHIDA_AND_WHITESPACE;
	halign = PARA_HALIGN_LEFT;
	back_color = Color(1, 1, 1, 0);
	line_spacing = 1.0f;
	indent = 0.0f;

	_update_paragraph();
}

void TLShapedParagraph::copy_properties(Ref<TLShapedParagraph> p_source) {

	ctx->copy_properties(p_source->ctx);

	width = p_source->width;
	brk_flags = p_source->brk_flags;
	jst_flags = p_source->jst_flags;
	halign = p_source->halign;
	back_color = p_source->back_color;
	line_spacing = p_source->line_spacing;
	indent = p_source->indent;

	_update_paragraph();
}

void TLShapedParagraph::_line_change_warning() {

	WARN_PRINTS("DO NOT EDIT");
	_update_paragraph(); //regenerate changed lines
}

void TLShapedParagraph::_update_paragraph() {

	for (int64_t i = 0; i < (int64_t)line_ctx.size(); i++) {
		line_ctx[i]->disconnect("string_changed", this, "_line_change_warning");
	}
	line_ctx.clear();
	word_bounds = ctx->break_words();
	if (width > 0.0f) {
		line_bounds = ctx->break_lines(width, brk_flags);
	} else {
		line_bounds = ctx->break_lines(-1, TEXT_BREAK_MANDATORY);
	}

	max_line_width = width;
	height = 0.0f;
	if (line_bounds.size() == 0) {
		height = ctx->get_height();
		if (max_line_width < min_paragraph_width)
			max_line_width = min_paragraph_width;
	} else {
		int64_t prev = 0;
		for (int64_t i = 0; i < (int64_t)line_bounds.size(); i++) {
			Ref<TLShapedAttributedString> line = ctx->substr(prev, line_bounds[i], TEXT_TRIM_BREAK);
			line->shape();
			line->connect("string_changed", this, "_line_change_warning");
			if ((halign == PARA_HALIGN_FILL) && (width > 0.0f)) {
				line->extend_to_width(width, jst_flags);
			}
			if (max_line_width < line->get_width())
				max_line_width = line->get_width();

			height += line->get_height() * line_spacing;

			line_ctx.push_back(line);
			prev = line_bounds[i];
		}
	}
	emit_signal("paragraph_changed");
}

Size2 TLShapedParagraph::get_size() const {

	return Size2(max_line_width, height);
}

std::vector<int> TLShapedParagraph::get_word_bounds() const {

	return word_bounds;
}

std::vector<int> TLShapedParagraph::get_line_bounds() const {

	return line_bounds;
}

Array TLShapedParagraph::_get_word_bounds() const {

	Array ret;
	for (int64_t i = 0; i < (int64_t)word_bounds.size(); i++) {
		ret.push_back(word_bounds[i]);
	}
	return ret;
}

Array TLShapedParagraph::_get_line_bounds() const {

	Array ret;
	for (int64_t i = 0; i < (int64_t)line_bounds.size(); i++) {
		ret.push_back(line_bounds[i]);
	}
	return ret;
}

void TLShapedParagraph::set_width(float p_width) {

	if (width != p_width) {
		width = p_width;
		_update_paragraph();
	}
}

float TLShapedParagraph::get_width() const {

	return width;
}

void TLShapedParagraph::set_indent(float p_indent) {

	if (indent != p_indent) {
		indent = p_indent;
		_update_paragraph();
	}
}
float TLShapedParagraph::get_indent() const {

	return indent;
}

void TLShapedParagraph::set_back_color(Color p_bcolor) {

	if (back_color != p_bcolor) {
		back_color = p_bcolor;
		_update_paragraph();
	}
}

Color TLShapedParagraph::get_back_color() const {

	return back_color;
}

void TLShapedParagraph::set_line_spacing(float p_line_spacing) {

	if (line_spacing != p_line_spacing) {
		line_spacing = p_line_spacing;
		_update_paragraph();
	}
}

float TLShapedParagraph::get_line_spacing() const {

	return line_spacing;
}

void TLShapedParagraph::set_string(Ref<TLShapedAttributedString> p_string) {

	if (p_string.is_null()) {
		ctx = Ref<TLShapedAttributedString>();
		ctx.instance();
		ctx->connect("string_changed", this, "_update_paragraph");
		_update_paragraph();
	} else if (ctx != p_string) {
		ctx->disconnect("string_changed", this, "_update_paragraph");
		ctx = p_string;
		ctx->connect("string_changed", this, "_update_paragraph");
		_update_paragraph();
	}
}

Ref<TLShapedAttributedString> TLShapedParagraph::get_string() const {

	return ctx;
}

void TLShapedParagraph::set_brk_flags(int p_flags) {

	if (brk_flags != p_flags) {
		brk_flags = (TextBreak)p_flags;
		_update_paragraph();
	}
}

int TLShapedParagraph::get_brk_flags() const {

	return brk_flags;
}

void TLShapedParagraph::set_jst_flags(int p_flags) {

	if (jst_flags != p_flags) {
		jst_flags = (TextJustification)p_flags;
		_update_paragraph();
	}
}

int TLShapedParagraph::get_jst_flags() const {

	return jst_flags;
}

void TLShapedParagraph::set_halign(int p_halign) {

	if (p_halign != halign) {
		halign = (ParaHAlign)p_halign;
		_update_paragraph();
	}
}

int TLShapedParagraph::get_halign() const {
	return halign;
}

int TLShapedParagraph::get_lines() const {

	return line_ctx.size();
}

Ref<TLShapedAttributedString> TLShapedParagraph::get_line(int p_index) const {

	if ((p_index < 0) || (p_index >= (int64_t)line_ctx.size()))
		return Ref<TLShapedAttributedString>();

	return line_ctx[p_index];
}

#ifdef GODOT_MODULE

void TLShapedParagraph::_bind_methods() {

	ClassDB::bind_method(D_METHOD("copy_properties", "source"), &TLShapedParagraph::copy_properties);

	ClassDB::bind_method(D_METHOD("_update_paragraph"), &TLShapedParagraph::_update_paragraph);
	ClassDB::bind_method(D_METHOD("_line_change_warning"), &TLShapedParagraph::_line_change_warning);

	ClassDB::bind_method(D_METHOD("get_lines"), &TLShapedParagraph::get_lines);
	ClassDB::bind_method(D_METHOD("get_line", "index"), &TLShapedParagraph::get_line);

	ClassDB::bind_method(D_METHOD("set_width", "width"), &TLShapedParagraph::set_width);
	ClassDB::bind_method(D_METHOD("get_width"), &TLShapedParagraph::get_width);
	ADD_PROPERTY(PropertyInfo(Variant::REAL, "width"), "set_width", "get_width");

	ClassDB::bind_method(D_METHOD("set_indent", "indent"), &TLShapedParagraph::set_indent);
	ClassDB::bind_method(D_METHOD("get_indent"), &TLShapedParagraph::get_indent);
	ADD_PROPERTY(PropertyInfo(Variant::REAL, "indent"), "set_indent", "get_indent");

	ClassDB::bind_method(D_METHOD("set_back_color", "bcolor"), &TLShapedParagraph::set_back_color);
	ClassDB::bind_method(D_METHOD("get_back_color"), &TLShapedParagraph::get_back_color);
	ADD_PROPERTY(PropertyInfo(Variant::COLOR, "back_color"), "set_back_color", "get_back_color");

	ClassDB::bind_method(D_METHOD("set_line_spacing", "line_spacing"), &TLShapedParagraph::set_line_spacing);
	ClassDB::bind_method(D_METHOD("get_line_spacing"), &TLShapedParagraph::get_line_spacing);
	ADD_PROPERTY(PropertyInfo(Variant::REAL, "line_spacing"), "set_line_spacing", "get_line_spacing");

	ClassDB::bind_method(D_METHOD("set_string", "string"), &TLShapedParagraph::set_string);
	ClassDB::bind_method(D_METHOD("get_string"), &TLShapedParagraph::get_string);
	ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "string", PROPERTY_HINT_RESOURCE_TYPE, "TLShapedAttributedString"), "set_string", "get_string");

	ClassDB::bind_method(D_METHOD("set_brk_flags", "flags"), &TLShapedParagraph::set_brk_flags);
	ClassDB::bind_method(D_METHOD("get_brk_flags"), &TLShapedParagraph::get_brk_flags);
	ADD_PROPERTY(PropertyInfo(Variant::INT, "brk_flags", PROPERTY_HINT_ENUM, "None,Mandatory,Mandatory and word bounds,Mandatory and cluster bounds"), "set_brk_flags", "get_brk_flags");

	ClassDB::bind_method(D_METHOD("set_jst_flags", "flags"), &TLShapedParagraph::set_jst_flags);
	ClassDB::bind_method(D_METHOD("get_jst_flags"), &TLShapedParagraph::get_jst_flags);
	ADD_PROPERTY(PropertyInfo(Variant::INT, "jst_flags", PROPERTY_HINT_ENUM, "None,Kashida and whitespace,Kashida only,Whitespace only"), "set_jst_flags", "get_jst_flags");

	ClassDB::bind_method(D_METHOD("set_halign", "haligh"), &TLShapedParagraph::set_halign);
	ClassDB::bind_method(D_METHOD("get_halign"), &TLShapedParagraph::get_halign);
	ADD_PROPERTY(PropertyInfo(Variant::INT, "halign", PROPERTY_HINT_ENUM, "Left,Center,Right,Fill"), "set_halign", "get_halign");

	ClassDB::bind_method(D_METHOD("get_size"), &TLShapedParagraph::get_size);
	ClassDB::bind_method(D_METHOD("get_word_bounds"), &TLShapedParagraph::_get_word_bounds);
	ClassDB::bind_method(D_METHOD("get_line_bounds"), &TLShapedParagraph::_get_line_bounds);

	ADD_SIGNAL(MethodInfo("paragraph_changed"));

	BIND_ENUM_CONSTANT(PARA_HALIGN_LEFT);
	BIND_ENUM_CONSTANT(PARA_HALIGN_CENTER);
	BIND_ENUM_CONSTANT(PARA_HALIGN_RIGHT);
	BIND_ENUM_CONSTANT(PARA_HALIGN_FILL);
}

#else

bool TLShapedParagraph::_set(String p_name, Variant p_value) {
	if (p_name == "string") {
		Object *p_obj = p_value;
		Ref<TLShapedAttributedString> sstr = Ref<TLShapedAttributedString>(Object::cast_to<TLShapedAttributedString>(p_obj));
		if (sstr.is_valid()) {
			set_string(sstr);
			return true;
		}
	}
	return false;
}

Variant TLShapedParagraph::_get(String p_name) const {
	if (p_name == "string") {
		return get_string();
	}
	return Variant();
}

Array TLShapedParagraph::_get_property_list() const {
	Array ret;
	Dictionary prop;
	prop["name"] = "string";
	prop["type"] = GlobalConstants::TYPE_OBJECT;
	prop["hint"] = GlobalConstants::PROPERTY_HINT_RESOURCE_TYPE;
	prop["hint_string"] = "TLShapedAttributedString";
	prop["usage"] = GlobalConstants::PROPERTY_USAGE_EDITOR | GODOT_PROPERTY_USAGE_STORAGE;
	ret.push_back(prop);
	return ret;
}

void TLShapedParagraph::_register_methods() {

	register_method("copy_properties", &TLShapedParagraph::copy_properties);

	register_method("_update_paragraph", &TLShapedParagraph::_update_paragraph);
	register_method("_line_change_warning", &TLShapedParagraph::_line_change_warning);

	register_method("get_lines", &TLShapedParagraph::get_lines);
	register_method("get_line", &TLShapedParagraph::get_line);

	register_method("set_width", &TLShapedParagraph::set_width);
	register_method("get_width", &TLShapedParagraph::get_width);
	register_property<TLShapedParagraph, float>("width", &TLShapedParagraph::set_width, &TLShapedParagraph::get_width, -1.0f);

	register_method("set_indent", &TLShapedParagraph::set_indent);
	register_method("get_indent", &TLShapedParagraph::get_indent);
	register_property<TLShapedParagraph, float>("indent", &TLShapedParagraph::set_indent, &TLShapedParagraph::get_indent, 0.0f);

	register_method("set_back_color", &TLShapedParagraph::set_back_color);
	register_method("get_back_color", &TLShapedParagraph::get_back_color);
	register_property<TLShapedParagraph, Color>("back_color", &TLShapedParagraph::set_back_color, &TLShapedParagraph::get_back_color, Color(1, 1, 1, 0));

	register_method("set_line_spacing", &TLShapedParagraph::set_line_spacing);
	register_method("get_line_spacing", &TLShapedParagraph::get_line_spacing);
	register_property<TLShapedParagraph, float>("line_spacing", &TLShapedParagraph::set_line_spacing, &TLShapedParagraph::get_line_spacing, 1.0f);

	register_method("set_string", &TLShapedParagraph::set_string);
	register_method("get_string", &TLShapedParagraph::get_string);

	register_method("set_brk_flags", &TLShapedParagraph::set_brk_flags);
	register_method("get_brk_flags", &TLShapedParagraph::get_brk_flags);
	register_property<TLShapedParagraph, int>("brk_flags", &TLShapedParagraph::set_brk_flags, &TLShapedParagraph::get_brk_flags, TEXT_BREAK_NONE, GODOT_METHOD_RPC_MODE_DISABLED, GODOT_PROPERTY_USAGE_DEFAULT, GODOT_PROPERTY_HINT_ENUM, String("None,Mandatory,Mandatory and word bounds,Mandatory and cluster bounds"));

	register_method("set_jst_flags", &TLShapedParagraph::set_jst_flags);
	register_method("get_jst_flags", &TLShapedParagraph::get_jst_flags);
	register_property<TLShapedParagraph, int>("jst_flags", &TLShapedParagraph::set_jst_flags, &TLShapedParagraph::get_jst_flags, TEXT_JUSTIFICATION_NONE, GODOT_METHOD_RPC_MODE_DISABLED, GODOT_PROPERTY_USAGE_DEFAULT, GODOT_PROPERTY_HINT_ENUM, String("None,Kashida and whitespace,Kashida only,Whitespace only"));

	register_method("set_halign", &TLShapedParagraph::set_halign);
	register_method("get_halign", &TLShapedParagraph::get_halign);
	register_property<TLShapedParagraph, int>("halign", &TLShapedParagraph::set_halign, &TLShapedParagraph::get_halign, PARA_HALIGN_LEFT, GODOT_METHOD_RPC_MODE_DISABLED, GODOT_PROPERTY_USAGE_DEFAULT, GODOT_PROPERTY_HINT_ENUM, String("Left,Center,Right,Fill"));

	register_method("get_size", &TLShapedParagraph::get_size);
	register_method("get_word_bounds", &TLShapedParagraph::_get_word_bounds);
	register_method("get_line_bounds", &TLShapedParagraph::_get_line_bounds);

	register_signal<TLShapedParagraph>("paragraph_changed");

	register_method("_get_property_list", &TLShapedParagraph::_get_property_list);
	register_method("_get", &TLShapedParagraph::_get);
	register_method("_set", &TLShapedParagraph::_set);
}

#endif


text:
/*************************************************************************/
/*  tl_font_family.hpp                                                   */
/*************************************************************************/

#ifndef TL_FONT_FAMILY_HPP
#define TL_FONT_FAMILY_HPP

#include "tl_font.hpp"

#include <cmath>
#include <map>
#include <vector>

#include <hb.h>

using namespace godot;

struct StyleData {
	std::vector<Ref<TLFontFace> > main_chain;
	std::map<hb_script_t, std::vector<Ref<TLFontFace> > > linked_src_chain;
	std::map<hb_language_t, std::vector<Ref<TLFontFace> > > linked_lang_chain;
};

class TLFontFallbackIterator {

protected:
	enum ChainID {
		LANG_CHAIN,
		SCRIPT_CHAIN,
		MAIN_CHAIN,
		SINGLE_FONT,
		INVALID_CHAIN
	};

	Ref<TLFontFace> font;
	const StyleData *data;

	hb_language_t lang;
	hb_script_t script;
	ChainID chain_id;

	int64_t index;

public:
	Ref<TLFontFace> value() const;

	bool is_valid() const;
	bool is_linked() const;
	TLFontFallbackIterator next() const;

	TLFontFallbackIterator() {
		data = NULL;

		script = HB_SCRIPT_INVALID;
		lang = HB_LANGUAGE_INVALID;
		chain_id = INVALID_CHAIN;
		index = -1;
	}
	TLFontFallbackIterator(Ref<TLFontFace> p_font);
	TLFontFallbackIterator(const StyleData *p_data);
	TLFontFallbackIterator(const StyleData *p_data, hb_script_t p_script);
	TLFontFallbackIterator(const StyleData *p_data, hb_language_t p_lang);
};

/*************************************************************************/

//GDScript/GDNative iterator wrapper
class TLFontIterator : public Reference {
	GODOT_CLASS(TLFontIterator, Reference);

	TLFontFallbackIterator _iter;

public:
	TLFontIterator();
	~TLFontIterator();

	void _set_iter(const TLFontFallbackIterator &p_iter);

	void _init();

	bool is_valid() const;
	bool is_linked() const;
	bool next();
	Ref<TLFontFace> value() const;

#ifdef GODOT_MODULE
	static void _bind_methods();
#else
	static void _register_methods();
#endif
};

/*************************************************************************/

class TLFontFamily : public Resource {
	GODOT_CLASS(TLFontFamily, Resource);

protected:
	std::map<String, StyleData> styles;
	std::map<Ref<TLFontFace>, int> _ref_count;

public:
	TLFontFamily();
	~TLFontFamily();

	void _init();

	void add_style(String p_style);
	void remove_style(String p_style);
	bool has_style(String p_style) const;

	void add_script(String p_style, String p_script);
	void remove_script(String p_style, String p_script);

	void add_language(String p_style, String p_lang);
	void remove_language(String p_style, String p_lang);

	TLFontFallbackIterator get_face(String p_style) const;
	TLFontFallbackIterator get_face_for_script(String p_style, hb_script_t p_script) const;
	TLFontFallbackIterator get_face_for_language(String p_style, hb_language_t p_lang) const;

	Ref<TLFontIterator> _get_face(String p_style) const;
	Ref<TLFontIterator> _get_face_for_script(String p_style, String p_script) const;
	Ref<TLFontIterator> _get_face_for_language(String p_style, String p_lang) const;

	void add_face(String p_style, Ref<TLFontFace> p_ref);
	void add_face_unlinked(String p_style, Ref<TLFontFace> p_ref);
	void add_face_for_script(String p_style, Ref<TLFontFace> p_ref, String p_script);
	void add_face_for_language(String p_style, Ref<TLFontFace> p_ref, String p_lang);

	void _font_changed();
	void _inc_ref(Ref<TLFontFace> &p_font);
	void _dec_ref(Ref<TLFontFace> &p_font);

#ifdef GODOT_MODULE
	bool _set(const StringName &p_name, const Variant &p_value);
	bool _get(const StringName &p_name, Variant &r_ret) const;
	void _get_property_list(List<PropertyInfo> *p_list) const;

	static void _bind_methods();
#else
	bool _set(String p_name, Variant p_value);
	Variant _get(String p_name) const;
	Array _get_property_list() const;

	static void _register_methods();
#endif
};

#endif /*TL_FONT_FAMILY_HPP*/


text:
/*************************************************************************/
/*  tl_shaped_attributed_string.hpp                                      */
/*************************************************************************/

#ifndef TL_SHAPED_ATTRIBUTED_STRING_HPP
#define TL_SHAPED_ATTRIBUTED_STRING_HPP

#include "tl_shaped_string.hpp"

#ifdef GODOT_MODULE
#include "core/map.h"
#else
#include "Map.hpp"
#endif

enum TextAttribute {

	TEXT_ATTRIBUTE_FONT = 1, //Ref<TLFontFamily>
	TEXT_ATTRIBUTE_FONT_STYLE = 2, //String
	TEXT_ATTRIBUTE_FONT_SIZE = 3, //int
	TEXT_ATTRIBUTE_FONT_FEATURES = 4, //String - 4 letter tag comma separated list
	TEXT_ATTRIBUTE_LANGUAGE = 5, //String - 4 letter tag + css mode
	TEXT_ATTRIBUTE_REPLACEMENT_IMAGE = 6, //Ref<Texture>
	TEXT_ATTRIBUTE_REPLACEMENT_RECT = 7, //Size2
	TEXT_ATTRIBUTE_REPLACEMENT_ID = 8, //Variant
	TEXT_ATTRIBUTE_REPLACEMENT_VALIGN = 9, //int (TextVAlign)

	TEXT_ATTRIBUTE_MAX_FORMAT_ATTRIBUTE = 30,

	TEXT_ATTRIBUTE_COLOR = 31, //Color
	TEXT_ATTRIBUTE_OUTLINE_COLOR = 32, //Color

	TEXT_ATTRIBUTE_UNDERLINE_COLOR = 41, //Color
	TEXT_ATTRIBUTE_UNDERLINE_WIDTH = 42, //int
	TEXT_ATTRIBUTE_STRIKETHROUGH_COLOR = 51, //Color
	TEXT_ATTRIBUTE_STRIKETHROUGH_WIDTH = 52, //int
	TEXT_ATTRIBUTE_OVERLINE_COLOR = 61, //Color
	TEXT_ATTRIBUTE_OVERLINE_WIDTH = 62, //int

	TEXT_ATTRIBUTE_HIGHLIGHT_COLOR = 71, //Color

	TEXT_ATTRIBUTE_META = 100 //Variant
};

enum TextVAlign {

	TEXT_VALIGN_TOP = 0,
	TEXT_VALIGN_CENTER = 1,
	TEXT_VALIGN_BOTTOM = 2
};

class TLShapedAttributedString : public TLShapedString {
	GODOT_SUBCLASS(TLShapedAttributedString, TLShapedString);

	TextAttribute _edited_attrib = TEXT_ATTRIBUTE_COLOR;
	Variant _edited_attrib_value;
	int64_t _edited_attrib_start = 0;
	int64_t _edited_attrib_end = 0;

protected:
	enum {
		_CLUSTER_TYPE_IMAGE = 11, //Embedded image
		_CLUSTER_TYPE_RECT = 12 //Reserved rect for embedded object
	};

	Map<int, Map<TextAttribute, Variant> > format_attributes;
	Map<int, Map<TextAttribute, Variant> > style_attributes;

	virtual void _disconnect_fonts();
	virtual void _reconnect_fonts();

	virtual bool _compare_attributes(const Map<TextAttribute, Variant> &p_first, const Map<TextAttribute, Variant> &p_second) const;
	virtual void _ensure_break(int64_t p_key, Map<int, Map<TextAttribute, Variant> > &p_attributes);
	virtual void _optimize_attributes(Map<int, Map<TextAttribute, Variant> > &p_attributes);

	void _shape_substring(TLShapedAttributedString *p_ref, int64_t p_start, int64_t p_end, int p_trim) const;

	virtual void _generate_justification_opportunies(int32_t p_start, int32_t p_end, const char *p_lang, /*out*/ std::vector<JustificationOpportunity> &p_ops) const;
	virtual void _generate_break_opportunies(int32_t p_start, int32_t p_end, const char *p_lang, /*out*/ std::vector<BreakOpportunity> &p_ops) const;

	virtual void _shape_single_cluster(int64_t p_start, int64_t p_end, hb_direction_t p_run_direction, hb_script_t p_run_script, UChar32 p_codepoint, TLFontFallbackIterator p_font, /*out*/ Cluster &p_cluster, bool p_font_override = false) const;
	virtual void _shape_bidi_script_attrib_run(hb_direction_t p_run_direction, hb_script_t p_run_script, const Map<TextAttribute, Variant> &p_attribs, int32_t p_run_start, int32_t p_run_end, TLFontFallbackIterator p_font, bool p_font_override = false);
	virtual void _shape_bidi_script_run(hb_direction_t p_run_direction, hb_script_t p_run_script, int32_t p_run_start, int32_t p_run_end, TLFontFallbackIterator p_font);
	virtual void _shape_rect_run(hb_direction_t p_run_direction, const Size2 &p_size, TextVAlign p_align, int32_t p_run_start, int32_t p_run_end);
	virtual void _shape_image_run(hb_direction_t p_run_direction, const Ref<Texture> &p_image, TextVAlign p_align, int32_t p_run_start, int32_t p_run_end);

public:
	virtual float get_cluster_face_size(int64_t p_index) const;

	virtual Ref<TLShapedString> substr(int64_t p_start, int64_t p_end, int p_trim) const;

	virtual void add_sstring(Ref<TLShapedString> p_text);
	virtual void replace_sstring(int64_t p_start, int64_t p_end, Ref<TLShapedString> p_text);

	virtual void replace_text(int64_t p_start, int64_t p_end, const String p_text);
	virtual void replace_utf8(int64_t p_start, int64_t p_end, const PoolByteArray p_text);
	virtual void replace_utf16(int64_t p_start, int64_t p_end, const PoolByteArray p_text);
	virtual void replace_utf32(int64_t p_start, int64_t p_end, const PoolByteArray p_text);

	virtual void add_attribute(int64_t p_attribute, Variant p_value, int64_t p_start, int64_t p_end);
	virtual void remove_attribute(int64_t p_attribute, int64_t p_start, int64_t p_end);
	virtual void remove_attributes(int64_t p_start, int64_t p_end);
	virtual void clear_attributes();
	virtual bool has_attribute(int64_t p_attribute, int64_t p_index) const;
	virtual Variant get_attribute(int64_t p_attribute, int64_t p_index) const;
	virtual int64_t get_attribute_start(int64_t p_attribute, int64_t p_index) const;
	virtual int64_t get_attribute_end(int64_t p_attribute, int64_t p_index) const;

	virtual void commit_attribute();
	virtual void reject_attribute();

	virtual void load_attributes_dict(Array p_array);
	virtual Array save_attributes_dict() const;

	virtual Vector2 draw_cluster(RID p_canvas_item, const Point2 p_position, int64_t p_index, const Color p_modulate = Color(1, 1, 1));
	virtual void draw(RID p_canvas_item, const Point2 p_position, const Color p_modulate = Color(1, 1, 1));

	Array get_embedded_rects();

#ifdef GODOT_MODULE
	bool _set(const StringName &p_name, const Variant &p_value);
	bool _get(const StringName &p_name, Variant &r_ret) const;
	void _get_property_list(List<PropertyInfo> *p_list) const;

	static void _bind_methods();
#else
	bool _set(String p_name, Variant p_value);
	Variant _get(String p_name) const;
	Array _get_property_list() const;

	static void _register_methods();
#endif
};

#ifdef GODOT_MODULE
VARIANT_ENUM_CAST(TextAttribute);
VARIANT_ENUM_CAST(TextVAlign);
#endif

#endif /*TL_SHAPED_ATTRIBUTED_STRING_HPP*/


text:
/*************************************************************************/
/*  tl_font_family.cpp                                                   */
/*************************************************************************/

#include "resources/tl_font_family.hpp"
Ref<TLFontFace> TLFontFallbackIterator::value() const {
	if (index >= 0) {
		switch (chain_id) {
			case SINGLE_FONT: {
				return font;
			} break;
			case MAIN_CHAIN: {
				if (index < (int64_t)data->main_chain.size()) {
					return data->main_chain[index];
				}
			} break;
			case SCRIPT_CHAIN: {
				if (index < (int64_t)data->linked_src_chain.at(script).size()) {
					return data->linked_src_chain.at(script)[index];
				}
			} break;
			case LANG_CHAIN: {
				if (index < (int64_t)data->linked_lang_chain.at(lang).size()) {
					return data->linked_lang_chain.at(lang)[index];
				}
			} break;
			default: {
				return NULL;
			} break;
		}
	}
	return NULL;
}

bool TLFontFallbackIterator::is_valid() const {
	return (index >= 0) && (chain_id != INVALID_CHAIN);
}

bool TLFontFallbackIterator::is_linked() const {
	return (index >= 0) && ((chain_id == SCRIPT_CHAIN) || (chain_id == LANG_CHAIN));
}

TLFontFallbackIterator TLFontFallbackIterator::next() const {
	switch (chain_id) {
		case SINGLE_FONT: {
			return TLFontFallbackIterator();
		} break;
		case MAIN_CHAIN: {
			if (index < (int64_t)data->main_chain.size() - 1) {
				TLFontFallbackIterator next;
				next.data = data;
				next.lang = lang;
				next.script = script;
				next.chain_id = chain_id;
				next.index = index + 1;
				return next;
			} else {
				return TLFontFallbackIterator();
			}
		}
		case SCRIPT_CHAIN: {
			if ((data->linked_src_chain.count(script) != 0) && (index < (int64_t)data->linked_src_chain.at(script).size() - 1)) {
				TLFontFallbackIterator next;
				next.data = data;
				next.lang = lang;
				next.script = script;
				next.chain_id = chain_id;
				next.index = index + 1;
				return next;
			} else {
				if (data->main_chain.size() > 0) {
					TLFontFallbackIterator next;
					next.data = data;
					next.lang = lang;
					next.script = script;
					next.chain_id = MAIN_CHAIN;
					next.index = 0;
					return next;
				} else {
					return TLFontFallbackIterator();
				}
			}
		}
		case LANG_CHAIN: {
			if ((data->linked_lang_chain.count(lang) != 0) && (index < (int64_t)data->linked_lang_chain.at(lang).size() - 1)) {
				TLFontFallbackIterator next;
				next.data = data;
				next.lang = lang;
				next.script = script;
				next.chain_id = chain_id;
				next.index = index + 1;
				return next;
			} else {
				if (data->main_chain.size() > 0) {
					TLFontFallbackIterator next;
					next.data = data;
					next.lang = lang;
					next.script = script;
					next.chain_id = MAIN_CHAIN;
					next.index = 0;
					return next;
				} else {
					return TLFontFallbackIterator();
				}
			}
		}
		default: {
			return TLFontFallbackIterator();
		} break;
	}
	return TLFontFallbackIterator();
}

TLFontFallbackIterator::TLFontFallbackIterator(Ref<TLFontFace> p_font) {

	font = p_font;
	data = NULL;
	script = HB_SCRIPT_INVALID;
	lang = HB_LANGUAGE_INVALID;
	index = 0;
	chain_id = SINGLE_FONT;
}

TLFontFallbackIterator::TLFontFallbackIterator(const StyleData *p_data) {
	data = p_data;
	script = HB_SCRIPT_INVALID;
	lang = HB_LANGUAGE_INVALID;

	if (data->main_chain.size() > 0) {
		index = 0;
		chain_id = MAIN_CHAIN;
	} else {
		index = -1;
		chain_id = INVALID_CHAIN;
	}
}

TLFontFallbackIterator::TLFontFallbackIterator(const StyleData *p_data, hb_script_t p_script) {
	data = p_data;
	script = p_script;
	lang = HB_LANGUAGE_INVALID;

	if ((data->linked_src_chain.count(script) > 0) && (data->linked_src_chain.at(script).size() > 0)) {
		index = 0;
		chain_id = SCRIPT_CHAIN;
	} else {
		if (data->main_chain.size() > 0) {
			index = 0;
			chain_id = MAIN_CHAIN;
		} else {
			index = -1;
			chain_id = INVALID_CHAIN;
		}
	}
}

TLFontFallbackIterator::TLFontFallbackIterator(const StyleData *p_data, hb_language_t p_lang) {
	data = p_data;
	script = HB_SCRIPT_INVALID;
	lang = p_lang;

	if ((data->linked_lang_chain.count(lang) > 0) && (data->linked_lang_chain.at(lang).size() > 0)) {
		index = 0;
		chain_id = LANG_CHAIN;
	} else {
		if (data->main_chain.size() > 0) {
			index = 0;
			chain_id = MAIN_CHAIN;
		} else {
			index = -1;
			chain_id = INVALID_CHAIN;
		}
	}
}

/*************************************************************************/

TLFontIterator::TLFontIterator() {
	//NOP
}

TLFontIterator::~TLFontIterator() {
	//NOP
}

void TLFontIterator::_set_iter(const TLFontFallbackIterator &p_iter) {
	_iter = p_iter;
}

void TLFontIterator::_init() {
	//NOP
}

bool TLFontIterator::is_valid() const {
	return _iter.is_valid();
}

bool TLFontIterator::is_linked() const {
	return _iter.is_linked();
}

bool TLFontIterator::next() {
	_iter = _iter.next();
	return _iter.is_valid();
}

Ref<TLFontFace> TLFontIterator::value() const {
	if (_iter.is_valid()) {
		return _iter.value();
	} else {
		return Ref<TLFontFace>();
	}
}

#ifdef GODOT_MODULE
void TLFontIterator::_bind_methods() {
	ClassDB::bind_method(D_METHOD("is_valid"), &TLFontIterator::is_valid);
	ClassDB::bind_method(D_METHOD("is_linked"), &TLFontIterator::is_linked);
	ClassDB::bind_method(D_METHOD("value"), &TLFontIterator::value);
	ClassDB::bind_method(D_METHOD("next"), &TLFontIterator::next);
}
#else
void TLFontIterator::_register_methods() {
	register_method("is_valid", &TLFontIterator::is_valid);
	register_method("is_linked", &TLFontIterator::is_linked);
	register_method("value", &TLFontIterator::value);
	register_method("next", &TLFontIterator::next);
}
#endif

/*************************************************************************/

TLFontFamily::TLFontFamily() {

#ifdef GODOT_MODULE
	_init();
#endif
}

void TLFontFamily::_init() {
	//NOP
}

TLFontFamily::~TLFontFamily() {
	//NOP
}

void TLFontFamily::add_style(String p_style) {
	if (styles.count(p_style.to_upper()) == 0) {
		styles[p_style.to_upper()] = StyleData();
		emit_signal(_CHANGED);
#ifdef GODOT_MODULE
		_change_notify();
#else
		property_list_changed_notify();
#endif
	}
}

void TLFontFamily::add_script(String p_style, String p_script) {
	if (styles.count(p_style.to_upper()) > 0) {
		hb_script_t scr = hb_script_from_string(p_script.ascii().get_data(), -1);
		if (styles[p_style.to_upper()].linked_src_chain.count(scr) == 0) {
			styles[p_style.to_upper()].linked_src_chain[scr] = std::vector<Ref<TLFontFace> >();
			emit_signal(_CHANGED);
#ifdef GODOT_MODULE
			_change_notify();
#else
			property_list_changed_notify();
#endif
		}
	}
}

void TLFontFamily::add_language(String p_style, String p_lang) {
	if (styles.count(p_style.to_upper()) > 0) {
		hb_language_t lang = hb_language_from_string(p_lang.ascii().get_data(), -1);
		if (styles[p_style.to_upper()].linked_lang_chain.count(lang) == 0) {
			styles[p_style.to_upper()].linked_lang_chain[lang] = std::vector<Ref<TLFontFace> >();
			emit_signal(_CHANGED);
#ifdef GODOT_MODULE
			_change_notify();
#else
			property_list_changed_notify();
#endif
		}
	}
}

void TLFontFamily::remove_script(String p_style, String p_script) {
	if (styles.count(p_style.to_upper()) > 0) {
		hb_script_t scr = hb_script_from_string(p_script.ascii().get_data(), -1);
		if (styles[p_style.to_upper()].linked_src_chain.count(scr) > 0) {
			for (auto E = styles[p_style.to_upper()].linked_src_chain[scr].begin(); E != styles[p_style.to_upper()].linked_src_chain[scr].end(); ++E) {
				_dec_ref(*E);
			}
			styles[p_style.to_upper()].linked_src_chain.erase(scr);
			emit_signal(_CHANGED);
#ifdef GODOT_MODULE
			_change_notify();
#else
			property_list_changed_notify();
#endif
		}
	}
}

void TLFontFamily::remove_language(String p_style, String p_lang) {
	if (styles.count(p_style.to_upper()) > 0) {
		hb_language_t lang = hb_language_from_string(p_lang.ascii().get_data(), -1);
		if (styles[p_style.to_upper()].linked_lang_chain.count(lang) > 0) {
			for (auto E = styles[p_style.to_upper()].linked_lang_chain[lang].begin(); E != styles[p_style.to_upper()].linked_lang_chain[lang].end(); ++E) {
				_dec_ref(*E);
			}
			styles[p_style.to_upper()].linked_lang_chain.erase(lang);
			emit_signal(_CHANGED);
#ifdef GODOT_MODULE
			_change_notify();
#else
			property_list_changed_notify();
#endif
		}
	}
}

void TLFontFamily::remove_style(String p_style) {

	for (auto E = styles[p_style.to_upper()].main_chain.begin(); E != styles[p_style.to_upper()].main_chain.end(); ++E) {
		_dec_ref(*E);
	}
	for (auto E = styles[p_style.to_upper()].linked_src_chain.begin(); E != styles[p_style.to_upper()].linked_src_chain.end(); ++E) {
		for (auto F = E->second.begin(); F != E->second.end(); ++F) {
			_dec_ref(*F);
		}
	}
	for (auto E = styles[p_style.to_upper()].linked_lang_chain.begin(); E != styles[p_style.to_upper()].linked_lang_chain.end(); ++E) {
		for (auto F = E->second.begin(); F != E->second.end(); ++F) {
			_dec_ref(*F);
		}
	}

	styles.erase(p_style.to_upper());
	emit_signal(_CHANGED);
#ifdef GODOT_MODULE
	_change_notify();
#else
	property_list_changed_notify();
#endif
}

bool TLFontFamily::has_style(String p_style) const {

	return styles.count(p_style.to_upper()) > 0;
}

TLFontFallbackIterator TLFontFamily::get_face(String p_style) const {

	if (styles.count(p_style.to_upper()) > 0) {
		return TLFontFallbackIterator(&styles.at(p_style.to_upper()));
	} else {
		return TLFontFallbackIterator();
	}
}

TLFontFallbackIterator TLFontFamily::get_face_for_script(String p_style, hb_script_t p_script) const {

	if (styles.count(p_style.to_upper()) > 0) {
		if (styles.at(p_style.to_upper()).linked_src_chain.count(p_script) > 0) {
			return TLFontFallbackIterator(&styles.at(p_style.to_upper()), p_script);
		} else {
			return TLFontFallbackIterator(&styles.at(p_style.to_upper()));
		}
	}
	return TLFontFallbackIterator();
}

TLFontFallbackIterator TLFontFamily::get_face_for_language(String p_style, hb_language_t p_lang) const {

	if (styles.count(p_style.to_upper()) > 0) {
		if (styles.at(p_style.to_upper()).linked_lang_chain.count(p_lang) > 0) {
			return TLFontFallbackIterator(&styles.at(p_style.to_upper()), p_lang);
		} else {
			return TLFontFallbackIterator(&styles.at(p_style.to_upper()));
		}
	}
	return TLFontFallbackIterator();
}

void TLFontFamily::add_face(String p_style, Ref<TLFontFace> p_ref) {

	if (p_ref.is_valid() && !cast_to<TLFontFace>(*p_ref)) {
		ERR_PRINTS("Type mismatch");
		return;
	}

	std::vector<hb_script_t> scripts = p_ref->unicode_scripts_supported();
	for (int64_t i = 0; i < (int64_t)scripts.size(); i++) {
		if (scripts[i] != HB_SCRIPT_COMMON) {
			//add to scr chain
			_inc_ref(p_ref);
			styles[p_style.to_upper()].linked_src_chain[scripts[i]].push_back(p_ref);
		} else {
			//add to main chain
			_inc_ref(p_ref);
			styles[p_style.to_upper()].main_chain.push_back(p_ref);
		}
	}
	emit_signal(_CHANGED);
#ifdef GODOT_MODULE
	_change_notify();
#else
	property_list_changed_notify();
#endif
}

void TLFontFamily::add_face_unlinked(String p_style, Ref<TLFontFace> p_ref) {

	if (p_ref.is_valid() && !cast_to<TLFontFace>(*p_ref)) {
		ERR_PRINTS("Type mismatch");
		return;
	}

	_inc_ref(p_ref);

	styles[p_style.to_upper()].main_chain.push_back(p_ref);
	emit_signal(_CHANGED);
#ifdef GODOT_MODULE
	_change_notify();
#else
	property_list_changed_notify();
#endif
}

void TLFontFamily::add_face_for_script(String p_style, Ref<TLFontFace> p_ref, String p_script) {

	if (p_ref.is_valid() && !cast_to<TLFontFace>(*p_ref)) {
		ERR_PRINTS("Type mismatch");
		return;
	}

	_inc_ref(p_ref);

	hb_script_t scr = hb_script_from_string(p_script.ascii().get_data(), -1);
	styles[p_style.to_upper()].linked_src_chain[scr].push_back(p_ref);
	emit_signal(_CHANGED);
#ifdef GODOT_MODULE
	_change_notify();
#else
	property_list_changed_notify();
#endif
}

void TLFontFamily::add_face_for_language(String p_style, Ref<TLFontFace> p_ref, String p_lang) {

	if (p_ref.is_valid() && !cast_to<TLFontFace>(*p_ref)) {
		ERR_PRINTS("Type mismatch");
		return;
	}

	_inc_ref(p_ref);

	hb_language_t lang = hb_language_from_string(p_lang.ascii().get_data(), -1);
	styles[p_style.to_upper()].linked_lang_chain[lang].push_back(p_ref);
	emit_signal(_CHANGED);
#ifdef GODOT_MODULE
	_change_notify();
#else
	property_list_changed_notify();
#endif
}

Ref<TLFontIterator> TLFontFamily::_get_face(String p_style) const {
	Ref<TLFontIterator> iter;
	iter.instance();
	iter->_set_iter(get_face(p_style));

	return iter;
}

Ref<TLFontIterator> TLFontFamily::_get_face_for_script(String p_style, String p_script) const {
	Ref<TLFontIterator> iter;
	iter.instance();
	hb_script_t scr = hb_script_from_string(p_script.ascii().get_data(), -1);
	iter->_set_iter(get_face_for_script(p_style, scr));

	return iter;
}

Ref<TLFontIterator> TLFontFamily::_get_face_for_language(String p_style, String p_lang) const {
	Ref<TLFontIterator> iter;
	iter.instance();
	hb_language_t lang = hb_language_from_string(p_lang.ascii().get_data(), -1);
	iter->_set_iter(get_face_for_language(p_style, lang));

	return iter;
}

void TLFontFamily::_font_changed() {
	emit_signal(_CHANGED);
#ifdef GODOT_MODULE
	_change_notify();
#else
	property_list_changed_notify();
#endif
}

void TLFontFamily::_inc_ref(Ref<TLFontFace> &p_font) {
	if (p_font.is_null()) return;
	if (_ref_count.count(p_font) == 0) {
		p_font->connect(_CHANGED, this, "_font_changed");
		_ref_count[p_font] = 1;
	} else {
		_ref_count[p_font]++;
	}
}

void TLFontFamily::_dec_ref(Ref<TLFontFace> &p_font) {
	if (p_font.is_null()) return;
	if (_ref_count.count(p_font) == 1) {
		if (p_font->is_connected(_CHANGED, this, "_font_changed")) {
			p_font->disconnect(_CHANGED, this, "_font_changed");
		}
		_ref_count.erase(p_font);
	} else {
		_ref_count[p_font]--;
	}
}

#ifdef GODOT_MODULE

bool TLFontFamily::_set(const StringName &p_name, const Variant &p_value) {

	String name = p_name;
	Vector<String> tokens = name.split("/");
	if (tokens.size() >= 2) {
		String style = tokens[0];
		if (tokens.size() == 2) {
			int64_t index = (int64_t)tokens[1].to_int64();
			if (index == (int64_t)styles[style.to_upper()].main_chain.size()) {
				Ref<TLFontFace> face = p_value;
				if (face.is_valid()) {
					_inc_ref(face);
					styles[style.to_upper()].main_chain.push_back(face);
					emit_signal(_CHANGED);
					_change_notify();
					return true;
				}
			} else if ((index >= 0) && (index < (int64_t)styles[style.to_upper()].main_chain.size())) {
				Ref<TLFontFace> face = p_value;
				if (face.is_null()) {
					_dec_ref(styles[style.to_upper()].main_chain[index]);
					styles[style.to_upper()].main_chain.erase(styles[style.to_upper()].main_chain.begin() + index);
					emit_signal(_CHANGED);
					_change_notify();
					return true;
				} else {
					_dec_ref(styles[style.to_upper()].main_chain[index]);
					_inc_ref(face);
					styles[style.to_upper()].main_chain[index] = face;
					emit_signal(_CHANGED);
					_change_notify();
					return true;
				}
			}
		} else if (tokens.size() == 4) {
			int64_t index = (int64_t)tokens[3].to_int64();
			if (tokens[1] == "script") {
				//add script
				hb_script_t scr = hb_script_from_string(tokens[2].ascii().get_data(), -1);
				if (index == (int64_t)styles[style.to_upper()].linked_src_chain[scr].size()) {
					Ref<TLFontFace> face = p_value;
					if (face.is_valid()) {
						_inc_ref(face);
						styles[style.to_upper()].linked_src_chain[scr].push_back(face);
						emit_signal(_CHANGED);
						_change_notify();
						return true;
					}
				} else if ((index >= 0) && (index < (int64_t)styles[style.to_upper()].linked_src_chain[scr].size())) {
					Ref<TLFontFace> face = p_value;
					if (face.is_null()) {
						_dec_ref(styles[style.to_upper()].linked_src_chain[scr][index]);
						styles[style.to_upper()].linked_src_chain[scr].erase(styles[style.to_upper()].linked_src_chain[scr].begin() + index);
						emit_signal(_CHANGED);
						_change_notify();
						return true;
					} else {
						_dec_ref(styles[style.to_upper()].linked_src_chain[scr][index]);
						_inc_ref(face);
						styles[style.to_upper()].linked_src_chain[scr][index] = face;
						emit_signal(_CHANGED);
						_change_notify();
						return true;
					}
				}
			} else if (tokens[1] == "lang") {
				//add lang
				hb_language_t lang = hb_language_from_string(tokens[2].ascii().get_data(), -1);
				if (index == (int64_t)styles[style.to_upper()].linked_lang_chain[lang].size()) {
					Ref<TLFontFace> face = p_value;
					if (face.is_valid()) {
						_inc_ref(face);
						styles[style.to_upper()].linked_lang_chain[lang].push_back(face);
						emit_signal(_CHANGED);
						_change_notify();
						return true;
					}
				} else if ((index >= 0) && (index < (int64_t)styles[style.to_upper()].linked_lang_chain[lang].size())) {
					Ref<TLFontFace> face = p_value;
					if (face.is_null()) {
						_dec_ref(styles[style.to_upper()].linked_lang_chain[lang][index]);
						styles[style.to_upper()].linked_lang_chain[lang].erase(styles[style.to_upper()].linked_lang_chain[lang].begin() + index);
						emit_signal(_CHANGED);
						_change_notify();
						return true;
					} else {
						_dec_ref(styles[style.to_upper()].linked_lang_chain[lang][index]);
						_inc_ref(face);
						styles[style.to_upper()].linked_lang_chain[lang][index] = face;
						emit_signal(_CHANGED);
						_change_notify();
						return true;
					}
				}
			}
		}
	}

	return false;
}

bool TLFontFamily::_get(const StringName &p_name, Variant &r_ret) const {

	String name = p_name;

	Vector<String> tokens = name.split("/");
	if (tokens.size() >= 2) {
		String style = tokens[0];
		if (styles.count(style.to_upper()) > 0) {
			if (tokens.size() == 2) {
				int index = (int)tokens[1].to_int();
				if ((index >= 0) && (index < (int64_t)styles.at(style.to_upper()).main_chain.size())) {
					r_ret = styles.at(style.to_upper()).main_chain[index];
					return true;
				}
			} else if (tokens.size() == 4) {
				int index = (int)tokens[3].to_int();
				if (tokens[1] == "script") {
					//add script
					hb_script_t scr = hb_script_from_string(tokens[2].ascii().get_data(), -1);
					if (styles.at(style.to_upper()).linked_src_chain.count(scr) > 0) {
						if ((index >= 0) && (index < (int64_t)styles.at(style.to_upper()).linked_src_chain.at(scr).size())) {
							r_ret = styles.at(style.to_upper()).linked_src_chain.at(scr)[index];
							return true;
						}
					}
				} else if (tokens[1] == "lang") {
					//add lang
					hb_language_t lang = hb_language_from_string(tokens[2].ascii().get_data(), -1);
					if (styles.at(style.to_upper()).linked_lang_chain.count(lang) > 0) {
						if ((index >= 0) && (index < (int64_t)styles.at(style.to_upper()).linked_lang_chain.at(lang).size())) {
							r_ret = styles.at(style.to_upper()).linked_lang_chain.at(lang)[index];
							return true;
						}
					}
				}
			}
		}
	}
	return false;
}

void TLFontFamily::_get_property_list(List<PropertyInfo> *p_list) const {

	for (auto it = styles.begin(); it != styles.end(); ++it) {
		p_list->push_back(PropertyInfo(Variant::NIL, it->first.to_lower() + "/" + "_prev_style", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_INTERNAL));
		for (int64_t i = 0; i < (int64_t)it->second.main_chain.size(); i++) {
			p_list->push_back(PropertyInfo(Variant::OBJECT, it->first.to_lower() + "/" + String::num_int64(i), PROPERTY_HINT_RESOURCE_TYPE, "TLFontFace"));
		}
		p_list->push_back(PropertyInfo(Variant::OBJECT, it->first.to_lower() + "/" + String::num_int64(it->second.main_chain.size()), PROPERTY_HINT_RESOURCE_TYPE, "TLFontFace"));

		for (auto sit = it->second.linked_src_chain.begin(); sit != it->second.linked_src_chain.end(); ++sit) {
			char tag[5] = "";
			hb_tag_to_string(hb_script_to_iso15924_tag(sit->first), tag);
			for (int64_t i = 0; i < (int64_t)sit->second.size(); i++) {
				p_list->push_back(PropertyInfo(Variant::OBJECT, it->first.to_lower() + "/script/" + tag + "/" + String::num_int64(i), PROPERTY_HINT_RESOURCE_TYPE, "TLFontFace"));
			}
			p_list->push_back(PropertyInfo(Variant::OBJECT, it->first.to_lower() + "/script/" + tag + "/" + String::num_int64(sit->second.size()), PROPERTY_HINT_RESOURCE_TYPE, "TLFontFace"));
			p_list->push_back(PropertyInfo(Variant::NIL, it->first.to_lower() + "/script/" + tag + "/" + "_remove_script", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_INTERNAL));
		}
		p_list->push_back(PropertyInfo(Variant::NIL, it->first.to_lower() + "/" + "_add_script", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_INTERNAL));
		for (auto sit = it->second.linked_lang_chain.begin(); sit != it->second.linked_lang_chain.end(); ++sit) {
			for (int64_t i = 0; i < (int64_t)sit->second.size(); i++) {
				p_list->push_back(PropertyInfo(Variant::OBJECT, it->first.to_lower() + "/lang/" + hb_language_to_string(sit->first) + "/" + String::num_int64(i), PROPERTY_HINT_RESOURCE_TYPE, "TLFontFace"));
			}
			p_list->push_back(PropertyInfo(Variant::OBJECT, it->first.to_lower() + "/lang/" + hb_language_to_string(sit->first) + "/" + String::num_int64(sit->second.size()), PROPERTY_HINT_RESOURCE_TYPE, "TLFontFace"));
			p_list->push_back(PropertyInfo(Variant::NIL, it->first.to_lower() + "/lang/" + hb_language_to_string(sit->first) + "/" + "_remove_lang", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_INTERNAL));
		}
		p_list->push_back(PropertyInfo(Variant::NIL, it->first.to_lower() + "/" + "_add_lang", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_INTERNAL));
		p_list->push_back(PropertyInfo(Variant::NIL, it->first.to_lower() + "/" + "_remove_style", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_INTERNAL));
	}
	p_list->push_back(PropertyInfo(Variant::NIL, "_new_style", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_INTERNAL));
}

void TLFontFamily::_bind_methods() {

	ClassDB::bind_method(D_METHOD("_font_changed"), &TLFontFamily::_font_changed);

	ClassDB::bind_method(D_METHOD("add_style", "style"), &TLFontFamily::add_style);
	ClassDB::bind_method(D_METHOD("remove_style", "style"), &TLFontFamily::remove_style);
	ClassDB::bind_method(D_METHOD("has_style", "style"), &TLFontFamily::has_style);

	ClassDB::bind_method(D_METHOD("add_script", "style", "script"), &TLFontFamily::add_script);
	ClassDB::bind_method(D_METHOD("remove_script", "style", "script"), &TLFontFamily::remove_script);

	ClassDB::bind_method(D_METHOD("add_language", "style", "language"), &TLFontFamily::add_language);
	ClassDB::bind_method(D_METHOD("remove_language", "style", "language"), &TLFontFamily::remove_language);

	ClassDB::bind_method(D_METHOD("add_face", "style", "ref"), &TLFontFamily::add_face);
	ClassDB::bind_method(D_METHOD("add_face_unlinked", "style", "ref"), &TLFontFamily::add_face_unlinked);
	ClassDB::bind_method(D_METHOD("add_face_for_script", "style", "ref", "script"), &TLFontFamily::add_face_for_script);
	ClassDB::bind_method(D_METHOD("add_face_for_language", "style", "ref", "lang"), &TLFontFamily::add_face_for_language);

	ClassDB::bind_method(D_METHOD("get_face", "style"), &TLFontFamily::_get_face);
	ClassDB::bind_method(D_METHOD("get_face_for_script", "style", "script"), &TLFontFamily::_get_face_for_script);
	ClassDB::bind_method(D_METHOD("get_face_for_language", "style", "lang"), &TLFontFamily::_get_face_for_language);
}

#else

bool TLFontFamily::_set(String p_name, Variant p_value) {

	String name = p_name;
	PoolStringArray tokens = name.split("/");
	if (tokens.size() >= 2) {
		String style = tokens[0];
		if (tokens.size() == 2) {
			int64_t index = (int64_t)tokens[1].to_int();
			if (index == (int64_t)styles[style.to_upper()].main_chain.size()) {
				Object *p_obj = p_value;
				Ref<TLFontFace> face = Ref<TLFontFace>(Object::cast_to<TLFontFace>(p_obj));
				if (face.is_valid()) {
					_inc_ref(face);
					styles[style.to_upper()].main_chain.push_back(face);
					emit_signal(_CHANGED);
					property_list_changed_notify();
					return true;
				}
			} else if ((index >= 0) && (index < (int64_t)styles[style.to_upper()].main_chain.size())) {
				Object *p_obj = p_value;
				Ref<TLFontFace> face = Ref<TLFontFace>(Object::cast_to<TLFontFace>(p_obj));
				if (face.is_null()) {
					_dec_ref(styles[style.to_upper()].main_chain[index]);
					styles[style.to_upper()].main_chain.erase(styles[style.to_upper()].main_chain.begin() + index);
					emit_signal(_CHANGED);
					property_list_changed_notify();
					return true;
				} else {
					_dec_ref(styles[style.to_upper()].main_chain[index]);
					_inc_ref(face);
					styles[style.to_upper()].main_chain[index] = face;
					emit_signal(_CHANGED);
					property_list_changed_notify();
					return true;
				}
			}
		} else if (tokens.size() == 4) {
			int64_t index = (int64_t)tokens[3].to_int();
			if (tokens[1] == "script") {
				//add script
				hb_script_t scr = hb_script_from_string(tokens[2].ascii().get_data(), -1);
				if (index == (int64_t)styles[style.to_upper()].linked_src_chain[scr].size()) {
					Object *p_obj = p_value;
					Ref<TLFontFace> face = Ref<TLFontFace>(Object::cast_to<TLFontFace>(p_obj));
					if (face.is_valid()) {
						_inc_ref(face);
						styles[style.to_upper()].linked_src_chain[scr].push_back(face);
						emit_signal(_CHANGED);
						property_list_changed_notify();
						return true;
					}
				} else if ((index >= 0) && (index < (int64_t)styles[style.to_upper()].linked_src_chain[scr].size())) {
					Object *p_obj = p_value;
					Ref<TLFontFace> face = Ref<TLFontFace>(Object::cast_to<TLFontFace>(p_obj));
					if (face.is_null()) {
						_dec_ref(styles[style.to_upper()].linked_src_chain[scr][index]);
						styles[style.to_upper()].linked_src_chain[scr].erase(styles[style.to_upper()].linked_src_chain[scr].begin() + index);
						emit_signal(_CHANGED);
						property_list_changed_notify();
						return true;
					} else {
						_dec_ref(styles[style.to_upper()].linked_src_chain[scr][index]);
						_inc_ref(face);
						styles[style.to_upper()].linked_src_chain[scr][index] = face;
						emit_signal(_CHANGED);
						property_list_changed_notify();
						return true;
					}
				}
			} else if (tokens[1] == "lang") {
				//add lang
				hb_language_t lang = hb_language_from_string(tokens[2].ascii().get_data(), -1);
				if (index == (int64_t)styles[style.to_upper()].linked_lang_chain[lang].size()) {
					Object *p_obj = p_value;
					Ref<TLFontFace> face = Ref<TLFontFace>(Object::cast_to<TLFontFace>(p_obj));
					if (face.is_valid()) {
						_inc_ref(face);
						styles[style.to_upper()].linked_lang_chain[lang].push_back(face);
						emit_signal(_CHANGED);
						property_list_changed_notify();
						return true;
					}
				} else if ((index >= 0) && (index < (int64_t)styles[style.to_upper()].linked_lang_chain[lang].size())) {
					Object *p_obj = p_value;
					Ref<TLFontFace> face = Ref<TLFontFace>(Object::cast_to<TLFontFace>(p_obj));
					if (face.is_null()) {
						_dec_ref(styles[style.to_upper()].linked_lang_chain[lang][index]);
						styles[style.to_upper()].linked_lang_chain[lang].erase(styles[style.to_upper()].linked_lang_chain[lang].begin() + index);
						emit_signal(_CHANGED);
						property_list_changed_notify();
						return true;
					} else {
						_dec_ref(styles[style.to_upper()].linked_lang_chain[lang][index]);
						_inc_ref(face);
						styles[style.to_upper()].linked_lang_chain[lang][index] = face;
						emit_signal(_CHANGED);
						property_list_changed_notify();
						return true;
					}
				}
			}
		}
	}

	return false;
}

Variant TLFontFamily::_get(String p_name) const {

	String name = p_name;

	PoolStringArray tokens = name.split("/");
	if (tokens.size() >= 2) {
		String style = tokens[0];
		if (styles.count(style.to_upper()) > 0) {
			if (tokens.size() == 2) {
				int64_t index = (int64_t)tokens[1].to_int();
				if ((index >= 0) && (index < (int64_t)styles.at(style.to_upper()).main_chain.size())) {
					return styles.at(style.to_upper()).main_chain[index];
				}
			} else if (tokens.size() == 4) {
				int64_t index = (int64_t)tokens[3].to_int();
				if (tokens[1] == "script") {
					//add script
					hb_script_t scr = hb_script_from_string(tokens[2].ascii().get_data(), -1);
					if (styles.at(style.to_upper()).linked_src_chain.count(scr) > 0) {
						if ((index >= 0) && (index < styles.at(style.to_upper()).linked_src_chain.at(scr).size())) {
							return styles.at(style.to_upper()).linked_src_chain.at(scr)[index];
						}
					}
				} else if (tokens[1] == "lang") {
					//add lang
					hb_language_t lang = hb_language_from_string(tokens[2].ascii().get_data(), -1);
					if (styles.at(style.to_upper()).linked_lang_chain.count(lang) > 0) {
						if ((index >= 0) && (index < styles.at(style.to_upper()).linked_lang_chain.at(lang).size())) {
							return styles.at(style.to_upper()).linked_lang_chain.at(lang)[index];
						}
					}
				}
			}
		}
	}

	return Variant();
}

Array TLFontFamily::_get_property_list() const {

	Array ret;
	for (auto it = styles.begin(); it != styles.end(); ++it) {

		{
			Dictionary prop;
			prop["name"] = it->first.to_lower() + "/" + "_prev_style";
			prop["type"] = GlobalConstants::TYPE_NIL;
			prop["hint"] = GlobalConstants::PROPERTY_HINT_NONE;
			prop["hint_string"] = "";
			prop["usage"] = GlobalConstants::PROPERTY_USAGE_EDITOR;
			ret.push_back(prop);
		}

		for (int i = 0; i < it->second.main_chain.size(); i++) {
			Dictionary prop;
			prop["name"] = it->first.to_lower() + "/" + String::num_int64(i);
			prop["type"] = GlobalConstants::TYPE_OBJECT;
			prop["hint"] = GlobalConstants::PROPERTY_HINT_RESOURCE_TYPE;
			prop["hint_string"] = "TLFontFace";
			prop["usage"] = GlobalConstants::PROPERTY_USAGE_EDITOR | GlobalConstants::PROPERTY_USAGE_STORAGE;
			ret.push_back(prop);
		}
		{
			Dictionary prop;
			prop["name"] = it->first.to_lower() + "/" + String::num_int64(it->second.main_chain.size());
			prop["type"] = GlobalConstants::TYPE_OBJECT;
			prop["hint"] = GlobalConstants::PROPERTY_HINT_RESOURCE_TYPE;
			prop["hint_string"] = "TLFontFace";
			prop["usage"] = GlobalConstants::PROPERTY_USAGE_EDITOR;
			ret.push_back(prop);
		}

		for (auto sit = it->second.linked_src_chain.begin(); sit != it->second.linked_src_chain.end(); ++sit) {
			char tag[5] = "";
			hb_tag_to_string(hb_script_to_iso15924_tag(sit->first), tag);
			for (int i = 0; i < sit->second.size(); i++) {
				Dictionary prop;
				prop["name"] = it->first.to_lower() + "/script/" + tag + "/" + String::num_int64(i);
				prop["type"] = GlobalConstants::TYPE_OBJECT;
				prop["hint"] = GlobalConstants::PROPERTY_HINT_RESOURCE_TYPE;
				prop["hint_string"] = "TLFontFace";
				prop["usage"] = GlobalConstants::PROPERTY_USAGE_EDITOR | GlobalConstants::PROPERTY_USAGE_STORAGE;
				ret.push_back(prop);
			}
			{
				Dictionary prop;
				prop["name"] = it->first.to_lower() + "/script/" + tag + "/" + String::num_int64(sit->second.size());
				prop["type"] = GlobalConstants::TYPE_OBJECT;
				prop["hint"] = GlobalConstants::PROPERTY_HINT_RESOURCE_TYPE;
				prop["hint_string"] = "TLFontFace";
				prop["usage"] = GlobalConstants::PROPERTY_USAGE_EDITOR | GlobalConstants::PROPERTY_USAGE_STORAGE;
				ret.push_back(prop);
			}
			{
				Dictionary prop;
				prop["name"] = it->first.to_lower() + "/script/" + tag + "/" + "_remove_script";
				prop["type"] = GlobalConstants::TYPE_NIL;
				prop["hint"] = GlobalConstants::PROPERTY_HINT_NONE;
				prop["hint_string"] = "";
				prop["usage"] = GlobalConstants::PROPERTY_USAGE_EDITOR;
				ret.push_back(prop);
			}
		}
		{
			Dictionary prop;
			prop["name"] = it->first.to_lower() + "/" + "_add_script";
			prop["type"] = GlobalConstants::TYPE_NIL;
			prop["hint"] = GlobalConstants::PROPERTY_HINT_NONE;
			prop["hint_string"] = "";
			prop["usage"] = GlobalConstants::PROPERTY_USAGE_EDITOR;
			ret.push_back(prop);
		}
		for (auto sit = it->second.linked_lang_chain.begin(); sit != it->second.linked_lang_chain.end(); ++sit) {

			for (int i = 0; i < sit->second.size(); i++) {
				Dictionary prop;
				prop["name"] = it->first.to_lower() + "/lang/" + hb_language_to_string(sit->first) + "/" + String::num_int64(i);
				prop["type"] = GlobalConstants::TYPE_OBJECT;
				prop["hint"] = GlobalConstants::PROPERTY_HINT_RESOURCE_TYPE;
				prop["hint_string"] = "TLFontFace";
				prop["usage"] = GlobalConstants::PROPERTY_USAGE_EDITOR | GlobalConstants::PROPERTY_USAGE_STORAGE;
				ret.push_back(prop);
			}
			{
				Dictionary prop;
				prop["name"] = it->first.to_lower() + "/lang/" + hb_language_to_string(sit->first) + "/" + String::num_int64(sit->second.size());
				prop["type"] = GlobalConstants::TYPE_OBJECT;
				prop["hint"] = GlobalConstants::PROPERTY_HINT_RESOURCE_TYPE;
				prop["hint_string"] = "TLFontFace";
				prop["usage"] = GlobalConstants::PROPERTY_USAGE_EDITOR | GlobalConstants::PROPERTY_USAGE_STORAGE;
				ret.push_back(prop);
			}
			{
				Dictionary prop;
				prop["name"] = it->first.to_lower() + "/lang/" + hb_language_to_string(sit->first) + "/" + "_remove_lang";
				prop["type"] = GlobalConstants::TYPE_NIL;
				prop["hint"] = GlobalConstants::PROPERTY_HINT_NONE;
				prop["hint_string"] = "";
				prop["usage"] = GlobalConstants::PROPERTY_USAGE_EDITOR;
				ret.push_back(prop);
			}
		}
		{
			Dictionary prop;
			prop["name"] = it->first.to_lower() + "/" + "_add_lang";
			prop["type"] = GlobalConstants::TYPE_NIL;
			prop["hint"] = GlobalConstants::PROPERTY_HINT_NONE;
			prop["hint_string"] = "";
			prop["usage"] = GlobalConstants::PROPERTY_USAGE_EDITOR;
			ret.push_back(prop);
		}
		{
			Dictionary prop;
			prop["name"] = it->first.to_lower() + "/" + "_remove_style";
			prop["type"] = GlobalConstants::TYPE_NIL;
			prop["hint"] = GlobalConstants::PROPERTY_HINT_NONE;
			prop["hint_string"] = "";
			prop["usage"] = GlobalConstants::PROPERTY_USAGE_EDITOR;
			ret.push_back(prop);
		}
	}
	{
		Dictionary prop;
		prop["name"] = "_new_style";
		prop["type"] = GlobalConstants::TYPE_NIL;
		prop["hint"] = GlobalConstants::PROPERTY_HINT_NONE;
		prop["hint_string"] = "";
		prop["usage"] = GlobalConstants::PROPERTY_USAGE_EDITOR;
		ret.push_back(prop);
	}
	return ret;
}

void TLFontFamily::_register_methods() {

	register_method("_font_changed", &TLFontFamily::_font_changed);

	register_method("add_style", &TLFontFamily::add_style);
	register_method("remove_style", &TLFontFamily::remove_style);
	register_method("has_style", &TLFontFamily::has_style);

	register_method("add_script", &TLFontFamily::add_script);
	register_method("remove_script", &TLFontFamily::remove_script);

	register_method("add_language", &TLFontFamily::add_language);
	register_method("remove_language", &TLFontFamily::remove_language);

	register_method("add_face", &TLFontFamily::add_face);
	register_method("add_face_unlinked", &TLFontFamily::add_face_unlinked);
	register_method("add_face_for_script", &TLFontFamily::add_face_for_script);
	register_method("add_face_for_language", &TLFontFamily::add_face_for_language);

	register_method("_get_property_list", &TLFontFamily::_get_property_list);
	register_method("_get", &TLFontFamily::_get);
	register_method("_set", &TLFontFamily::_set);

	register_method("get_face", &TLFontFamily::_get_face);
	register_method("get_face_for_script", &TLFontFamily::_get_face_for_script);
	register_method("get_face_for_language", &TLFontFamily::_get_face_for_language);
}

#endif