Merge pull request #2148 from ghostty-org/ft-bold

font/freetype: synthetic bold
This commit is contained in:
Mitchell Hashimoto
2024-08-25 09:54:50 -07:00
committed by GitHub
3 changed files with 63 additions and 7 deletions

View File

@ -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. /// Return a pointer to a given SFNT table stored within a face.
pub fn getSfntTable(self: Face, comptime tag: SfntTag) ?*tag.DataType() { pub fn getSfntTable(self: Face, comptime tag: SfntTag) ?*tag.DataType() {
return @ptrCast(@alignCast(c.FT_Get_Sfnt_Table( return @ptrCast(@alignCast(c.FT_Get_Sfnt_Table(
@ -231,6 +239,16 @@ pub const Encoding = enum(u31) {
apple_roman = c.FT_ENCODING_APPLE_ROMAN, 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 /// A list of bit field constants for FT_Load_Glyph to indicate what kind of
/// operations to perform during glyph loading. /// operations to perform during glyph loading.
pub const LoadFlags = packed struct { pub const LoadFlags = packed struct {

View File

@ -2,5 +2,6 @@
#include FT_FREETYPE_H #include FT_FREETYPE_H
#include FT_TRUETYPE_TABLES_H #include FT_TRUETYPE_TABLES_H
#include <freetype/ftmm.h> #include <freetype/ftmm.h>
#include <freetype/ftoutln.h>
#include <freetype/ftsnames.h> #include <freetype/ftsnames.h>
#include <freetype/ttnameid.h> #include <freetype/ttnameid.h>

View File

@ -38,7 +38,10 @@ pub const Face = struct {
quirks_disable_default_font_features: bool = false, quirks_disable_default_font_features: bool = false,
/// Set to true to apply a synthetic italic to the face. /// 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. /// The matrix applied to a regular font to create a synthetic italic.
const italic_matrix: freetype.c.FT_Matrix = .{ const italic_matrix: freetype.c.FT_Matrix = .{
@ -130,6 +133,25 @@ pub const Face = struct {
return ""; 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 /// Return a new face that is the same as this but has a transformation
/// matrix applied to italicize it. /// matrix applied to italicize it.
pub fn syntheticItalic(self: *const Face, opts: font.face.Options) !Face { pub fn syntheticItalic(self: *const Face, opts: font.face.Options) !Face {
@ -143,7 +165,8 @@ pub const Face = struct {
opts, opts,
); );
errdefer f.deinit(); errdefer f.deinit();
f.synthetic_italic = true; f.synthetic = self.synthetic;
f.synthetic.italic = true;
return f; return f;
} }
@ -278,14 +301,17 @@ pub const Face = struct {
// If we have synthetic italic, then we apply a transformation matrix. // If we have synthetic italic, then we apply a transformation matrix.
// We have to undo this because synthetic italic works by increasing // We have to undo this because synthetic italic works by increasing
// the ref count of the base face. // the ref count of the base face.
if (self.synthetic_italic) self.face.setTransform(&italic_matrix, null); if (self.synthetic.italic) self.face.setTransform(&italic_matrix, null);
defer if (self.synthetic_italic) self.face.setTransform(null, null); defer if (self.synthetic.italic) self.face.setTransform(null, null);
// If our glyph has color, we want to render the color // If our glyph has color, we want to render the color
try self.face.loadGlyph(glyph_index, .{ try self.face.loadGlyph(glyph_index, .{
.render = true,
.color = self.face.hasColor(), .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 // Disable bitmap strikes for now since it causes issues with
// our cell metrics and rasterization. In the future, this is // our cell metrics and rasterization. In the future, this is
// all fixable so we can enable it. // all fixable so we can enable it.
@ -294,12 +320,23 @@ pub const Face = struct {
// often colored bitmaps, which we support. // often colored bitmaps, which we support.
.no_bitmap = !self.face.hasColor(), .no_bitmap = !self.face.hasColor(),
}); });
const glyph = self.face.handle.*.glyph; 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. // 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. // 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 .{ if (bitmap_ft.rows == 0) return .{
.width = 0, .width = 0,
.height = 0, .height = 0,