From 23cc50b12c1b670ff3f96c63437f61742d3b4d3c Mon Sep 17 00:00:00 2001 From: Qwerasd Date: Mon, 7 Jul 2025 09:09:37 -0600 Subject: [PATCH 1/3] font/Metrics: remove original_cell_width, no longer needed --- src/font/Metrics.zig | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/font/Metrics.zig b/src/font/Metrics.zig index f96d753b3..097f600cb 100644 --- a/src/font/Metrics.zig +++ b/src/font/Metrics.zig @@ -35,10 +35,6 @@ cursor_thickness: u32 = 1, /// The height in pixels of the cursor sprite. cursor_height: u32, -/// Original cell width in pixels. This is used to keep -/// glyphs centered if the cell width is adjusted wider. -original_cell_width: ?u32 = null, - /// Minimum acceptable values for some fields to prevent modifiers /// from being able to, for example, cause 0-thickness underlines. const Minimums = struct { @@ -214,11 +210,6 @@ pub fn apply(self: *Metrics, mods: ModifierSet) void { const new = @max(entry.value_ptr.apply(original), 1); if (new == original) continue; - // Preserve the original cell width if not set. - if (self.original_cell_width == null) { - self.original_cell_width = self.cell_width; - } - // Set the new value @field(self, @tagName(tag)) = new; From c47459b4a2887a8301927a92368f01da738743e3 Mon Sep 17 00:00:00 2001 From: Qwerasd Date: Mon, 7 Jul 2025 09:34:56 -0600 Subject: [PATCH 2/3] font: add icon height to nerd font constraints Icons were often WAY too big before because they were filling the whole cell in height, which isn't great lol. This commit adds an `icon_height` metric which is used to constrain glyphs that shouldn't be the size of the entire cell. --- src/font/Collection.zig | 2 ++ src/font/Metrics.zig | 19 +++++++++++- src/font/face.zig | 51 +++++++++++++++++++++---------- src/font/face/coretext.zig | 5 ++- src/font/face/freetype.zig | 6 ++-- src/font/nerd_font_attributes.zig | 6 ++-- src/font/nerd_font_codegen.py | 11 ++++--- 7 files changed, 71 insertions(+), 29 deletions(-) diff --git a/src/font/Collection.zig b/src/font/Collection.zig index 1d85d8a28..eb4349fb0 100644 --- a/src/font/Collection.zig +++ b/src/font/Collection.zig @@ -1072,6 +1072,7 @@ test "metrics" { .overline_thickness = 1, .box_thickness = 1, .cursor_height = 17, + .icon_height = 11, }, c.metrics); // Resize should change metrics @@ -1088,6 +1089,7 @@ test "metrics" { .overline_thickness = 2, .box_thickness = 2, .cursor_height = 34, + .icon_height = 23, }, c.metrics); } diff --git a/src/font/Metrics.zig b/src/font/Metrics.zig index 097f600cb..89f6a507f 100644 --- a/src/font/Metrics.zig +++ b/src/font/Metrics.zig @@ -35,6 +35,9 @@ cursor_thickness: u32 = 1, /// The height in pixels of the cursor sprite. cursor_height: u32, +/// The constraint height for nerd fonts icons. +icon_height: u32, + /// Minimum acceptable values for some fields to prevent modifiers /// from being able to, for example, cause 0-thickness underlines. const Minimums = struct { @@ -46,6 +49,7 @@ const Minimums = struct { const box_thickness = 1; const cursor_thickness = 1; const cursor_height = 1; + const icon_height = 1; }; /// Metrics extracted from a font face, based on @@ -129,7 +133,7 @@ pub fn calc(face: FaceMetrics) Metrics { // that the cell is large enough for the provided size, since we cast // it to an integer later. const cell_width = @ceil(face.cell_width); - const cell_height = @ceil(face.ascent - face.descent + face.line_gap); + const cell_height = @ceil(face.lineHeight()); // We split our line gap in two parts, and put half of it on the top // of the cell and the other half on the bottom, so that our text never @@ -173,6 +177,17 @@ pub fn calc(face: FaceMetrics) Metrics { (face.strikethrough_position orelse ex_height * 0.5 + strikethrough_thickness * 0.5)); + // The calculation for icon height in the nerd fonts patcher + // is two thirds cap height to one third line height, but we + // use an opinionated default of 1.2 * cap height instead. + // + // Doing this prevents fonts with very large line heights + // from having excessively oversized icons, and allows fonts + // with very small line heights to still have roomy icons. + // + // We do cap it at `cell_height` though for obvious reasons. + const icon_height = @min(cell_height, cap_height * 1.2); + var result: Metrics = .{ .cell_width = @intFromFloat(cell_width), .cell_height = @intFromFloat(cell_height), @@ -185,6 +200,7 @@ pub fn calc(face: FaceMetrics) Metrics { .overline_thickness = @intFromFloat(underline_thickness), .box_thickness = @intFromFloat(underline_thickness), .cursor_height = @intFromFloat(cell_height), + .icon_height = @intFromFloat(icon_height), }; // Ensure all metrics are within their allowable range. @@ -423,6 +439,7 @@ fn init() Metrics { .overline_thickness = 0, .box_thickness = 0, .cursor_height = 0, + .icon_height = 0, }; } diff --git a/src/font/face.zig b/src/font/face.zig index 245edcf4b..8c1171fb4 100644 --- a/src/font/face.zig +++ b/src/font/face.zig @@ -150,6 +150,9 @@ pub const RenderOptions = struct { /// Maximum number of cells horizontally to use. max_constraint_width: u2 = 2, + /// What to use as the height metric when constraining the glyph. + height: Height = .cell, + pub const Size = enum { /// Don't change the size of this glyph. none, @@ -176,6 +179,13 @@ pub const RenderOptions = struct { center, }; + pub const Height = enum { + /// Use the full height of the cell for constraining this glyph. + cell, + /// Use the "icon height" from the grid metrics as the height. + icon, + }; + /// The size and position of a glyph. pub const GlyphSize = struct { width: f64, @@ -189,35 +199,35 @@ pub const RenderOptions = struct { pub fn constrain( self: Constraint, glyph: GlyphSize, - /// Width of one cell. - cell_width: f64, - /// Height of one cell. - cell_height: f64, + metrics: Metrics, /// Number of cells horizontally available for this glyph. constraint_width: u2, ) GlyphSize { var g = glyph; - const available_width = - cell_width * @as(f64, @floatFromInt( - @min( - self.max_constraint_width, - constraint_width, - ), - )); + const available_width: f64 = @floatFromInt( + metrics.cell_width * @min( + self.max_constraint_width, + constraint_width, + ), + ); + const available_height: f64 = @floatFromInt(switch (self.height) { + .cell => metrics.cell_height, + .icon => metrics.icon_height, + }); const w = available_width - self.pad_left * available_width - self.pad_right * available_width; - const h = cell_height - - self.pad_top * cell_height - - self.pad_bottom * cell_height; + const h = available_height - + self.pad_top * available_height - + self.pad_bottom * available_height; // Subtract padding from the bearings so that our // alignment and sizing code works correctly. We // re-add before returning. g.x -= self.pad_left * available_width; - g.y -= self.pad_bottom * cell_height; + g.y -= self.pad_bottom * available_height; switch (self.size_horizontal) { .none => {}, @@ -319,7 +329,16 @@ pub const RenderOptions = struct { // Re-add our padding before returning. g.x += self.pad_left * available_width; - g.y += self.pad_bottom * cell_height; + g.y += self.pad_bottom * available_height; + + // If the available height is less than the cell height, we + // add half of the difference to center it in the full height. + // + // If necessary, in the future, we can adjust this to account + // for alignment, but that isn't necessary with any of the nf + // icons afaict. + const cell_height: f64 = @floatFromInt(metrics.cell_height); + g.y += (cell_height - available_height) / 2; return g; } diff --git a/src/font/face/coretext.zig b/src/font/face/coretext.zig index 00cc31b26..009b4b2b3 100644 --- a/src/font/face/coretext.zig +++ b/src/font/face/coretext.zig @@ -341,7 +341,7 @@ pub const Face = struct { const metrics = opts.grid_metrics; const cell_width: f64 = @floatFromInt(metrics.cell_width); - const cell_height: f64 = @floatFromInt(metrics.cell_height); + // const cell_height: f64 = @floatFromInt(metrics.cell_height); const glyph_size = opts.constraint.constrain( .{ @@ -350,8 +350,7 @@ pub const Face = struct { .x = rect.origin.x, .y = rect.origin.y + @as(f64, @floatFromInt(metrics.cell_baseline)), }, - cell_width, - cell_height, + metrics, opts.constraint_width, ); diff --git a/src/font/face/freetype.zig b/src/font/face/freetype.zig index ae3bd0968..f42868e5c 100644 --- a/src/font/face/freetype.zig +++ b/src/font/face/freetype.zig @@ -395,7 +395,7 @@ pub const Face = struct { const metrics = opts.grid_metrics; const cell_width: f64 = @floatFromInt(metrics.cell_width); - const cell_height: f64 = @floatFromInt(metrics.cell_height); + // const cell_height: f64 = @floatFromInt(metrics.cell_height); const glyph_x: f64 = f26dot6ToF64(glyph.*.metrics.horiBearingX); const glyph_y: f64 = f26dot6ToF64(glyph.*.metrics.horiBearingY) - glyph_height; @@ -407,8 +407,7 @@ pub const Face = struct { .x = glyph_x, .y = glyph_y + @as(f64, @floatFromInt(metrics.cell_baseline)), }, - cell_width, - cell_height, + metrics, opts.constraint_width, ); @@ -1058,6 +1057,7 @@ test "color emoji" { .overline_thickness = 0, .box_thickness = 0, .cursor_height = 0, + .icon_height = 0, }, .constraint_width = 2, .constraint = .{ .size_horizontal = .cover, .size_vertical = .cover, diff --git a/src/font/nerd_font_attributes.zig b/src/font/nerd_font_attributes.zig index 70920bb0a..e72c7a00e 100644 --- a/src/font/nerd_font_attributes.zig +++ b/src/font/nerd_font_attributes.zig @@ -25,6 +25,7 @@ pub fn getConstraint(cp: u21) Constraint { => .{ .size_horizontal = .cover, .size_vertical = .fit, + .height = .icon, .max_constraint_width = 1, .align_horizontal = .center, .align_vertical = .center, @@ -285,7 +286,7 @@ pub fn getConstraint(cp: u21) Constraint { 0xe0d0...0xe0d1, => .{ .size_horizontal = .cover, - .size_vertical = .cover, + .size_vertical = .fit, .align_horizontal = .start, .align_vertical = .center, }, @@ -294,7 +295,7 @@ pub fn getConstraint(cp: u21) Constraint { 0xe0d5, => .{ .size_horizontal = .cover, - .size_vertical = .cover, + .size_vertical = .fit, .align_horizontal = .center, .align_vertical = .center, }, @@ -362,6 +363,7 @@ pub fn getConstraint(cp: u21) Constraint { => .{ .size_horizontal = .fit, .size_vertical = .fit, + .height = .icon, .align_horizontal = .center, .align_vertical = .center, }, diff --git a/src/font/nerd_font_codegen.py b/src/font/nerd_font_codegen.py index e74b2ead1..4087b9ac6 100644 --- a/src/font/nerd_font_codegen.py +++ b/src/font/nerd_font_codegen.py @@ -180,16 +180,19 @@ def emit_zig_entry_multikey(codepoints: list[int], attr: PatchSetAttributeEntry) if "xy" in stretch: s += " .size_horizontal = .stretch,\n" s += " .size_vertical = .stretch,\n" - elif "!" in stretch: + elif "!" in stretch or "^" in stretch: s += " .size_horizontal = .cover,\n" s += " .size_vertical = .fit,\n" - elif "^" in stretch: - s += " .size_horizontal = .cover,\n" - s += " .size_vertical = .cover,\n" else: s += " .size_horizontal = .fit,\n" s += " .size_vertical = .fit,\n" + # `^` indicates that scaling should fill + # the whole cell, not just the icon height. + if "^" not in stretch: + s += " .height = .icon,\n" + + # There are two cases where we want to limit the constraint width to 1: # - If there's a `1` in the stretch mode string. # - If the stretch mode is `xy` and there's not an explicit `2`. From bcb6ee6db676c6f6c05fbb3ab43aa66c6b7b1433 Mon Sep 17 00:00:00 2001 From: Qwerasd Date: Mon, 7 Jul 2025 09:43:33 -0600 Subject: [PATCH 3/3] config: add adjust-icon-height option Metric modifier for the `icon_height` metric. --- src/config/Config.zig | 12 ++++++++++++ src/font/SharedGridSet.zig | 3 +++ 2 files changed, 15 insertions(+) diff --git a/src/config/Config.zig b/src/config/Config.zig index a53986bc9..8ca8d3154 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -396,6 +396,18 @@ pub const compatibility = std.StaticStringMap( /// Thickness in pixels or percentage adjustment of box drawing characters. /// See the notes about adjustments in `adjust-cell-width`. @"adjust-box-thickness": ?MetricModifier = null, +/// Height in pixels or percentage adjustment of maximum height for nerd font icons. +/// +/// Increasing this value will allow nerd font icons to be larger, but won't +/// necessarily force them to be. Decreasing this value will make nerd font +/// icons smaller. +/// +/// The default value for the icon height is 1.2 times the height of capital +/// letters in your primary font, so something like -16.6% would make icons +/// roughly the same height as capital letters. +/// +/// See the notes about adjustments in `adjust-cell-width`. +@"adjust-icon-height": ?MetricModifier = null, /// The method to use for calculating the cell width of a grapheme cluster. /// The default value is `unicode` which uses the Unicode standard to determine diff --git a/src/font/SharedGridSet.zig b/src/font/SharedGridSet.zig index b77b44f23..14a8babad 100644 --- a/src/font/SharedGridSet.zig +++ b/src/font/SharedGridSet.zig @@ -449,6 +449,7 @@ pub const DerivedConfig = struct { @"adjust-cursor-thickness": ?Metrics.Modifier, @"adjust-cursor-height": ?Metrics.Modifier, @"adjust-box-thickness": ?Metrics.Modifier, + @"adjust-icon-height": ?Metrics.Modifier, @"freetype-load-flags": font.face.FreetypeLoadFlags, /// Initialize a DerivedConfig. The config should be either a @@ -488,6 +489,7 @@ pub const DerivedConfig = struct { .@"adjust-cursor-thickness" = config.@"adjust-cursor-thickness", .@"adjust-cursor-height" = config.@"adjust-cursor-height", .@"adjust-box-thickness" = config.@"adjust-box-thickness", + .@"adjust-icon-height" = config.@"adjust-icon-height", .@"freetype-load-flags" = if (font.face.FreetypeLoadFlags != void) config.@"freetype-load-flags" else {}, // This must be last so the arena contains all our allocations @@ -634,6 +636,7 @@ pub const Key = struct { if (config.@"adjust-cursor-thickness") |m| try set.put(alloc, .cursor_thickness, m); if (config.@"adjust-cursor-height") |m| try set.put(alloc, .cursor_height, m); if (config.@"adjust-box-thickness") |m| try set.put(alloc, .box_thickness, m); + if (config.@"adjust-icon-height") |m| try set.put(alloc, .icon_height, m); break :set set; };