From d4a75492225e571f91fcd6ef79662563fad9be94 Mon Sep 17 00:00:00 2001 From: Qwerasd Date: Wed, 8 May 2024 14:23:20 -0400 Subject: [PATCH 1/9] feat(font): Non-integer point sizes Allows for high dpi displays to get odd numbered pixel sizes, for example, 13.5pt @ 2px/pt for 27px font. This implementation performs all the sizing calculations with f32, rounding to the nearest pixel size when it comes to rendering. In the future this can be enhanced by adding fractional scaling to support fractional pixel sizes. --- src/Surface.zig | 14 +++++++------- src/cli/args.zig | 6 ++++-- src/config/Config.zig | 8 ++++++-- src/font/SharedGridSet.zig | 4 +++- src/font/discovery.zig | 8 ++++---- src/font/face.zig | 9 ++++----- src/input/Binding.zig | 4 ++-- 7 files changed, 30 insertions(+), 23 deletions(-) diff --git a/src/Surface.zig b/src/Surface.zig index 8266f3dbf..84d8cec2b 100644 --- a/src/Surface.zig +++ b/src/Surface.zig @@ -194,7 +194,7 @@ const DerivedConfig = struct { arena: ArenaAllocator, /// For docs for these, see the associated config they are derived from. - original_font_size: u8, + original_font_size: f32, keybind: configpkg.Keybinds, clipboard_read: configpkg.ClipboardAccess, clipboard_write: configpkg.ClipboardAccess, @@ -321,8 +321,8 @@ pub fn init( // The font size we desire along with the DPI determined for the surface const font_size: font.face.DesiredSize = .{ .points = config.@"font-size", - .xdpi = @intFromFloat(x_dpi), - .ydpi = @intFromFloat(y_dpi), + .xdpi = x_dpi, + .ydpi = y_dpi, }; // Setup our font group. This will reuse an existing font group if @@ -1703,8 +1703,8 @@ pub fn contentScaleCallback(self: *Surface, content_scale: apprt.ContentScale) ! // Update our font size which is dependent on the DPI const size = size: { var size = self.font_size; - size.xdpi = @intFromFloat(x_dpi); - size.ydpi = @intFromFloat(y_dpi); + size.xdpi = x_dpi; + size.ydpi = y_dpi; break :size size; }; @@ -3011,7 +3011,7 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool log.debug("increase font size={}", .{delta}); var size = self.font_size; - size.points +|= delta; + size.points = size.points + delta; try self.setFontSize(size); }, @@ -3019,7 +3019,7 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool log.debug("decrease font size={}", .{delta}); var size = self.font_size; - size.points = @max(1, size.points -| delta); + size.points = @max(1, size.points - delta); try self.setFontSize(size); }, diff --git a/src/cli/args.zig b/src/cli/args.zig index 49c5152ac..707416e38 100644 --- a/src/cli/args.zig +++ b/src/cli/args.zig @@ -250,8 +250,10 @@ fn parseIntoField( 0, ) catch return error.InvalidValue, - f64 => std.fmt.parseFloat( - f64, + f32, + f64, + => |Float| std.fmt.parseFloat( + Float, value orelse return error.ValueRequired, ) catch return error.InvalidValue, diff --git a/src/config/Config.zig b/src/config/Config.zig index a2d6f39a8..c565d538d 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -99,8 +99,12 @@ const c = @cImport({ /// separate repetitive entries in your config). @"font-feature": RepeatableString = .{}, -/// Font size in points -@"font-size": u8 = switch (builtin.os.tag) { +/// Font size in points. This value can be a non-integer and the nearest integer +/// pixel size will be selected. If you have a high dpi display where 1pt = 2px +/// then you can get an odd numbered pixel size by specifying a half point. +/// +/// For example, 13.5pt @ 2px/pt = 27px +@"font-size": f32 = switch (builtin.os.tag) { // On macOS we default a little bigger since this tends to look better. This // is purely subjective but this is easy to modify. .macos => 13, diff --git a/src/font/SharedGridSet.zig b/src/font/SharedGridSet.zig index daf39b54f..5114f18a1 100644 --- a/src/font/SharedGridSet.zig +++ b/src/font/SharedGridSet.zig @@ -575,7 +575,9 @@ pub const Key = struct { /// Hash the key with the given hasher. pub fn hash(self: Key, hasher: anytype) void { const autoHash = std.hash.autoHash; - autoHash(hasher, self.font_size); + autoHash(hasher, @as(u32, @bitCast(self.font_size.points))); + autoHash(hasher, @as(u32, @bitCast(self.font_size.xdpi))); + autoHash(hasher, @as(u32, @bitCast(self.font_size.ydpi))); autoHash(hasher, self.descriptors.len); for (self.descriptors) |d| d.hash(hasher); self.codepoint_map.hash(hasher); diff --git a/src/font/discovery.zig b/src/font/discovery.zig index a93574e07..eb2451768 100644 --- a/src/font/discovery.zig +++ b/src/font/discovery.zig @@ -49,7 +49,7 @@ pub const Descriptor = struct { /// Font size in points that the font should support. For conversion /// to pixels, we will use 72 DPI for Mac and 96 DPI for everything else. /// (If pixel conversion is necessary, i.e. emoji fonts) - size: u16 = 0, + size: f32 = 0, /// True if we want to search specifically for a font that supports /// specific styles. @@ -69,7 +69,7 @@ pub const Descriptor = struct { autoHashStrat(hasher, self.family, .Deep); autoHashStrat(hasher, self.style, .Deep); autoHash(hasher, self.codepoint); - autoHash(hasher, self.size); + autoHash(hasher, @as(u32, @bitCast(self.size))); autoHash(hasher, self.bold); autoHash(hasher, self.italic); autoHash(hasher, self.monospace); @@ -125,7 +125,7 @@ pub const Descriptor = struct { } if (self.size > 0) assert(pat.add( .size, - .{ .integer = self.size }, + .{ .integer = @round(self.size) }, false, )); if (self.bold) assert(pat.add( @@ -183,7 +183,7 @@ pub const Descriptor = struct { // Set our size attribute if set if (self.size > 0) { - const size32 = @as(i32, @intCast(self.size)); + const size32: i32 = @intFromFloat(@round(self.size)); const size = try macos.foundation.Number.create( .sint32, &size32, diff --git a/src/font/face.zig b/src/font/face.zig index 815629b44..0028d7396 100644 --- a/src/font/face.zig +++ b/src/font/face.zig @@ -35,17 +35,16 @@ pub const Options = struct { /// The desired size for loading a font. pub const DesiredSize = struct { // Desired size in points - points: u8, + points: f32, // The DPI of the screen so we can convert points to pixels. - xdpi: u16 = default_dpi, - ydpi: u16 = default_dpi, + xdpi: f32 = default_dpi, + ydpi: f32 = default_dpi, // Converts points to pixels pub fn pixels(self: DesiredSize) u16 { // 1 point = 1/72 inch - const points_u16: u16 = @intCast(self.points); - return (points_u16 * self.ydpi) / 72; + return @intFromFloat(@round((self.points * self.ydpi) / 72)); } }; diff --git a/src/input/Binding.zig b/src/input/Binding.zig index 4a153035d..200fd6d58 100644 --- a/src/input/Binding.zig +++ b/src/input/Binding.zig @@ -158,8 +158,8 @@ pub const Action = union(enum) { paste_from_selection: void, /// Increase/decrease the font size by a certain amount. - increase_font_size: u8, - decrease_font_size: u8, + increase_font_size: f32, + decrease_font_size: f32, /// Reset the font size to the original configured size. reset_font_size: void, From 3156df261f39c8dd4f6e54907de4bb50ecc9b141 Mon Sep 17 00:00:00 2001 From: Qwerasd Date: Wed, 8 May 2024 14:47:01 -0400 Subject: [PATCH 2/9] fix a couple test failures --- src/config/c_get.zig | 8 ++++---- src/font/discovery.zig | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/config/c_get.zig b/src/config/c_get.zig index 442d5e6a3..ff3523c29 100644 --- a/src/config/c_get.zig +++ b/src/config/c_get.zig @@ -42,8 +42,8 @@ fn getValue(ptr_raw: *anyopaque, value: anytype) bool { ptr.* = @intCast(value); }, - f32, f64 => { - const ptr: *f64 = @ptrCast(@alignCast(ptr_raw)); + f32, f64 => |Float| { + const ptr: *Float = @ptrCast(@alignCast(ptr_raw)); ptr.* = @floatCast(value); }, @@ -102,9 +102,9 @@ test "u8" { defer c.deinit(); c.@"font-size" = 24; - var cval: c_uint = undefined; + var cval: f32 = undefined; try testing.expect(get(&c, .@"font-size", &cval)); - try testing.expectEqual(@as(c_uint, 24), cval); + try testing.expectEqual(@as(f32, 24), cval); } test "enum" { diff --git a/src/font/discovery.zig b/src/font/discovery.zig index eb2451768..c9176adc6 100644 --- a/src/font/discovery.zig +++ b/src/font/discovery.zig @@ -125,7 +125,7 @@ pub const Descriptor = struct { } if (self.size > 0) assert(pat.add( .size, - .{ .integer = @round(self.size) }, + .{ .integer = @intFromFloat(@round(self.size)) }, false, )); if (self.bold) assert(pat.add( From a9daba6d6d1beefaa4a74073c497192c26cea27c Mon Sep 17 00:00:00 2001 From: Qwerasd Date: Wed, 8 May 2024 14:56:38 -0400 Subject: [PATCH 3/9] fix freetype face size calculation --- src/font/face/freetype.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/font/face/freetype.zig b/src/font/face/freetype.zig index d32217a02..2860503b4 100644 --- a/src/font/face/freetype.zig +++ b/src/font/face/freetype.zig @@ -137,7 +137,7 @@ pub const Face = struct { // to what the user requested. Otherwise, we can choose an arbitrary // pixel size. if (face.isScalable()) { - const size_26dot6 = @as(i32, @intCast(size.points)) << 6; // mult by 64 + const size_26dot6: i32 = @intFromFloat(@round(size.points * 64)); try face.setCharSize(0, size_26dot6, size.xdpi, size.ydpi); } else try selectSizeNearest(face, size.pixels()); } From fa45c18a6aeea18132a6b8d5943ccf6df046bea1 Mon Sep 17 00:00:00 2001 From: Qwerasd Date: Wed, 8 May 2024 15:00:28 -0400 Subject: [PATCH 4/9] fix point size type --- src/font/DeferredFace.zig | 2 +- src/font/face/web_canvas.zig | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/font/DeferredFace.zig b/src/font/DeferredFace.zig index de4293ba1..8051895a4 100644 --- a/src/font/DeferredFace.zig +++ b/src/font/DeferredFace.zig @@ -371,7 +371,7 @@ pub const Wasm = struct { } } - export fn deferred_face_load(self: *DeferredFace, pts: u16) void { + export fn deferred_face_load(self: *DeferredFace, pts: f32) void { self.load(.{}, .{ .points = pts }) catch |err| { log.warn("error loading deferred face err={}", .{err}); return; diff --git a/src/font/face/web_canvas.zig b/src/font/face/web_canvas.zig index 6470f6a8b..036dfa921 100644 --- a/src/font/face/web_canvas.zig +++ b/src/font/face/web_canvas.zig @@ -497,7 +497,7 @@ pub const Wasm = struct { return face_new_(ptr, len, pts, p) catch null; } - fn face_new_(ptr: [*]const u8, len: usize, pts: u16, presentation: u16) !*Face { + fn face_new_(ptr: [*]const u8, len: usize, pts: f32, presentation: u16) !*Face { var face = try Face.initNamed( alloc, ptr[0..len], From d01db9f79398cc8ae0150271688c04ce6492494c Mon Sep 17 00:00:00 2001 From: Qwerasd Date: Wed, 8 May 2024 15:17:02 -0400 Subject: [PATCH 5/9] revert dpi type to u16 --- src/font/SharedGridSet.zig | 4 ++-- src/font/face.zig | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/font/SharedGridSet.zig b/src/font/SharedGridSet.zig index 5114f18a1..857368a13 100644 --- a/src/font/SharedGridSet.zig +++ b/src/font/SharedGridSet.zig @@ -576,8 +576,8 @@ pub const Key = struct { pub fn hash(self: Key, hasher: anytype) void { const autoHash = std.hash.autoHash; autoHash(hasher, @as(u32, @bitCast(self.font_size.points))); - autoHash(hasher, @as(u32, @bitCast(self.font_size.xdpi))); - autoHash(hasher, @as(u32, @bitCast(self.font_size.ydpi))); + autoHash(hasher, self.font_size.xdpi); + autoHash(hasher, self.font_size.ydpi); autoHash(hasher, self.descriptors.len); for (self.descriptors) |d| d.hash(hasher); self.codepoint_map.hash(hasher); diff --git a/src/font/face.zig b/src/font/face.zig index 0028d7396..8bcfb8209 100644 --- a/src/font/face.zig +++ b/src/font/face.zig @@ -38,13 +38,13 @@ pub const DesiredSize = struct { points: f32, // The DPI of the screen so we can convert points to pixels. - xdpi: f32 = default_dpi, - ydpi: f32 = default_dpi, + xdpi: u16 = default_dpi, + ydpi: u16 = default_dpi, // Converts points to pixels pub fn pixels(self: DesiredSize) u16 { // 1 point = 1/72 inch - return @intFromFloat(@round((self.points * self.ydpi) / 72)); + return @intFromFloat(@round((self.points * @as(f32, @floatFromInt(self.ydpi))) / 72)); } }; From 9056771509828e47a4a4be9db4bd49bde382a5c7 Mon Sep 17 00:00:00 2001 From: Qwerasd Date: Wed, 8 May 2024 15:20:17 -0400 Subject: [PATCH 6/9] keep font size in sane range --- src/Surface.zig | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/Surface.zig b/src/Surface.zig index 84d8cec2b..1cbf256b0 100644 --- a/src/Surface.zig +++ b/src/Surface.zig @@ -3008,18 +3008,25 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool ), .increase_font_size => |delta| { - log.debug("increase font size={}", .{delta}); + // Max delta is somewhat arbitrary. + const clamped_delta = @max(0, @min(255, delta)); + + log.debug("increase font size={}", .{clamped_delta}); var size = self.font_size; - size.points = size.points + delta; + // Max point size is somewhat arbitrary. + size.points = @min(size.points + clamped_delta, 255); try self.setFontSize(size); }, .decrease_font_size => |delta| { - log.debug("decrease font size={}", .{delta}); + // Max delta is somewhat arbitrary. + const clamped_delta = @max(0, @min(255, delta)); + + log.debug("decrease font size={}", .{clamped_delta}); var size = self.font_size; - size.points = @max(1, size.points - delta); + size.points = @max(1, size.points - clamped_delta); try self.setFontSize(size); }, From fb913f0d9ea07c72d880f302316079750dfe5e08 Mon Sep 17 00:00:00 2001 From: Qwerasd Date: Wed, 8 May 2024 16:14:30 -0400 Subject: [PATCH 7/9] Enable libpng in harfbuzz freetype dep Without this, this replaces the dep on the main level, breaking color glyphs --- pkg/harfbuzz/build.zig | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pkg/harfbuzz/build.zig b/pkg/harfbuzz/build.zig index 1329250ba..45d242699 100644 --- a/pkg/harfbuzz/build.zig +++ b/pkg/harfbuzz/build.zig @@ -41,7 +41,11 @@ pub fn build(b: *std.Build) !void { try apple_sdk.addPaths(b, module); } - const freetype_dep = b.dependency("freetype", .{ .target = target, .optimize = optimize }); + const freetype_dep = b.dependency("freetype", .{ + .target = target, + .optimize = optimize, + .@"enable-libpng" = true, + }); lib.linkLibrary(freetype_dep.artifact("freetype")); module.addIncludePath(freetype_dep.builder.dependency("freetype", .{}).path("include")); From 575a477be57d4a26f9a6a4e34b00a44d39596637 Mon Sep 17 00:00:00 2001 From: Qwerasd Date: Wed, 8 May 2024 16:27:14 -0400 Subject: [PATCH 8/9] cast dpi to int again forgot to change this back when I reverted the dpi from f32 to u16 --- src/Surface.zig | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Surface.zig b/src/Surface.zig index 1cbf256b0..fea791be1 100644 --- a/src/Surface.zig +++ b/src/Surface.zig @@ -321,8 +321,8 @@ pub fn init( // The font size we desire along with the DPI determined for the surface const font_size: font.face.DesiredSize = .{ .points = config.@"font-size", - .xdpi = x_dpi, - .ydpi = y_dpi, + .xdpi = @intFromFloat(x_dpi), + .ydpi = @intFromFloat(y_dpi), }; // Setup our font group. This will reuse an existing font group if @@ -1703,8 +1703,8 @@ pub fn contentScaleCallback(self: *Surface, content_scale: apprt.ContentScale) ! // Update our font size which is dependent on the DPI const size = size: { var size = self.font_size; - size.xdpi = x_dpi; - size.ydpi = y_dpi; + size.xdpi = @intFromFloat(x_dpi); + size.ydpi = @intFromFloat(y_dpi); break :size size; }; From 5df0935f82069f4d2f48603539680ddde0b42f59 Mon Sep 17 00:00:00 2001 From: Qwerasd Date: Wed, 8 May 2024 16:40:29 -0400 Subject: [PATCH 9/9] update font_size to f32 in libghostty --- include/ghostty.h | 2 +- macos/Sources/Ghostty/SurfaceView.swift | 2 +- src/apprt/embedded.zig | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/include/ghostty.h b/include/ghostty.h index 22d42449c..890e5f50e 100644 --- a/include/ghostty.h +++ b/include/ghostty.h @@ -379,7 +379,7 @@ typedef struct { ghostty_platform_u platform; void* userdata; double scale_factor; - uint8_t font_size; + float font_size; const char* working_directory; const char* command; } ghostty_surface_config_s; diff --git a/macos/Sources/Ghostty/SurfaceView.swift b/macos/Sources/Ghostty/SurfaceView.swift index d007a898c..4d755e70e 100644 --- a/macos/Sources/Ghostty/SurfaceView.swift +++ b/macos/Sources/Ghostty/SurfaceView.swift @@ -252,7 +252,7 @@ extension Ghostty { /// libghostty, usually from the Ghostty configuration. struct SurfaceConfiguration { /// Explicit font size to use in points - var fontSize: UInt8? = nil + var fontSize: Float32? = nil /// Explicit working directory to set var workingDirectory: String? = nil diff --git a/src/apprt/embedded.zig b/src/apprt/embedded.zig index 341de5eb7..4c9db1988 100644 --- a/src/apprt/embedded.zig +++ b/src/apprt/embedded.zig @@ -314,7 +314,7 @@ pub const Surface = struct { scale_factor: f64 = 1, /// The font size to inherit. If 0, default font size will be used. - font_size: u8 = 0, + font_size: f32 = 0, /// The working directory to load into. working_directory: [*:0]const u8 = "", @@ -1049,7 +1049,7 @@ pub const Surface = struct { } fn newSurfaceOptions(self: *const Surface) apprt.Surface.Options { - const font_size: u8 = font_size: { + const font_size: f32 = font_size: { if (!self.app.config.@"window-inherit-font-size") break :font_size 0; break :font_size self.core_surface.font_size.points; };