From 74291793db0437d2c5d201f7f38d15dfcaf0ba83 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 23 Aug 2024 20:34:19 -0700 Subject: [PATCH 1/4] font: rename auto-italicize to synthetic italic --- src/font/Collection.zig | 10 +++++----- src/font/face/coretext.zig | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/font/Collection.zig b/src/font/Collection.zig index 2a8368053..256c6b73e 100644 --- a/src/font/Collection.zig +++ b/src/font/Collection.zig @@ -306,19 +306,19 @@ pub fn completeStyles(self: *Collection, alloc: Allocator) CompleteError!void { // Create an synthetic italic font face from the given entry and return it. fn syntheticItalic(self: *Collection, entry: *Entry) !Face { - // Not all font backends support auto-italicization. - if (comptime !@hasDecl(Face, "italicize")) return error.SyntheticItalicUnavailable; + // Not all font backends support synthetic italicization. + if (comptime !@hasDecl(Face, "syntheticItalic")) return error.SyntheticItalicUnavailable; - // We require loading options to auto-italicize. + // We require loading options to create a synthetic italic face. const opts = self.load_options orelse return error.DeferredLoadingUnavailable; // Try to italicize it. const regular = try self.getFaceFromEntry(entry); - const face = try regular.italicize(opts.faceOptions()); + const face = try regular.syntheticItalic(opts.faceOptions()); var buf: [256]u8 = undefined; if (face.name(&buf)) |name| { - log.info("font auto-italicized: {s}", .{name}); + log.info("font synthetic italic created family={s}", .{name}); } else |_| {} return face; diff --git a/src/font/face/coretext.zig b/src/font/face/coretext.zig index adafd93d0..55d6dffdf 100644 --- a/src/font/face/coretext.zig +++ b/src/font/face/coretext.zig @@ -169,7 +169,7 @@ pub const Face = struct { /// Return a new face that is the same as this but has a transformation /// matrix applied to italicize it. - pub fn italicize(self: *const Face, opts: font.face.Options) !Face { + pub fn syntheticItalic(self: *const Face, opts: font.face.Options) !Face { const ct_font = try self.font.copyWithAttributes(0.0, &italic_skew, null); errdefer ct_font.release(); return try initFont(ct_font, opts); From d22551cd31b76a4d25d5f9cdbc08451d5d35d3b1 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 23 Aug 2024 20:52:13 -0700 Subject: [PATCH 2/4] font/coretext: support synthetic bold --- src/config/Config.zig | 4 ++- src/font/Collection.zig | 32 ++++++++++++++++++++--- src/font/face/coretext.zig | 52 ++++++++++++++++++++++++++++++++++---- 3 files changed, 79 insertions(+), 9 deletions(-) diff --git a/src/config/Config.zig b/src/config/Config.zig index 10b2e9524..e2f5999ed 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -62,7 +62,9 @@ const c = @cImport({ /// Finally, some styles may be synthesized if they are not supported. /// For example, if a font does not have an italic style and no alternative /// italic font is specified, Ghostty will synthesize an italic style by -/// applying a slant to the regular style. +/// applying a slant to the regular style. If you want to disable these +/// synthesized styles then you can use the `font-style` configurations +/// as documented below. /// /// You can disable styles completely by using the `font-style` set of /// configurations. See the documentation for `font-style` for more information. diff --git a/src/font/Collection.zig b/src/font/Collection.zig index 256c6b73e..01237a107 100644 --- a/src/font/Collection.zig +++ b/src/font/Collection.zig @@ -273,9 +273,15 @@ pub fn completeStyles(self: *Collection, alloc: Allocator) CompleteError!void { // If we don't have bold, use the regular font. const bold_list = self.faces.getPtr(.bold); - if (bold_list.count() == 0) { - log.warn("bold style not available, using regular font", .{}); - try bold_list.append(alloc, .{ .alias = regular_entry }); + if (bold_list.count() == 0) bold: { + const synthetic = self.syntheticBold(regular_entry) catch |err| { + log.warn("failed to create synthetic bold, bold style will not be available err={}", .{err}); + try bold_list.append(alloc, .{ .alias = regular_entry }); + break :bold; + }; + + log.info("synthetic bold face created", .{}); + try bold_list.append(alloc, .{ .loaded = synthetic }); } // If we don't have bold italic, use the regular italic font. @@ -304,6 +310,26 @@ pub fn completeStyles(self: *Collection, alloc: Allocator) CompleteError!void { } } +// Create an synthetic italic font face from the given entry and return it. +fn syntheticBold(self: *Collection, entry: *Entry) !Face { + // Not all font backends support synthetic bold. + if (comptime !@hasDecl(Face, "syntheticBold")) return error.SyntheticBoldUnavailable; + + // We require loading options to create a synthetic bold face. + const opts = self.load_options orelse return error.DeferredLoadingUnavailable; + + // Try to bold it. + const regular = try self.getFaceFromEntry(entry); + const face = try regular.syntheticBold(opts.faceOptions()); + + var buf: [256]u8 = undefined; + if (face.name(&buf)) |name| { + log.info("font synthetic bold created family={s}", .{name}); + } else |_| {} + + return face; +} + // Create an synthetic italic font face from the given entry and return it. fn syntheticItalic(self: *Collection, entry: *Entry) !Face { // Not all font backends support synthetic italicization. diff --git a/src/font/face/coretext.zig b/src/font/face/coretext.zig index 55d6dffdf..835994542 100644 --- a/src/font/face/coretext.zig +++ b/src/font/face/coretext.zig @@ -24,6 +24,10 @@ pub const Face = struct { /// Set quirks.disableDefaultFontFeatures quirks_disable_default_font_features: bool = false, + /// True if this font face should be rasterized with a synthetic bold + /// effect. This is used for fonts that don't have a bold variant. + synthetic_bold: ?f64 = null, + /// If the face can possibly be colored, then this is the state /// used to check for color information. This is null if the font /// can't possibly be colored (i.e. doesn't have SVG, sbix, etc @@ -175,6 +179,25 @@ pub const Face = struct { return try initFont(ct_font, opts); } + /// Return a new face that is the same as this but applies a synthetic + /// bold effect to it. This is useful for fonts that don't have a bold + /// variant. + pub fn syntheticBold(self: *const Face, opts: font.face.Options) !Face { + const ct_font = try self.font.copyWithAttributes(0.0, null, null); + errdefer ct_font.release(); + var face = try initFont(ct_font, opts); + + // TO determine our synthetic bold line width we get a multiplier + // from the font size in points. This is a heuristic that is based + // on the fact that a line width of 1 looks good to me at 12 points + // and we want to scale that up roughly linearly with the font size. + const points_f64: f64 = @floatCast(opts.size.points); + const line_width = @max(points_f64 / 12.0, 1); + face.synthetic_bold = line_width; + + return face; + } + /// Returns the font name. If allocation is required, buf will be used, /// but sometimes allocation isn't required and a static string is /// returned. @@ -300,11 +323,23 @@ pub const Face = struct { .advance_x = 0, }; - // If we're doing thicken, then getBoundsForGlyphs does not take - // into account the anti-aliasing that will be added to the glyph. - // We need to add some padding to allow that to happen. A padding of - // 2 is usually enough for anti-aliasing. - const padding_ctx: u32 = if (opts.thicken) 2 else 0; + // Additional padding we need to add to the bitmap context itself + // due to the glyph being larger than standard. + const padding_ctx: u32 = padding_ctx: { + // If we're doing thicken, then getBoundsForGlyphs does not take + // into account the anti-aliasing that will be added to the glyph. + // We need to add some padding to allow that to happen. A padding of + // 2 is usually enough for anti-aliasing. + var result: u32 = if (opts.thicken) 2 else 0; + + // If we have a synthetic bold, add padding for the stroke width + if (self.synthetic_bold) |line_width| { + // x2 for top and bottom padding + result += @intFromFloat(@ceil(line_width) * 2); + } + + break :padding_ctx result; + }; const padded_width: u32 = width + (padding_ctx * 2); const padded_height: u32 = height + (padding_ctx * 2); @@ -390,6 +425,13 @@ pub const Face = struct { context.setGrayStrokeColor(ctx, 1, 1); } + // If we are drawing with synthetic bold then use a fill stroke + // which strokes the outlines of the glyph making a more bold look. + if (self.synthetic_bold) |line_width| { + context.setTextDrawingMode(ctx, .fill_stroke); + context.setLineWidth(ctx, line_width); + } + // We want to render the glyphs at (0,0), but the glyphs themselves // are offset by bearings, so we have to undo those bearings in order // to get them to 0,0. We also add the padding so that they render From ce6c5517afe140a050ca4978aab90bf4e5ffcdb4 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 23 Aug 2024 21:03:33 -0700 Subject: [PATCH 3/4] font: synthesize bold italic --- src/font/Collection.zig | 62 ++++++++++++++++++++++++++--------------- 1 file changed, 40 insertions(+), 22 deletions(-) diff --git a/src/font/Collection.zig b/src/font/Collection.zig index 01237a107..fbc23f007 100644 --- a/src/font/Collection.zig +++ b/src/font/Collection.zig @@ -260,7 +260,8 @@ pub fn completeStyles(self: *Collection, alloc: Allocator) CompleteError!void { // If we can't create a synthetic italic face, we'll just use the regular // face for italic. const italic_list = self.faces.getPtr(.italic); - if (italic_list.count() == 0) italic: { + const have_italic = italic_list.count() > 0; + if (!have_italic) italic: { const synthetic = self.syntheticItalic(regular_entry) catch |err| { log.warn("failed to create synthetic italic, italic style will not be available err={}", .{err}); try italic_list.append(alloc, .{ .alias = regular_entry }); @@ -273,7 +274,8 @@ pub fn completeStyles(self: *Collection, alloc: Allocator) CompleteError!void { // If we don't have bold, use the regular font. const bold_list = self.faces.getPtr(.bold); - if (bold_list.count() == 0) bold: { + const have_bold = bold_list.count() > 0; + if (!have_bold) bold: { const synthetic = self.syntheticBold(regular_entry) catch |err| { log.warn("failed to create synthetic bold, bold style will not be available err={}", .{err}); try bold_list.append(alloc, .{ .alias = regular_entry }); @@ -284,29 +286,45 @@ pub fn completeStyles(self: *Collection, alloc: Allocator) CompleteError!void { try bold_list.append(alloc, .{ .loaded = synthetic }); } - // If we don't have bold italic, use the regular italic font. + // If we don't have bold italic, we attempt to synthesize a bold variant + // of the italic font. If we can't do that, we'll use the italic font. const bold_italic_list = self.faces.getPtr(.bold_italic); - if (bold_italic_list.count() == 0) { - log.warn("bold italic style not available, using italic font", .{}); + if (bold_italic_list.count() == 0) bold_italic: { + // Prefer to synthesize on top of the face we already had. If we + // have bold then we try to synthesize italic on top of bold. + if (have_bold) { + if (self.syntheticItalic(bold_list.at(0))) |synthetic| { + log.info("synthetic bold italic face created from bold", .{}); + try bold_italic_list.append(alloc, .{ .loaded = synthetic }); + break :bold_italic; + } else |_| {} - // Nested alias isn't allowed so if the italic entry is an - // alias then we use the aliased entry. - const italic_entry = italic_list.at(0); - switch (italic_entry.*) { - .alias => |v| try bold_italic_list.append( - alloc, - .{ .alias = v }, - ), - - .loaded, - .fallback_loaded, - .deferred, - .fallback_deferred, - => try bold_italic_list.append( - alloc, - .{ .alias = italic_entry }, - ), + // If synthesizing italic failed, then we try to synthesize + // boldon whatever italic font we have. } + + // Nested alias isn't allowed so we need to unwrap the italic entry. + const base_entry = base: { + const italic_entry = italic_list.at(0); + break :base switch (italic_entry.*) { + .alias => |v| v, + + .loaded, + .fallback_loaded, + .deferred, + .fallback_deferred, + => italic_entry, + }; + }; + + if (self.syntheticBold(base_entry)) |synthetic| { + log.info("synthetic bold italic face created from italic", .{}); + try bold_italic_list.append(alloc, .{ .loaded = synthetic }); + break :bold_italic; + } else |_| {} + + log.warn("bold italic style not available, using italic font", .{}); + try bold_italic_list.append(alloc, .{ .alias = base_entry }); } } From ac3e2163f342ae89c8c683b19833c52dfff6cc4c Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 23 Aug 2024 21:19:20 -0700 Subject: [PATCH 4/4] typos --- src/font/Collection.zig | 6 +++--- src/font/face/coretext.zig | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/font/Collection.zig b/src/font/Collection.zig index fbc23f007..842867930 100644 --- a/src/font/Collection.zig +++ b/src/font/Collection.zig @@ -300,7 +300,7 @@ pub fn completeStyles(self: *Collection, alloc: Allocator) CompleteError!void { } else |_| {} // If synthesizing italic failed, then we try to synthesize - // boldon whatever italic font we have. + // bold on whatever italic font we have. } // Nested alias isn't allowed so we need to unwrap the italic entry. @@ -328,7 +328,7 @@ pub fn completeStyles(self: *Collection, alloc: Allocator) CompleteError!void { } } -// Create an synthetic italic font face from the given entry and return it. +// Create a synthetic bold font face from the given entry and return it. fn syntheticBold(self: *Collection, entry: *Entry) !Face { // Not all font backends support synthetic bold. if (comptime !@hasDecl(Face, "syntheticBold")) return error.SyntheticBoldUnavailable; @@ -348,7 +348,7 @@ fn syntheticBold(self: *Collection, entry: *Entry) !Face { return face; } -// Create an synthetic italic font face from the given entry and return it. +// Create a synthetic italic font face from the given entry and return it. fn syntheticItalic(self: *Collection, entry: *Entry) !Face { // Not all font backends support synthetic italicization. if (comptime !@hasDecl(Face, "syntheticItalic")) return error.SyntheticItalicUnavailable; diff --git a/src/font/face/coretext.zig b/src/font/face/coretext.zig index 835994542..202230d5d 100644 --- a/src/font/face/coretext.zig +++ b/src/font/face/coretext.zig @@ -187,7 +187,7 @@ pub const Face = struct { errdefer ct_font.release(); var face = try initFont(ct_font, opts); - // TO determine our synthetic bold line width we get a multiplier + // To determine our synthetic bold line width we get a multiplier // from the font size in points. This is a heuristic that is based // on the fact that a line width of 1 looks good to me at 12 points // and we want to scale that up roughly linearly with the font size.