|
|
@@ -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);
|
|
|
+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,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;
|
|
|
}
|
|
|
|
|
|
-
|
|
|
-
|
|
|
// Initialises the handle so it is able to render text.
|
|
|
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;
|
|
|
|
|
|
- // 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);
|
|
|
|
|
|
// 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,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);
|
|
|
|
|
|
@@ -210,7 +203,7 @@ static void BuildGlyphMap(FT_Face ft_face, int size, FontGlyphMap& glyphs)
|
|
|
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);
|
|
|
+ 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 +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);
|
|
|
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 +280,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 +345,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 +416,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
|