Browse Source

Merge pull request #54794 from bruvzg/runtime_bmfont_parser

Rémi Verschelde 3 years ago
parent
commit
a2d323c67e
4 changed files with 787 additions and 739 deletions
  1. 16 0
      doc/classes/FontData.xml
  2. 3 739
      editor/import/resource_importer_bmfont.cpp
  3. 759 0
      scene/resources/font.cpp
  4. 9 0
      scene/resources/font.h

+ 16 - 0
doc/classes/FontData.xml

@@ -306,6 +306,22 @@
 				Returns [code]true[/code], if font supports given script ([url=https://en.wikipedia.org/wiki/ISO_15924]ISO 15924[/url] code).
 			</description>
 		</method>
+		<method name="load_bitmap_font">
+			<return type="int" enum="Error" />
+			<argument index="0" name="path" type="String" />
+			<description>
+				Loads an AngelCode BMFont (.fnt, .font) bitmap font from file [code]path[/code].
+				[b]Warning:[/b] This method should only be used in the editor or in cases when you need to load external fonts at run-time, such as fonts located at the [code]user://[/code] directory.
+			</description>
+		</method>
+		<method name="load_dynamic_font">
+			<return type="int" enum="Error" />
+			<argument index="0" name="path" type="String" />
+			<description>
+				Loads a TrueType (.ttf), OpenType (.otf), WOFF (.woff) or Type 1 (.pfb, .pfm) dynamic font from file [code]path[/code].
+				[b]Warning:[/b] This method should only be used in the editor or in cases when you need to load external fonts at run-time, such as fonts located at the [code]user://[/code] directory.
+			</description>
+		</method>
 		<method name="remove_cache">
 			<return type="void" />
 			<argument index="0" name="cache_index" type="int" />

+ 3 - 739
editor/import/resource_importer_bmfont.cpp

@@ -30,7 +30,6 @@
 
 #include "resource_importer_bmfont.h"
 
-#include "core/io/image_loader.h"
 #include "core/io/resource_saver.h"
 
 String ResourceImporterBMFont::get_importer_name() const {
@@ -64,749 +63,14 @@ void ResourceImporterBMFont::get_import_options(const String &p_path, List<Impor
 	r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "compress"), true));
 }
 
-void _convert_packed_8bit(Ref<FontData> &r_font, Ref<Image> &p_source, int p_page, int p_sz) {
-	int w = p_source->get_width();
-	int h = p_source->get_height();
-
-	PackedByteArray imgdata = p_source->get_data();
-	const uint8_t *r = imgdata.ptr();
-
-	PackedByteArray imgdata_r;
-	imgdata_r.resize(w * h * 2);
-	uint8_t *wr = imgdata_r.ptrw();
-
-	PackedByteArray imgdata_g;
-	imgdata_g.resize(w * h * 2);
-	uint8_t *wg = imgdata_g.ptrw();
-
-	PackedByteArray imgdata_b;
-	imgdata_b.resize(w * h * 2);
-	uint8_t *wb = imgdata_b.ptrw();
-
-	PackedByteArray imgdata_a;
-	imgdata_a.resize(w * h * 2);
-	uint8_t *wa = imgdata_a.ptrw();
-
-	for (int i = 0; i < h; i++) {
-		for (int j = 0; j < w; j++) {
-			int ofs_src = (i * w + j) * 4;
-			int ofs_dst = (i * w + j) * 2;
-			wr[ofs_dst + 0] = 255;
-			wr[ofs_dst + 1] = r[ofs_src + 0];
-			wg[ofs_dst + 0] = 255;
-			wg[ofs_dst + 1] = r[ofs_src + 1];
-			wb[ofs_dst + 0] = 255;
-			wb[ofs_dst + 1] = r[ofs_src + 2];
-			wa[ofs_dst + 0] = 255;
-			wa[ofs_dst + 1] = r[ofs_src + 3];
-		}
-	}
-	Ref<Image> img_r = memnew(Image(w, h, 0, Image::FORMAT_LA8, imgdata_r));
-	r_font->set_texture_image(0, Vector2i(p_sz, 0), p_page * 4 + 0, img_r);
-	Ref<Image> img_g = memnew(Image(w, h, 0, Image::FORMAT_LA8, imgdata_g));
-	r_font->set_texture_image(0, Vector2i(p_sz, 0), p_page * 4 + 1, img_g);
-	Ref<Image> img_b = memnew(Image(w, h, 0, Image::FORMAT_LA8, imgdata_b));
-	r_font->set_texture_image(0, Vector2i(p_sz, 0), p_page * 4 + 2, img_b);
-	Ref<Image> img_a = memnew(Image(w, h, 0, Image::FORMAT_LA8, imgdata_a));
-	r_font->set_texture_image(0, Vector2i(p_sz, 0), p_page * 4 + 3, img_a);
-}
-
-void _convert_packed_4bit(Ref<FontData> &r_font, Ref<Image> &p_source, int p_page, int p_sz) {
-	int w = p_source->get_width();
-	int h = p_source->get_height();
-
-	PackedByteArray imgdata = p_source->get_data();
-	const uint8_t *r = imgdata.ptr();
-
-	PackedByteArray imgdata_r;
-	imgdata_r.resize(w * h * 2);
-	uint8_t *wr = imgdata_r.ptrw();
-
-	PackedByteArray imgdata_g;
-	imgdata_g.resize(w * h * 2);
-	uint8_t *wg = imgdata_g.ptrw();
-
-	PackedByteArray imgdata_b;
-	imgdata_b.resize(w * h * 2);
-	uint8_t *wb = imgdata_b.ptrw();
-
-	PackedByteArray imgdata_a;
-	imgdata_a.resize(w * h * 2);
-	uint8_t *wa = imgdata_a.ptrw();
-
-	PackedByteArray imgdata_ro;
-	imgdata_ro.resize(w * h * 2);
-	uint8_t *wro = imgdata_ro.ptrw();
-
-	PackedByteArray imgdata_go;
-	imgdata_go.resize(w * h * 2);
-	uint8_t *wgo = imgdata_go.ptrw();
-
-	PackedByteArray imgdata_bo;
-	imgdata_bo.resize(w * h * 2);
-	uint8_t *wbo = imgdata_bo.ptrw();
-
-	PackedByteArray imgdata_ao;
-	imgdata_ao.resize(w * h * 2);
-	uint8_t *wao = imgdata_ao.ptrw();
-
-	for (int i = 0; i < h; i++) {
-		for (int j = 0; j < w; j++) {
-			int ofs_src = (i * w + j) * 4;
-			int ofs_dst = (i * w + j) * 2;
-			wr[ofs_dst + 0] = 255;
-			wro[ofs_dst + 0] = 255;
-			if (r[ofs_src + 0] > 0x0F) {
-				wr[ofs_dst + 1] = (r[ofs_src + 0] - 0x0F) * 2;
-				wro[ofs_dst + 1] = 0;
-			} else {
-				wr[ofs_dst + 1] = 0;
-				wro[ofs_dst + 1] = r[ofs_src + 0] * 2;
-			}
-			wg[ofs_dst + 0] = 255;
-			wgo[ofs_dst + 0] = 255;
-			if (r[ofs_src + 1] > 0x0F) {
-				wg[ofs_dst + 1] = (r[ofs_src + 1] - 0x0F) * 2;
-				wgo[ofs_dst + 1] = 0;
-			} else {
-				wg[ofs_dst + 1] = 0;
-				wgo[ofs_dst + 1] = r[ofs_src + 1] * 2;
-			}
-			wb[ofs_dst + 0] = 255;
-			wbo[ofs_dst + 0] = 255;
-			if (r[ofs_src + 2] > 0x0F) {
-				wb[ofs_dst + 1] = (r[ofs_src + 2] - 0x0F) * 2;
-				wbo[ofs_dst + 1] = 0;
-			} else {
-				wb[ofs_dst + 1] = 0;
-				wbo[ofs_dst + 1] = r[ofs_src + 2] * 2;
-			}
-			wa[ofs_dst + 0] = 255;
-			wao[ofs_dst + 0] = 255;
-			if (r[ofs_src + 3] > 0x0F) {
-				wa[ofs_dst + 1] = (r[ofs_src + 3] - 0x0F) * 2;
-				wao[ofs_dst + 1] = 0;
-			} else {
-				wa[ofs_dst + 1] = 0;
-				wao[ofs_dst + 1] = r[ofs_src + 3] * 2;
-			}
-		}
-	}
-	Ref<Image> img_r = memnew(Image(w, h, 0, Image::FORMAT_LA8, imgdata_r));
-	r_font->set_texture_image(0, Vector2i(p_sz, 0), p_page * 4 + 0, img_r);
-	Ref<Image> img_g = memnew(Image(w, h, 0, Image::FORMAT_LA8, imgdata_g));
-	r_font->set_texture_image(0, Vector2i(p_sz, 0), p_page * 4 + 1, img_g);
-	Ref<Image> img_b = memnew(Image(w, h, 0, Image::FORMAT_LA8, imgdata_b));
-	r_font->set_texture_image(0, Vector2i(p_sz, 0), p_page * 4 + 2, img_b);
-	Ref<Image> img_a = memnew(Image(w, h, 0, Image::FORMAT_LA8, imgdata_a));
-	r_font->set_texture_image(0, Vector2i(p_sz, 0), p_page * 4 + 3, img_a);
-
-	Ref<Image> img_ro = memnew(Image(w, h, 0, Image::FORMAT_LA8, imgdata_ro));
-	r_font->set_texture_image(0, Vector2i(p_sz, 1), p_page * 4 + 0, img_ro);
-	Ref<Image> img_go = memnew(Image(w, h, 0, Image::FORMAT_LA8, imgdata_go));
-	r_font->set_texture_image(0, Vector2i(p_sz, 1), p_page * 4 + 1, img_go);
-	Ref<Image> img_bo = memnew(Image(w, h, 0, Image::FORMAT_LA8, imgdata_bo));
-	r_font->set_texture_image(0, Vector2i(p_sz, 1), p_page * 4 + 2, img_bo);
-	Ref<Image> img_ao = memnew(Image(w, h, 0, Image::FORMAT_LA8, imgdata_ao));
-	r_font->set_texture_image(0, Vector2i(p_sz, 1), p_page * 4 + 3, img_ao);
-}
-
-void _convert_rgba_4bit(Ref<FontData> &r_font, Ref<Image> &p_source, int p_page, int p_sz) {
-	int w = p_source->get_width();
-	int h = p_source->get_height();
-
-	PackedByteArray imgdata = p_source->get_data();
-	const uint8_t *r = imgdata.ptr();
-
-	PackedByteArray imgdata_g;
-	imgdata_g.resize(w * h * 4);
-	uint8_t *wg = imgdata_g.ptrw();
-
-	PackedByteArray imgdata_o;
-	imgdata_o.resize(w * h * 4);
-	uint8_t *wo = imgdata_o.ptrw();
-
-	for (int i = 0; i < h; i++) {
-		for (int j = 0; j < w; j++) {
-			int ofs = (i * w + j) * 4;
-
-			if (r[ofs + 0] > 0x7F) {
-				wg[ofs + 0] = r[ofs + 0];
-				wo[ofs + 0] = 0;
-			} else {
-				wg[ofs + 0] = 0;
-				wo[ofs + 0] = r[ofs + 0] * 2;
-			}
-			if (r[ofs + 1] > 0x7F) {
-				wg[ofs + 1] = r[ofs + 1];
-				wo[ofs + 1] = 0;
-			} else {
-				wg[ofs + 1] = 0;
-				wo[ofs + 1] = r[ofs + 1] * 2;
-			}
-			if (r[ofs + 2] > 0x7F) {
-				wg[ofs + 2] = r[ofs + 2];
-				wo[ofs + 2] = 0;
-			} else {
-				wg[ofs + 2] = 0;
-				wo[ofs + 2] = r[ofs + 2] * 2;
-			}
-			if (r[ofs + 3] > 0x7F) {
-				wg[ofs + 3] = r[ofs + 3];
-				wo[ofs + 3] = 0;
-			} else {
-				wg[ofs + 3] = 0;
-				wo[ofs + 3] = r[ofs + 3] * 2;
-			}
-		}
-	}
-	Ref<Image> img_g = memnew(Image(w, h, 0, Image::FORMAT_RGBA8, imgdata_g));
-	r_font->set_texture_image(0, Vector2i(p_sz, 0), p_page, img_g);
-
-	Ref<Image> img_o = memnew(Image(w, h, 0, Image::FORMAT_RGBA8, imgdata_o));
-	r_font->set_texture_image(0, Vector2i(p_sz, 1), p_page, img_o);
-}
-
-void _convert_mono_8bit(Ref<FontData> &r_font, Ref<Image> &p_source, int p_page, int p_ch, int p_sz, int p_ol) {
-	int w = p_source->get_width();
-	int h = p_source->get_height();
-
-	PackedByteArray imgdata = p_source->get_data();
-	const uint8_t *r = imgdata.ptr();
-
-	int size = 4;
-	if (p_source->get_format() == Image::FORMAT_L8) {
-		size = 1;
-		p_ch = 0;
-	}
-
-	PackedByteArray imgdata_g;
-	imgdata_g.resize(w * h * 2);
-	uint8_t *wg = imgdata_g.ptrw();
-
-	for (int i = 0; i < h; i++) {
-		for (int j = 0; j < w; j++) {
-			int ofs_src = (i * w + j) * size;
-			int ofs_dst = (i * w + j) * 2;
-			wg[ofs_dst + 0] = 255;
-			wg[ofs_dst + 1] = r[ofs_src + p_ch];
-		}
-	}
-	Ref<Image> img_g = memnew(Image(w, h, 0, Image::FORMAT_LA8, imgdata_g));
-	r_font->set_texture_image(0, Vector2i(p_sz, p_ol), p_page, img_g);
-}
-
-void _convert_mono_4bit(Ref<FontData> &r_font, Ref<Image> &p_source, int p_page, int p_ch, int p_sz, int p_ol) {
-	int w = p_source->get_width();
-	int h = p_source->get_height();
-
-	PackedByteArray imgdata = p_source->get_data();
-	const uint8_t *r = imgdata.ptr();
-
-	int size = 4;
-	if (p_source->get_format() == Image::FORMAT_L8) {
-		size = 1;
-		p_ch = 0;
-	}
-
-	PackedByteArray imgdata_g;
-	imgdata_g.resize(w * h * 2);
-	uint8_t *wg = imgdata_g.ptrw();
-
-	PackedByteArray imgdata_o;
-	imgdata_o.resize(w * h * 2);
-	uint8_t *wo = imgdata_o.ptrw();
-
-	for (int i = 0; i < h; i++) {
-		for (int j = 0; j < w; j++) {
-			int ofs_src = (i * w + j) * size;
-			int ofs_dst = (i * w + j) * 2;
-			wg[ofs_dst + 0] = 255;
-			wo[ofs_dst + 0] = 255;
-			if (r[ofs_src + p_ch] > 0x7F) {
-				wg[ofs_dst + 1] = r[ofs_src + p_ch];
-				wo[ofs_dst + 1] = 0;
-			} else {
-				wg[ofs_dst + 1] = 0;
-				wo[ofs_dst + 1] = r[ofs_src + p_ch] * 2;
-			}
-		}
-	}
-	Ref<Image> img_g = memnew(Image(w, h, 0, Image::FORMAT_LA8, imgdata_g));
-	r_font->set_texture_image(0, Vector2i(p_sz, 0), p_page, img_g);
-
-	Ref<Image> img_o = memnew(Image(w, h, 0, Image::FORMAT_LA8, imgdata_o));
-	r_font->set_texture_image(0, Vector2i(p_sz, p_ol), p_page, img_o);
-}
-
 Error ResourceImporterBMFont::import(const String &p_source_file, const String &p_save_path, const Map<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files, Variant *r_metadata) {
 	print_verbose("Importing BMFont font from: " + p_source_file);
 
 	Ref<FontData> font;
 	font.instantiate();
-	font->set_antialiased(false);
-	font->set_multichannel_signed_distance_field(false);
-	font->set_force_autohinter(false);
-	font->set_hinting(TextServer::HINTING_NONE);
-	font->set_oversampling(1.0f);
-
-	FileAccessRef f = FileAccess::open(p_source_file, FileAccess::READ);
-	if (f == nullptr) {
-		ERR_FAIL_V_MSG(ERR_CANT_CREATE, TTR("Cannot open font from file ") + "\"" + p_source_file + "\".");
-	}
-
-	int base_size = 16;
-	int height = 0;
-	int ascent = 0;
-	int outline = 0;
-	uint32_t st_flags = 0;
-	String font_name;
-
-	bool packed = false;
-	uint8_t ch[4] = { 0, 0, 0, 0 }; // RGBA
-	int first_gl_ch = -1;
-	int first_ol_ch = -1;
-	int first_cm_ch = -1;
-
-	unsigned char magic[4];
-	f->get_buffer((unsigned char *)&magic, 4);
-	if (magic[0] == 'B' && magic[1] == 'M' && magic[2] == 'F') {
-		// Binary BMFont file.
-		ERR_FAIL_COND_V_MSG(magic[3] != 3, ERR_CANT_CREATE, vformat(TTR("Version %d of BMFont is not supported."), (int)magic[3]));
-
-		uint8_t block_type = f->get_8();
-		uint32_t block_size = f->get_32();
-		while (!f->eof_reached()) {
-			uint64_t off = f->get_position();
-			switch (block_type) {
-				case 1: /* info */ {
-					ERR_FAIL_COND_V_MSG(block_size < 15, ERR_CANT_CREATE, TTR("Invalid BMFont info block size."));
-					base_size = f->get_16();
-					uint8_t flags = f->get_8();
-					ERR_FAIL_COND_V_MSG(flags & 0x02, ERR_CANT_CREATE, TTR("Non-unicode version of BMFont is not supported."));
-					if (flags & (1 << 3)) {
-						st_flags |= TextServer::FONT_BOLD;
-					}
-					if (flags & (1 << 2)) {
-						st_flags |= TextServer::FONT_ITALIC;
-					}
-					f->get_8(); // non-unicode charset, skip
-					f->get_16(); // stretch_h, skip
-					f->get_8(); // aa, skip
-					f->get_32(); // padding, skip
-					f->get_16(); // spacing, skip
-					outline = f->get_8();
-					// font name
-					PackedByteArray name_data;
-					name_data.resize(block_size - 14);
-					f->get_buffer(name_data.ptrw(), block_size - 14);
-					font_name = String::utf8((const char *)name_data.ptr(), block_size - 14);
-					font->set_fixed_size(base_size);
-				} break;
-				case 2: /* common */ {
-					ERR_FAIL_COND_V_MSG(block_size != 15, ERR_CANT_CREATE, TTR("Invalid BMFont common block size."));
-					height = f->get_16();
-					ascent = f->get_16();
-					f->get_32(); // scale, skip
-					f->get_16(); // pages, skip
-					uint8_t flags = f->get_8();
-					packed = (flags & 0x01);
-					ch[3] = f->get_8();
-					ch[0] = f->get_8();
-					ch[1] = f->get_8();
-					ch[2] = f->get_8();
-					for (int i = 0; i < 4; i++) {
-						if (ch[i] == 0 && first_gl_ch == -1) {
-							first_gl_ch = i;
-						}
-						if (ch[i] == 1 && first_ol_ch == -1) {
-							first_ol_ch = i;
-						}
-						if (ch[i] == 2 && first_cm_ch == -1) {
-							first_cm_ch = i;
-						}
-					}
-				} break;
-				case 3: /* pages */ {
-					int page = 0;
-					CharString cs;
-					char32_t c = f->get_8();
-					while (!f->eof_reached() && f->get_position() <= off + block_size) {
-						if (c == '\0') {
-							String base_dir = p_source_file.get_base_dir();
-							String file = base_dir.plus_file(String::utf8(cs.ptr(), cs.length()));
-							if (RenderingServer::get_singleton() != nullptr) {
-								Ref<Image> img;
-								img.instantiate();
-								Error err = ImageLoader::load_image(file, img);
-								ERR_FAIL_COND_V_MSG(err != OK, ERR_FILE_CANT_READ, TTR("Can't load font texture: ") + "\"" + file + "\".");
-
-								if (packed) {
-									if (ch[3] == 0) { // 4 x 8 bit monochrome, no outline
-										outline = 0;
-										ERR_FAIL_COND_V_MSG(img->get_format() != Image::FORMAT_RGBA8, ERR_FILE_CANT_READ, TTR("Unsupported BMFont texture format."));
-										_convert_packed_8bit(font, img, page, base_size);
-									} else if ((ch[3] == 2) && (outline > 0)) { // 4 x 4 bit monochrome, gl + outline
-										ERR_FAIL_COND_V_MSG(img->get_format() != Image::FORMAT_RGBA8, ERR_FILE_CANT_READ, TTR("Unsupported BMFont texture format."));
-										_convert_packed_4bit(font, img, page, base_size);
-									} else {
-										ERR_FAIL_V_MSG(ERR_CANT_CREATE, TTR("Unsupported BMFont texture format."));
-									}
-								} else {
-									if ((ch[0] == 0) && (ch[1] == 0) && (ch[2] == 0) && (ch[3] == 0)) { // RGBA8 color, no outline
-										outline = 0;
-										ERR_FAIL_COND_V_MSG(img->get_format() != Image::FORMAT_RGBA8, ERR_FILE_CANT_READ, TTR("Unsupported BMFont texture format."));
-										font->set_texture_image(0, Vector2i(base_size, 0), page, img);
-									} else if ((ch[0] == 2) && (ch[1] == 2) && (ch[2] == 2) && (ch[3] == 2) && (outline > 0)) { // RGBA4 color, gl + outline
-										ERR_FAIL_COND_V_MSG(img->get_format() != Image::FORMAT_RGBA8, ERR_FILE_CANT_READ, TTR("Unsupported BMFont texture format."));
-										_convert_rgba_4bit(font, img, page, base_size);
-									} else if ((first_gl_ch >= 0) && (first_ol_ch >= 0) && (outline > 0)) { // 1 x 8 bit monochrome, gl + outline
-										ERR_FAIL_COND_V_MSG(img->get_format() != Image::FORMAT_RGBA8 && img->get_format() != Image::FORMAT_L8, ERR_FILE_CANT_READ, TTR("Unsupported BMFont texture format."));
-										_convert_mono_8bit(font, img, page, first_gl_ch, base_size, 0);
-										_convert_mono_8bit(font, img, page, first_ol_ch, base_size, 1);
-									} else if ((first_cm_ch >= 0) && (outline > 0)) { // 1 x 4 bit monochrome, gl + outline
-										ERR_FAIL_COND_V_MSG(img->get_format() != Image::FORMAT_RGBA8 && img->get_format() != Image::FORMAT_L8, ERR_FILE_CANT_READ, TTR("Unsupported BMFont texture format."));
-										_convert_mono_4bit(font, img, page, first_cm_ch, base_size, 1);
-									} else if (first_gl_ch >= 0) { // 1 x 8 bit monochrome, no outline
-										outline = 0;
-										ERR_FAIL_COND_V_MSG(img->get_format() != Image::FORMAT_RGBA8 && img->get_format() != Image::FORMAT_L8, ERR_FILE_CANT_READ, TTR("Unsupported BMFont texture format."));
-										_convert_mono_8bit(font, img, page, first_gl_ch, base_size, 0);
-									} else {
-										ERR_FAIL_V_MSG(ERR_CANT_CREATE, TTR("Unsupported BMFont texture format."));
-									}
-								}
-							}
-							page++;
-							cs = "";
-						} else {
-							cs += c;
-						}
-						c = f->get_8();
-					}
-				} break;
-				case 4: /* chars */ {
-					int char_count = block_size / 20;
-					for (int i = 0; i < char_count; i++) {
-						Vector2 advance;
-						Vector2 size;
-						Vector2 offset;
-						Rect2 uv_rect;
-
-						char32_t idx = f->get_32();
-						uv_rect.position.x = (int16_t)f->get_16();
-						uv_rect.position.y = (int16_t)f->get_16();
-						uv_rect.size.width = (int16_t)f->get_16();
-						size.width = uv_rect.size.width;
-						uv_rect.size.height = (int16_t)f->get_16();
-						size.height = uv_rect.size.height;
-						offset.x = (int16_t)f->get_16();
-						offset.y = (int16_t)f->get_16() - ascent;
-						advance.x = (int16_t)f->get_16();
-						if (advance.x < 0) {
-							advance.x = size.width + 1;
-						}
-
-						int texture_idx = f->get_8();
-						uint8_t channel = f->get_8();
-
-						ERR_FAIL_COND_V_MSG(!packed && channel != 15, ERR_CANT_CREATE, TTR("Invalid glyph channel."));
-						int ch_off = 0;
-						switch (channel) {
-							case 1:
-								ch_off = 2;
-								break; // B
-							case 2:
-								ch_off = 1;
-								break; // G
-							case 4:
-								ch_off = 0;
-								break; // R
-							case 8:
-								ch_off = 3;
-								break; // A
-							default:
-								ch_off = 0;
-								break;
-						}
-						font->set_glyph_advance(0, base_size, idx, advance);
-						font->set_glyph_offset(0, Vector2i(base_size, 0), idx, offset);
-						font->set_glyph_size(0, Vector2i(base_size, 0), idx, size);
-						font->set_glyph_uv_rect(0, Vector2i(base_size, 0), idx, uv_rect);
-						font->set_glyph_texture_idx(0, Vector2i(base_size, 0), idx, texture_idx * (packed ? 4 : 1) + ch_off);
-						if (outline > 0) {
-							font->set_glyph_offset(0, Vector2i(base_size, 1), idx, offset);
-							font->set_glyph_size(0, Vector2i(base_size, 1), idx, size);
-							font->set_glyph_uv_rect(0, Vector2i(base_size, 1), idx, uv_rect);
-							font->set_glyph_texture_idx(0, Vector2i(base_size, 1), idx, texture_idx * (packed ? 4 : 1) + ch_off);
-						}
-					}
-				} break;
-				case 5: /* kerning */ {
-					int pair_count = block_size / 10;
-					for (int i = 0; i < pair_count; i++) {
-						Vector2i kpk;
-						kpk.x = f->get_32();
-						kpk.y = f->get_32();
-						font->set_kerning(0, base_size, kpk, Vector2((int16_t)f->get_16(), 0));
-					}
-				} break;
-				default: {
-					ERR_FAIL_V_MSG(ERR_CANT_CREATE, TTR("Invalid BMFont block type."));
-				} break;
-			}
-			f->seek(off + block_size);
-			block_type = f->get_8();
-			block_size = f->get_32();
-		}
-
-	} else {
-		// Text BMFont file.
-		f->seek(0);
-		while (true) {
-			String line = f->get_line();
-
-			int delimiter = line.find(" ");
-			String type = line.substr(0, delimiter);
-			int pos = delimiter + 1;
-			Map<String, String> keys;
-
-			while (pos < line.size() && line[pos] == ' ') {
-				pos++;
-			}
-
-			while (pos < line.size()) {
-				int eq = line.find("=", pos);
-				if (eq == -1) {
-					break;
-				}
-				String key = line.substr(pos, eq - pos);
-				int end = -1;
-				String value;
-				if (line[eq + 1] == '"') {
-					end = line.find("\"", eq + 2);
-					if (end == -1) {
-						break;
-					}
-					value = line.substr(eq + 2, end - 1 - eq - 1);
-					pos = end + 1;
-				} else {
-					end = line.find(" ", eq + 1);
-					if (end == -1) {
-						end = line.size();
-					}
-					value = line.substr(eq + 1, end - eq);
-					pos = end;
-				}
-
-				while (pos < line.size() && line[pos] == ' ') {
-					pos++;
-				}
-
-				keys[key] = value;
-			}
-
-			if (type == "info") {
-				if (keys.has("size")) {
-					base_size = keys["size"].to_int();
-					font->set_fixed_size(base_size);
-				}
-				if (keys.has("outline")) {
-					outline = keys["outline"].to_int();
-				}
-				if (keys.has("bold")) {
-					if (keys["bold"].to_int()) {
-						st_flags |= TextServer::FONT_BOLD;
-					}
-				}
-				if (keys.has("italic")) {
-					if (keys["italic"].to_int()) {
-						st_flags |= TextServer::FONT_ITALIC;
-					}
-				}
-				if (keys.has("face")) {
-					font_name = keys["face"];
-				}
-				ERR_FAIL_COND_V_MSG((!keys.has("unicode") || keys["unicode"].to_int() != 1), ERR_CANT_CREATE, TTR("Non-unicode version of BMFont is not supported."));
-			} else if (type == "common") {
-				if (keys.has("lineHeight")) {
-					height = keys["lineHeight"].to_int();
-				}
-				if (keys.has("base")) {
-					ascent = keys["base"].to_int();
-				}
-				if (keys.has("packed")) {
-					packed = (keys["packed"].to_int() == 1);
-				}
-				if (keys.has("alphaChnl")) {
-					ch[3] = keys["alphaChnl"].to_int();
-				}
-				if (keys.has("redChnl")) {
-					ch[0] = keys["redChnl"].to_int();
-				}
-				if (keys.has("greenChnl")) {
-					ch[1] = keys["greenChnl"].to_int();
-				}
-				if (keys.has("blueChnl")) {
-					ch[2] = keys["blueChnl"].to_int();
-				}
-				for (int i = 0; i < 4; i++) {
-					if (ch[i] == 0 && first_gl_ch == -1) {
-						first_gl_ch = i;
-					}
-					if (ch[i] == 1 && first_ol_ch == -1) {
-						first_ol_ch = i;
-					}
-					if (ch[i] == 2 && first_cm_ch == -1) {
-						first_cm_ch = i;
-					}
-				}
-			} else if (type == "page") {
-				int page = 0;
-				if (keys.has("id")) {
-					page = keys["id"].to_int();
-				}
-				if (keys.has("file")) {
-					String base_dir = p_source_file.get_base_dir();
-					String file = base_dir.plus_file(keys["file"]);
-					if (RenderingServer::get_singleton() != nullptr) {
-						Ref<Image> img;
-						img.instantiate();
-						Error err = ImageLoader::load_image(file, img);
-						ERR_FAIL_COND_V_MSG(err != OK, ERR_FILE_CANT_READ, TTR("Can't load font texture: ") + "\"" + file + "\".");
-						if (packed) {
-							if (ch[3] == 0) { // 4 x 8 bit monochrome, no outline
-								outline = 0;
-								ERR_FAIL_COND_V_MSG(img->get_format() != Image::FORMAT_RGBA8, ERR_FILE_CANT_READ, TTR("Unsupported BMFont texture format."));
-								_convert_packed_8bit(font, img, page, base_size);
-							} else if ((ch[3] == 2) && (outline > 0)) { // 4 x 4 bit monochrome, gl + outline
-								ERR_FAIL_COND_V_MSG(img->get_format() != Image::FORMAT_RGBA8, ERR_FILE_CANT_READ, TTR("Unsupported BMFont texture format."));
-								_convert_packed_4bit(font, img, page, base_size);
-							} else {
-								ERR_FAIL_V_MSG(ERR_CANT_CREATE, TTR("Unsupported BMFont texture format."));
-							}
-						} else {
-							if ((ch[0] == 0) && (ch[1] == 0) && (ch[2] == 0) && (ch[3] == 0)) { // RGBA8 color, no outline
-								outline = 0;
-								ERR_FAIL_COND_V_MSG(img->get_format() != Image::FORMAT_RGBA8, ERR_FILE_CANT_READ, TTR("Unsupported BMFont texture format."));
-								font->set_texture_image(0, Vector2i(base_size, 0), page, img);
-							} else if ((ch[0] == 2) && (ch[1] == 2) && (ch[2] == 2) && (ch[3] == 2) && (outline > 0)) { // RGBA4 color, gl + outline
-								ERR_FAIL_COND_V_MSG(img->get_format() != Image::FORMAT_RGBA8, ERR_FILE_CANT_READ, TTR("Unsupported BMFont texture format."));
-								_convert_rgba_4bit(font, img, page, base_size);
-							} else if ((first_gl_ch >= 0) && (first_ol_ch >= 0) && (outline > 0)) { // 1 x 8 bit monochrome, gl + outline
-								ERR_FAIL_COND_V_MSG(img->get_format() != Image::FORMAT_RGBA8 && img->get_format() != Image::FORMAT_L8, ERR_FILE_CANT_READ, TTR("Unsupported BMFont texture format."));
-								_convert_mono_8bit(font, img, page, first_gl_ch, base_size, 0);
-								_convert_mono_8bit(font, img, page, first_ol_ch, base_size, 1);
-							} else if ((first_cm_ch >= 0) && (outline > 0)) { // 1 x 4 bit monochrome, gl + outline
-								ERR_FAIL_COND_V_MSG(img->get_format() != Image::FORMAT_RGBA8 && img->get_format() != Image::FORMAT_L8, ERR_FILE_CANT_READ, TTR("Unsupported BMFont texture format."));
-								_convert_mono_4bit(font, img, page, first_cm_ch, base_size, 1);
-							} else if (first_gl_ch >= 0) { // 1 x 8 bit monochrome, no outline
-								outline = 0;
-								ERR_FAIL_COND_V_MSG(img->get_format() != Image::FORMAT_RGBA8 && img->get_format() != Image::FORMAT_L8, ERR_FILE_CANT_READ, TTR("Unsupported BMFont texture format."));
-								_convert_mono_8bit(font, img, page, first_gl_ch, base_size, 0);
-							} else {
-								ERR_FAIL_V_MSG(ERR_CANT_CREATE, TTR("Unsupported BMFont texture format."));
-							}
-						}
-					}
-				}
-			} else if (type == "char") {
-				char32_t idx = 0;
-				Vector2 advance;
-				Vector2 size;
-				Vector2 offset;
-				Rect2 uv_rect;
-				int texture_idx = -1;
-				uint8_t channel = 15;
-
-				if (keys.has("id")) {
-					idx = keys["id"].to_int();
-				}
-				if (keys.has("x")) {
-					uv_rect.position.x = keys["x"].to_int();
-				}
-				if (keys.has("y")) {
-					uv_rect.position.y = keys["y"].to_int();
-				}
-				if (keys.has("width")) {
-					uv_rect.size.width = keys["width"].to_int();
-					size.width = keys["width"].to_int();
-				}
-				if (keys.has("height")) {
-					uv_rect.size.height = keys["height"].to_int();
-					size.height = keys["height"].to_int();
-				}
-				if (keys.has("xoffset")) {
-					offset.x = keys["xoffset"].to_int();
-				}
-				if (keys.has("yoffset")) {
-					offset.y = keys["yoffset"].to_int() - ascent;
-				}
-				if (keys.has("page")) {
-					texture_idx = keys["page"].to_int();
-				}
-				if (keys.has("xadvance")) {
-					advance.x = keys["xadvance"].to_int();
-				}
-				if (advance.x < 0) {
-					advance.x = size.width + 1;
-				}
-				if (keys.has("chnl")) {
-					channel = keys["chnl"].to_int();
-				}
-
-				ERR_FAIL_COND_V_MSG(!packed && channel != 15, ERR_CANT_CREATE, TTR("Invalid glyph channel."));
-				int ch_off = 0;
-				switch (channel) {
-					case 1:
-						ch_off = 2;
-						break; // B
-					case 2:
-						ch_off = 1;
-						break; // G
-					case 4:
-						ch_off = 0;
-						break; // R
-					case 8:
-						ch_off = 3;
-						break; // A
-					default:
-						ch_off = 0;
-						break;
-				}
-				font->set_glyph_advance(0, base_size, idx, advance);
-				font->set_glyph_offset(0, Vector2i(base_size, 0), idx, offset);
-				font->set_glyph_size(0, Vector2i(base_size, 0), idx, size);
-				font->set_glyph_uv_rect(0, Vector2i(base_size, 0), idx, uv_rect);
-				font->set_glyph_texture_idx(0, Vector2i(base_size, 0), idx, texture_idx * (packed ? 4 : 1) + ch_off);
-				if (outline > 0) {
-					font->set_glyph_offset(0, Vector2i(base_size, 1), idx, offset);
-					font->set_glyph_size(0, Vector2i(base_size, 1), idx, size);
-					font->set_glyph_uv_rect(0, Vector2i(base_size, 1), idx, uv_rect);
-					font->set_glyph_texture_idx(0, Vector2i(base_size, 1), idx, texture_idx * (packed ? 4 : 1) + ch_off);
-				}
-			} else if (type == "kerning") {
-				Vector2i kpk;
-				if (keys.has("first")) {
-					kpk.x = keys["first"].to_int();
-				}
-				if (keys.has("second")) {
-					kpk.y = keys["second"].to_int();
-				}
-				if (keys.has("amount")) {
-					font->set_kerning(0, base_size, kpk, Vector2(keys["amount"].to_int(), 0));
-				}
-			}
-
-			if (f->eof_reached()) {
-				break;
-			}
-		}
-	}
 
-	font->set_font_name(font_name);
-	font->set_font_style(st_flags);
-	font->set_ascent(0, base_size, ascent);
-	font->set_descent(0, base_size, height - ascent);
+	Error err = font->load_bitmap_font(p_source_file);
+	ERR_FAIL_COND_V_MSG(err != OK, err, "Cannot load font to file \"" + p_source_file + "\".");
 
 	int flg = ResourceSaver::SaverFlags::FLAG_BUNDLE_RESOURCES | ResourceSaver::FLAG_REPLACE_SUBRESOURCE_PATHS;
 	if ((bool)p_options["compress"]) {
@@ -814,7 +78,7 @@ Error ResourceImporterBMFont::import(const String &p_source_file, const String &
 	}
 
 	print_verbose("Saving to: " + p_save_path + ".fontdata");
-	Error err = ResourceSaver::save(p_save_path + ".fontdata", font, flg);
+	err = ResourceSaver::save(p_save_path + ".fontdata", font, flg);
 	ERR_FAIL_COND_V_MSG(err != OK, err, "Cannot save font to file \"" + p_save_path + ".res\".");
 	print_verbose("Done saving to: " + p_save_path + ".fontdata");
 	return OK;

+ 759 - 0
scene/resources/font.cpp

@@ -30,6 +30,7 @@
 
 #include "font.h"
 
+#include "core/io/image_loader.h"
 #include "core/io/resource_loader.h"
 #include "core/string/translation.h"
 #include "core/templates/hashfuncs.h"
@@ -64,6 +65,9 @@ _FORCE_INLINE_ void FontData::_ensure_rid(int p_cache_index) const {
 }
 
 void FontData::_bind_methods() {
+	ClassDB::bind_method(D_METHOD("load_bitmap_font", "path"), &FontData::load_bitmap_font);
+	ClassDB::bind_method(D_METHOD("load_dynamic_font", "path"), &FontData::load_dynamic_font);
+
 	ClassDB::bind_method(D_METHOD("set_data", "data"), &FontData::set_data);
 	ClassDB::bind_method(D_METHOD("get_data"), &FontData::get_data);
 
@@ -428,11 +432,766 @@ void FontData::reset_state() {
 	hinting = TextServer::HINTING_LIGHT;
 	msdf_pixel_range = 14;
 	msdf_size = 128;
+	fixed_size = 0;
 	oversampling = 0.f;
 }
 
+void FontData::_convert_packed_8bit(Ref<Image> &p_source, int p_page, int p_sz) {
+	int w = p_source->get_width();
+	int h = p_source->get_height();
+
+	PackedByteArray imgdata = p_source->get_data();
+	const uint8_t *r = imgdata.ptr();
+
+	PackedByteArray imgdata_r;
+	imgdata_r.resize(w * h * 2);
+	uint8_t *wr = imgdata_r.ptrw();
+
+	PackedByteArray imgdata_g;
+	imgdata_g.resize(w * h * 2);
+	uint8_t *wg = imgdata_g.ptrw();
+
+	PackedByteArray imgdata_b;
+	imgdata_b.resize(w * h * 2);
+	uint8_t *wb = imgdata_b.ptrw();
+
+	PackedByteArray imgdata_a;
+	imgdata_a.resize(w * h * 2);
+	uint8_t *wa = imgdata_a.ptrw();
+
+	for (int i = 0; i < h; i++) {
+		for (int j = 0; j < w; j++) {
+			int ofs_src = (i * w + j) * 4;
+			int ofs_dst = (i * w + j) * 2;
+			wr[ofs_dst + 0] = 255;
+			wr[ofs_dst + 1] = r[ofs_src + 0];
+			wg[ofs_dst + 0] = 255;
+			wg[ofs_dst + 1] = r[ofs_src + 1];
+			wb[ofs_dst + 0] = 255;
+			wb[ofs_dst + 1] = r[ofs_src + 2];
+			wa[ofs_dst + 0] = 255;
+			wa[ofs_dst + 1] = r[ofs_src + 3];
+		}
+	}
+	Ref<Image> img_r = memnew(Image(w, h, 0, Image::FORMAT_LA8, imgdata_r));
+	set_texture_image(0, Vector2i(p_sz, 0), p_page * 4 + 0, img_r);
+	Ref<Image> img_g = memnew(Image(w, h, 0, Image::FORMAT_LA8, imgdata_g));
+	set_texture_image(0, Vector2i(p_sz, 0), p_page * 4 + 1, img_g);
+	Ref<Image> img_b = memnew(Image(w, h, 0, Image::FORMAT_LA8, imgdata_b));
+	set_texture_image(0, Vector2i(p_sz, 0), p_page * 4 + 2, img_b);
+	Ref<Image> img_a = memnew(Image(w, h, 0, Image::FORMAT_LA8, imgdata_a));
+	set_texture_image(0, Vector2i(p_sz, 0), p_page * 4 + 3, img_a);
+}
+
+void FontData::_convert_packed_4bit(Ref<Image> &p_source, int p_page, int p_sz) {
+	int w = p_source->get_width();
+	int h = p_source->get_height();
+
+	PackedByteArray imgdata = p_source->get_data();
+	const uint8_t *r = imgdata.ptr();
+
+	PackedByteArray imgdata_r;
+	imgdata_r.resize(w * h * 2);
+	uint8_t *wr = imgdata_r.ptrw();
+
+	PackedByteArray imgdata_g;
+	imgdata_g.resize(w * h * 2);
+	uint8_t *wg = imgdata_g.ptrw();
+
+	PackedByteArray imgdata_b;
+	imgdata_b.resize(w * h * 2);
+	uint8_t *wb = imgdata_b.ptrw();
+
+	PackedByteArray imgdata_a;
+	imgdata_a.resize(w * h * 2);
+	uint8_t *wa = imgdata_a.ptrw();
+
+	PackedByteArray imgdata_ro;
+	imgdata_ro.resize(w * h * 2);
+	uint8_t *wro = imgdata_ro.ptrw();
+
+	PackedByteArray imgdata_go;
+	imgdata_go.resize(w * h * 2);
+	uint8_t *wgo = imgdata_go.ptrw();
+
+	PackedByteArray imgdata_bo;
+	imgdata_bo.resize(w * h * 2);
+	uint8_t *wbo = imgdata_bo.ptrw();
+
+	PackedByteArray imgdata_ao;
+	imgdata_ao.resize(w * h * 2);
+	uint8_t *wao = imgdata_ao.ptrw();
+
+	for (int i = 0; i < h; i++) {
+		for (int j = 0; j < w; j++) {
+			int ofs_src = (i * w + j) * 4;
+			int ofs_dst = (i * w + j) * 2;
+			wr[ofs_dst + 0] = 255;
+			wro[ofs_dst + 0] = 255;
+			if (r[ofs_src + 0] > 0x0F) {
+				wr[ofs_dst + 1] = (r[ofs_src + 0] - 0x0F) * 2;
+				wro[ofs_dst + 1] = 0;
+			} else {
+				wr[ofs_dst + 1] = 0;
+				wro[ofs_dst + 1] = r[ofs_src + 0] * 2;
+			}
+			wg[ofs_dst + 0] = 255;
+			wgo[ofs_dst + 0] = 255;
+			if (r[ofs_src + 1] > 0x0F) {
+				wg[ofs_dst + 1] = (r[ofs_src + 1] - 0x0F) * 2;
+				wgo[ofs_dst + 1] = 0;
+			} else {
+				wg[ofs_dst + 1] = 0;
+				wgo[ofs_dst + 1] = r[ofs_src + 1] * 2;
+			}
+			wb[ofs_dst + 0] = 255;
+			wbo[ofs_dst + 0] = 255;
+			if (r[ofs_src + 2] > 0x0F) {
+				wb[ofs_dst + 1] = (r[ofs_src + 2] - 0x0F) * 2;
+				wbo[ofs_dst + 1] = 0;
+			} else {
+				wb[ofs_dst + 1] = 0;
+				wbo[ofs_dst + 1] = r[ofs_src + 2] * 2;
+			}
+			wa[ofs_dst + 0] = 255;
+			wao[ofs_dst + 0] = 255;
+			if (r[ofs_src + 3] > 0x0F) {
+				wa[ofs_dst + 1] = (r[ofs_src + 3] - 0x0F) * 2;
+				wao[ofs_dst + 1] = 0;
+			} else {
+				wa[ofs_dst + 1] = 0;
+				wao[ofs_dst + 1] = r[ofs_src + 3] * 2;
+			}
+		}
+	}
+	Ref<Image> img_r = memnew(Image(w, h, 0, Image::FORMAT_LA8, imgdata_r));
+	set_texture_image(0, Vector2i(p_sz, 0), p_page * 4 + 0, img_r);
+	Ref<Image> img_g = memnew(Image(w, h, 0, Image::FORMAT_LA8, imgdata_g));
+	set_texture_image(0, Vector2i(p_sz, 0), p_page * 4 + 1, img_g);
+	Ref<Image> img_b = memnew(Image(w, h, 0, Image::FORMAT_LA8, imgdata_b));
+	set_texture_image(0, Vector2i(p_sz, 0), p_page * 4 + 2, img_b);
+	Ref<Image> img_a = memnew(Image(w, h, 0, Image::FORMAT_LA8, imgdata_a));
+	set_texture_image(0, Vector2i(p_sz, 0), p_page * 4 + 3, img_a);
+
+	Ref<Image> img_ro = memnew(Image(w, h, 0, Image::FORMAT_LA8, imgdata_ro));
+	set_texture_image(0, Vector2i(p_sz, 1), p_page * 4 + 0, img_ro);
+	Ref<Image> img_go = memnew(Image(w, h, 0, Image::FORMAT_LA8, imgdata_go));
+	set_texture_image(0, Vector2i(p_sz, 1), p_page * 4 + 1, img_go);
+	Ref<Image> img_bo = memnew(Image(w, h, 0, Image::FORMAT_LA8, imgdata_bo));
+	set_texture_image(0, Vector2i(p_sz, 1), p_page * 4 + 2, img_bo);
+	Ref<Image> img_ao = memnew(Image(w, h, 0, Image::FORMAT_LA8, imgdata_ao));
+	set_texture_image(0, Vector2i(p_sz, 1), p_page * 4 + 3, img_ao);
+}
+
+void FontData::_convert_rgba_4bit(Ref<Image> &p_source, int p_page, int p_sz) {
+	int w = p_source->get_width();
+	int h = p_source->get_height();
+
+	PackedByteArray imgdata = p_source->get_data();
+	const uint8_t *r = imgdata.ptr();
+
+	PackedByteArray imgdata_g;
+	imgdata_g.resize(w * h * 4);
+	uint8_t *wg = imgdata_g.ptrw();
+
+	PackedByteArray imgdata_o;
+	imgdata_o.resize(w * h * 4);
+	uint8_t *wo = imgdata_o.ptrw();
+
+	for (int i = 0; i < h; i++) {
+		for (int j = 0; j < w; j++) {
+			int ofs = (i * w + j) * 4;
+
+			if (r[ofs + 0] > 0x7F) {
+				wg[ofs + 0] = r[ofs + 0];
+				wo[ofs + 0] = 0;
+			} else {
+				wg[ofs + 0] = 0;
+				wo[ofs + 0] = r[ofs + 0] * 2;
+			}
+			if (r[ofs + 1] > 0x7F) {
+				wg[ofs + 1] = r[ofs + 1];
+				wo[ofs + 1] = 0;
+			} else {
+				wg[ofs + 1] = 0;
+				wo[ofs + 1] = r[ofs + 1] * 2;
+			}
+			if (r[ofs + 2] > 0x7F) {
+				wg[ofs + 2] = r[ofs + 2];
+				wo[ofs + 2] = 0;
+			} else {
+				wg[ofs + 2] = 0;
+				wo[ofs + 2] = r[ofs + 2] * 2;
+			}
+			if (r[ofs + 3] > 0x7F) {
+				wg[ofs + 3] = r[ofs + 3];
+				wo[ofs + 3] = 0;
+			} else {
+				wg[ofs + 3] = 0;
+				wo[ofs + 3] = r[ofs + 3] * 2;
+			}
+		}
+	}
+	Ref<Image> img_g = memnew(Image(w, h, 0, Image::FORMAT_RGBA8, imgdata_g));
+	set_texture_image(0, Vector2i(p_sz, 0), p_page, img_g);
+
+	Ref<Image> img_o = memnew(Image(w, h, 0, Image::FORMAT_RGBA8, imgdata_o));
+	set_texture_image(0, Vector2i(p_sz, 1), p_page, img_o);
+}
+
+void FontData::_convert_mono_8bit(Ref<Image> &p_source, int p_page, int p_ch, int p_sz, int p_ol) {
+	int w = p_source->get_width();
+	int h = p_source->get_height();
+
+	PackedByteArray imgdata = p_source->get_data();
+	const uint8_t *r = imgdata.ptr();
+
+	int size = 4;
+	if (p_source->get_format() == Image::FORMAT_L8) {
+		size = 1;
+		p_ch = 0;
+	}
+
+	PackedByteArray imgdata_g;
+	imgdata_g.resize(w * h * 2);
+	uint8_t *wg = imgdata_g.ptrw();
+
+	for (int i = 0; i < h; i++) {
+		for (int j = 0; j < w; j++) {
+			int ofs_src = (i * w + j) * size;
+			int ofs_dst = (i * w + j) * 2;
+			wg[ofs_dst + 0] = 255;
+			wg[ofs_dst + 1] = r[ofs_src + p_ch];
+		}
+	}
+	Ref<Image> img_g = memnew(Image(w, h, 0, Image::FORMAT_LA8, imgdata_g));
+	set_texture_image(0, Vector2i(p_sz, p_ol), p_page, img_g);
+}
+
+void FontData::_convert_mono_4bit(Ref<Image> &p_source, int p_page, int p_ch, int p_sz, int p_ol) {
+	int w = p_source->get_width();
+	int h = p_source->get_height();
+
+	PackedByteArray imgdata = p_source->get_data();
+	const uint8_t *r = imgdata.ptr();
+
+	int size = 4;
+	if (p_source->get_format() == Image::FORMAT_L8) {
+		size = 1;
+		p_ch = 0;
+	}
+
+	PackedByteArray imgdata_g;
+	imgdata_g.resize(w * h * 2);
+	uint8_t *wg = imgdata_g.ptrw();
+
+	PackedByteArray imgdata_o;
+	imgdata_o.resize(w * h * 2);
+	uint8_t *wo = imgdata_o.ptrw();
+
+	for (int i = 0; i < h; i++) {
+		for (int j = 0; j < w; j++) {
+			int ofs_src = (i * w + j) * size;
+			int ofs_dst = (i * w + j) * 2;
+			wg[ofs_dst + 0] = 255;
+			wo[ofs_dst + 0] = 255;
+			if (r[ofs_src + p_ch] > 0x7F) {
+				wg[ofs_dst + 1] = r[ofs_src + p_ch];
+				wo[ofs_dst + 1] = 0;
+			} else {
+				wg[ofs_dst + 1] = 0;
+				wo[ofs_dst + 1] = r[ofs_src + p_ch] * 2;
+			}
+		}
+	}
+	Ref<Image> img_g = memnew(Image(w, h, 0, Image::FORMAT_LA8, imgdata_g));
+	set_texture_image(0, Vector2i(p_sz, 0), p_page, img_g);
+
+	Ref<Image> img_o = memnew(Image(w, h, 0, Image::FORMAT_LA8, imgdata_o));
+	set_texture_image(0, Vector2i(p_sz, p_ol), p_page, img_o);
+}
+
 /*************************************************************************/
 
+Error FontData::load_bitmap_font(const String &p_path) {
+	reset_state();
+
+	antialiased = false;
+	msdf = false;
+	force_autohinter = false;
+	hinting = TextServer::HINTING_NONE;
+	oversampling = 1.0f;
+
+	FileAccessRef f = FileAccess::open(p_path, FileAccess::READ);
+	if (f == nullptr) {
+		ERR_FAIL_V_MSG(ERR_CANT_CREATE, TTR("Cannot open font from file ") + "\"" + p_path + "\".");
+	}
+
+	int base_size = 16;
+	int height = 0;
+	int ascent = 0;
+	int outline = 0;
+	uint32_t st_flags = 0;
+	String font_name;
+
+	bool packed = false;
+	uint8_t ch[4] = { 0, 0, 0, 0 }; // RGBA
+	int first_gl_ch = -1;
+	int first_ol_ch = -1;
+	int first_cm_ch = -1;
+
+	unsigned char magic[4];
+	f->get_buffer((unsigned char *)&magic, 4);
+	if (magic[0] == 'B' && magic[1] == 'M' && magic[2] == 'F') {
+		// Binary BMFont file.
+		ERR_FAIL_COND_V_MSG(magic[3] != 3, ERR_CANT_CREATE, vformat(TTR("Version %d of BMFont is not supported."), (int)magic[3]));
+
+		uint8_t block_type = f->get_8();
+		uint32_t block_size = f->get_32();
+		while (!f->eof_reached()) {
+			uint64_t off = f->get_position();
+			switch (block_type) {
+				case 1: /* info */ {
+					ERR_FAIL_COND_V_MSG(block_size < 15, ERR_CANT_CREATE, TTR("Invalid BMFont info block size."));
+					base_size = f->get_16();
+					uint8_t flags = f->get_8();
+					ERR_FAIL_COND_V_MSG(flags & 0x02, ERR_CANT_CREATE, TTR("Non-unicode version of BMFont is not supported."));
+					if (flags & (1 << 3)) {
+						st_flags |= TextServer::FONT_BOLD;
+					}
+					if (flags & (1 << 2)) {
+						st_flags |= TextServer::FONT_ITALIC;
+					}
+					f->get_8(); // non-unicode charset, skip
+					f->get_16(); // stretch_h, skip
+					f->get_8(); // aa, skip
+					f->get_32(); // padding, skip
+					f->get_16(); // spacing, skip
+					outline = f->get_8();
+					// font name
+					PackedByteArray name_data;
+					name_data.resize(block_size - 14);
+					f->get_buffer(name_data.ptrw(), block_size - 14);
+					font_name = String::utf8((const char *)name_data.ptr(), block_size - 14);
+					set_fixed_size(base_size);
+				} break;
+				case 2: /* common */ {
+					ERR_FAIL_COND_V_MSG(block_size != 15, ERR_CANT_CREATE, TTR("Invalid BMFont common block size."));
+					height = f->get_16();
+					ascent = f->get_16();
+					f->get_32(); // scale, skip
+					f->get_16(); // pages, skip
+					uint8_t flags = f->get_8();
+					packed = (flags & 0x01);
+					ch[3] = f->get_8();
+					ch[0] = f->get_8();
+					ch[1] = f->get_8();
+					ch[2] = f->get_8();
+					for (int i = 0; i < 4; i++) {
+						if (ch[i] == 0 && first_gl_ch == -1) {
+							first_gl_ch = i;
+						}
+						if (ch[i] == 1 && first_ol_ch == -1) {
+							first_ol_ch = i;
+						}
+						if (ch[i] == 2 && first_cm_ch == -1) {
+							first_cm_ch = i;
+						}
+					}
+				} break;
+				case 3: /* pages */ {
+					int page = 0;
+					CharString cs;
+					char32_t c = f->get_8();
+					while (!f->eof_reached() && f->get_position() <= off + block_size) {
+						if (c == '\0') {
+							String base_dir = p_path.get_base_dir();
+							String file = base_dir.plus_file(String::utf8(cs.ptr(), cs.length()));
+							if (RenderingServer::get_singleton() != nullptr) {
+								Ref<Image> img;
+								img.instantiate();
+								Error err = ImageLoader::load_image(file, img);
+								ERR_FAIL_COND_V_MSG(err != OK, ERR_FILE_CANT_READ, TTR("Can't load font texture: ") + "\"" + file + "\".");
+
+								if (packed) {
+									if (ch[3] == 0) { // 4 x 8 bit monochrome, no outline
+										outline = 0;
+										ERR_FAIL_COND_V_MSG(img->get_format() != Image::FORMAT_RGBA8, ERR_FILE_CANT_READ, TTR("Unsupported BMFont texture format."));
+										_convert_packed_8bit(img, page, base_size);
+									} else if ((ch[3] == 2) && (outline > 0)) { // 4 x 4 bit monochrome, gl + outline
+										ERR_FAIL_COND_V_MSG(img->get_format() != Image::FORMAT_RGBA8, ERR_FILE_CANT_READ, TTR("Unsupported BMFont texture format."));
+										_convert_packed_4bit(img, page, base_size);
+									} else {
+										ERR_FAIL_V_MSG(ERR_CANT_CREATE, TTR("Unsupported BMFont texture format."));
+									}
+								} else {
+									if ((ch[0] == 0) && (ch[1] == 0) && (ch[2] == 0) && (ch[3] == 0)) { // RGBA8 color, no outline
+										outline = 0;
+										ERR_FAIL_COND_V_MSG(img->get_format() != Image::FORMAT_RGBA8, ERR_FILE_CANT_READ, TTR("Unsupported BMFont texture format."));
+										set_texture_image(0, Vector2i(base_size, 0), page, img);
+									} else if ((ch[0] == 2) && (ch[1] == 2) && (ch[2] == 2) && (ch[3] == 2) && (outline > 0)) { // RGBA4 color, gl + outline
+										ERR_FAIL_COND_V_MSG(img->get_format() != Image::FORMAT_RGBA8, ERR_FILE_CANT_READ, TTR("Unsupported BMFont texture format."));
+										_convert_rgba_4bit(img, page, base_size);
+									} else if ((first_gl_ch >= 0) && (first_ol_ch >= 0) && (outline > 0)) { // 1 x 8 bit monochrome, gl + outline
+										ERR_FAIL_COND_V_MSG(img->get_format() != Image::FORMAT_RGBA8 && img->get_format() != Image::FORMAT_L8, ERR_FILE_CANT_READ, TTR("Unsupported BMFont texture format."));
+										_convert_mono_8bit(img, page, first_gl_ch, base_size, 0);
+										_convert_mono_8bit(img, page, first_ol_ch, base_size, 1);
+									} else if ((first_cm_ch >= 0) && (outline > 0)) { // 1 x 4 bit monochrome, gl + outline
+										ERR_FAIL_COND_V_MSG(img->get_format() != Image::FORMAT_RGBA8 && img->get_format() != Image::FORMAT_L8, ERR_FILE_CANT_READ, TTR("Unsupported BMFont texture format."));
+										_convert_mono_4bit(img, page, first_cm_ch, base_size, 1);
+									} else if (first_gl_ch >= 0) { // 1 x 8 bit monochrome, no outline
+										outline = 0;
+										ERR_FAIL_COND_V_MSG(img->get_format() != Image::FORMAT_RGBA8 && img->get_format() != Image::FORMAT_L8, ERR_FILE_CANT_READ, TTR("Unsupported BMFont texture format."));
+										_convert_mono_8bit(img, page, first_gl_ch, base_size, 0);
+									} else {
+										ERR_FAIL_V_MSG(ERR_CANT_CREATE, TTR("Unsupported BMFont texture format."));
+									}
+								}
+							}
+							page++;
+							cs = "";
+						} else {
+							cs += c;
+						}
+						c = f->get_8();
+					}
+				} break;
+				case 4: /* chars */ {
+					int char_count = block_size / 20;
+					for (int i = 0; i < char_count; i++) {
+						Vector2 advance;
+						Vector2 size;
+						Vector2 offset;
+						Rect2 uv_rect;
+
+						char32_t idx = f->get_32();
+						uv_rect.position.x = (int16_t)f->get_16();
+						uv_rect.position.y = (int16_t)f->get_16();
+						uv_rect.size.width = (int16_t)f->get_16();
+						size.width = uv_rect.size.width;
+						uv_rect.size.height = (int16_t)f->get_16();
+						size.height = uv_rect.size.height;
+						offset.x = (int16_t)f->get_16();
+						offset.y = (int16_t)f->get_16() - ascent;
+						advance.x = (int16_t)f->get_16();
+						if (advance.x < 0) {
+							advance.x = size.width + 1;
+						}
+
+						int texture_idx = f->get_8();
+						uint8_t channel = f->get_8();
+
+						ERR_FAIL_COND_V_MSG(!packed && channel != 15, ERR_CANT_CREATE, TTR("Invalid glyph channel."));
+						int ch_off = 0;
+						switch (channel) {
+							case 1:
+								ch_off = 2;
+								break; // B
+							case 2:
+								ch_off = 1;
+								break; // G
+							case 4:
+								ch_off = 0;
+								break; // R
+							case 8:
+								ch_off = 3;
+								break; // A
+							default:
+								ch_off = 0;
+								break;
+						}
+						set_glyph_advance(0, base_size, idx, advance);
+						set_glyph_offset(0, Vector2i(base_size, 0), idx, offset);
+						set_glyph_size(0, Vector2i(base_size, 0), idx, size);
+						set_glyph_uv_rect(0, Vector2i(base_size, 0), idx, uv_rect);
+						set_glyph_texture_idx(0, Vector2i(base_size, 0), idx, texture_idx * (packed ? 4 : 1) + ch_off);
+						if (outline > 0) {
+							set_glyph_offset(0, Vector2i(base_size, 1), idx, offset);
+							set_glyph_size(0, Vector2i(base_size, 1), idx, size);
+							set_glyph_uv_rect(0, Vector2i(base_size, 1), idx, uv_rect);
+							set_glyph_texture_idx(0, Vector2i(base_size, 1), idx, texture_idx * (packed ? 4 : 1) + ch_off);
+						}
+					}
+				} break;
+				case 5: /* kerning */ {
+					int pair_count = block_size / 10;
+					for (int i = 0; i < pair_count; i++) {
+						Vector2i kpk;
+						kpk.x = f->get_32();
+						kpk.y = f->get_32();
+						set_kerning(0, base_size, kpk, Vector2((int16_t)f->get_16(), 0));
+					}
+				} break;
+				default: {
+					ERR_FAIL_V_MSG(ERR_CANT_CREATE, TTR("Invalid BMFont block type."));
+				} break;
+			}
+			f->seek(off + block_size);
+			block_type = f->get_8();
+			block_size = f->get_32();
+		}
+
+	} else {
+		// Text BMFont file.
+		f->seek(0);
+		while (true) {
+			String line = f->get_line();
+
+			int delimiter = line.find(" ");
+			String type = line.substr(0, delimiter);
+			int pos = delimiter + 1;
+			Map<String, String> keys;
+
+			while (pos < line.size() && line[pos] == ' ') {
+				pos++;
+			}
+
+			while (pos < line.size()) {
+				int eq = line.find("=", pos);
+				if (eq == -1) {
+					break;
+				}
+				String key = line.substr(pos, eq - pos);
+				int end = -1;
+				String value;
+				if (line[eq + 1] == '"') {
+					end = line.find("\"", eq + 2);
+					if (end == -1) {
+						break;
+					}
+					value = line.substr(eq + 2, end - 1 - eq - 1);
+					pos = end + 1;
+				} else {
+					end = line.find(" ", eq + 1);
+					if (end == -1) {
+						end = line.size();
+					}
+					value = line.substr(eq + 1, end - eq);
+					pos = end;
+				}
+
+				while (pos < line.size() && line[pos] == ' ') {
+					pos++;
+				}
+
+				keys[key] = value;
+			}
+
+			if (type == "info") {
+				if (keys.has("size")) {
+					base_size = keys["size"].to_int();
+					set_fixed_size(base_size);
+				}
+				if (keys.has("outline")) {
+					outline = keys["outline"].to_int();
+				}
+				if (keys.has("bold")) {
+					if (keys["bold"].to_int()) {
+						st_flags |= TextServer::FONT_BOLD;
+					}
+				}
+				if (keys.has("italic")) {
+					if (keys["italic"].to_int()) {
+						st_flags |= TextServer::FONT_ITALIC;
+					}
+				}
+				if (keys.has("face")) {
+					font_name = keys["face"];
+				}
+				ERR_FAIL_COND_V_MSG((!keys.has("unicode") || keys["unicode"].to_int() != 1), ERR_CANT_CREATE, TTR("Non-unicode version of BMFont is not supported."));
+			} else if (type == "common") {
+				if (keys.has("lineHeight")) {
+					height = keys["lineHeight"].to_int();
+				}
+				if (keys.has("base")) {
+					ascent = keys["base"].to_int();
+				}
+				if (keys.has("packed")) {
+					packed = (keys["packed"].to_int() == 1);
+				}
+				if (keys.has("alphaChnl")) {
+					ch[3] = keys["alphaChnl"].to_int();
+				}
+				if (keys.has("redChnl")) {
+					ch[0] = keys["redChnl"].to_int();
+				}
+				if (keys.has("greenChnl")) {
+					ch[1] = keys["greenChnl"].to_int();
+				}
+				if (keys.has("blueChnl")) {
+					ch[2] = keys["blueChnl"].to_int();
+				}
+				for (int i = 0; i < 4; i++) {
+					if (ch[i] == 0 && first_gl_ch == -1) {
+						first_gl_ch = i;
+					}
+					if (ch[i] == 1 && first_ol_ch == -1) {
+						first_ol_ch = i;
+					}
+					if (ch[i] == 2 && first_cm_ch == -1) {
+						first_cm_ch = i;
+					}
+				}
+			} else if (type == "page") {
+				int page = 0;
+				if (keys.has("id")) {
+					page = keys["id"].to_int();
+				}
+				if (keys.has("file")) {
+					String base_dir = p_path.get_base_dir();
+					String file = base_dir.plus_file(keys["file"]);
+					if (RenderingServer::get_singleton() != nullptr) {
+						Ref<Image> img;
+						img.instantiate();
+						Error err = ImageLoader::load_image(file, img);
+						ERR_FAIL_COND_V_MSG(err != OK, ERR_FILE_CANT_READ, TTR("Can't load font texture: ") + "\"" + file + "\".");
+						if (packed) {
+							if (ch[3] == 0) { // 4 x 8 bit monochrome, no outline
+								outline = 0;
+								ERR_FAIL_COND_V_MSG(img->get_format() != Image::FORMAT_RGBA8, ERR_FILE_CANT_READ, TTR("Unsupported BMFont texture format."));
+								_convert_packed_8bit(img, page, base_size);
+							} else if ((ch[3] == 2) && (outline > 0)) { // 4 x 4 bit monochrome, gl + outline
+								ERR_FAIL_COND_V_MSG(img->get_format() != Image::FORMAT_RGBA8, ERR_FILE_CANT_READ, TTR("Unsupported BMFont texture format."));
+								_convert_packed_4bit(img, page, base_size);
+							} else {
+								ERR_FAIL_V_MSG(ERR_CANT_CREATE, TTR("Unsupported BMFont texture format."));
+							}
+						} else {
+							if ((ch[0] == 0) && (ch[1] == 0) && (ch[2] == 0) && (ch[3] == 0)) { // RGBA8 color, no outline
+								outline = 0;
+								ERR_FAIL_COND_V_MSG(img->get_format() != Image::FORMAT_RGBA8, ERR_FILE_CANT_READ, TTR("Unsupported BMFont texture format."));
+								set_texture_image(0, Vector2i(base_size, 0), page, img);
+							} else if ((ch[0] == 2) && (ch[1] == 2) && (ch[2] == 2) && (ch[3] == 2) && (outline > 0)) { // RGBA4 color, gl + outline
+								ERR_FAIL_COND_V_MSG(img->get_format() != Image::FORMAT_RGBA8, ERR_FILE_CANT_READ, TTR("Unsupported BMFont texture format."));
+								_convert_rgba_4bit(img, page, base_size);
+							} else if ((first_gl_ch >= 0) && (first_ol_ch >= 0) && (outline > 0)) { // 1 x 8 bit monochrome, gl + outline
+								ERR_FAIL_COND_V_MSG(img->get_format() != Image::FORMAT_RGBA8 && img->get_format() != Image::FORMAT_L8, ERR_FILE_CANT_READ, TTR("Unsupported BMFont texture format."));
+								_convert_mono_8bit(img, page, first_gl_ch, base_size, 0);
+								_convert_mono_8bit(img, page, first_ol_ch, base_size, 1);
+							} else if ((first_cm_ch >= 0) && (outline > 0)) { // 1 x 4 bit monochrome, gl + outline
+								ERR_FAIL_COND_V_MSG(img->get_format() != Image::FORMAT_RGBA8 && img->get_format() != Image::FORMAT_L8, ERR_FILE_CANT_READ, TTR("Unsupported BMFont texture format."));
+								_convert_mono_4bit(img, page, first_cm_ch, base_size, 1);
+							} else if (first_gl_ch >= 0) { // 1 x 8 bit monochrome, no outline
+								outline = 0;
+								ERR_FAIL_COND_V_MSG(img->get_format() != Image::FORMAT_RGBA8 && img->get_format() != Image::FORMAT_L8, ERR_FILE_CANT_READ, TTR("Unsupported BMFont texture format."));
+								_convert_mono_8bit(img, page, first_gl_ch, base_size, 0);
+							} else {
+								ERR_FAIL_V_MSG(ERR_CANT_CREATE, TTR("Unsupported BMFont texture format."));
+							}
+						}
+					}
+				}
+			} else if (type == "char") {
+				char32_t idx = 0;
+				Vector2 advance;
+				Vector2 size;
+				Vector2 offset;
+				Rect2 uv_rect;
+				int texture_idx = -1;
+				uint8_t channel = 15;
+
+				if (keys.has("id")) {
+					idx = keys["id"].to_int();
+				}
+				if (keys.has("x")) {
+					uv_rect.position.x = keys["x"].to_int();
+				}
+				if (keys.has("y")) {
+					uv_rect.position.y = keys["y"].to_int();
+				}
+				if (keys.has("width")) {
+					uv_rect.size.width = keys["width"].to_int();
+					size.width = keys["width"].to_int();
+				}
+				if (keys.has("height")) {
+					uv_rect.size.height = keys["height"].to_int();
+					size.height = keys["height"].to_int();
+				}
+				if (keys.has("xoffset")) {
+					offset.x = keys["xoffset"].to_int();
+				}
+				if (keys.has("yoffset")) {
+					offset.y = keys["yoffset"].to_int() - ascent;
+				}
+				if (keys.has("page")) {
+					texture_idx = keys["page"].to_int();
+				}
+				if (keys.has("xadvance")) {
+					advance.x = keys["xadvance"].to_int();
+				}
+				if (advance.x < 0) {
+					advance.x = size.width + 1;
+				}
+				if (keys.has("chnl")) {
+					channel = keys["chnl"].to_int();
+				}
+
+				ERR_FAIL_COND_V_MSG(!packed && channel != 15, ERR_CANT_CREATE, TTR("Invalid glyph channel."));
+				int ch_off = 0;
+				switch (channel) {
+					case 1:
+						ch_off = 2;
+						break; // B
+					case 2:
+						ch_off = 1;
+						break; // G
+					case 4:
+						ch_off = 0;
+						break; // R
+					case 8:
+						ch_off = 3;
+						break; // A
+					default:
+						ch_off = 0;
+						break;
+				}
+				set_glyph_advance(0, base_size, idx, advance);
+				set_glyph_offset(0, Vector2i(base_size, 0), idx, offset);
+				set_glyph_size(0, Vector2i(base_size, 0), idx, size);
+				set_glyph_uv_rect(0, Vector2i(base_size, 0), idx, uv_rect);
+				set_glyph_texture_idx(0, Vector2i(base_size, 0), idx, texture_idx * (packed ? 4 : 1) + ch_off);
+				if (outline > 0) {
+					set_glyph_offset(0, Vector2i(base_size, 1), idx, offset);
+					set_glyph_size(0, Vector2i(base_size, 1), idx, size);
+					set_glyph_uv_rect(0, Vector2i(base_size, 1), idx, uv_rect);
+					set_glyph_texture_idx(0, Vector2i(base_size, 1), idx, texture_idx * (packed ? 4 : 1) + ch_off);
+				}
+			} else if (type == "kerning") {
+				Vector2i kpk;
+				if (keys.has("first")) {
+					kpk.x = keys["first"].to_int();
+				}
+				if (keys.has("second")) {
+					kpk.y = keys["second"].to_int();
+				}
+				if (keys.has("amount")) {
+					set_kerning(0, base_size, kpk, Vector2(keys["amount"].to_int(), 0));
+				}
+			}
+
+			if (f->eof_reached()) {
+				break;
+			}
+		}
+	}
+
+	set_font_name(font_name);
+	set_font_style(st_flags);
+	set_ascent(0, base_size, ascent);
+	set_descent(0, base_size, height - ascent);
+
+	return OK;
+}
+
+Error FontData::load_dynamic_font(const String &p_path) {
+	reset_state();
+
+	Vector<uint8_t> data = FileAccess::get_file_as_array(p_path);
+	set_data(data);
+
+	return OK;
+}
+
 void FontData::set_data_ptr(const uint8_t *p_data, size_t p_size) {
 	data.clear();
 	data_ptr = p_data;

+ 9 - 0
scene/resources/font.h

@@ -63,6 +63,12 @@ class FontData : public Resource {
 	_FORCE_INLINE_ void _clear_cache();
 	_FORCE_INLINE_ void _ensure_rid(int p_cache_index) const;
 
+	void _convert_packed_8bit(Ref<Image> &p_source, int p_page, int p_sz);
+	void _convert_packed_4bit(Ref<Image> &p_source, int p_page, int p_sz);
+	void _convert_rgba_4bit(Ref<Image> &p_source, int p_page, int p_sz);
+	void _convert_mono_8bit(Ref<Image> &p_source, int p_page, int p_ch, int p_sz, int p_ol);
+	void _convert_mono_4bit(Ref<Image> &p_source, int p_page, int p_ch, int p_sz, int p_ol);
+
 protected:
 	static void _bind_methods();
 
@@ -73,6 +79,9 @@ protected:
 	virtual void reset_state() override;
 
 public:
+	Error load_bitmap_font(const String &p_path);
+	Error load_dynamic_font(const String &p_path);
+
 	// Font source data.
 	virtual void set_data_ptr(const uint8_t *p_data, size_t p_size);
 	virtual void set_data(const PackedByteArray &p_data);