Browse Source

Font support for color emojis and other color glyphs, in addition to both grayscale and color bitmap glyphs.

Michael Ragazzon 3 years ago
parent
commit
f0ea32b591

+ 0 - 6
Include/RmlUi/Core/ConvolutionFilter.h

@@ -43,12 +43,6 @@ enum class FilterOperation {
 	Erosion
 	Erosion
 };
 };
 
 
-enum class ColorFormat {
-	RGBA8,
-	A8
-};
-
-
 /**
 /**
 	A programmable convolution filter, designed to aid in the generation of texture data by custom
 	A programmable convolution filter, designed to aid in the generation of texture data by custom
 	FontEffect types.
 	FontEffect types.

+ 7 - 8
Include/RmlUi/Core/FontGlyph.h

@@ -39,11 +39,9 @@ namespace Rml {
 	@author Peter Curry
 	@author Peter Curry
  */
  */
 
 
-class RMLUICORE_API FontGlyph
-{
+class RMLUICORE_API FontGlyph {
 public:
 public:
-	FontGlyph() : dimensions(0,0), bearing(0,0), advance(0), bitmap_data(nullptr), bitmap_dimensions(0,0)
-	{}
+	FontGlyph() : dimensions(0, 0), bearing(0, 0), advance(0), bitmap_data(nullptr), bitmap_dimensions(0, 0), color_format(ColorFormat::A8) {}
 
 
 	/// The glyph's bounding box. Not to be confused with the dimensions of the glyph's bitmap!
 	/// The glyph's bounding box. Not to be confused with the dimensions of the glyph's bitmap!
 	Vector2i dimensions;
 	Vector2i dimensions;
@@ -54,13 +52,13 @@ public:
 	/// character.
 	/// character.
 	int advance;
 	int advance;
 
 
-	/// 8-bit opacity information for the glyph's bitmap. The size of the data is given by the
-	/// dimensions, below. This will be nullptr if the glyph has no bitmap data.
+	/// Bitmap data defining this glyph. The dimensions and format of the data is given below. This will be nullptr if the glyph has no bitmap data.
 	const byte* bitmap_data;
 	const byte* bitmap_data;
-	/// The dimensions of the glyph's bitmap.
+
 	Vector2i bitmap_dimensions;
 	Vector2i bitmap_dimensions;
+	ColorFormat color_format;
 
 
-	// Bitmap_data may point to this member or another font glyph data.
+	// bitmap_data may point to this member or another font glyph data.
 	UniquePtr<byte[]> bitmap_owned_data;
 	UniquePtr<byte[]> bitmap_owned_data;
 
 
 	// Create a copy with its bitmap data owned by another glyph.
 	// Create a copy with its bitmap data owned by another glyph.
@@ -72,6 +70,7 @@ public:
 		glyph.advance = advance;
 		glyph.advance = advance;
 		glyph.bitmap_data = bitmap_data;
 		glyph.bitmap_data = bitmap_data;
 		glyph.bitmap_dimensions = bitmap_dimensions;
 		glyph.bitmap_dimensions = bitmap_dimensions;
+		glyph.color_format = color_format;
 		return glyph;
 		return glyph;
 	}
 	}
 };
 };

+ 1 - 0
Include/RmlUi/Core/Types.h

@@ -58,6 +58,7 @@ enum class Character : char32_t { Null, Replacement = 0xfffd };
 namespace Rml {
 namespace Rml {
 
 
 // Color and linear algebra
 // Color and linear algebra
+enum class ColorFormat { RGBA8, A8 };
 using Colourf = Colour< float, 1 >;
 using Colourf = Colour< float, 1 >;
 using Colourb = Colour< byte, 255 >;
 using Colourb = Colour< byte, 255 >;
 using Vector2i = Vector2< int >;
 using Vector2i = Vector2< int >;

+ 5 - 1
Source/Core/FontEngineDefault/FontFaceHandleDefault.cpp

@@ -283,7 +283,11 @@ int FontFaceHandleDefault::GenerateString(GeometryList& geometry, const String&
 			// Adjust the cursor for the kerning between this character and the previous one.
 			// Adjust the cursor for the kerning between this character and the previous one.
 			line_width += GetKerning(prior_character, character);
 			line_width += GetKerning(prior_character, character);
 
 
-			layer->GenerateGeometry(&geometry[geometry_index], character, Vector2f(position.x + line_width, position.y), layer_colour);
+			// Use white vertex colors on RGB glyphs.
+			const Colourb glyph_color =
+				(layer == base_layer && glyph->color_format == ColorFormat::RGBA8 ? Colourb(255, layer_colour.alpha) : layer_colour);
+
+			layer->GenerateGeometry(&geometry[geometry_index], character, Vector2f(position.x + line_width, position.y), glyph_color);
 
 
 			line_width += glyph->advance;
 			line_width += glyph->advance;
 			prior_character = character;
 			prior_character = character;

+ 20 - 6
Source/Core/FontEngineDefault/FontFaceLayer.cpp

@@ -28,6 +28,7 @@
 
 
 #include "FontFaceLayer.h"
 #include "FontFaceLayer.h"
 #include "FontFaceHandleDefault.h"
 #include "FontFaceHandleDefault.h"
+#include <string.h>
 
 
 namespace Rml {
 namespace Rml {
 
 
@@ -82,8 +83,8 @@ bool FontFaceLayer::Generate(const FontFaceHandleDefault* handle, const FontFace
 
 
 				TextureBox& box = it->second;
 				TextureBox& box = it->second;
 
 
-				Vector2i glyph_origin(Math::RealToInteger(box.origin.x), Math::RealToInteger(box.origin.y));
-				Vector2i glyph_dimensions(Math::RealToInteger(box.dimensions.x), Math::RealToInteger(box.dimensions.y));
+				Vector2i glyph_origin = Vector2i(box.origin);
+				Vector2i glyph_dimensions = Vector2i(box.dimensions);
 
 
 				if (effect->GetGlyphMetrics(glyph_origin, glyph_dimensions, glyph))
 				if (effect->GetGlyphMetrics(glyph_origin, glyph_dimensions, glyph))
 					box.origin = Vector2f(glyph_origin);
 					box.origin = Vector2f(glyph_origin);
@@ -208,20 +209,33 @@ bool FontFaceLayer::GenerateTexture(UniquePtr<const byte[]>& texture_data, Vecto
 			{
 			{
 				byte* destination = rectangle.GetTextureData();
 				byte* destination = rectangle.GetTextureData();
 				const byte* source = glyph.bitmap_data;
 				const byte* source = glyph.bitmap_data;
+				const int num_bytes_per_line = glyph.bitmap_dimensions.x * (glyph.color_format == ColorFormat::RGBA8 ? 4 : 1);
 
 
 				for (int j = 0; j < glyph.bitmap_dimensions.y; ++j)
 				for (int j = 0; j < glyph.bitmap_dimensions.y; ++j)
 				{
 				{
-					for (int k = 0; k < glyph.bitmap_dimensions.x; ++k)
-						destination[k * 4 + 3] = source[k];
+					switch (glyph.color_format)
+					{
+					case ColorFormat::A8:
+					{
+						for (int k = 0; k < num_bytes_per_line; ++k)
+							destination[k * 4 + 3] = source[k];
+					}
+					break;
+					case ColorFormat::RGBA8:
+					{
+						memcpy(destination, source, num_bytes_per_line);
+					}
+					break;
+					}
 
 
 					destination += rectangle.GetTextureStride();
 					destination += rectangle.GetTextureStride();
-					source += glyph.bitmap_dimensions.x;
+					source += num_bytes_per_line;
 				}
 				}
 			}
 			}
 		}
 		}
 		else
 		else
 		{
 		{
-			effect->GenerateGlyphTexture(rectangle.GetTextureData(), Vector2i(Math::RealToInteger(box.dimensions.x), Math::RealToInteger(box.dimensions.y)), rectangle.GetTextureStride(), glyph);
+			effect->GenerateGlyphTexture(rectangle.GetTextureData(), Vector2i(box.dimensions), rectangle.GetTextureStride(), glyph);
 		}
 		}
 	}
 	}
 
 

+ 205 - 51
Source/Core/FontEngineDefault/FreeTypeInterface.cpp

@@ -30,6 +30,7 @@
 #include "../../../Include/RmlUi/Core/Log.h"
 #include "../../../Include/RmlUi/Core/Log.h"
 
 
 #include <string.h>
 #include <string.h>
+#include <limits.h>
 #include <ft2build.h>
 #include <ft2build.h>
 #include FT_FREETYPE_H
 #include FT_FREETYPE_H
 
 
@@ -37,11 +38,12 @@ namespace Rml {
 
 
 static FT_Library ft_library = nullptr;
 static FT_Library ft_library = nullptr;
 
 
-
-static bool BuildGlyph(FT_Face ft_face, Character character, FontGlyphMap& glyphs);
-static void BuildGlyphMap(FT_Face ft_face, int size, FontGlyphMap& glyphs);
-static void GenerateMetrics(FT_Face ft_face, FontMetrics& metrics);
-
+static bool BuildGlyph(FT_Face ft_face, Character character, FontGlyphMap& glyphs, float bitmap_scaling_factor);
+static void BuildGlyphMap(FT_Face ft_face, int size, FontGlyphMap& glyphs, float bitmap_scaling_factor);
+static void GenerateMetrics(FT_Face ft_face, FontMetrics& metrics, float bitmap_scaling_factor);
+static bool SetFontSize(FT_Face ft_face, int font_size, float& out_bitmap_scaling_factor);
+static void BitmapDownscale(byte* bitmap_new, int new_width, int new_height, const byte* bitmap_source, int width, int height, int pitch,
+	ColorFormat color_format);
 
 
 bool FreeType::Initialise()
 bool FreeType::Initialise()
 {
 {
@@ -112,8 +114,6 @@ void FreeType::GetFaceStyle(FontFaceHandleFreetype in_face, String& font_family,
 	weight = face->style_flags & FT_STYLE_FLAG_BOLD ? Style::FontWeight::Bold : Style::FontWeight::Normal;
 	weight = face->style_flags & FT_STYLE_FLAG_BOLD ? Style::FontWeight::Bold : Style::FontWeight::Normal;
 }
 }
 
 
-
-
 // Initialises the handle so it is able to render text.
 // Initialises the handle so it is able to render text.
 bool FreeType::InitialiseFaceHandle(FontFaceHandleFreetype face, int font_size, FontGlyphMap& glyphs, FontMetrics& metrics)
 bool FreeType::InitialiseFaceHandle(FontFaceHandleFreetype face, int font_size, FontGlyphMap& glyphs, FontMetrics& metrics)
 {
 {
@@ -121,19 +121,15 @@ bool FreeType::InitialiseFaceHandle(FontFaceHandleFreetype face, int font_size,
 
 
 	metrics.size = font_size;
 	metrics.size = font_size;
 
 
-	// Set the character size on the font face.
-	FT_Error error = FT_Set_Char_Size(ft_face, 0, font_size << 6, 0, 0);
-	if (error != 0)
-	{
-		Log::Message(Log::LT_ERROR, "Unable to set the character size '%d' on the font face '%s %s'.", font_size, ft_face->family_name, ft_face->style_name);
+	float bitmap_scaling_factor = 1.0f;
+	if (!SetFontSize(ft_face, font_size, bitmap_scaling_factor))
 		return false;
 		return false;
-	}
 
 
 	// Construct the initial list of glyphs.
 	// Construct the initial list of glyphs.
-	BuildGlyphMap(ft_face, font_size, glyphs);
+	BuildGlyphMap(ft_face, font_size, glyphs, bitmap_scaling_factor);
 
 
 	// Generate the metrics for the handle.
 	// Generate the metrics for the handle.
-	GenerateMetrics(ft_face, metrics);
+	GenerateMetrics(ft_face, metrics, bitmap_scaling_factor);
 
 
 	return true;
 	return true;
 }
 }
@@ -146,14 +142,11 @@ bool FreeType::AppendGlyph(FontFaceHandleFreetype face, int font_size, Character
 	RMLUI_ASSERT(ft_face);
 	RMLUI_ASSERT(ft_face);
 
 
 	// Set face size again in case it was used at another size in another font face handle.
 	// Set face size again in case it was used at another size in another font face handle.
-	FT_Error error = FT_Set_Char_Size(ft_face, 0, font_size << 6, 0, 0);
-	if (error != 0)
-	{
-		Log::Message(Log::LT_ERROR, "Unable to set the character size '%d' on the font face '%s %s'.", font_size, ft_face->family_name, ft_face->style_name);
+	float bitmap_scaling_factor = 1.0f;
+	if (!SetFontSize(ft_face, font_size, bitmap_scaling_factor))
 		return false;
 		return false;
-	}
 
 
-	if (!BuildGlyph(ft_face, character, glyphs))
+	if (!BuildGlyph(ft_face, character, glyphs, bitmap_scaling_factor))
 		return false;
 		return false;
 
 
 	return true;
 	return true;
@@ -170,8 +163,8 @@ int FreeType::GetKerning(FontFaceHandleFreetype face, int font_size, Character l
 	// Font size value of zero assumes it is already set.
 	// Font size value of zero assumes it is already set.
 	if (font_size > 0)
 	if (font_size > 0)
 	{
 	{
-		FT_Error ft_error = FT_Set_Char_Size(ft_face, 0, font_size << 6, 0, 0);
-		if (ft_error)
+		float bitmap_scaling_factor = 1.0f;
+		if (!SetFontSize(ft_face, font_size, bitmap_scaling_factor) || bitmap_scaling_factor != 1.0f)
 			return 0;
 			return 0;
 	}
 	}
 
 
@@ -201,7 +194,7 @@ bool FreeType::HasKerning(FontFaceHandleFreetype face)
 
 
 
 
 
 
-static void BuildGlyphMap(FT_Face ft_face, int size, FontGlyphMap& glyphs)
+static void BuildGlyphMap(FT_Face ft_face, int size, FontGlyphMap& glyphs, const float bitmap_scaling_factor)
 {
 {
 	glyphs.reserve(128);
 	glyphs.reserve(128);
 
 
@@ -210,7 +203,7 @@ static void BuildGlyphMap(FT_Face ft_face, int size, FontGlyphMap& glyphs)
 	FT_ULong code_max = 126;
 	FT_ULong code_max = 126;
 
 
 	for (FT_ULong character_code = code_min; character_code <= code_max; ++character_code)
 	for (FT_ULong character_code = code_min; character_code <= code_max; ++character_code)
-		BuildGlyph(ft_face, (Character)character_code, glyphs);
+		BuildGlyph(ft_face, (Character)character_code, glyphs, bitmap_scaling_factor);
 
 
 	// Add a replacement character for rendering unknown characters.
 	// Add a replacement character for rendering unknown characters.
 	Character replacement_character = Character::Replacement;
 	Character replacement_character = Character::Replacement;
@@ -241,13 +234,13 @@ static void BuildGlyphMap(FT_Face ft_face, int size, FontGlyphMap& glyphs)
 	}
 	}
 }
 }
 
 
-static bool BuildGlyph(FT_Face ft_face, Character character, FontGlyphMap& glyphs)
+static bool BuildGlyph(FT_Face ft_face, const Character character, FontGlyphMap& glyphs, const float bitmap_scaling_factor)
 {
 {
 	FT_UInt index = FT_Get_Char_Index(ft_face, (FT_ULong)character);
 	FT_UInt index = FT_Get_Char_Index(ft_face, (FT_ULong)character);
 	if (index == 0)
 	if (index == 0)
 		return false;
 		return false;
 
 
-	FT_Error error = FT_Load_Glyph(ft_face, index, 0);
+	FT_Error error = FT_Load_Glyph(ft_face, index, FT_LOAD_COLOR);
 	if (error != 0)
 	if (error != 0)
 	{
 	{
 		Log::Message(Log::LT_WARNING, "Unable to load glyph for character '%u' on the font face '%s %s'; error code: %d.", (unsigned int)character, ft_face->family_name, ft_face->style_name, error);
 		Log::Message(Log::LT_WARNING, "Unable to load glyph for character '%u' on the font face '%s %s'; error code: %d.", (unsigned int)character, ft_face->family_name, ft_face->style_name, error);
@@ -287,31 +280,47 @@ static bool BuildGlyph(FT_Face ft_face, Character character, FontGlyphMap& glyph
 	glyph.bitmap_dimensions.x = ft_glyph->bitmap.width;
 	glyph.bitmap_dimensions.x = ft_glyph->bitmap.width;
 	glyph.bitmap_dimensions.y = ft_glyph->bitmap.rows;
 	glyph.bitmap_dimensions.y = ft_glyph->bitmap.rows;
 
 
+	// Determine new metrics if we need to scale the bitmap received from FreeType. Only allow bitmap downscaling.
+	const bool scale_bitmap = (bitmap_scaling_factor < 1.f);
+	if (scale_bitmap)
+	{
+		glyph.dimensions = Vector2i(Vector2f(glyph.dimensions) * bitmap_scaling_factor);
+		glyph.bearing = Vector2i(Vector2f(glyph.bearing) * bitmap_scaling_factor);
+		glyph.advance = int(float(glyph.advance) * bitmap_scaling_factor);
+		glyph.bitmap_dimensions = Vector2i(Vector2f(glyph.bitmap_dimensions) * bitmap_scaling_factor);
+	}
+
 	// Copy the glyph's bitmap data from the FreeType glyph handle to our glyph handle.
 	// Copy the glyph's bitmap data from the FreeType glyph handle to our glyph handle.
 	if (glyph.bitmap_dimensions.x * glyph.bitmap_dimensions.y != 0)
 	if (glyph.bitmap_dimensions.x * glyph.bitmap_dimensions.y != 0)
 	{
 	{
-		// Check the pixel mode is supported.
-		if (ft_glyph->bitmap.pixel_mode != FT_PIXEL_MODE_MONO &&
-			ft_glyph->bitmap.pixel_mode != FT_PIXEL_MODE_GRAY)
+		// Check if the pixel mode is supported.
+		if (ft_glyph->bitmap.pixel_mode != FT_PIXEL_MODE_MONO && ft_glyph->bitmap.pixel_mode != FT_PIXEL_MODE_GRAY &&
+			ft_glyph->bitmap.pixel_mode != FT_PIXEL_MODE_BGRA)
+		{
+			Log::Message(Log::LT_WARNING, "Unable to render glyph on the font face '%s %s': unsupported pixel mode (%d).",
+				ft_glyph->face->family_name, ft_glyph->face->style_name, ft_glyph->bitmap.pixel_mode);
+		}
+		else if (ft_glyph->bitmap.pixel_mode == FT_PIXEL_MODE_MONO && scale_bitmap)
 		{
 		{
-			glyph.bitmap_owned_data.reset();
-			glyph.bitmap_data = nullptr;
-			Log::Message(Log::LT_WARNING, "Unable to render glyph on the font face '%s %s'; unsupported pixel mode (%d).", ft_glyph->face->family_name, ft_glyph->face->style_name, ft_glyph->bitmap.pixel_mode);
+			Log::Message(Log::LT_WARNING, "Unable to render glyph on the font face '%s %s': bitmap scaling unsupported in mono pixel mode.",
+				ft_glyph->face->family_name, ft_glyph->face->style_name);
 		}
 		}
 		else
 		else
 		{
 		{
-			glyph.bitmap_owned_data.reset(new byte[glyph.bitmap_dimensions.x * glyph.bitmap_dimensions.y]);
-			glyph.bitmap_data = glyph.bitmap_owned_data.get();
+			const int num_bytes_per_pixel = (ft_glyph->bitmap.pixel_mode == FT_PIXEL_MODE_BGRA ? 4 : 1);
+			glyph.color_format = (ft_glyph->bitmap.pixel_mode == FT_PIXEL_MODE_BGRA ? ColorFormat::RGBA8 : ColorFormat::A8);
 
 
-			const byte* source_bitmap = ft_glyph->bitmap.buffer;
+			glyph.bitmap_owned_data.reset(new byte[glyph.bitmap_dimensions.x * glyph.bitmap_dimensions.y * num_bytes_per_pixel]);
+			glyph.bitmap_data = glyph.bitmap_owned_data.get();
 			byte* destination_bitmap = glyph.bitmap_owned_data.get();
 			byte* destination_bitmap = glyph.bitmap_owned_data.get();
+			const byte* source_bitmap = ft_glyph->bitmap.buffer;
 
 
 			// Copy the bitmap data into the newly-allocated space on our glyph.
 			// Copy the bitmap data into the newly-allocated space on our glyph.
 			switch (ft_glyph->bitmap.pixel_mode)
 			switch (ft_glyph->bitmap.pixel_mode)
 			{
 			{
-				// Unpack 1-bit data into 8-bit.
 			case FT_PIXEL_MODE_MONO:
 			case FT_PIXEL_MODE_MONO:
 			{
 			{
+				// Unpack 1-bit data into 8-bit.
 				for (int i = 0; i < glyph.bitmap_dimensions.y; ++i)
 				for (int i = 0; i < glyph.bitmap_dimensions.y; ++i)
 				{
 				{
 					int mask = 0x80;
 					int mask = 0x80;
@@ -336,37 +345,69 @@ static bool BuildGlyph(FT_Face ft_face, Character character, FontGlyphMap& glyph
 				}
 				}
 			}
 			}
 			break;
 			break;
-
-			// Directly copy 8-bit data.
 			case FT_PIXEL_MODE_GRAY:
 			case FT_PIXEL_MODE_GRAY:
+			case FT_PIXEL_MODE_BGRA:
 			{
 			{
-				for (int i = 0; i < glyph.bitmap_dimensions.y; ++i)
+				if (scale_bitmap)
 				{
 				{
-					memcpy(destination_bitmap, source_bitmap, glyph.bitmap_dimensions.x);
-					destination_bitmap += glyph.bitmap_dimensions.x;
-					source_bitmap += ft_glyph->bitmap.pitch;
+					// Resize the glyph data to the new dimensions.
+					BitmapDownscale(destination_bitmap, glyph.bitmap_dimensions.x, glyph.bitmap_dimensions.y, source_bitmap,
+						(int)ft_glyph->bitmap.width, (int)ft_glyph->bitmap.rows, ft_glyph->bitmap.pitch, glyph.color_format);
+				}
+				else
+				{
+					// Copy the glyph data directly.
+					const int num_bytes_per_line = glyph.bitmap_dimensions.x * num_bytes_per_pixel;
+					for (int i = 0; i < glyph.bitmap_dimensions.y; ++i)
+					{
+						memcpy(destination_bitmap, source_bitmap, num_bytes_per_line);
+						destination_bitmap += num_bytes_per_line;
+						source_bitmap += ft_glyph->bitmap.pitch;
+					}
+				}
+
+				if (glyph.color_format == ColorFormat::RGBA8)
+				{
+					// Swizzle channels (BGRA -> RGBA) and un-premultiply alpha.
+					destination_bitmap = glyph.bitmap_owned_data.get();
+
+					for (int k = 0; k < glyph.bitmap_dimensions.x * glyph.bitmap_dimensions.y * num_bytes_per_pixel; k += 4)
+					{
+						byte b = destination_bitmap[k];
+						byte g = destination_bitmap[k + 1];
+						byte r = destination_bitmap[k + 2];
+						const byte alpha = destination_bitmap[k + 3];
+						RMLUI_ASSERTMSG(b <= alpha && g <= alpha && r <= alpha, "Assumption of glyph data being premultiplied is broken.");
+
+						if (alpha > 0 && alpha < 255)
+						{
+							b = byte((b * 255) / alpha);
+							g = byte((g * 255) / alpha);
+							r = byte((r * 255) / alpha);
+						}
+
+						destination_bitmap[k] = r;
+						destination_bitmap[k + 1] = g;
+						destination_bitmap[k + 2] = b;
+						destination_bitmap[k + 3] = alpha;
+					}
 				}
 				}
 			}
 			}
 			break;
 			break;
 			}
 			}
 		}
 		}
 	}
 	}
-	else
-	{
-		glyph.bitmap_owned_data.reset();
-		glyph.bitmap_data = nullptr;
-	}
 
 
 	return true;
 	return true;
 }
 }
 
 
-static void GenerateMetrics(FT_Face ft_face, FontMetrics& metrics)
+static void GenerateMetrics(FT_Face ft_face, FontMetrics& metrics, float bitmap_scaling_factor)
 {
 {
 	metrics.line_height = ft_face->size->metrics.height >> 6;
 	metrics.line_height = ft_face->size->metrics.height >> 6;
 	metrics.baseline = metrics.line_height - (ft_face->size->metrics.ascender >> 6);
 	metrics.baseline = metrics.line_height - (ft_face->size->metrics.ascender >> 6);
 
 
-	metrics.underline_position = FT_MulFix(ft_face->underline_position, ft_face->size->metrics.y_scale) / float(1 << 6);
-	metrics.underline_thickness = FT_MulFix(ft_face->underline_thickness, ft_face->size->metrics.y_scale) / float(1 << 6);
+	metrics.underline_position = FT_MulFix(ft_face->underline_position, ft_face->size->metrics.y_scale) * bitmap_scaling_factor / float(1 << 6);
+	metrics.underline_thickness = FT_MulFix(ft_face->underline_thickness, ft_face->size->metrics.y_scale) * bitmap_scaling_factor / float(1 << 6);
 	metrics.underline_thickness = Math::Max(metrics.underline_thickness, 1.0f);
 	metrics.underline_thickness = Math::Max(metrics.underline_thickness, 1.0f);
 
 
 	// Determine the x-height of this font face.
 	// Determine the x-height of this font face.
@@ -375,7 +416,120 @@ static void GenerateMetrics(FT_Face ft_face, FontMetrics& metrics)
 		metrics.x_height = ft_face->glyph->metrics.height >> 6;
 		metrics.x_height = ft_face->glyph->metrics.height >> 6;
 	else
 	else
 		metrics.x_height = metrics.line_height / 2;
 		metrics.x_height = metrics.line_height / 2;
+
+	if (bitmap_scaling_factor != 1.f)
+	{
+		metrics.line_height = int(float(metrics.line_height) * bitmap_scaling_factor);
+		metrics.baseline = int(float(metrics.baseline) * bitmap_scaling_factor);
+		metrics.x_height = int(float(metrics.x_height) * bitmap_scaling_factor);
+	}
+}
+
+static bool SetFontSize(FT_Face ft_face, int font_size, float& out_bitmap_scaling_factor)
+{
+	RMLUI_ASSERT(out_bitmap_scaling_factor == 1.f);
+
+	FT_Error error = 0;
+
+	// Set the character size on the font face.
+	error = FT_Set_Char_Size(ft_face, 0, font_size << 6, 0, 0);
+
+	// If setting char size fails, try to select a bitmap strike instead when available.
+	if (error != 0 && FT_HAS_FIXED_SIZES(ft_face))
+	{
+		constexpr int a_big_number = INT_MAX / 2;
+		int heuristic_min = INT_MAX;
+		int index_min = -1;
+
+		// Select the bitmap strike with the smallest size *above* font_size, or else the largest size.
+		for (int i = 0; i < ft_face->num_fixed_sizes; i++)
+		{
+			const int size_diff = ft_face->available_sizes[i].height - font_size;
+			const int heuristic = (size_diff < 0 ? a_big_number - size_diff : size_diff);
+
+			if (heuristic < heuristic_min)
+			{
+				index_min = i;
+				heuristic_min = heuristic;
+			}
+		}
+
+		if (index_min >= 0)
+		{
+			out_bitmap_scaling_factor = float(font_size) / ft_face->available_sizes[index_min].height;
+
+			// Add some tolerance to the scaling factor to avoid unnecessary scaling. Only allow downscaling.
+			constexpr float bitmap_scaling_factor_threshold = 0.95f;
+			if (out_bitmap_scaling_factor >= bitmap_scaling_factor_threshold)
+				out_bitmap_scaling_factor = 1.f;
+
+			error = FT_Select_Size(ft_face, index_min);
+		}
+	}
+
+	if (error != 0)
+	{
+		Log::Message(Log::LT_ERROR, "Unable to set the character size '%d' on the font face '%s %s'.", font_size, ft_face->family_name,
+			ft_face->style_name);
+		return false;
+	}
+
+	return true;
 }
 }
 
 
+static void BitmapDownscale(byte* bitmap_new, const int new_width, const int new_height, const byte* bitmap_source, const int width, const int height,
+	const int pitch, const ColorFormat color_format)
+{
+	// Average filter for downscaling bitmap images, based on https://stackoverflow.com/a/9571580
+	constexpr int max_num_channels = 4;
+	const int num_channels = (color_format == ColorFormat::RGBA8 ? 4 : 1);
+
+	const float xscale = float(new_width) / width;
+	const float yscale = float(new_height) / height;
+	const float sumscale = xscale * yscale;
+
+	float yend = 0.0f;
+	for (int f = 0; f < new_height; f++) // y on output
+	{
+		const float ystart = yend;
+		yend = (f + 1) * (1.f / yscale);
+		if (yend >= height)
+			yend = height - 0.001f;
+
+		float xend = 0.0;
+		for (int g = 0; g < new_width; g++) // x on output
+		{
+			float xstart = xend;
+			xend = (g + 1) * (1.f / xscale);
+			if (xend >= width)
+				xend = width - 0.001f;
+
+			float sum[max_num_channels] = {};
+			for (int y = (int)ystart; y <= (int)yend; ++y)
+			{
+				float yportion = 1.0f;
+				if (y == (int)ystart)
+					yportion -= ystart - y;
+				if (y == (int)yend)
+					yportion -= y + 1 - yend;
+
+				for (int x = (int)xstart; x <= (int)xend; ++x)
+				{
+					float xportion = 1.0f;
+					if (x == (int)xstart)
+						xportion -= xstart - x;
+					if (x == (int)xend)
+						xportion -= x + 1 - xend;
+
+					for (int i = 0; i < num_channels; i++)
+						sum[i] += bitmap_source[y * pitch + x * num_channels + i] * yportion * xportion;
+				}
+			}
+
+			for (int i = 0; i < num_channels; i++)
+				bitmap_new[(f * new_width + g) * num_channels + i] = (byte)Math::Min(sum[i] * sumscale, 255.f);
+		}
+	}
+}
 
 
 } // namespace Rml
 } // namespace Rml