Explorar o código

Merge branch 'color_emoji'

Michael Ragazzon %!s(int64=3) %!d(string=hai) anos
pai
achega
25ceb70b21

+ 3 - 7
Include/RmlUi/Core/ConvolutionFilter.h

@@ -43,12 +43,6 @@ enum class FilterOperation {
 	Erosion
 };
 
-enum class ColorFormat {
-	RGBA8,
-	A8
-};
-
-
 /**
 	A programmable convolution filter, designed to aid in the generation of texture data by custom
 	FontEffect types.
@@ -85,7 +79,9 @@ public:
 	/// @param[in] source The opacity information for the source buffer.
 	/// @param[in] source_dimensions The size of the source region (in pixels). The stride is assumed to be equivalent to the horizontal width.
 	/// @param[in] source_offset The offset of the source region from the destination region. This is usually the same as the kernel size.
-	void Run(byte* destination, Vector2i destination_dimensions, int destination_stride, ColorFormat destination_color_format, const byte* source, Vector2i source_dimensions, Vector2i source_offset) const;
+	/// @param[in] source_color_format Determines the representation of the bytes in the source texture, only the alpha channel will be used.
+	void Run(byte* destination, Vector2i destination_dimensions, int destination_stride, ColorFormat destination_color_format, const byte* source,
+		Vector2i source_dimensions, Vector2i source_offset, ColorFormat source_color_format) const;
 
 private:
 	Vector2i kernel_size;

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

@@ -39,11 +39,9 @@ namespace Rml {
 	@author Peter Curry
  */
 
-class RMLUICORE_API FontGlyph
-{
+class RMLUICORE_API FontGlyph {
 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!
 	Vector2i dimensions;
@@ -54,13 +52,13 @@ public:
 	/// character.
 	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;
-	/// The dimensions of the glyph's bitmap.
+
 	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;
 
 	// Create a copy with its bitmap data owned by another glyph.
@@ -72,6 +70,7 @@ public:
 		glyph.advance = advance;
 		glyph.bitmap_data = bitmap_data;
 		glyph.bitmap_dimensions = bitmap_dimensions;
+		glyph.color_format = color_format;
 		return glyph;
 	}
 };

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

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

+ 3 - 3
Samples/basic/demo/data/demo.rml

@@ -233,7 +233,7 @@ p.title
 #font_effects div 
 {
 	display: inline-block;
-	width: 150dp;
+	width: 180dp;
 	margin: 0 30dp 30dp;
 	text-align: center;
 	font-size: 35dp;
@@ -270,12 +270,12 @@ p.title
 }
 #font_effects .blur_small
 {
-	color: transparent;
+	color: #fff2;
 	font-effect: blur(3dp #ed5);
 }
 #font_effects .blur_big
 {
-	color: transparent;
+	color: #fff1;
 	font-effect: blur(10dp #ed5);
 }
 #font_effects .shadow_up

+ 15 - 16
Source/Core/ConvolutionFilter.cpp

@@ -54,8 +54,7 @@ bool ConvolutionFilter::Initialise(Vector2i _kernel_radii, FilterOperation _oper
 
 	kernel_size = _kernel_radii * 2 + Vector2i(1);
 
-	kernel = UniquePtr<float[]>(new float[kernel_size.x * kernel_size.y]);
-	memset(kernel.get(), 0, kernel_size.x * kernel_size.y * sizeof(float));
+	kernel = UniquePtr<float[]>(new float[kernel_size.x * kernel_size.y]());
 
 	operation = _operation;
 	return true;
@@ -70,10 +69,17 @@ float* ConvolutionFilter::operator[](int kernel_y_index)
 	return kernel.get() + kernel_size.x * kernel_y_index;
 }
 
-void ConvolutionFilter::Run(byte* destination, const Vector2i destination_dimensions, const int destination_stride, const ColorFormat destination_color_format, const byte* source, const Vector2i source_dimensions, const Vector2i source_offset) const
+void ConvolutionFilter::Run(byte* destination, const Vector2i destination_dimensions, const int destination_stride,
+	const ColorFormat destination_color_format, const byte* source, const Vector2i source_dimensions, const Vector2i source_offset,
+	const ColorFormat source_color_format) const
 {
 	RMLUI_ZoneScopedNC("ConvFilter::Run", 0xd6bf49);
 
+	const int destination_bytes_per_pixel = (destination_color_format == ColorFormat::RGBA8 ? 4 : 1);
+	const int destination_alpha_offset = (destination_color_format == ColorFormat::RGBA8 ? 3 : 0);
+	const int source_bytes_per_pixel = (source_color_format == ColorFormat::RGBA8 ? 4 : 1);
+	const int source_alpha_offset = (source_color_format == ColorFormat::RGBA8 ? 3 : 0);
+
 	const float initial_opacity = (operation == FilterOperation::Erosion ? FLT_MAX : 0.f);
 
 	const Vector2i kernel_radius = (kernel_size - Vector2i(1)) / 2;
@@ -86,20 +92,19 @@ void ConvolutionFilter::Run(byte* destination, const Vector2i destination_dimens
 
 			for (int kernel_y = 0; kernel_y < kernel_size.y; ++kernel_y)
 			{
-				int source_y = y - source_offset.y - kernel_radius.y + kernel_y;
+				const int source_y = y - source_offset.y - kernel_radius.y + kernel_y;
 
 				for (int kernel_x = 0; kernel_x < kernel_size.x; ++kernel_x)
 				{
-					float pixel_opacity;
+					float pixel_opacity = 0.f;
 
-					int source_x = x - source_offset.x - kernel_radius.x + kernel_x;
+					const int source_x = x - source_offset.x - kernel_radius.x + kernel_x;
 					if (source_y >= 0 && source_y < source_dimensions.y &&
 						source_x >= 0 && source_x < source_dimensions.x)
 					{
-						pixel_opacity = float(source[source_y * source_dimensions.x + source_x]) * kernel[kernel_y * kernel_size.x + kernel_x];
+						const int source_index = (source_y * source_dimensions.x + source_x) * source_bytes_per_pixel + source_alpha_offset;
+						pixel_opacity = float(source[source_index]) * kernel[kernel_y * kernel_size.x + kernel_x];
 					}
-					else
-						pixel_opacity = 0;
 
 					switch (operation)
 					{
@@ -112,13 +117,7 @@ void ConvolutionFilter::Run(byte* destination, const Vector2i destination_dimens
 
 			opacity = Math::Min(255.f, opacity);
 
-			int destination_index = 0;
-			switch (destination_color_format)
-			{
-			case ColorFormat::RGBA8: destination_index = x * 4 + 3; break;
-			case ColorFormat::A8:    destination_index = x; break;
-			}
-
+			const int destination_index = x * destination_bytes_per_pixel + destination_alpha_offset;
 			destination[destination_index] = byte(opacity);
 		}
 

+ 4 - 2
Source/Core/FontEffectBlur.cpp

@@ -108,9 +108,11 @@ void FontEffectBlur::GenerateGlyphTexture(byte* destination_data, const Vector2i
 	const int buf_size = buf_dimensions.x * buf_dimensions.y;
 	DynamicArray<byte, GlobalStackAllocator<byte>> x_output(buf_size);
 
-	filter_x.Run(x_output.data(), buf_dimensions, buf_stride, ColorFormat::A8, glyph.bitmap_data, glyph.bitmap_dimensions, Vector2i(width));
+	filter_x.Run(x_output.data(), buf_dimensions, buf_stride, ColorFormat::A8, glyph.bitmap_data, glyph.bitmap_dimensions, Vector2i(width),
+		glyph.color_format);
 
-	filter_y.Run(destination_data, destination_dimensions, destination_stride, ColorFormat::RGBA8, x_output.data(), buf_dimensions, Vector2i(0));
+	filter_y.Run(destination_data, destination_dimensions, destination_stride, ColorFormat::RGBA8, x_output.data(), buf_dimensions, Vector2i(0),
+		ColorFormat::A8);
 }
 
 

+ 6 - 3
Source/Core/FontEffectGlow.cpp

@@ -136,11 +136,14 @@ void FontEffectGlow::GenerateGlyphTexture(byte* destination_data, const Vector2i
 	DynamicArray<byte, GlobalStackAllocator<byte>> outline_output(buf_size);
 	DynamicArray<byte, GlobalStackAllocator<byte>> blur_x_output(buf_size);
 
-	filter_outline.Run(outline_output.data(), buf_dimensions, buf_stride, ColorFormat::A8, glyph.bitmap_data, glyph.bitmap_dimensions, Vector2i(combined_width));
+	filter_outline.Run(outline_output.data(), buf_dimensions, buf_stride, ColorFormat::A8, glyph.bitmap_data, glyph.bitmap_dimensions,
+		Vector2i(combined_width), glyph.color_format);
 	
-	filter_blur_x.Run(blur_x_output.data(), buf_dimensions, buf_stride, ColorFormat::A8, outline_output.data(), buf_dimensions, Vector2i(0));
+	filter_blur_x.Run(blur_x_output.data(), buf_dimensions, buf_stride, ColorFormat::A8, outline_output.data(), buf_dimensions, Vector2i(0),
+		ColorFormat::A8);
 
-	filter_blur_y.Run(destination_data, destination_dimensions, destination_stride, ColorFormat::RGBA8, blur_x_output.data(), buf_dimensions, Vector2i(0));
+	filter_blur_y.Run(destination_data, destination_dimensions, destination_stride, ColorFormat::RGBA8, blur_x_output.data(), buf_dimensions,
+		Vector2i(0), ColorFormat::A8);
 }
 
 

+ 2 - 1
Source/Core/FontEffectOutline.cpp

@@ -94,7 +94,8 @@ bool FontEffectOutline::GetGlyphMetrics(Vector2i& origin, Vector2i& dimensions,
 
 void FontEffectOutline::GenerateGlyphTexture(byte* destination_data, const Vector2i destination_dimensions, int destination_stride, const FontGlyph& glyph) const
 {
-	filter.Run(destination_data, destination_dimensions, destination_stride, ColorFormat::RGBA8, glyph.bitmap_data, glyph.bitmap_dimensions, Vector2i(width));
+	filter.Run(destination_data, destination_dimensions, destination_stride, ColorFormat::RGBA8, glyph.bitmap_data, glyph.bitmap_dimensions,
+		Vector2i(width), glyph.color_format);
 }
 
 

+ 4 - 2
Source/Core/FontEffectShadow.cpp

@@ -51,10 +51,12 @@ bool FontEffectShadow::HasUniqueTexture() const
 	return false;
 }
 
-bool FontEffectShadow::GetGlyphMetrics(Vector2i& origin, Vector2i& RMLUI_UNUSED_PARAMETER(dimensions), const FontGlyph& RMLUI_UNUSED_PARAMETER(glyph)) const
+bool FontEffectShadow::GetGlyphMetrics(Vector2i& origin, Vector2i& RMLUI_UNUSED_PARAMETER(dimensions), const FontGlyph& glyph) const
 {
 	RMLUI_UNUSED(dimensions);
-	RMLUI_UNUSED(glyph);
+
+	if (glyph.color_format == ColorFormat::RGBA8)
+		return false;
 
 	origin += offset;
 	return true;

+ 3 - 2
Source/Core/FontEngineDefault/FontFace.cpp

@@ -65,7 +65,8 @@ Style::FontWeight FontFace::GetWeight() const
 	return weight;
 }
 
-FontFaceHandleDefault* FontFace::GetHandle(int size) {
+FontFaceHandleDefault* FontFace::GetHandle(int size, bool load_default_glyphs)
+{
 	auto it = handles.find(size);
 	if (it != handles.end())
 		return it->second.get();
@@ -79,7 +80,7 @@ FontFaceHandleDefault* FontFace::GetHandle(int size) {
 
 	// Construct and initialise the new handle.
 	auto handle = MakeUnique<FontFaceHandleDefault>();
-	if (!handle->Initialize(face, size))
+	if (!handle->Initialize(face, size, load_default_glyphs))
 	{
 		handles[size] = nullptr;
 		return nullptr;

+ 2 - 1
Source/Core/FontEngineDefault/FontFace.h

@@ -51,8 +51,9 @@ public:
 
 	/// Returns a handle for positioning and rendering this face at the given size.
 	/// @param[in] size The size of the desired handle, in points.
+	/// @param[in] load_default_glyphs True to load the default set of glyph (ASCII range).
 	/// @return The font handle.
-	FontFaceHandleDefault* GetHandle(int size);
+	FontFaceHandleDefault* GetHandle(int size, bool load_default_glyphs);
 
 private:
 	Style::FontStyle style;

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

@@ -52,16 +52,14 @@ FontFaceHandleDefault::~FontFaceHandleDefault()
 	layers.clear();
 }
 
-bool FontFaceHandleDefault::Initialize(FontFaceHandleFreetype face, int font_size)
+bool FontFaceHandleDefault::Initialize(FontFaceHandleFreetype face, int font_size, bool load_default_glyphs)
 {
 	ft_face = face;
 
 	RMLUI_ASSERTMSG(layer_configurations.empty(), "Initialize must only be called once.");
 
-	if (!FreeType::InitialiseFaceHandle(ft_face, font_size, glyphs, metrics))
-	{
+	if (!FreeType::InitialiseFaceHandle(ft_face, font_size, glyphs, metrics, load_default_glyphs))
 		return false;
-	}
 
 	has_kerning = FreeType::HasKerning(ft_face);
 	FillKerningPairCache();
@@ -283,7 +281,11 @@ int FontFaceHandleDefault::GenerateString(GeometryList& geometry, const String&
 			// Adjust the cursor for the kerning between this character and the previous one.
 			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;
 			prior_character = character;

+ 1 - 1
Source/Core/FontEngineDefault/FontFaceHandleDefault.h

@@ -51,7 +51,7 @@ public:
 	FontFaceHandleDefault();
 	~FontFaceHandleDefault();
 
-	bool Initialize(FontFaceHandleFreetype face, int font_size);
+	bool Initialize(FontFaceHandleFreetype face, int font_size, bool load_default_glyphs);
 
 	/// Returns the point size of this font face.
 	int GetSize() const;

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

@@ -28,6 +28,7 @@
 
 #include "FontFaceLayer.h"
 #include "FontFaceHandleDefault.h"
+#include <string.h>
 
 namespace Rml {
 
@@ -82,8 +83,8 @@ bool FontFaceLayer::Generate(const FontFaceHandleDefault* handle, const FontFace
 
 				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))
 					box.origin = Vector2f(glyph_origin);
@@ -208,20 +209,33 @@ bool FontFaceLayer::GenerateTexture(UniquePtr<const byte[]>& texture_data, Vecto
 			{
 				byte* destination = rectangle.GetTextureData();
 				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 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();
-					source += glyph.bitmap_dimensions.x;
+					source += num_bytes_per_line;
 				}
 			}
 		}
 		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);
 		}
 	}
 

+ 1 - 1
Source/Core/FontEngineDefault/FontFamily.cpp

@@ -56,7 +56,7 @@ FontFaceHandleDefault* FontFamily::GetFaceHandle(Style::FontStyle style, Style::
 	if (matching_face == nullptr)
 		return nullptr;
 
-	return matching_face->GetHandle(size);
+	return matching_face->GetHandle(size, false);
 }
 
 

+ 1 - 1
Source/Core/FontEngineDefault/FontProvider.cpp

@@ -97,7 +97,7 @@ FontFaceHandleDefault* FontProvider::GetFallbackFontFace(int index, int font_siz
 	auto& faces = FontProvider::Get().fallback_font_faces;
 
 	if (index >= 0 && index < (int)faces.size())
-		return faces[index]->GetHandle(font_size);
+		return faces[index]->GetHandle(font_size, true);
 
 	return nullptr;
 }

+ 214 - 57
Source/Core/FontEngineDefault/FreeTypeInterface.cpp

@@ -30,6 +30,7 @@
 #include "../../../Include/RmlUi/Core/Log.h"
 
 #include <string.h>
+#include <limits.h>
 #include <ft2build.h>
 #include FT_FREETYPE_H
 
@@ -37,11 +38,12 @@ namespace Rml {
 
 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, bool load_default_glyphs);
+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()
 {
@@ -112,28 +114,22 @@ void FreeType::GetFaceStyle(FontFaceHandleFreetype in_face, String& font_family,
 	weight = face->style_flags & FT_STYLE_FLAG_BOLD ? Style::FontWeight::Bold : Style::FontWeight::Normal;
 }
 
-
-
 // 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, bool load_default_glyphs)
 {
 	FT_Face ft_face = (FT_Face)face;
 
 	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;
-	}
 
 	// Construct the initial list of glyphs.
-	BuildGlyphMap(ft_face, font_size, glyphs);
+	BuildGlyphMap(ft_face, font_size, glyphs, bitmap_scaling_factor, load_default_glyphs);
 
 	// Generate the metrics for the handle.
-	GenerateMetrics(ft_face, metrics);
+	GenerateMetrics(ft_face, metrics, bitmap_scaling_factor);
 
 	return true;
 }
@@ -146,14 +142,11 @@ bool FreeType::AppendGlyph(FontFaceHandleFreetype face, int font_size, Character
 	RMLUI_ASSERT(ft_face);
 
 	// 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;
-	}
 
-	if (!BuildGlyph(ft_face, character, glyphs))
+	if (!BuildGlyph(ft_face, character, glyphs, bitmap_scaling_factor))
 		return false;
 
 	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.
 	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;
 	}
 
@@ -201,16 +194,19 @@ 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, const bool load_default_glyphs)
 {
-	glyphs.reserve(128);
+	if (load_default_glyphs)
+	{
+		glyphs.reserve(128);
 
-	// Add the ASCII characters now. Other characters are added later as needed.
-	FT_ULong code_min = 32;
-	FT_ULong code_max = 126;
+		// Add the ASCII characters now. Other characters are added later as needed.
+		FT_ULong code_min = 32;
+		FT_ULong code_max = 126;
 
-	for (FT_ULong character_code = code_min; character_code <= code_max; ++character_code)
-		BuildGlyph(ft_face, (Character)character_code, glyphs);
+		for (FT_ULong character_code = code_min; character_code <= code_max; ++character_code)
+			BuildGlyph(ft_face, (Character)character_code, glyphs, bitmap_scaling_factor);
+	}
 
 	// Add a replacement character for rendering unknown characters.
 	Character replacement_character = Character::Replacement;
@@ -241,13 +237,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);
 	if (index == 0)
 		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)
 	{
 		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 +283,47 @@ static bool BuildGlyph(FT_Face ft_face, Character character, FontGlyphMap& glyph
 	glyph.bitmap_dimensions.x = ft_glyph->bitmap.width;
 	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.
 	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
 		{
-			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();
+			const byte* source_bitmap = ft_glyph->bitmap.buffer;
 
 			// Copy the bitmap data into the newly-allocated space on our glyph.
 			switch (ft_glyph->bitmap.pixel_mode)
 			{
-				// Unpack 1-bit data into 8-bit.
 			case FT_PIXEL_MODE_MONO:
 			{
+				// Unpack 1-bit data into 8-bit.
 				for (int i = 0; i < glyph.bitmap_dimensions.y; ++i)
 				{
 					int mask = 0x80;
@@ -336,37 +348,69 @@ static bool BuildGlyph(FT_Face ft_face, Character character, FontGlyphMap& glyph
 				}
 			}
 			break;
-
-			// Directly copy 8-bit data.
 			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;
 			}
 		}
 	}
-	else
-	{
-		glyph.bitmap_owned_data.reset();
-		glyph.bitmap_data = nullptr;
-	}
 
 	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.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);
 
 	// Determine the x-height of this font face.
@@ -375,7 +419,120 @@ static void GenerateMetrics(FT_Face ft_face, FontMetrics& metrics)
 		metrics.x_height = ft_face->glyph->metrics.height >> 6;
 	else
 		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

+ 1 - 1
Source/Core/FontEngineDefault/FreeTypeInterface.h

@@ -50,7 +50,7 @@ bool ReleaseFace(FontFaceHandleFreetype face);
 void GetFaceStyle(FontFaceHandleFreetype face, String& font_family, Style::FontStyle& style, Style::FontWeight& weight);
 
 // Initializes a face for a given font size. Glyphs are filled with the ASCII subset, and the font face metrics are set.
-bool InitialiseFaceHandle(FontFaceHandleFreetype face, int font_size, FontGlyphMap& glyphs, FontMetrics& metrics);
+bool InitialiseFaceHandle(FontFaceHandleFreetype face, int font_size, FontGlyphMap& glyphs, FontMetrics& metrics, bool load_default_glyphs);
 
 // Build a new glyph representing the given code point and append to 'glyphs'.
 bool AppendGlyph(FontFaceHandleFreetype face, int font_size, Character character, FontGlyphMap& glyphs);