font(coretext): add config to adjust strength of font-thicken. (#4531)

This is achieved by rendering to an alpha-only context rather than a
normal single-channel context, and adjusting the brightness at which
CoreText thinks it's drawing the glyph, which affects how it applies
font smoothing (which is what `font-thicken` enables).
This commit is contained in:
Mitchell Hashimoto
2025-01-05 12:53:14 -08:00
committed by GitHub
5 changed files with 32 additions and 6 deletions

View File

@ -234,10 +234,20 @@ const c = @cImport({
/// i.e. new windows, tabs, etc. /// i.e. new windows, tabs, etc.
@"font-codepoint-map": RepeatableCodepointMap = .{}, @"font-codepoint-map": RepeatableCodepointMap = .{},
/// Draw fonts with a thicker stroke, if supported. This is only supported /// Draw fonts with a thicker stroke, if supported.
/// currently on macOS. /// This is currently only supported on macOS.
@"font-thicken": bool = false, @"font-thicken": bool = false,
/// Strength of thickening when `font-thicken` is enabled.
///
/// Valid values are integers between `0` and `255`. `0` does not correspond to
/// *no* thickening, rather it corresponds to the lightest available thickening.
///
/// Has no effect when `font-thicken` is set to `false`.
///
/// This is currently only supported on macOS.
@"font-thicken-strength": u8 = 255,
/// All of the configurations behavior adjust various metrics determined by the /// All of the configurations behavior adjust various metrics determined by the
/// font. The values can be integers (1, -1, etc.) or a percentage (20%, -15%, /// font. The values can be integers (1, -1, etc.) or a percentage (20%, -15%,
/// etc.). In each case, the values represent the amount to change the original /// etc.). In each case, the values represent the amount to change the original

View File

@ -100,6 +100,15 @@ pub const RenderOptions = struct {
/// ///
/// This only works with CoreText currently. /// This only works with CoreText currently.
thicken: bool = false, thicken: bool = false,
/// "Strength" of the thickening, between `0` and `255`.
/// Only has an effect when `thicken` is enabled.
///
/// `0` does not correspond to *no* thickening,
/// just the *lightest* thickening available.
///
/// CoreText only.
thicken_strength: u8 = 255,
}; };
test { test {

View File

@ -354,7 +354,7 @@ pub const Face = struct {
.depth = 1, .depth = 1,
.space = try macos.graphics.ColorSpace.createDeviceGray(), .space = try macos.graphics.ColorSpace.createDeviceGray(),
.context_opts = @intFromEnum(macos.graphics.BitmapInfo.alpha_mask) & .context_opts = @intFromEnum(macos.graphics.BitmapInfo.alpha_mask) &
@intFromEnum(macos.graphics.ImageAlphaInfo.none), @intFromEnum(macos.graphics.ImageAlphaInfo.only),
} else .{ } else .{
.color = true, .color = true,
.depth = 4, .depth = 4,
@ -398,7 +398,7 @@ pub const Face = struct {
if (color.color) if (color.color)
context.setRGBFillColor(ctx, 1, 1, 1, 0) context.setRGBFillColor(ctx, 1, 1, 1, 0)
else else
context.setGrayFillColor(ctx, 0, 0); context.setGrayFillColor(ctx, 1, 0);
context.fillRect(ctx, .{ context.fillRect(ctx, .{
.origin = .{ .x = 0, .y = 0 }, .origin = .{ .x = 0, .y = 0 },
.size = .{ .size = .{
@ -421,8 +421,9 @@ pub const Face = struct {
context.setRGBFillColor(ctx, 1, 1, 1, 1); context.setRGBFillColor(ctx, 1, 1, 1, 1);
context.setRGBStrokeColor(ctx, 1, 1, 1, 1); context.setRGBStrokeColor(ctx, 1, 1, 1, 1);
} else { } else {
context.setGrayFillColor(ctx, 1, 1); const strength: f64 = @floatFromInt(opts.thicken_strength);
context.setGrayStrokeColor(ctx, 1, 1); context.setGrayFillColor(ctx, strength / 255.0, 1);
context.setGrayStrokeColor(ctx, strength / 255.0, 1);
} }
// If we are drawing with synthetic bold then use a fill stroke // If we are drawing with synthetic bold then use a fill stroke

View File

@ -360,6 +360,7 @@ pub const DerivedConfig = struct {
arena: ArenaAllocator, arena: ArenaAllocator,
font_thicken: bool, font_thicken: bool,
font_thicken_strength: u8,
font_features: std.ArrayListUnmanaged([:0]const u8), font_features: std.ArrayListUnmanaged([:0]const u8),
font_styles: font.CodepointResolver.StyleStatus, font_styles: font.CodepointResolver.StyleStatus,
cursor_color: ?terminal.color.RGB, cursor_color: ?terminal.color.RGB,
@ -410,6 +411,7 @@ pub const DerivedConfig = struct {
return .{ return .{
.background_opacity = @max(0, @min(1, config.@"background-opacity")), .background_opacity = @max(0, @min(1, config.@"background-opacity")),
.font_thicken = config.@"font-thicken", .font_thicken = config.@"font-thicken",
.font_thicken_strength = config.@"font-thicken-strength",
.font_features = font_features.list, .font_features = font_features.list,
.font_styles = font_styles, .font_styles = font_styles,
@ -2837,6 +2839,7 @@ fn addGlyph(
.{ .{
.grid_metrics = self.grid_metrics, .grid_metrics = self.grid_metrics,
.thicken = self.config.font_thicken, .thicken = self.config.font_thicken,
.thicken_strength = self.config.font_thicken_strength,
}, },
); );

View File

@ -272,6 +272,7 @@ pub const DerivedConfig = struct {
arena: ArenaAllocator, arena: ArenaAllocator,
font_thicken: bool, font_thicken: bool,
font_thicken_strength: u8,
font_features: std.ArrayListUnmanaged([:0]const u8), font_features: std.ArrayListUnmanaged([:0]const u8),
font_styles: font.CodepointResolver.StyleStatus, font_styles: font.CodepointResolver.StyleStatus,
cursor_color: ?terminal.color.RGB, cursor_color: ?terminal.color.RGB,
@ -321,6 +322,7 @@ pub const DerivedConfig = struct {
return .{ return .{
.background_opacity = @max(0, @min(1, config.@"background-opacity")), .background_opacity = @max(0, @min(1, config.@"background-opacity")),
.font_thicken = config.@"font-thicken", .font_thicken = config.@"font-thicken",
.font_thicken_strength = config.@"font-thicken-strength",
.font_features = font_features.list, .font_features = font_features.list,
.font_styles = font_styles, .font_styles = font_styles,
@ -2093,6 +2095,7 @@ fn addGlyph(
.{ .{
.grid_metrics = self.grid_metrics, .grid_metrics = self.grid_metrics,
.thicken = self.config.font_thicken, .thicken = self.config.font_thicken,
.thicken_strength = self.config.font_thicken_strength,
}, },
); );