From 9601920b4dab1ddfb1e26ef4397a12a443f04c23 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 25 Aug 2022 12:29:28 -0700 Subject: [PATCH] font size is now in font points, determine size based on window DPI --- src/Grid.zig | 12 +++++++----- src/Window.zig | 19 ++++++++++++++++++- src/config.zig | 7 ++----- src/font/Face.zig | 41 ++++++++++++++++++++++++++++++++-------- src/font/FallbackSet.zig | 4 ++-- src/font/Family.zig | 4 ++-- 6 files changed, 64 insertions(+), 23 deletions(-) diff --git a/src/Grid.zig b/src/Grid.zig index eab81e522..967402a8d 100644 --- a/src/Grid.zig +++ b/src/Grid.zig @@ -11,7 +11,6 @@ const terminal = @import("terminal/main.zig"); const Terminal = terminal.Terminal; const gl = @import("opengl.zig"); const trace = @import("tracy").trace; -const Config = @import("config.zig").Config; const math = @import("math.zig"); const log = std.log.scoped(.grid); @@ -134,7 +133,10 @@ const GPUCellMode = enum(u8) { } }; -pub fn init(alloc: Allocator, config: *const Config) !Grid { +pub fn init( + alloc: Allocator, + font_size: font.Face.DesiredSize, +) !Grid { // Initialize our font atlas. We will initially populate the // font atlas with all the visible ASCII characters since they are common. var atlas = try Atlas.init(alloc, 512, .greyscale); @@ -153,8 +155,8 @@ pub fn init(alloc: Allocator, config: *const Config) !Grid { font_set.families.appendAssumeCapacity(fam: { var fam = try font.Family.init(atlas); errdefer fam.deinit(alloc); - try fam.loadFaceFromMemory(.regular, face_ttf, config.@"font-size"); - try fam.loadFaceFromMemory(.bold, face_bold_ttf, config.@"font-size"); + try fam.loadFaceFromMemory(.regular, face_ttf, font_size); + try fam.loadFaceFromMemory(.bold, face_bold_ttf, font_size); break :fam fam; }); @@ -162,7 +164,7 @@ pub fn init(alloc: Allocator, config: *const Config) !Grid { font_set.families.appendAssumeCapacity(fam: { var fam_emoji = try font.Family.init(atlas_color); errdefer fam_emoji.deinit(alloc); - try fam_emoji.loadFaceFromMemory(.regular, face_emoji_ttf, config.@"font-size"); + try fam_emoji.loadFaceFromMemory(.regular, face_emoji_ttf, font_size); break :fam fam_emoji; }); diff --git a/src/Window.zig b/src/Window.zig index 5eb806e20..154a9befb 100644 --- a/src/Window.zig +++ b/src/Window.zig @@ -14,6 +14,7 @@ const glfw = @import("glfw"); const gl = @import("opengl.zig"); const libuv = @import("libuv"); const Pty = @import("Pty.zig"); +const font = @import("font/main.zig"); const Command = @import("Command.zig"); const SegmentedPool = @import("segmented_pool.zig").SegmentedPool; const trace = @import("tracy").trace; @@ -192,6 +193,18 @@ pub fn create(alloc: Allocator, loop: libuv.Loop, config: *const Config) !*Windo } } + // Determine our DPI configurations so we can properly configure + // font points to pixels and handle other high-DPI scaling factors. + const content_scale = try window.getContentScale(); + const x_dpi = content_scale.x_scale * font.Face.default_dpi; + const y_dpi = content_scale.y_scale * font.Face.default_dpi; + log.debug("xscale={} yscale={} xdpi={} ydpi={}", .{ + content_scale.x_scale, + content_scale.y_scale, + x_dpi, + y_dpi, + }); + // Culling, probably not necessary. We have to change the winding // order since our 0,0 is top-left. gl.c.glEnable(gl.c.GL_CULL_FACE); @@ -203,7 +216,11 @@ pub fn create(alloc: Allocator, loop: libuv.Loop, config: *const Config) !*Windo // Create our terminal grid with the initial window size const window_size = try window.getSize(); - var grid = try Grid.init(alloc, config); + var grid = try Grid.init(alloc, .{ + .points = config.@"font-size", + .xdpi = @floatToInt(u32, x_dpi), + .ydpi = @floatToInt(u32, y_dpi), + }); try grid.setScreenSize(.{ .width = window_size.width, .height = window_size.height }); grid.background = .{ .r = config.background.r, diff --git a/src/config.zig b/src/config.zig index c8a622ce7..1fa3dc931 100644 --- a/src/config.zig +++ b/src/config.zig @@ -6,11 +6,8 @@ const inputpkg = @import("input.zig"); /// Config is the main config struct. These fields map directly to the /// CLI flag names hence we use a lot of `@""` syntax to support hyphens. pub const Config = struct { - /// Font size - /// TODO: this default size is too big, what we need to do is use a reasonable - /// size and then mult a high-DPI scaling factor. This is only high because - /// all our test machines are high-DPI right now. - @"font-size": u8 = 32, + /// Font size in points + @"font-size": u8 = 12, /// Background color for the window. background: Color = .{ .r = 0, .g = 0, .b = 0 }, diff --git a/src/font/Face.zig b/src/font/Face.zig index c5ba7002a..a1d11449c 100644 --- a/src/font/Face.zig +++ b/src/font/Face.zig @@ -6,6 +6,7 @@ const Face = @This(); const std = @import("std"); +const builtin = @import("builtin"); const assert = std.debug.assert; const testing = std.testing; const Allocator = std.mem.Allocator; @@ -22,6 +23,11 @@ ft_library: ftc.FT_Library, /// Our font face. ft_face: ftc.FT_Face = null, +/// If a DPI can't be calculated, this DPI is used. This is probably +/// wrong on modern devices so it is highly recommended you get the DPI +/// using whatever platform method you can. +pub const default_dpi = if (builtin.os.tag == .macos) 72 else 96; + pub fn init(lib: ftc.FT_Library) !Face { return Face{ .ft_library = lib, @@ -37,10 +43,28 @@ pub fn deinit(self: *Face) void { self.* = undefined; } -/// Loads a font to use. -/// -/// This can only be called if a font is not already loaded. -pub fn loadFaceFromMemory(self: *Face, source: [:0]const u8, size: u32) !void { +/// The desired size for loading a font. +pub const DesiredSize = struct { + // Desired size in points + points: u32, + + // The DPI of the screen so we can convert points to pixels. + xdpi: u32 = default_dpi, + ydpi: u32 = default_dpi, + + // Converts points to pixels + pub fn pixels(self: DesiredSize) u32 { + // 1 point = 1/72 inch + return (self.points * self.ydpi) / 72; + } +}; + +/// Loads a font to use. This can only be called if a font is not already loaded. +pub fn loadFaceFromMemory( + self: *Face, + source: [:0]const u8, + size: DesiredSize, +) !void { assert(self.ft_face == null); if (ftc.FT_New_Memory_Face( @@ -62,9 +86,10 @@ pub fn loadFaceFromMemory(self: *Face, source: [:0]const u8, size: u32) !void { // to what the user requested. Otherwise, we can choose an arbitrary // pixel size. if (!ftc.FT_HAS_FIXED_SIZES(self.ft_face)) { - if (ftc.FT_Set_Pixel_Sizes(self.ft_face, size, size) != ftok) + const size_26dot6 = size.points << 6; // mult by 64 + if (ftc.FT_Set_Char_Size(self.ft_face, 0, size_26dot6, size.xdpi, size.ydpi) != ftok) return error.FaceLoadFailed; - } else try self.selectSizeNearest(size); + } else try self.selectSizeNearest(size.pixels()); } /// Selects the fixed size in the loaded face that is closest to the @@ -202,7 +227,7 @@ test { var font = try init(ft_lib); defer font.deinit(); - try font.loadFaceFromMemory(testFont, 48); + try font.loadFaceFromMemory(testFont, .{ .points = 12 }); // Generate all visible ASCII var i: u8 = 32; @@ -226,6 +251,6 @@ test "color emoji" { var font = try init(ft_lib); defer font.deinit(); - try font.loadFaceFromMemory(testFont, 48); + try font.loadFaceFromMemory(testFont, .{ .points = 12 }); _ = try font.loadGlyph(alloc, &atlas, '🥸'); } diff --git a/src/font/FallbackSet.zig b/src/font/FallbackSet.zig index 416f8fef5..026d66e5c 100644 --- a/src/font/FallbackSet.zig +++ b/src/font/FallbackSet.zig @@ -128,12 +128,12 @@ test { var set: FallbackSet = .{}; try set.families.append(alloc, fam: { var fam = try Family.init(try Atlas.init(alloc, 512, .greyscale)); - try fam.loadFaceFromMemory(.regular, fontRegular, 48); + try fam.loadFaceFromMemory(.regular, fontRegular, .{ .points = 48 }); break :fam fam; }); try set.families.append(alloc, fam: { var fam = try Family.init(try Atlas.init(alloc, 512, .rgba)); - try fam.loadFaceFromMemory(.regular, fontEmoji, 48); + try fam.loadFaceFromMemory(.regular, fontEmoji, .{ .points = 48 }); break :fam fam; }); diff --git a/src/font/Family.zig b/src/font/Family.zig index f6c068078..5f28d0f01 100644 --- a/src/font/Family.zig +++ b/src/font/Family.zig @@ -72,7 +72,7 @@ pub fn loadFaceFromMemory( self: *Family, comptime style: Style, source: [:0]const u8, - size: u32, + size: Face.DesiredSize, ) !void { var face = try Face.init(self.ft_library); errdefer face.deinit(); @@ -148,7 +148,7 @@ test { var fam = try init(try Atlas.init(alloc, 512, .greyscale)); defer fam.deinit(alloc); defer fam.atlas.deinit(alloc); - try fam.loadFaceFromMemory(.regular, testFont, 48); + try fam.loadFaceFromMemory(.regular, testFont, .{ .points = 12 }); // Generate all visible ASCII var i: u8 = 32;