From 915ec029eefe2512c781855a8ebf79f2b4cc6f61 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 25 Aug 2024 09:43:54 -0700 Subject: [PATCH] font/freetype: synthetic bold --- pkg/freetype/face.zig | 18 +++++++++++++ pkg/freetype/freetype-zig.h | 1 + src/font/face/freetype.zig | 51 ++++++++++++++++++++++++++++++++----- 3 files changed, 63 insertions(+), 7 deletions(-) diff --git a/pkg/freetype/face.zig b/pkg/freetype/face.zig index 367d409e5..8bbc75616 100644 --- a/pkg/freetype/face.zig +++ b/pkg/freetype/face.zig @@ -95,6 +95,14 @@ pub const Face = struct { )); } + /// Convert a given glyph image to a bitmap. + pub fn renderGlyph(self: Face, render_mode: RenderMode) Error!void { + return intToError(c.FT_Render_Glyph( + self.handle.*.glyph, + @intFromEnum(render_mode), + )); + } + /// Return a pointer to a given SFNT table stored within a face. pub fn getSfntTable(self: Face, comptime tag: SfntTag) ?*tag.DataType() { return @ptrCast(@alignCast(c.FT_Get_Sfnt_Table( @@ -231,6 +239,16 @@ pub const Encoding = enum(u31) { apple_roman = c.FT_ENCODING_APPLE_ROMAN, }; +/// https://freetype.org/freetype2/docs/reference/ft2-glyph_retrieval.html#ft_render_mode +pub const RenderMode = enum(c_uint) { + normal = c.FT_RENDER_MODE_NORMAL, + light = c.FT_RENDER_MODE_LIGHT, + mono = c.FT_RENDER_MODE_MONO, + lcd = c.FT_RENDER_MODE_LCD, + lcd_v = c.FT_RENDER_MODE_LCD_V, + sdf = c.FT_RENDER_MODE_SDF, +}; + /// A list of bit field constants for FT_Load_Glyph to indicate what kind of /// operations to perform during glyph loading. pub const LoadFlags = packed struct { diff --git a/pkg/freetype/freetype-zig.h b/pkg/freetype/freetype-zig.h index e430aef52..29e546154 100644 --- a/pkg/freetype/freetype-zig.h +++ b/pkg/freetype/freetype-zig.h @@ -2,5 +2,6 @@ #include FT_FREETYPE_H #include FT_TRUETYPE_TABLES_H #include +#include #include #include diff --git a/src/font/face/freetype.zig b/src/font/face/freetype.zig index d1e375647..5004a040a 100644 --- a/src/font/face/freetype.zig +++ b/src/font/face/freetype.zig @@ -38,7 +38,10 @@ pub const Face = struct { quirks_disable_default_font_features: bool = false, /// Set to true to apply a synthetic italic to the face. - synthetic_italic: bool = false, + synthetic: packed struct { + italic: bool = false, + bold: bool = false, + } = .{}, /// The matrix applied to a regular font to create a synthetic italic. const italic_matrix: freetype.c.FT_Matrix = .{ @@ -130,6 +133,25 @@ pub const Face = struct { return ""; } + /// Return a new face that is the same as this but also has synthetic + /// bold applied. + pub fn syntheticBold(self: *const Face, opts: font.face.Options) !Face { + // Increase face ref count + self.face.ref(); + errdefer self.face.deinit(); + + var f = try initFace( + .{ .lib = self.lib }, + self.face, + opts, + ); + errdefer f.deinit(); + f.synthetic = self.synthetic; + f.synthetic.bold = true; + + return f; + } + /// Return a new face that is the same as this but has a transformation /// matrix applied to italicize it. pub fn syntheticItalic(self: *const Face, opts: font.face.Options) !Face { @@ -143,7 +165,8 @@ pub const Face = struct { opts, ); errdefer f.deinit(); - f.synthetic_italic = true; + f.synthetic = self.synthetic; + f.synthetic.italic = true; return f; } @@ -278,14 +301,17 @@ pub const Face = struct { // If we have synthetic italic, then we apply a transformation matrix. // We have to undo this because synthetic italic works by increasing // the ref count of the base face. - if (self.synthetic_italic) self.face.setTransform(&italic_matrix, null); - defer if (self.synthetic_italic) self.face.setTransform(null, null); + if (self.synthetic.italic) self.face.setTransform(&italic_matrix, null); + defer if (self.synthetic.italic) self.face.setTransform(null, null); // If our glyph has color, we want to render the color try self.face.loadGlyph(glyph_index, .{ - .render = true, .color = self.face.hasColor(), + // If we have synthetic bold, we have to set some additional + // glyph properties before render so we don't render here. + .render = !self.synthetic.bold, + // Disable bitmap strikes for now since it causes issues with // our cell metrics and rasterization. In the future, this is // all fixable so we can enable it. @@ -294,12 +320,23 @@ pub const Face = struct { // often colored bitmaps, which we support. .no_bitmap = !self.face.hasColor(), }); - const glyph = self.face.handle.*.glyph; - const bitmap_ft = glyph.*.bitmap; + + // For synthetic bold, we embolden the glyph and render it. + if (self.synthetic.bold) { + // We need to scale the embolden amount based on the font size. + // This is a heuristic I found worked well across a variety of + // founts: 1 pixel per 64 units of height. + const height: f64 = @floatFromInt(self.face.handle.*.size.*.metrics.height); + const ratio: f64 = 64.0 / 2048.0; + const amount = @ceil(height * ratio); + _ = freetype.c.FT_Outline_Embolden(&glyph.*.outline, @intFromFloat(amount)); + try self.face.renderGlyph(.normal); + } // This bitmap is blank. I've seen it happen in a font, I don't know why. // If it is empty, we just return a valid glyph struct that does nothing. + const bitmap_ft = glyph.*.bitmap; if (bitmap_ft.rows == 0) return .{ .width = 0, .height = 0,