Merge branch 'main' into ssh-integration

This commit is contained in:
Jason Rayne
2025-07-07 10:01:22 -07:00
9 changed files with 85 additions and 37 deletions

View File

@ -396,6 +396,18 @@ pub const compatibility = std.StaticStringMap(
/// Thickness in pixels or percentage adjustment of box drawing characters. /// Thickness in pixels or percentage adjustment of box drawing characters.
/// See the notes about adjustments in `adjust-cell-width`. /// See the notes about adjustments in `adjust-cell-width`.
@"adjust-box-thickness": ?MetricModifier = null, @"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 method to use for calculating the cell width of a grapheme cluster.
/// The default value is `unicode` which uses the Unicode standard to determine /// The default value is `unicode` which uses the Unicode standard to determine

View File

@ -1072,6 +1072,7 @@ test "metrics" {
.overline_thickness = 1, .overline_thickness = 1,
.box_thickness = 1, .box_thickness = 1,
.cursor_height = 17, .cursor_height = 17,
.icon_height = 11,
}, c.metrics); }, c.metrics);
// Resize should change metrics // Resize should change metrics
@ -1088,6 +1089,7 @@ test "metrics" {
.overline_thickness = 2, .overline_thickness = 2,
.box_thickness = 2, .box_thickness = 2,
.cursor_height = 34, .cursor_height = 34,
.icon_height = 23,
}, c.metrics); }, c.metrics);
} }

View File

@ -35,9 +35,8 @@ cursor_thickness: u32 = 1,
/// The height in pixels of the cursor sprite. /// The height in pixels of the cursor sprite.
cursor_height: u32, cursor_height: u32,
/// Original cell width in pixels. This is used to keep /// The constraint height for nerd fonts icons.
/// glyphs centered if the cell width is adjusted wider. icon_height: u32,
original_cell_width: ?u32 = null,
/// Minimum acceptable values for some fields to prevent modifiers /// Minimum acceptable values for some fields to prevent modifiers
/// from being able to, for example, cause 0-thickness underlines. /// from being able to, for example, cause 0-thickness underlines.
@ -50,6 +49,7 @@ const Minimums = struct {
const box_thickness = 1; const box_thickness = 1;
const cursor_thickness = 1; const cursor_thickness = 1;
const cursor_height = 1; const cursor_height = 1;
const icon_height = 1;
}; };
/// Metrics extracted from a font face, based on /// 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 // that the cell is large enough for the provided size, since we cast
// it to an integer later. // it to an integer later.
const cell_width = @ceil(face.cell_width); 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 // 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 // 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 (face.strikethrough_position orelse
ex_height * 0.5 + strikethrough_thickness * 0.5)); 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 = .{ var result: Metrics = .{
.cell_width = @intFromFloat(cell_width), .cell_width = @intFromFloat(cell_width),
.cell_height = @intFromFloat(cell_height), .cell_height = @intFromFloat(cell_height),
@ -189,6 +200,7 @@ pub fn calc(face: FaceMetrics) Metrics {
.overline_thickness = @intFromFloat(underline_thickness), .overline_thickness = @intFromFloat(underline_thickness),
.box_thickness = @intFromFloat(underline_thickness), .box_thickness = @intFromFloat(underline_thickness),
.cursor_height = @intFromFloat(cell_height), .cursor_height = @intFromFloat(cell_height),
.icon_height = @intFromFloat(icon_height),
}; };
// Ensure all metrics are within their allowable range. // 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); const new = @max(entry.value_ptr.apply(original), 1);
if (new == original) continue; 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 // Set the new value
@field(self, @tagName(tag)) = new; @field(self, @tagName(tag)) = new;
@ -432,6 +439,7 @@ fn init() Metrics {
.overline_thickness = 0, .overline_thickness = 0,
.box_thickness = 0, .box_thickness = 0,
.cursor_height = 0, .cursor_height = 0,
.icon_height = 0,
}; };
} }

View File

@ -449,6 +449,7 @@ pub const DerivedConfig = struct {
@"adjust-cursor-thickness": ?Metrics.Modifier, @"adjust-cursor-thickness": ?Metrics.Modifier,
@"adjust-cursor-height": ?Metrics.Modifier, @"adjust-cursor-height": ?Metrics.Modifier,
@"adjust-box-thickness": ?Metrics.Modifier, @"adjust-box-thickness": ?Metrics.Modifier,
@"adjust-icon-height": ?Metrics.Modifier,
@"freetype-load-flags": font.face.FreetypeLoadFlags, @"freetype-load-flags": font.face.FreetypeLoadFlags,
/// Initialize a DerivedConfig. The config should be either a /// 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-thickness" = config.@"adjust-cursor-thickness",
.@"adjust-cursor-height" = config.@"adjust-cursor-height", .@"adjust-cursor-height" = config.@"adjust-cursor-height",
.@"adjust-box-thickness" = config.@"adjust-box-thickness", .@"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 {}, .@"freetype-load-flags" = if (font.face.FreetypeLoadFlags != void) config.@"freetype-load-flags" else {},
// This must be last so the arena contains all our allocations // 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-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-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-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; break :set set;
}; };

View File

@ -150,6 +150,9 @@ pub const RenderOptions = struct {
/// Maximum number of cells horizontally to use. /// Maximum number of cells horizontally to use.
max_constraint_width: u2 = 2, max_constraint_width: u2 = 2,
/// What to use as the height metric when constraining the glyph.
height: Height = .cell,
pub const Size = enum { pub const Size = enum {
/// Don't change the size of this glyph. /// Don't change the size of this glyph.
none, none,
@ -176,6 +179,13 @@ pub const RenderOptions = struct {
center, 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. /// The size and position of a glyph.
pub const GlyphSize = struct { pub const GlyphSize = struct {
width: f64, width: f64,
@ -189,35 +199,35 @@ pub const RenderOptions = struct {
pub fn constrain( pub fn constrain(
self: Constraint, self: Constraint,
glyph: GlyphSize, glyph: GlyphSize,
/// Width of one cell. metrics: Metrics,
cell_width: f64,
/// Height of one cell.
cell_height: f64,
/// Number of cells horizontally available for this glyph. /// Number of cells horizontally available for this glyph.
constraint_width: u2, constraint_width: u2,
) GlyphSize { ) GlyphSize {
var g = glyph; var g = glyph;
const available_width = const available_width: f64 = @floatFromInt(
cell_width * @as(f64, @floatFromInt( metrics.cell_width * @min(
@min( self.max_constraint_width,
self.max_constraint_width, constraint_width,
constraint_width, ),
), );
)); const available_height: f64 = @floatFromInt(switch (self.height) {
.cell => metrics.cell_height,
.icon => metrics.icon_height,
});
const w = available_width - const w = available_width -
self.pad_left * available_width - self.pad_left * available_width -
self.pad_right * available_width; self.pad_right * available_width;
const h = cell_height - const h = available_height -
self.pad_top * cell_height - self.pad_top * available_height -
self.pad_bottom * cell_height; self.pad_bottom * available_height;
// Subtract padding from the bearings so that our // Subtract padding from the bearings so that our
// alignment and sizing code works correctly. We // alignment and sizing code works correctly. We
// re-add before returning. // re-add before returning.
g.x -= self.pad_left * available_width; 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) { switch (self.size_horizontal) {
.none => {}, .none => {},
@ -319,7 +329,16 @@ pub const RenderOptions = struct {
// Re-add our padding before returning. // Re-add our padding before returning.
g.x += self.pad_left * available_width; 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; return g;
} }

View File

@ -341,7 +341,7 @@ pub const Face = struct {
const metrics = opts.grid_metrics; const metrics = opts.grid_metrics;
const cell_width: f64 = @floatFromInt(metrics.cell_width); 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( const glyph_size = opts.constraint.constrain(
.{ .{
@ -350,8 +350,7 @@ pub const Face = struct {
.x = rect.origin.x, .x = rect.origin.x,
.y = rect.origin.y + @as(f64, @floatFromInt(metrics.cell_baseline)), .y = rect.origin.y + @as(f64, @floatFromInt(metrics.cell_baseline)),
}, },
cell_width, metrics,
cell_height,
opts.constraint_width, opts.constraint_width,
); );

View File

@ -395,7 +395,7 @@ pub const Face = struct {
const metrics = opts.grid_metrics; const metrics = opts.grid_metrics;
const cell_width: f64 = @floatFromInt(metrics.cell_width); 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_x: f64 = f26dot6ToF64(glyph.*.metrics.horiBearingX);
const glyph_y: f64 = f26dot6ToF64(glyph.*.metrics.horiBearingY) - glyph_height; const glyph_y: f64 = f26dot6ToF64(glyph.*.metrics.horiBearingY) - glyph_height;
@ -407,8 +407,7 @@ pub const Face = struct {
.x = glyph_x, .x = glyph_x,
.y = glyph_y + @as(f64, @floatFromInt(metrics.cell_baseline)), .y = glyph_y + @as(f64, @floatFromInt(metrics.cell_baseline)),
}, },
cell_width, metrics,
cell_height,
opts.constraint_width, opts.constraint_width,
); );
@ -1058,6 +1057,7 @@ test "color emoji" {
.overline_thickness = 0, .overline_thickness = 0,
.box_thickness = 0, .box_thickness = 0,
.cursor_height = 0, .cursor_height = 0,
.icon_height = 0,
}, .constraint_width = 2, .constraint = .{ }, .constraint_width = 2, .constraint = .{
.size_horizontal = .cover, .size_horizontal = .cover,
.size_vertical = .cover, .size_vertical = .cover,

View File

@ -25,6 +25,7 @@ pub fn getConstraint(cp: u21) Constraint {
=> .{ => .{
.size_horizontal = .cover, .size_horizontal = .cover,
.size_vertical = .fit, .size_vertical = .fit,
.height = .icon,
.max_constraint_width = 1, .max_constraint_width = 1,
.align_horizontal = .center, .align_horizontal = .center,
.align_vertical = .center, .align_vertical = .center,
@ -285,7 +286,7 @@ pub fn getConstraint(cp: u21) Constraint {
0xe0d0...0xe0d1, 0xe0d0...0xe0d1,
=> .{ => .{
.size_horizontal = .cover, .size_horizontal = .cover,
.size_vertical = .cover, .size_vertical = .fit,
.align_horizontal = .start, .align_horizontal = .start,
.align_vertical = .center, .align_vertical = .center,
}, },
@ -294,7 +295,7 @@ pub fn getConstraint(cp: u21) Constraint {
0xe0d5, 0xe0d5,
=> .{ => .{
.size_horizontal = .cover, .size_horizontal = .cover,
.size_vertical = .cover, .size_vertical = .fit,
.align_horizontal = .center, .align_horizontal = .center,
.align_vertical = .center, .align_vertical = .center,
}, },
@ -362,6 +363,7 @@ pub fn getConstraint(cp: u21) Constraint {
=> .{ => .{
.size_horizontal = .fit, .size_horizontal = .fit,
.size_vertical = .fit, .size_vertical = .fit,
.height = .icon,
.align_horizontal = .center, .align_horizontal = .center,
.align_vertical = .center, .align_vertical = .center,
}, },

View File

@ -180,16 +180,19 @@ def emit_zig_entry_multikey(codepoints: list[int], attr: PatchSetAttributeEntry)
if "xy" in stretch: if "xy" in stretch:
s += " .size_horizontal = .stretch,\n" s += " .size_horizontal = .stretch,\n"
s += " .size_vertical = .stretch,\n" s += " .size_vertical = .stretch,\n"
elif "!" in stretch: elif "!" in stretch or "^" in stretch:
s += " .size_horizontal = .cover,\n" s += " .size_horizontal = .cover,\n"
s += " .size_vertical = .fit,\n" s += " .size_vertical = .fit,\n"
elif "^" in stretch:
s += " .size_horizontal = .cover,\n"
s += " .size_vertical = .cover,\n"
else: else:
s += " .size_horizontal = .fit,\n" s += " .size_horizontal = .fit,\n"
s += " .size_vertical = .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: # 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 there's a `1` in the stretch mode string.
# - If the stretch mode is `xy` and there's not an explicit `2`. # - If the stretch mode is `xy` and there's not an explicit `2`.