diff --git a/src/config/Config.zig b/src/config/Config.zig index de8274cab..6f342358f 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -147,6 +147,13 @@ const c = @cImport({ /// position is clamped to the height of a cell. If you set the underline /// position so high that it extends beyond the bottom of the cell size, /// it will be clamped to the bottom of the cell. +/// +/// "adjust-cell-height" has some additional behaviors to describe: +/// - The font will be centered vertically in the cell. +/// - The cursor will remain the same size as the font. +/// - Powerline glyphs will be adjusted along with the cell height so +/// that things like status lines continue to look aligned. +/// @"adjust-cell-width": ?MetricModifier = null, @"adjust-cell-height": ?MetricModifier = null, @"adjust-font-baseline": ?MetricModifier = null, diff --git a/src/font/face/Metrics.zig b/src/font/face/Metrics.zig index 3f4c76d86..0d2af177c 100644 --- a/src/font/face/Metrics.zig +++ b/src/font/face/Metrics.zig @@ -21,6 +21,11 @@ underline_thickness: u32, strikethrough_position: u32, strikethrough_thickness: u32, +/// Original cell width and height. These are used to render the cursor +/// in the original cell size after modification. +original_cell_width: ?u32 = null, +original_cell_height: ?u32 = null, + /// Apply a set of modifiers. pub fn apply(self: *Metrics, mods: ModifierSet) void { var it = mods.iterator(); @@ -31,8 +36,18 @@ pub fn apply(self: *Metrics, mods: ModifierSet) void { inline .cell_width, .cell_height, => |tag| { + // Compute the new value. If it is the same avoid the work. const original = @field(self, @tagName(tag)); const new = @max(entry.value_ptr.apply(original), 1); + if (new == original) continue; + + // Preserve the original cell width and height if not set. + if (self.original_cell_width == null) { + self.original_cell_width = self.cell_width; + self.original_cell_height = self.cell_height; + } + + // Set the new value @field(self, @tagName(tag)) = new; // For cell height, we have to also modify some positions @@ -42,10 +57,17 @@ pub fn apply(self: *Metrics, mods: ModifierSet) void { if (comptime tag == .cell_height) { // We split the difference in half because we want to // center the baseline in the cell. - const diff = (new - original) / 2; - self.cell_baseline += diff; - self.underline_position += diff; - self.strikethrough_position += diff; + if (new > original) { + const diff = (new - original) / 2; + self.cell_baseline +|= diff; + self.underline_position +|= diff; + self.strikethrough_position +|= diff; + } else { + const diff = (original - new) / 2; + self.cell_baseline -|= diff; + self.underline_position -|= diff; + self.strikethrough_position -|= diff; + } } }, @@ -124,18 +146,18 @@ pub const Modifier = union(enum) { pub const Key = key: { const field_infos = std.meta.fields(Metrics); var enumFields: [field_infos.len]std.builtin.Type.EnumField = undefined; + var count: usize = 0; for (field_infos, 0..) |field, i| { - enumFields[i] = .{ - .name = field.name, - .value = i, - }; + if (field.type != u32) continue; + enumFields[i] = .{ .name = field.name, .value = i }; + count += 1; } var decls = [_]std.builtin.Type.Declaration{}; break :key @Type(.{ .Enum = .{ - .tag_type = std.math.IntFittingRange(0, field_infos.len - 1), - .fields = &enumFields, + .tag_type = std.math.IntFittingRange(0, count - 1), + .fields = enumFields[0..count], .decls = &decls, .is_exhaustive = true, }, @@ -170,6 +192,48 @@ test "Metrics: apply modifiers" { try testing.expectEqual(@as(u32, 120), m.cell_width); } +test "Metrics: adjust cell height smaller" { + const testing = std.testing; + const alloc = testing.allocator; + + var set: ModifierSet = .{}; + defer set.deinit(alloc); + try set.put(alloc, .cell_height, .{ .percent = 0.5 }); + + var m: Metrics = init(); + m.cell_baseline = 50; + m.underline_position = 55; + m.strikethrough_position = 30; + m.cell_height = 100; + m.apply(set); + try testing.expectEqual(@as(u32, 50), m.cell_height); + try testing.expectEqual(@as(u32, 25), m.cell_baseline); + try testing.expectEqual(@as(u32, 30), m.underline_position); + try testing.expectEqual(@as(u32, 5), m.strikethrough_position); + try testing.expectEqual(@as(u32, 100), m.original_cell_height.?); +} + +test "Metrics: adjust cell height larger" { + const testing = std.testing; + const alloc = testing.allocator; + + var set: ModifierSet = .{}; + defer set.deinit(alloc); + try set.put(alloc, .cell_height, .{ .percent = 2 }); + + var m: Metrics = init(); + m.cell_baseline = 50; + m.underline_position = 55; + m.strikethrough_position = 30; + m.cell_height = 100; + m.apply(set); + try testing.expectEqual(@as(u32, 200), m.cell_height); + try testing.expectEqual(@as(u32, 100), m.cell_baseline); + try testing.expectEqual(@as(u32, 105), m.underline_position); + try testing.expectEqual(@as(u32, 80), m.strikethrough_position); + try testing.expectEqual(@as(u32, 100), m.original_cell_height.?); +} + test "Modifier: parse absolute" { const testing = std.testing; diff --git a/src/font/sprite/Face.zig b/src/font/sprite/Face.zig index e524a9fd4..2b480905c 100644 --- a/src/font/sprite/Face.zig +++ b/src/font/sprite/Face.zig @@ -68,13 +68,31 @@ pub fn renderGlyph( // Safe to ".?" because of the above assertion. return switch (Kind.init(cp).?) { .box => box: { + // For box fonts, we want to adjust the height of the box + // based on the original cell height, not the adjusted height. + // This is because adjusted cell height doesn't change font size. + const height, const offset = metrics: { + const metrics = opts.grid_metrics orelse break :metrics .{ self.height, 0 }; + const height = metrics.original_cell_height orelse break :metrics .{ self.height, 0 }; + + // If our height shrunk, then we use the original adjusted + // height because we don't want to overflow the cell. + if (height >= self.height) break :metrics .{ self.height, 0 }; + + // The offset is divided by two because it is vertically + // centered. + break :metrics .{ height, (self.height - height) / 2 }; + }; + const f: Box = .{ .width = width, - .height = self.height, + .height = height, .thickness = self.thickness, }; - break :box try f.renderGlyph(alloc, atlas, cp); + var g = try f.renderGlyph(alloc, atlas, cp); + g.offset_y += @intCast(offset); + break :box g; }, .underline => try underline.renderGlyph(