diff --git a/src/config.zig b/src/config.zig index 3be645cc3..082c842c9 100644 --- a/src/config.zig +++ b/src/config.zig @@ -14,6 +14,7 @@ pub const formatEntry = formatter.formatEntry; pub const ClipboardAccess = Config.ClipboardAccess; pub const CopyOnSelect = Config.CopyOnSelect; pub const CustomShaderAnimation = Config.CustomShaderAnimation; +pub const FontSyntheticStyle = Config.FontSyntheticStyle; pub const FontStyle = Config.FontStyle; pub const Keybinds = Config.Keybinds; pub const MouseShiftCapture = Config.MouseShiftCapture; diff --git a/src/config/Config.zig b/src/config/Config.zig index 6a9e9b3b3..3d2f881df 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -107,6 +107,38 @@ const c = @cImport({ @"font-style-italic": FontStyle = .{ .default = {} }, @"font-style-bold-italic": FontStyle = .{ .default = {} }, +/// Control whether Ghostty should synthesize a style if the requested style is +/// not available in the specified font-family. +/// +/// Ghostty can synthesize bold, italic, and bold italic styles if the font +/// does not have a specific style. For bold, this is done by drawing an +/// outline around the glyph of varying thickness. For italic, this is done by +/// applying a slant to the glyph. For bold italic, both of these are applied. +/// +/// Synthetic styles are not perfect and will generally not look as good +/// as a font that has the style natively. However, they are useful to +/// provide styled text when the font does not have the style. +/// +/// Set this to "false" or "true" to disable or enable synthetic styles +/// completely. You can disable specific styles using "no-bold", "no-italic", +/// and "no-bold-italic". You can disable multiple styles by separating them +/// with a comma. For example, "no-bold,no-italic". +/// +/// Available style keys are: `bold`, `italic`, `bold-italic`. +/// +/// If synthetic styles are disabled, then the regular style will be used +/// instead if the requested style is not available. If the font has the +/// requested style, then the font will be used as-is since the style is +/// not synthetic. +/// +/// Warning! An easy mistake is to disable `bold` or `italic` but not +/// `bold-italic`. Disabling only `bold` or `italic` will NOT disable either +/// in the `bold-italic` style. If you want to disable `bold-italic`, you must +/// explicitly disable it. You cannot partially disable `bold-italic`. +/// +/// By default, synthetic styles are enabled. +@"font-synthetic-style": FontSyntheticStyle = .{}, + /// Apply a font feature. This can be repeated multiple times to enable multiple /// font features. You can NOT set multiple font features with a single value /// (yet). @@ -3882,6 +3914,13 @@ pub const FontStyle = union(enum) { } }; +/// See `font-synthetic-style` for documentation. +pub const FontSyntheticStyle = packed struct { + bold: bool = true, + italic: bool = true, + @"bold-italic": bool = true, +}; + /// See "link" for documentation. pub const RepeatableLink = struct { const Self = @This(); diff --git a/src/font/Collection.zig b/src/font/Collection.zig index 842867930..4601a012e 100644 --- a/src/font/Collection.zig +++ b/src/font/Collection.zig @@ -18,6 +18,7 @@ const Collection = @This(); const std = @import("std"); const assert = std.debug.assert; const Allocator = std.mem.Allocator; +const config = @import("../config.zig"); const font = @import("main.zig"); const options = font.options; const DeferredFace = font.DeferredFace; @@ -207,7 +208,11 @@ pub const CompleteError = Allocator.Error || error{ /// This requires that a regular font face is already loaded. /// This is asserted. If a font style is missing, we will synthesize /// it if possible. Otherwise, we will use the regular font style. -pub fn completeStyles(self: *Collection, alloc: Allocator) CompleteError!void { +pub fn completeStyles( + self: *Collection, + alloc: Allocator, + synthetic_config: config.FontSyntheticStyle, +) CompleteError!void { // If every style has at least one entry then we're done! // This is the most common case. empty: { @@ -262,6 +267,12 @@ pub fn completeStyles(self: *Collection, alloc: Allocator) CompleteError!void { const italic_list = self.faces.getPtr(.italic); const have_italic = italic_list.count() > 0; if (!have_italic) italic: { + if (!synthetic_config.italic) { + log.info("italic style not available and synthetic italic disabled", .{}); + try italic_list.append(alloc, .{ .alias = regular_entry }); + break :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 }); @@ -276,6 +287,12 @@ pub fn completeStyles(self: *Collection, alloc: Allocator) CompleteError!void { const bold_list = self.faces.getPtr(.bold); const have_bold = bold_list.count() > 0; if (!have_bold) bold: { + if (!synthetic_config.bold) { + log.info("bold style not available and synthetic bold disabled", .{}); + try bold_list.append(alloc, .{ .alias = regular_entry }); + break :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 }); @@ -290,6 +307,12 @@ pub fn completeStyles(self: *Collection, alloc: Allocator) CompleteError!void { // 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) bold_italic: { + if (!synthetic_config.@"bold-italic") { + log.info("bold italic style not available and synthetic bold italic disabled", .{}); + try bold_italic_list.append(alloc, .{ .alias = regular_entry }); + break :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) { @@ -759,7 +782,7 @@ test completeStyles { try testing.expect(c.getIndex('A', .bold, .{ .any = {} }) == null); try testing.expect(c.getIndex('A', .italic, .{ .any = {} }) == null); try testing.expect(c.getIndex('A', .bold_italic, .{ .any = {} }) == null); - try c.completeStyles(alloc); + try c.completeStyles(alloc, .{}); try testing.expect(c.getIndex('A', .bold, .{ .any = {} }) != null); try testing.expect(c.getIndex('A', .italic, .{ .any = {} }) != null); try testing.expect(c.getIndex('A', .bold_italic, .{ .any = {} }) != null); diff --git a/src/font/SharedGridSet.zig b/src/font/SharedGridSet.zig index ef8feebc3..25d1f0410 100644 --- a/src/font/SharedGridSet.zig +++ b/src/font/SharedGridSet.zig @@ -128,7 +128,7 @@ pub fn ref( // Build our collection. This is the expensive operation that // involves finding fonts, loading them (maybe, some are deferred), // etc. - var c = try self.collection(&key, font_size); + var c = try self.collection(&key, font_size, config); errdefer c.deinit(self.alloc); // Setup our enabled/disabled styles @@ -156,6 +156,7 @@ fn collection( self: *SharedGridSet, key: *const Key, size: DesiredSize, + config: *const DerivedConfig, ) !Collection { // A quick note on memory management: // - font_lib is owned by the SharedGridSet @@ -300,7 +301,7 @@ fn collection( // Complete our styles to ensure we have something to satisfy every // possible style request. - try c.completeStyles(self.alloc); + try c.completeStyles(self.alloc, config.@"font-synthetic-style"); return c; } @@ -386,6 +387,7 @@ pub const DerivedConfig = struct { @"font-variation-italic": configpkg.RepeatableFontVariation, @"font-variation-bold-italic": configpkg.RepeatableFontVariation, @"font-codepoint-map": configpkg.RepeatableCodepointMap, + @"font-synthetic-style": configpkg.FontSyntheticStyle, @"adjust-cell-width": ?Metrics.Modifier, @"adjust-cell-height": ?Metrics.Modifier, @"adjust-font-baseline": ?Metrics.Modifier, @@ -416,6 +418,7 @@ pub const DerivedConfig = struct { .@"font-variation-italic" = try config.@"font-variation-italic".clone(alloc), .@"font-variation-bold-italic" = try config.@"font-variation-bold-italic".clone(alloc), .@"font-codepoint-map" = try config.@"font-codepoint-map".clone(alloc), + .@"font-synthetic-style" = config.@"font-synthetic-style", .@"adjust-cell-width" = config.@"adjust-cell-width", .@"adjust-cell-height" = config.@"adjust-cell-height", .@"adjust-font-baseline" = config.@"adjust-font-baseline",