Browse Source

Merge pull request #59013 from bruvzg/fake_bold_italics

Rémi Verschelde 3 years ago
parent
commit
e56b69269f

+ 7 - 0
doc/classes/FontData.xml

@@ -567,6 +567,9 @@
 		<member name="data" type="PackedByteArray" setter="set_data" getter="get_data" default="PackedByteArray()">
 			Contents of the dynamic font source file.
 		</member>
+		<member name="embolden" type="float" setter="set_embolden" getter="get_embolden" default="0.0">
+			If is not equal to zero, emboldens the font outlines. Negative values reduce the outline thickness.
+		</member>
 		<member name="fixed_size" type="int" setter="set_fixed_size" getter="get_fixed_size" default="0">
 			Font size, used only for the bitmap fonts.
 		</member>
@@ -603,5 +606,9 @@
 		<member name="subpixel_positioning" type="int" setter="set_subpixel_positioning" getter="get_subpixel_positioning" enum="TextServer.SubpixelPositioning" default="1">
 			Font glyph sub-pixel positioning mode. Subpixel positioning provides shaper text and better kerning for smaller font sizes, at the cost of memory usage and font rasterization speed. Use [constant TextServer.SUBPIXEL_POSITIONING_AUTO] to automatically enable it based on the font size.
 		</member>
+		<member name="transform" type="Transform2D" setter="set_transform" getter="get_transform" default="Transform2D(1, 0, 0, 1, 0, 0)">
+			2D transform, applied to the font outlines, can be used for slanting, flipping and rotating glyphs.
+			For example, to simulate italic typeface by slanting, apply the following transform [code]Transform2D(1.0, slant, 0.0, 1.0, 0.0, 0.0)[/code].
+		</member>
 	</members>
 </class>

+ 31 - 0
doc/classes/TextServer.xml

@@ -112,6 +112,13 @@
 				Returns the font descent (number of pixels below the baseline).
 			</description>
 		</method>
+		<method name="font_get_embolden" qualifiers="const">
+			<return type="float" />
+			<argument index="0" name="font_rid" type="RID" />
+			<description>
+				Returns font embolden strength.
+			</description>
+		</method>
 		<method name="font_get_fixed_size" qualifiers="const">
 			<return type="int" />
 			<argument index="0" name="font_rid" type="RID" />
@@ -368,6 +375,13 @@
 				Returns array containing the first free pixel in the each column of texture. Should be the same size as texture width or empty.
 			</description>
 		</method>
+		<method name="font_get_transform" qualifiers="const">
+			<return type="Transform2D" />
+			<argument index="0" name="font_rid" type="RID" />
+			<description>
+				Returns 2D transform applied to the font outlines.
+			</description>
+		</method>
 		<method name="font_get_underline_position" qualifiers="const">
 			<return type="float" />
 			<argument index="0" name="font_rid" type="RID" />
@@ -542,6 +556,14 @@
 				Sets the font descent (number of pixels below the baseline).
 			</description>
 		</method>
+		<method name="font_set_embolden">
+			<return type="void" />
+			<argument index="0" name="font_rid" type="RID" />
+			<argument index="1" name="strength" type="float" />
+			<description>
+				Sets font embolden strength. If [code]strength[/code] is not equal to zero, emboldens the font outlines. Negative values reduce the outline thickness.
+			</description>
+		</method>
 		<method name="font_set_fixed_size">
 			<return type="void" />
 			<argument index="0" name="font_rid" type="RID" />
@@ -765,6 +787,15 @@
 				Sets array containing the first free pixel in the each column of texture. Should be the same size as texture width or empty.
 			</description>
 		</method>
+		<method name="font_set_transform">
+			<return type="void" />
+			<argument index="0" name="font_rid" type="RID" />
+			<argument index="1" name="transform" type="Transform2D" />
+			<description>
+				Sets 2D transform, applied to the font outlines, can be used for slanting, flipping and rotating glyphs.
+				For example, to simulate italic typeface by slanting, apply the following transform [code]Transform2D(1.0, slant, 0.0, 1.0, 0.0, 0.0)[/code].
+			</description>
+		</method>
 		<method name="font_set_underline_position">
 			<return type="void" />
 			<argument index="0" name="font_rid" type="RID" />

+ 31 - 0
doc/classes/TextServerExtension.xml

@@ -112,6 +112,13 @@
 				Returns the font descent (number of pixels below the baseline).
 			</description>
 		</method>
+		<method name="_font_get_embolden" qualifiers="virtual const">
+			<return type="float" />
+			<argument index="0" name="font_rid" type="RID" />
+			<description>
+				Returns font embolden strength.
+			</description>
+		</method>
 		<method name="_font_get_fixed_size" qualifiers="virtual const">
 			<return type="int" />
 			<argument index="0" name="font_rid" type="RID" />
@@ -368,6 +375,13 @@
 				Returns array containing the first free pixel in the each column of texture. Should be the same size as texture width or empty.
 			</description>
 		</method>
+		<method name="_font_get_transform" qualifiers="virtual const">
+			<return type="Transform2D" />
+			<argument index="0" name="font_rid" type="RID" />
+			<description>
+				Retruns 2D transform applied to the font outlines.
+			</description>
+		</method>
 		<method name="_font_get_underline_position" qualifiers="virtual const">
 			<return type="float" />
 			<argument index="0" name="font_rid" type="RID" />
@@ -551,6 +565,14 @@
 				Sets bitmap font fixed size. If set to value greater than zero, same cache entry will be used for all font sizes.
 			</description>
 		</method>
+		<method name="_font_set_embolden" qualifiers="virtual">
+			<return type="void" />
+			<argument index="0" name="font_rid" type="RID" />
+			<argument index="1" name="strength" type="float" />
+			<description>
+				Sets font embolden strength. If [code]strength[/code] is not equal to zero, emboldens the font outlines. Negative values reduce the outline thickness.
+			</description>
+		</method>
 		<method name="_font_set_fixed_size" qualifiers="virtual">
 			<return type="void" />
 			<argument index="0" name="font_rid" type="RID" />
@@ -773,6 +795,15 @@
 				Sets array containing the first free pixel in the each column of texture. Should be the same size as texture width or empty.
 			</description>
 		</method>
+		<method name="_font_set_transform" qualifiers="virtual">
+			<return type="void" />
+			<argument index="0" name="font_rid" type="RID" />
+			<argument index="1" name="transform" type="Transform2D" />
+			<description>
+				Sets 2D transform, applied to the font outlines, can be used for slanting, flipping and rotating glyphs.
+				For example, to simulate italic typeface by slanting, apply the following transform [code]Transform2D(1.0, slant, 0.0, 1.0, 0.0, 0.0)[/code].
+			</description>
+		</method>
 		<method name="_font_set_underline_position" qualifiers="virtual">
 			<return type="void" />
 			<argument index="0" name="font_rid" type="RID" />

+ 14 - 0
editor/import/dynamic_font_import_settings.cpp

@@ -484,6 +484,14 @@ void DynamicFontImportSettings::_main_prop_changed(const String &p_edited_proper
 		if (font_preview->get_data_count() > 0) {
 			font_preview->get_data(0)->set_subpixel_positioning((TextServer::SubpixelPositioning)import_settings_data->get("subpixel_positioning").operator int());
 		}
+	} else if (p_edited_property == "embolden") {
+		if (font_preview->get_data_count() > 0) {
+			font_preview->get_data(0)->set_embolden(import_settings_data->get("embolden"));
+		}
+	} else if (p_edited_property == "transform") {
+		if (font_preview->get_data_count() > 0) {
+			font_preview->get_data(0)->set_transform(import_settings_data->get("transform"));
+		}
 	} else if (p_edited_property == "oversampling") {
 		if (font_preview->get_data_count() > 0) {
 			font_preview->get_data(0)->set_oversampling(import_settings_data->get("oversampling"));
@@ -924,6 +932,8 @@ void DynamicFontImportSettings::_re_import() {
 	main_settings["force_autohinter"] = import_settings_data->get("force_autohinter");
 	main_settings["hinting"] = import_settings_data->get("hinting");
 	main_settings["subpixel_positioning"] = import_settings_data->get("subpixel_positioning");
+	main_settings["embolden"] = import_settings_data->get("embolden");
+	main_settings["transform"] = import_settings_data->get("transform");
 	main_settings["oversampling"] = import_settings_data->get("oversampling");
 	main_settings["compress"] = import_settings_data->get("compress");
 
@@ -1275,6 +1285,8 @@ void DynamicFontImportSettings::open_settings(const String &p_path) {
 		font_preview->get_data(0)->set_force_autohinter(import_settings_data->get("force_autohinter"));
 		font_preview->get_data(0)->set_hinting((TextServer::Hinting)import_settings_data->get("hinting").operator int());
 		font_preview->get_data(0)->set_subpixel_positioning((TextServer::SubpixelPositioning)import_settings_data->get("subpixel_positioning").operator int());
+		font_preview->get_data(0)->set_embolden(import_settings_data->get("embolden"));
+		font_preview->get_data(0)->set_transform(import_settings_data->get("transform"));
 		font_preview->get_data(0)->set_oversampling(import_settings_data->get("oversampling"));
 	}
 	font_preview_label->add_theme_font_override("font", font_preview);
@@ -1334,6 +1346,8 @@ DynamicFontImportSettings::DynamicFontImportSettings() {
 	options_general.push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::BOOL, "force_autohinter"), false));
 	options_general.push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::INT, "hinting", PROPERTY_HINT_ENUM, "None,Light,Normal"), 1));
 	options_general.push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::INT, "subpixel_positioning", PROPERTY_HINT_ENUM, "Disabled,Auto,One half of a pixel,One quarter of a pixel"), 1));
+	options_general.push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::FLOAT, "embolden", PROPERTY_HINT_RANGE, "-2,2,0.01"), 0.f));
+	options_general.push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::TRANSFORM2D, "transform"), Transform2D()));
 	options_general.push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::FLOAT, "oversampling", PROPERTY_HINT_RANGE, "0,10,0.1"), 0.0));
 	options_general.push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::BOOL, "compress", PROPERTY_HINT_NONE, ""), false));
 

+ 6 - 0
editor/import/resource_importer_dynamic_font.cpp

@@ -109,6 +109,8 @@ void ResourceImporterDynamicFont::get_import_options(const String &p_path, List<
 	r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "force_autohinter"), false));
 	r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "hinting", PROPERTY_HINT_ENUM, "None,Light,Normal"), 1));
 	r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "subpixel_positioning", PROPERTY_HINT_ENUM, "Disabled,Auto,One half of a pixel,One quarter of a pixel"), 1));
+	r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "embolden", PROPERTY_HINT_RANGE, "-2,2,0.01"), 0.f));
+	r_options->push_back(ImportOption(PropertyInfo(Variant::TRANSFORM2D, "transform"), Transform2D()));
 	r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "oversampling", PROPERTY_HINT_RANGE, "0,10,0.1"), 0.0));
 
 	r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "compress"), true));
@@ -186,6 +188,8 @@ Error ResourceImporterDynamicFont::import(const String &p_source_file, const Str
 	int hinting = p_options["hinting"];
 	int subpixel_positioning = p_options["subpixel_positioning"];
 	real_t oversampling = p_options["oversampling"];
+	real_t embolden = p_options["embolden"];
+	Transform2D transform = p_options["transform"];
 
 	// Load base font data.
 	Vector<uint8_t> data = FileAccess::get_file_as_array(p_source_file);
@@ -202,6 +206,8 @@ Error ResourceImporterDynamicFont::import(const String &p_source_file, const Str
 	font->set_fixed_size(0);
 	font->set_force_autohinter(autohinter);
 	font->set_subpixel_positioning((TextServer::SubpixelPositioning)subpixel_positioning);
+	font->set_embolden(embolden);
+	font->set_transform(transform);
 	font->set_hinting((TextServer::Hinting)hinting);
 	font->set_oversampling(oversampling);
 

+ 60 - 9
modules/text_server_adv/text_server_adv.cpp

@@ -704,7 +704,7 @@ String TextServerAdvanced::tag_to_name(int32_t p_tag) const {
 /* Font Glyph Rendering                                                  */
 /*************************************************************************/
 
-_FORCE_INLINE_ TextServerAdvanced::FontTexturePosition TextServerAdvanced::find_texture_pos_for_glyph(FontDataForSizeAdvanced *p_data, int p_color_size, Image::Format p_image_format, int p_width, int p_height) const {
+_FORCE_INLINE_ TextServerAdvanced::FontTexturePosition TextServerAdvanced::find_texture_pos_for_glyph(FontDataForSizeAdvanced *p_data, int p_color_size, Image::Format p_image_format, int p_width, int p_height, bool p_msdf) const {
 	FontTexturePosition ret;
 	ret.index = -1;
 
@@ -769,8 +769,11 @@ _FORCE_INLINE_ TextServerAdvanced::FontTexturePosition TextServerAdvanced::find_
 		}
 
 		texsize = next_power_of_2(texsize);
-
-		texsize = MIN(texsize, 4096);
+		if (p_msdf) {
+			texsize = MIN(texsize, 2048);
+		} else {
+			texsize = MIN(texsize, 1024);
+		}
 
 		FontTexture tex;
 		tex.texture_w = texsize;
@@ -935,10 +938,10 @@ _FORCE_INLINE_ TextServerAdvanced::FontGlyph TextServerAdvanced::rasterize_msdf(
 		int mw = w + p_rect_margin * 2;
 		int mh = h + p_rect_margin * 2;
 
-		ERR_FAIL_COND_V(mw > 4096, FontGlyph());
-		ERR_FAIL_COND_V(mh > 4096, FontGlyph());
+		ERR_FAIL_COND_V(mw > 1024, FontGlyph());
+		ERR_FAIL_COND_V(mh > 1024, FontGlyph());
 
-		FontTexturePosition tex_pos = find_texture_pos_for_glyph(p_data, 4, Image::FORMAT_RGBA8, mw, mh);
+		FontTexturePosition tex_pos = find_texture_pos_for_glyph(p_data, 4, Image::FORMAT_RGBA8, mw, mh, true);
 		ERR_FAIL_COND_V(tex_pos.index < 0, FontGlyph());
 		FontTexture &tex = p_data->textures.write[tex_pos.index];
 
@@ -1013,13 +1016,13 @@ _FORCE_INLINE_ TextServerAdvanced::FontGlyph TextServerAdvanced::rasterize_bitma
 	int mw = w + p_rect_margin * 2;
 	int mh = h + p_rect_margin * 2;
 
-	ERR_FAIL_COND_V(mw > 4096, FontGlyph());
-	ERR_FAIL_COND_V(mh > 4096, FontGlyph());
+	ERR_FAIL_COND_V(mw > 1024, FontGlyph());
+	ERR_FAIL_COND_V(mh > 1024, FontGlyph());
 
 	int color_size = bitmap.pixel_mode == FT_PIXEL_MODE_BGRA ? 4 : 2;
 	Image::Format require_format = color_size == 4 ? Image::FORMAT_RGBA8 : Image::FORMAT_LA8;
 
-	FontTexturePosition tex_pos = find_texture_pos_for_glyph(p_data, color_size, require_format, mw, mh);
+	FontTexturePosition tex_pos = find_texture_pos_for_glyph(p_data, color_size, require_format, mw, mh, false);
 	ERR_FAIL_COND_V(tex_pos.index < 0, FontGlyph());
 
 	// Fit character in char texture.
@@ -1155,6 +1158,16 @@ _FORCE_INLINE_ bool TextServerAdvanced::_ensure_glyph(FontDataAdvanced *p_font_d
 			}
 		}
 
+		if (p_font_data->embolden != 0.f) {
+			FT_Pos strength = p_font_data->embolden * p_size.x * 4; // 26.6 fractional units (1 / 64).
+			FT_Outline_Embolden(&fd->face->glyph->outline, strength);
+		}
+
+		if (p_font_data->transform != Transform2D()) {
+			FT_Matrix mat = { FT_Fixed(p_font_data->transform[0][0] * 65536), FT_Fixed(p_font_data->transform[0][1] * 65536), FT_Fixed(p_font_data->transform[1][0] * 65536), FT_Fixed(p_font_data->transform[1][1] * 65536) }; // 16.16 fractional units (1 / 65536).
+			FT_Outline_Transform(&fd->face->glyph->outline, &mat);
+		}
+
 		if (!outline) {
 			if (!p_font_data->msdf) {
 				error = FT_Render_Glyph(fd->face->glyph, p_font_data->antialiased ? FT_RENDER_MODE_NORMAL : FT_RENDER_MODE_MONO);
@@ -1874,6 +1887,44 @@ TextServer::SubpixelPositioning TextServerAdvanced::font_get_subpixel_positionin
 	return fd->subpixel_positioning;
 }
 
+void TextServerAdvanced::font_set_embolden(RID p_font_rid, float p_strength) {
+	FontDataAdvanced *fd = font_owner.get_or_null(p_font_rid);
+	ERR_FAIL_COND(!fd);
+
+	MutexLock lock(fd->mutex);
+	if (fd->embolden != p_strength) {
+		_font_clear_cache(fd);
+		fd->embolden = p_strength;
+	}
+}
+
+float TextServerAdvanced::font_get_embolden(RID p_font_rid) const {
+	FontDataAdvanced *fd = font_owner.get_or_null(p_font_rid);
+	ERR_FAIL_COND_V(!fd, 0.f);
+
+	MutexLock lock(fd->mutex);
+	return fd->embolden;
+}
+
+void TextServerAdvanced::font_set_transform(RID p_font_rid, Transform2D p_transform) {
+	FontDataAdvanced *fd = font_owner.get_or_null(p_font_rid);
+	ERR_FAIL_COND(!fd);
+
+	MutexLock lock(fd->mutex);
+	if (fd->transform != p_transform) {
+		_font_clear_cache(fd);
+		fd->transform = p_transform;
+	}
+}
+
+Transform2D TextServerAdvanced::font_get_transform(RID p_font_rid) const {
+	FontDataAdvanced *fd = font_owner.get_or_null(p_font_rid);
+	ERR_FAIL_COND_V(!fd, Transform2D());
+
+	MutexLock lock(fd->mutex);
+	return fd->transform;
+}
+
 void TextServerAdvanced::font_set_variation_coordinates(RID p_font_rid, const Dictionary &p_variation_coordinates) {
 	FontDataAdvanced *fd = font_owner.get_or_null(p_font_rid);
 	ERR_FAIL_COND(!fd);

+ 9 - 1
modules/text_server_adv/text_server_adv.h

@@ -177,6 +177,8 @@ class TextServerAdvanced : public TextServer {
 		TextServer::SubpixelPositioning subpixel_positioning = TextServer::SUBPIXEL_POSITIONING_AUTO;
 		Dictionary variation_coordinates;
 		float oversampling = 0.f;
+		float embolden = 0.f;
+		Transform2D transform;
 
 		uint32_t style_flags = 0;
 		String font_name;
@@ -208,7 +210,7 @@ class TextServerAdvanced : public TextServer {
 		}
 	};
 
-	_FORCE_INLINE_ FontTexturePosition find_texture_pos_for_glyph(FontDataForSizeAdvanced *p_data, int p_color_size, Image::Format p_image_format, int p_width, int p_height) const;
+	_FORCE_INLINE_ FontTexturePosition find_texture_pos_for_glyph(FontDataForSizeAdvanced *p_data, int p_color_size, Image::Format p_image_format, int p_width, int p_height, bool p_msdf) const;
 #ifdef MODULE_MSDFGEN_ENABLED
 	_FORCE_INLINE_ FontGlyph rasterize_msdf(FontDataAdvanced *p_font_data, FontDataForSizeAdvanced *p_data, int p_pixel_range, int p_rect_margin, FT_Outline *outline, const Vector2 &advance) const;
 #endif
@@ -383,6 +385,12 @@ public:
 	virtual void font_set_subpixel_positioning(RID p_font_rid, SubpixelPositioning p_subpixel) override;
 	virtual SubpixelPositioning font_get_subpixel_positioning(RID p_font_rid) const override;
 
+	virtual void font_set_embolden(RID p_font_rid, float p_strength) override;
+	virtual float font_get_embolden(RID p_font_rid) const override;
+
+	virtual void font_set_transform(RID p_font_rid, Transform2D p_transform) override;
+	virtual Transform2D font_get_transform(RID p_font_rid) const override;
+
 	virtual void font_set_variation_coordinates(RID p_font_rid, const Dictionary &p_variation_coordinates) override;
 	virtual Dictionary font_get_variation_coordinates(RID p_font_rid) const override;
 

+ 62 - 8
modules/text_server_fb/text_server_fb.cpp

@@ -155,7 +155,7 @@ String TextServerFallback::tag_to_name(int32_t p_tag) const {
 /* Font Glyph Rendering                                                  */
 /*************************************************************************/
 
-_FORCE_INLINE_ TextServerFallback::FontTexturePosition TextServerFallback::find_texture_pos_for_glyph(FontDataForSizeFallback *p_data, int p_color_size, Image::Format p_image_format, int p_width, int p_height) const {
+_FORCE_INLINE_ TextServerFallback::FontTexturePosition TextServerFallback::find_texture_pos_for_glyph(FontDataForSizeFallback *p_data, int p_color_size, Image::Format p_image_format, int p_width, int p_height, bool p_msdf) const {
 	FontTexturePosition ret;
 	ret.index = -1;
 
@@ -221,7 +221,11 @@ _FORCE_INLINE_ TextServerFallback::FontTexturePosition TextServerFallback::find_
 
 		texsize = next_power_of_2(texsize);
 
-		texsize = MIN(texsize, 4096);
+		if (p_msdf) {
+			texsize = MIN(texsize, 2048);
+		} else {
+			texsize = MIN(texsize, 1024);
+		}
 
 		FontTexture tex;
 		tex.texture_w = texsize;
@@ -386,10 +390,10 @@ _FORCE_INLINE_ TextServerFallback::FontGlyph TextServerFallback::rasterize_msdf(
 		int mw = w + p_rect_margin * 2;
 		int mh = h + p_rect_margin * 2;
 
-		ERR_FAIL_COND_V(mw > 4096, FontGlyph());
-		ERR_FAIL_COND_V(mh > 4096, FontGlyph());
+		ERR_FAIL_COND_V(mw > 1024, FontGlyph());
+		ERR_FAIL_COND_V(mh > 1024, FontGlyph());
 
-		FontTexturePosition tex_pos = find_texture_pos_for_glyph(p_data, 4, Image::FORMAT_RGBA8, mw, mh);
+		FontTexturePosition tex_pos = find_texture_pos_for_glyph(p_data, 4, Image::FORMAT_RGBA8, mw, mh, true);
 		ERR_FAIL_COND_V(tex_pos.index < 0, FontGlyph());
 		FontTexture &tex = p_data->textures.write[tex_pos.index];
 
@@ -464,13 +468,13 @@ _FORCE_INLINE_ TextServerFallback::FontGlyph TextServerFallback::rasterize_bitma
 	int mw = w + p_rect_margin * 2;
 	int mh = h + p_rect_margin * 2;
 
-	ERR_FAIL_COND_V(mw > 4096, FontGlyph());
-	ERR_FAIL_COND_V(mh > 4096, FontGlyph());
+	ERR_FAIL_COND_V(mw > 1024, FontGlyph());
+	ERR_FAIL_COND_V(mh > 1024, FontGlyph());
 
 	int color_size = bitmap.pixel_mode == FT_PIXEL_MODE_BGRA ? 4 : 2;
 	Image::Format require_format = color_size == 4 ? Image::FORMAT_RGBA8 : Image::FORMAT_LA8;
 
-	FontTexturePosition tex_pos = find_texture_pos_for_glyph(p_data, color_size, require_format, mw, mh);
+	FontTexturePosition tex_pos = find_texture_pos_for_glyph(p_data, color_size, require_format, mw, mh, false);
 	ERR_FAIL_COND_V(tex_pos.index < 0, FontGlyph());
 
 	// Fit character in char texture.
@@ -586,6 +590,8 @@ _FORCE_INLINE_ bool TextServerFallback::_ensure_glyph(FontDataFallback *p_font_d
 			flags |= FT_LOAD_COLOR;
 		}
 
+		glyph_index = FT_Get_Char_Index(fd->face, glyph_index);
+
 		FT_Fixed v, h;
 		FT_Get_Advance(fd->face, glyph_index, flags, &h);
 		FT_Get_Advance(fd->face, glyph_index, flags | FT_LOAD_VERTICAL_LAYOUT, &v);
@@ -606,6 +612,16 @@ _FORCE_INLINE_ bool TextServerFallback::_ensure_glyph(FontDataFallback *p_font_d
 			}
 		}
 
+		if (p_font_data->embolden != 0.f) {
+			FT_Pos strength = p_font_data->embolden * p_size.x * 4; // 26.6 fractional units (1 / 64).
+			FT_Outline_Embolden(&fd->face->glyph->outline, strength);
+		}
+
+		if (p_font_data->transform != Transform2D()) {
+			FT_Matrix mat = { FT_Fixed(p_font_data->transform[0][0] * 65536), FT_Fixed(p_font_data->transform[0][1] * 65536), FT_Fixed(p_font_data->transform[1][0] * 65536), FT_Fixed(p_font_data->transform[1][1] * 65536) }; // 16.16 fractional units (1 / 65536).
+			FT_Outline_Transform(&fd->face->glyph->outline, &mat);
+		}
+
 		if (!outline) {
 			if (!p_font_data->msdf) {
 				error = FT_Render_Glyph(fd->face->glyph, p_font_data->antialiased ? FT_RENDER_MODE_NORMAL : FT_RENDER_MODE_MONO);
@@ -1044,6 +1060,44 @@ TextServer::SubpixelPositioning TextServerFallback::font_get_subpixel_positionin
 	return fd->subpixel_positioning;
 }
 
+void TextServerFallback::font_set_embolden(RID p_font_rid, float p_strength) {
+	FontDataFallback *fd = font_owner.get_or_null(p_font_rid);
+	ERR_FAIL_COND(!fd);
+
+	MutexLock lock(fd->mutex);
+	if (fd->embolden != p_strength) {
+		_font_clear_cache(fd);
+		fd->embolden = p_strength;
+	}
+}
+
+float TextServerFallback::font_get_embolden(RID p_font_rid) const {
+	FontDataFallback *fd = font_owner.get_or_null(p_font_rid);
+	ERR_FAIL_COND_V(!fd, 0.f);
+
+	MutexLock lock(fd->mutex);
+	return fd->embolden;
+}
+
+void TextServerFallback::font_set_transform(RID p_font_rid, Transform2D p_transform) {
+	FontDataFallback *fd = font_owner.get_or_null(p_font_rid);
+	ERR_FAIL_COND(!fd);
+
+	MutexLock lock(fd->mutex);
+	if (fd->transform != p_transform) {
+		_font_clear_cache(fd);
+		fd->transform = p_transform;
+	}
+}
+
+Transform2D TextServerFallback::font_get_transform(RID p_font_rid) const {
+	FontDataFallback *fd = font_owner.get_or_null(p_font_rid);
+	ERR_FAIL_COND_V(!fd, Transform2D());
+
+	MutexLock lock(fd->mutex);
+	return fd->transform;
+}
+
 void TextServerFallback::font_set_variation_coordinates(RID p_font_rid, const Dictionary &p_variation_coordinates) {
 	FontDataFallback *fd = font_owner.get_or_null(p_font_rid);
 	ERR_FAIL_COND(!fd);

+ 9 - 1
modules/text_server_fb/text_server_fb.h

@@ -142,6 +142,8 @@ class TextServerFallback : public TextServer {
 		TextServer::SubpixelPositioning subpixel_positioning = TextServer::SUBPIXEL_POSITIONING_AUTO;
 		Dictionary variation_coordinates;
 		float oversampling = 0.f;
+		float embolden = 0.f;
+		Transform2D transform;
 
 		uint32_t style_flags = 0;
 		String font_name;
@@ -172,7 +174,7 @@ class TextServerFallback : public TextServer {
 		}
 	};
 
-	_FORCE_INLINE_ FontTexturePosition find_texture_pos_for_glyph(FontDataForSizeFallback *p_data, int p_color_size, Image::Format p_image_format, int p_width, int p_height) const;
+	_FORCE_INLINE_ FontTexturePosition find_texture_pos_for_glyph(FontDataForSizeFallback *p_data, int p_color_size, Image::Format p_image_format, int p_width, int p_height, bool p_msdf) const;
 #ifdef MODULE_MSDFGEN_ENABLED
 	_FORCE_INLINE_ FontGlyph rasterize_msdf(FontDataFallback *p_font_data, FontDataForSizeFallback *p_data, int p_pixel_range, int p_rect_margin, FT_Outline *outline, const Vector2 &advance) const;
 #endif
@@ -294,6 +296,12 @@ public:
 	virtual void font_set_subpixel_positioning(RID p_font_rid, SubpixelPositioning p_subpixel) override;
 	virtual SubpixelPositioning font_get_subpixel_positioning(RID p_font_rid) const override;
 
+	virtual void font_set_embolden(RID p_font_rid, float p_strength) override;
+	virtual float font_get_embolden(RID p_font_rid) const override;
+
+	virtual void font_set_transform(RID p_font_rid, Transform2D p_transform) override;
+	virtual Transform2D font_get_transform(RID p_font_rid) const override;
+
 	virtual void font_set_variation_coordinates(RID p_font_rid, const Dictionary &p_variation_coordinates) override;
 	virtual Dictionary font_get_variation_coordinates(RID p_font_rid) const override;
 

+ 32 - 5
scene/resources/default_theme/default_theme.cpp

@@ -103,7 +103,7 @@ static Ref<StyleBox> make_empty_stylebox(float p_margin_left = -1, float p_margi
 	return style;
 }
 
-void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, Ref<Texture2D> &default_icon, Ref<StyleBox> &default_style, float p_scale) {
+void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const Ref<Font> &bold_font, const Ref<Font> &bold_italics_font, const Ref<Font> &italics_font, Ref<Texture2D> &default_icon, Ref<StyleBox> &default_style, float p_scale) {
 	scale = p_scale;
 
 	// Font colors
@@ -924,9 +924,9 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, Ref<Te
 	theme->set_stylebox("normal", "RichTextLabel", make_empty_stylebox(0, 0, 0, 0));
 
 	theme->set_font("normal_font", "RichTextLabel", Ref<Font>());
-	theme->set_font("bold_font", "RichTextLabel", Ref<Font>());
-	theme->set_font("italics_font", "RichTextLabel", Ref<Font>());
-	theme->set_font("bold_italics_font", "RichTextLabel", Ref<Font>());
+	theme->set_font("bold_font", "RichTextLabel", bold_font);
+	theme->set_font("italics_font", "RichTextLabel", italics_font);
+	theme->set_font("bold_italics_font", "RichTextLabel", bold_italics_font);
 	theme->set_font("mono_font", "RichTextLabel", Ref<Font>());
 
 	theme->set_font_size("normal_font_size", "RichTextLabel", -1);
@@ -1025,6 +1025,9 @@ void make_default_theme(float p_scale, Ref<Font> p_font, TextServer::SubpixelPos
 	Ref<StyleBox> default_style;
 	Ref<Texture2D> default_icon;
 	Ref<Font> default_font;
+	Ref<Font> bold_font;
+	Ref<Font> bold_italics_font;
+	Ref<Font> italics_font;
 	float default_scale = CLAMP(p_scale, 0.5, 8.0);
 
 	if (p_font.is_valid()) {
@@ -1048,7 +1051,31 @@ void make_default_theme(float p_scale, Ref<Font> p_font, TextServer::SubpixelPos
 		default_font = dynamic_font;
 	}
 
-	fill_default_theme(t, default_font, default_icon, default_style, default_scale);
+	if (default_font.is_valid()) {
+		bold_font.instantiate();
+		for (int i = 0; i < default_font->get_data_count(); i++) {
+			Ref<FontData> data = default_font->get_data(i)->duplicate();
+			data->set_embolden(1.2);
+			bold_font->add_data(data);
+		}
+
+		bold_italics_font.instantiate();
+		for (int i = 0; i < default_font->get_data_count(); i++) {
+			Ref<FontData> data = default_font->get_data(i)->duplicate();
+			data->set_embolden(1.2);
+			data->set_transform(Transform2D(1.0, 0.4, 0.0, 1.0, 0.0, 0.0));
+			bold_italics_font->add_data(data);
+		}
+
+		italics_font.instantiate();
+		for (int i = 0; i < default_font->get_data_count(); i++) {
+			Ref<FontData> data = default_font->get_data(i)->duplicate();
+			data->set_transform(Transform2D(1.0, 0.4, 0.0, 1.0, 0.0, 0.0));
+			italics_font->add_data(data);
+		}
+	}
+
+	fill_default_theme(t, default_font, bold_font, bold_italics_font, italics_font, default_icon, default_style, default_scale);
 
 	Theme::set_default(t);
 	Theme::set_fallback_base_scale(default_scale);

+ 1 - 1
scene/resources/default_theme/default_theme.h

@@ -35,7 +35,7 @@
 
 const int default_font_size = 16;
 
-void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, Ref<Texture2D> &default_icon, Ref<StyleBox> &default_style, float p_scale);
+void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const Ref<Font> &bold_font, const Ref<Font> &bold_italics_font, const Ref<Font> &italics_font, Ref<Texture2D> &default_icon, Ref<StyleBox> &default_style, float p_scale);
 void make_default_theme(float p_scale, Ref<Font> p_font, TextServer::SubpixelPositioning p_subpixel, TextServer::Hinting p_hinting, bool p_aa);
 void clear_default_theme();
 

+ 42 - 0
scene/resources/font.cpp

@@ -61,6 +61,8 @@ _FORCE_INLINE_ void FontData::_ensure_rid(int p_cache_index) const {
 		TS->font_set_force_autohinter(cache[p_cache_index], force_autohinter);
 		TS->font_set_hinting(cache[p_cache_index], hinting);
 		TS->font_set_subpixel_positioning(cache[p_cache_index], subpixel_positioning);
+		TS->font_set_embolden(cache[p_cache_index], embolden);
+		TS->font_set_transform(cache[p_cache_index], transform);
 		TS->font_set_oversampling(cache[p_cache_index], oversampling);
 	}
 }
@@ -105,6 +107,12 @@ void FontData::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("set_subpixel_positioning", "subpixel_positioning"), &FontData::set_subpixel_positioning);
 	ClassDB::bind_method(D_METHOD("get_subpixel_positioning"), &FontData::get_subpixel_positioning);
 
+	ClassDB::bind_method(D_METHOD("set_embolden", "strength"), &FontData::set_embolden);
+	ClassDB::bind_method(D_METHOD("get_embolden"), &FontData::get_embolden);
+
+	ClassDB::bind_method(D_METHOD("set_transform", "transform"), &FontData::set_transform);
+	ClassDB::bind_method(D_METHOD("get_transform"), &FontData::get_transform);
+
 	ClassDB::bind_method(D_METHOD("set_oversampling", "oversampling"), &FontData::set_oversampling);
 	ClassDB::bind_method(D_METHOD("get_oversampling"), &FontData::get_oversampling);
 
@@ -209,6 +217,8 @@ void FontData::_bind_methods() {
 	ADD_PROPERTY(PropertyInfo(Variant::STRING, "style_name", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE), "set_font_style_name", "get_font_style_name");
 	ADD_PROPERTY(PropertyInfo(Variant::INT, "font_style", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE), "set_font_style", "get_font_style");
 	ADD_PROPERTY(PropertyInfo(Variant::INT, "subpixel_positioning", PROPERTY_HINT_ENUM, "Disabled,Auto,One half of a pixel,One quarter of a pixel", PROPERTY_USAGE_STORAGE), "set_subpixel_positioning", "get_subpixel_positioning");
+	ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "embolden", PROPERTY_HINT_RANGE, "-2,2,0.01", PROPERTY_USAGE_STORAGE), "set_embolden", "get_embolden");
+	ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM2D, "transform", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE), "set_transform", "get_transform");
 	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "multichannel_signed_distance_field", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE), "set_multichannel_signed_distance_field", "is_multichannel_signed_distance_field");
 	ADD_PROPERTY(PropertyInfo(Variant::INT, "msdf_pixel_range", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE), "set_msdf_pixel_range", "get_msdf_pixel_range");
 	ADD_PROPERTY(PropertyInfo(Variant::INT, "msdf_size", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE), "set_msdf_size", "get_msdf_size");
@@ -439,6 +449,8 @@ void FontData::reset_state() {
 	msdf_pixel_range = 14;
 	msdf_size = 128;
 	fixed_size = 0;
+	embolden = 0.f;
+	transform = Transform2D();
 	oversampling = 0.f;
 }
 
@@ -1385,6 +1397,36 @@ TextServer::SubpixelPositioning FontData::get_subpixel_positioning() const {
 	return subpixel_positioning;
 }
 
+void FontData::set_embolden(float p_strength) {
+	if (embolden != p_strength) {
+		embolden = p_strength;
+		for (int i = 0; i < cache.size(); i++) {
+			_ensure_rid(i);
+			TS->font_set_embolden(cache[i], embolden);
+		}
+		emit_changed();
+	}
+}
+
+float FontData::get_embolden() const {
+	return embolden;
+}
+
+void FontData::set_transform(Transform2D p_transform) {
+	if (transform != p_transform) {
+		transform = p_transform;
+		for (int i = 0; i < cache.size(); i++) {
+			_ensure_rid(i);
+			TS->font_set_transform(cache[i], transform);
+		}
+		emit_changed();
+	}
+}
+
+Transform2D FontData::get_transform() const {
+	return transform;
+}
+
 void FontData::set_oversampling(real_t p_oversampling) {
 	if (oversampling != p_oversampling) {
 		oversampling = p_oversampling;

+ 8 - 0
scene/resources/font.h

@@ -57,6 +57,8 @@ class FontData : public Resource {
 	TextServer::Hinting hinting = TextServer::HINTING_LIGHT;
 	TextServer::SubpixelPositioning subpixel_positioning = TextServer::SUBPIXEL_POSITIONING_AUTO;
 	real_t oversampling = 0.f;
+	real_t embolden = 0.f;
+	Transform2D transform;
 
 	// Cache.
 	mutable Vector<RID> cache;
@@ -122,6 +124,12 @@ public:
 	virtual void set_subpixel_positioning(TextServer::SubpixelPositioning p_subpixel);
 	virtual TextServer::SubpixelPositioning get_subpixel_positioning() const;
 
+	virtual void set_embolden(float p_strength);
+	virtual float get_embolden() const;
+
+	virtual void set_transform(Transform2D p_transform);
+	virtual Transform2D get_transform() const;
+
 	virtual void set_oversampling(real_t p_oversampling);
 	virtual real_t get_oversampling() const;
 

+ 30 - 0
servers/text/text_server_extension.cpp

@@ -88,6 +88,12 @@ void TextServerExtension::_bind_methods() {
 	GDVIRTUAL_BIND(_font_set_subpixel_positioning, "font_rid", "subpixel_positioning");
 	GDVIRTUAL_BIND(_font_get_subpixel_positioning, "font_rid");
 
+	GDVIRTUAL_BIND(_font_set_embolden, "font_rid", "strength");
+	GDVIRTUAL_BIND(_font_get_embolden, "font_rid");
+
+	GDVIRTUAL_BIND(_font_set_transform, "font_rid", "transform");
+	GDVIRTUAL_BIND(_font_get_transform, "font_rid");
+
 	GDVIRTUAL_BIND(_font_set_variation_coordinates, "font_rid", "variation_coordinates");
 	GDVIRTUAL_BIND(_font_get_variation_coordinates, "font_rid");
 
@@ -527,6 +533,30 @@ TextServer::SubpixelPositioning TextServerExtension::font_get_subpixel_positioni
 	return TextServer::SubpixelPositioning::SUBPIXEL_POSITIONING_DISABLED;
 }
 
+void TextServerExtension::font_set_embolden(RID p_font_rid, float p_strength) {
+	GDVIRTUAL_CALL(_font_set_embolden, p_font_rid, p_strength);
+}
+
+float TextServerExtension::font_get_embolden(RID p_font_rid) const {
+	float ret;
+	if (GDVIRTUAL_CALL(_font_get_embolden, p_font_rid, ret)) {
+		return ret;
+	}
+	return 0.f;
+}
+
+void TextServerExtension::font_set_transform(RID p_font_rid, Transform2D p_transform) {
+	GDVIRTUAL_CALL(_font_set_transform, p_font_rid, p_transform);
+}
+
+Transform2D TextServerExtension::font_get_transform(RID p_font_rid) const {
+	Transform2D ret;
+	if (GDVIRTUAL_CALL(_font_get_transform, p_font_rid, ret)) {
+		return ret;
+	}
+	return Transform2D();
+}
+
 void TextServerExtension::font_set_variation_coordinates(RID p_font_rid, const Dictionary &p_variation_coordinates) {
 	GDVIRTUAL_CALL(_font_set_variation_coordinates, p_font_rid, p_variation_coordinates);
 }

+ 10 - 0
servers/text/text_server_extension.h

@@ -139,6 +139,16 @@ public:
 	GDVIRTUAL2(_font_set_subpixel_positioning, RID, SubpixelPositioning);
 	GDVIRTUAL1RC(SubpixelPositioning, _font_get_subpixel_positioning, RID);
 
+	virtual void font_set_embolden(RID p_font_rid, float p_strength) override;
+	virtual float font_get_embolden(RID p_font_rid) const override;
+	GDVIRTUAL2(_font_set_embolden, RID, float);
+	GDVIRTUAL1RC(float, _font_get_embolden, RID);
+
+	virtual void font_set_transform(RID p_font_rid, Transform2D p_transform) override;
+	virtual Transform2D font_get_transform(RID p_font_rid) const override;
+	GDVIRTUAL2(_font_set_transform, RID, Transform2D);
+	GDVIRTUAL1RC(Transform2D, _font_get_transform, RID);
+
 	virtual void font_set_variation_coordinates(RID p_font_rid, const Dictionary &p_variation_coordinates) override;
 	virtual Dictionary font_get_variation_coordinates(RID p_font_rid) const override;
 	GDVIRTUAL2(_font_set_variation_coordinates, RID, Dictionary);

+ 6 - 0
servers/text_server.cpp

@@ -241,6 +241,12 @@ void TextServer::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("font_set_subpixel_positioning", "font_rid", "subpixel_positioning"), &TextServer::font_set_subpixel_positioning);
 	ClassDB::bind_method(D_METHOD("font_get_subpixel_positioning", "font_rid"), &TextServer::font_get_subpixel_positioning);
 
+	ClassDB::bind_method(D_METHOD("font_set_embolden", "font_rid", "strength"), &TextServer::font_set_embolden);
+	ClassDB::bind_method(D_METHOD("font_get_embolden", "font_rid"), &TextServer::font_get_embolden);
+
+	ClassDB::bind_method(D_METHOD("font_set_transform", "font_rid", "transform"), &TextServer::font_set_transform);
+	ClassDB::bind_method(D_METHOD("font_get_transform", "font_rid"), &TextServer::font_get_transform);
+
 	ClassDB::bind_method(D_METHOD("font_set_variation_coordinates", "font_rid", "variation_coordinates"), &TextServer::font_set_variation_coordinates);
 	ClassDB::bind_method(D_METHOD("font_get_variation_coordinates", "font_rid"), &TextServer::font_get_variation_coordinates);
 

+ 6 - 0
servers/text_server.h

@@ -261,6 +261,12 @@ public:
 	virtual void font_set_subpixel_positioning(RID p_font_rid, SubpixelPositioning p_subpixel) = 0;
 	virtual SubpixelPositioning font_get_subpixel_positioning(RID p_font_rid) const = 0;
 
+	virtual void font_set_embolden(RID p_font_rid, float p_strength) = 0;
+	virtual float font_get_embolden(RID p_font_rid) const = 0;
+
+	virtual void font_set_transform(RID p_font_rid, Transform2D p_transform) = 0;
+	virtual Transform2D font_get_transform(RID p_font_rid) const = 0;
+
 	virtual void font_set_variation_coordinates(RID p_font_rid, const Dictionary &p_variation_coordinates) = 0;
 	virtual Dictionary font_get_variation_coordinates(RID p_font_rid) const = 0;