Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions src/config/Config.zig
Original file line number Diff line number Diff line change
Expand Up @@ -416,9 +416,12 @@ pub const compatibility = std.StaticStringMap(
/// 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.
/// This value only applies to icons that are constrained to a single cell by
/// neighboring characters. An icon that is free to spread across two cells
/// can always use up to the full line height of the primary font.
///
/// The default value is 2/3 times the height of capital letters in your primary
/// font plus 1/3 times the font's line height.
///
/// See the notes about adjustments in `adjust-cell-width`.
///
Expand Down
13 changes: 11 additions & 2 deletions src/font/Collection.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1213,6 +1213,9 @@ test "metrics" {
// and 1em should be the point size * dpi scale, so 12 * (96/72)
// which is 16, and 16 * 1.049 = 16.784, which finally is rounded
// to 17.
//
// The icon height is (2 * cap_height + face_height) / 3
// = (2 * 623 + 1049) / 3 = 765, and 16 * 0.765 = 12.24.
.cell_height = 17,
.cell_baseline = 3,
.underline_position = 17,
Expand All @@ -1223,7 +1226,10 @@ test "metrics" {
.overline_thickness = 1,
.box_thickness = 1,
.cursor_height = 17,
.icon_height = 11,
.icon_height = 12.24,
.face_width = 8.0,
.face_height = 16.784,
.face_y = @round(3.04) - @as(f64, 3.04), // use f64, not comptime float, for exact match with runtime value
}, c.metrics);

// Resize should change metrics
Expand All @@ -1240,7 +1246,10 @@ test "metrics" {
.overline_thickness = 2,
.box_thickness = 2,
.cursor_height = 34,
.icon_height = 23,
.icon_height = 24.48,
.face_width = 16.0,
.face_height = 33.568,
.face_y = @round(6.08) - @as(f64, 6.08), // use f64, not comptime float, for exact match with runtime value
}, c.metrics);
}

Expand Down
109 changes: 66 additions & 43 deletions src/font/Metrics.zig
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,17 @@ cursor_thickness: u32 = 1,
cursor_height: u32,

/// The constraint height for nerd fonts icons.
icon_height: u32,
icon_height: f64,

/// 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 unrounded face width, used in scaling calculations.
face_width: f64,

/// The unrounded face height, used in scaling calculations.
face_height: f64,

/// The vertical bearing of face within the pixel-rounded
/// and possibly height-adjusted cell
face_y: f64,

/// Minimum acceptable values for some fields to prevent modifiers
/// from being able to, for example, cause 0-thickness underlines.
Expand All @@ -53,7 +59,9 @@ const Minimums = struct {
const box_thickness = 1;
const cursor_thickness = 1;
const cursor_height = 1;
const icon_height = 1;
const icon_height = 1.0;
const face_height = 1.0;
const face_width = 1.0;
};

/// Metrics extracted from a font face, based on
Expand Down Expand Up @@ -214,8 +222,10 @@ pub fn calc(face: FaceMetrics) Metrics {
// We use the ceiling of the provided cell width and height to ensure
// 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.lineHeight());
const face_width = face.cell_width;
const face_height = face.lineHeight();
const cell_width = @ceil(face_width);
const cell_height = @ceil(face_height);

// 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
Expand All @@ -224,7 +234,11 @@ pub fn calc(face: FaceMetrics) Metrics {

// Unlike all our other metrics, `cell_baseline` is relative to the
// BOTTOM of the cell.
const cell_baseline = @round(half_line_gap - face.descent);
const face_baseline = half_line_gap - face.descent;
const cell_baseline = @round(face_baseline);

// We keep track of the vertical bearing of the face in the cell
const face_y = cell_baseline - face_baseline;

// We calculate a top_to_baseline to make following calculations simpler.
const top_to_baseline = cell_height - cell_baseline;
Expand All @@ -237,16 +251,8 @@ pub fn calc(face: FaceMetrics) Metrics {
const underline_position = @round(top_to_baseline - face.underlinePosition());
const strikethrough_position = @round(top_to_baseline - face.strikethroughPosition());

// 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);
// Same heuristic as the font_patcher script
const icon_height = (2 * cap_height + face_height) / 3;

var result: Metrics = .{
.cell_width = @intFromFloat(cell_width),
Expand All @@ -260,7 +266,10 @@ 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),
.icon_height = icon_height,
.face_width = face_width,
.face_height = face_height,
.face_y = face_y,
};

// Ensure all metrics are within their allowable range.
Expand All @@ -286,11 +295,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;

Expand All @@ -307,6 +311,7 @@ pub fn apply(self: *Metrics, mods: ModifierSet) void {
const diff = new - original;
const diff_bottom = diff / 2;
const diff_top = diff - diff_bottom;
self.face_y += @floatFromInt(diff_bottom);
self.cell_baseline +|= diff_bottom;
self.underline_position +|= diff_top;
self.strikethrough_position +|= diff_top;
Expand All @@ -315,6 +320,7 @@ pub fn apply(self: *Metrics, mods: ModifierSet) void {
const diff = original - new;
const diff_bottom = diff / 2;
const diff_top = diff - diff_bottom;
self.face_y -= @floatFromInt(diff_bottom);
self.cell_baseline -|= diff_bottom;
self.underline_position -|= diff_top;
self.strikethrough_position -|= diff_top;
Expand Down Expand Up @@ -417,25 +423,35 @@ pub const Modifier = union(enum) {
/// Apply a modifier to a numeric value.
pub fn apply(self: Modifier, v: anytype) @TypeOf(v) {
const T = @TypeOf(v);
const signed = @typeInfo(T).int.signedness == .signed;
return switch (self) {
.percent => |p| percent: {
const p_clamped: f64 = @max(0, p);
const v_f64: f64 = @floatFromInt(v);
const applied_f64: f64 = @round(v_f64 * p_clamped);
const applied_T: T = @intFromFloat(applied_f64);
break :percent applied_T;
const Tinfo = @typeInfo(T);
return switch (comptime Tinfo) {
.int, .comptime_int => switch (self) {
.percent => |p| percent: {
const p_clamped: f64 = @max(0, p);
const v_f64: f64 = @floatFromInt(v);
const applied_f64: f64 = @round(v_f64 * p_clamped);
const applied_T: T = @intFromFloat(applied_f64);
break :percent applied_T;
},

.absolute => |abs| absolute: {
const v_i64: i64 = @intCast(v);
const abs_i64: i64 = @intCast(abs);
const applied_i64: i64 = v_i64 +| abs_i64;
const clamped_i64: i64 = if (Tinfo.int.signedness == .signed)
applied_i64
else
@max(0, applied_i64);
const applied_T: T = std.math.cast(T, clamped_i64) orelse
std.math.maxInt(T) * @as(T, @intCast(std.math.sign(clamped_i64)));
break :absolute applied_T;
},
},

.absolute => |abs| absolute: {
const v_i64: i64 = @intCast(v);
const abs_i64: i64 = @intCast(abs);
const applied_i64: i64 = v_i64 +| abs_i64;
const clamped_i64: i64 = if (signed) applied_i64 else @max(0, applied_i64);
const applied_T: T = std.math.cast(T, clamped_i64) orelse
std.math.maxInt(T) * @as(T, @intCast(std.math.sign(clamped_i64)));
break :absolute applied_T;
.float, .comptime_float => return switch (self) {
.percent => |p| v * @max(0, p),
.absolute => |abs| v + @as(T, @floatFromInt(abs)),
},
else => {},
};
}

Expand Down Expand Up @@ -481,7 +497,7 @@ pub const Key = key: {
var enumFields: [field_infos.len]std.builtin.Type.EnumField = undefined;
var count: usize = 0;
for (field_infos, 0..) |field, i| {
if (field.type != u32 and field.type != i32) continue;
if (field.type != u32 and field.type != i32 and field.type != f64) continue;
enumFields[i] = .{ .name = field.name, .value = i };
count += 1;
}
Expand Down Expand Up @@ -512,7 +528,10 @@ fn init() Metrics {
.overline_thickness = 0,
.box_thickness = 0,
.cursor_height = 0,
.icon_height = 0,
.icon_height = 0.0,
.face_width = 0.0,
.face_height = 0.0,
.face_y = 0.0,
};
}

Expand Down Expand Up @@ -542,13 +561,15 @@ test "Metrics: adjust cell height smaller" {
try set.put(alloc, .cell_height, .{ .percent = 0.75 });

var m: Metrics = init();
m.face_y = 0.33;
m.cell_baseline = 50;
m.underline_position = 55;
m.strikethrough_position = 30;
m.overline_position = 0;
m.cell_height = 100;
m.cursor_height = 100;
m.apply(set);
try testing.expectEqual(-11.67, m.face_y);
try testing.expectEqual(@as(u32, 75), m.cell_height);
try testing.expectEqual(@as(u32, 38), m.cell_baseline);
try testing.expectEqual(@as(u32, 42), m.underline_position);
Expand All @@ -570,13 +591,15 @@ test "Metrics: adjust cell height larger" {
try set.put(alloc, .cell_height, .{ .percent = 1.75 });

var m: Metrics = init();
m.face_y = 0.33;
m.cell_baseline = 50;
m.underline_position = 55;
m.strikethrough_position = 30;
m.overline_position = 0;
m.cell_height = 100;
m.cursor_height = 100;
m.apply(set);
try testing.expectEqual(37.33, m.face_y);
try testing.expectEqual(@as(u32, 175), m.cell_height);
try testing.expectEqual(@as(u32, 87), m.cell_baseline);
try testing.expectEqual(@as(u32, 93), m.underline_position);
Expand Down
8 changes: 3 additions & 5 deletions src/font/SharedGrid.zig
Original file line number Diff line number Diff line change
Expand Up @@ -270,11 +270,9 @@ pub fn renderGlyph(
// Always use these constraints for emoji.
if (p == .emoji) {
render_opts.constraint = .{
// Make the emoji as wide as possible, scaling proportionally,
// but then scale it down as necessary if its new size exceeds
// the cell height.
.size_horizontal = .cover,
.size_vertical = .fit,
// Scale emoji to be as large as possible
// while preserving their aspect ratio.
.size = .cover,

// Center the emoji in its cells.
.align_horizontal = .center,
Expand Down
Loading