diff --git a/src/config/Config.zig b/src/config/Config.zig index 70f7b2ae8..4dd845371 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/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 f96d753b3..89f6a507f 100644 --- a/src/font/Metrics.zig +++ b/src/font/Metrics.zig @@ -35,9 +35,8 @@ 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, +/// 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. @@ -50,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 @@ -133,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 @@ -177,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), @@ -189,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. @@ -214,11 +226,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; @@ -432,6 +439,7 @@ fn init() Metrics { .overline_thickness = 0, .box_thickness = 0, .cursor_height = 0, + .icon_height = 0, }; } 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; }; 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 83f993715..c98f511b6 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`.