font: add cursor-height metric, and adjust- config for it. (#3062)

Subsumes #2580 (which has multiple conflicts with main due to recent
changes to metrics); I figured it'd be easier to just implement it this
way.

#2580 claimed to solve #2487 but I don't think it really does- ideally
we can think of a good way to configure each individual cursor type, but
I don't wanna just do something ad hoc and add a bunch of config keys
blindly so I limited the scope of this.
This commit is contained in:
Mitchell Hashimoto
2024-12-22 10:46:52 -08:00
committed by GitHub
6 changed files with 36 additions and 27 deletions

View File

@ -241,16 +241,15 @@ const c = @cImport({
/// `-100%`) can cause the terminal to be unusable. Use with caution and reason.
///
/// Some values are clamped to minimum or maximum values. This can make it
/// appear that certain values are ignored. For example, the underline 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.
/// appear that certain values are ignored. For example, many `*-thickness`
/// adjustments cannot go below 1px.
///
/// `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.
/// * The cursor will remain the same size as the font, but may be
/// adjusted separately with `adjust-cursor-height`.
///
/// * Powerline glyphs will be adjusted along with the cell height so
/// that things like status lines continue to look aligned.
@ -276,6 +275,9 @@ const c = @cImport({
@"adjust-overline-thickness": ?MetricModifier = null,
/// Thickness in pixels of the bar cursor and outlined rect cursor.
@"adjust-cursor-thickness": ?MetricModifier = null,
/// Height in pixels of the cursor. Currently applies to all cursor types:
/// bar, rect, and outlined rect.
@"adjust-cursor-height": ?MetricModifier = null,
/// Thickness in pixels of box drawing characters.
@"adjust-box-thickness": ?MetricModifier = null,

View File

@ -430,6 +430,7 @@ pub const DerivedConfig = struct {
@"adjust-overline-position": ?Metrics.Modifier,
@"adjust-overline-thickness": ?Metrics.Modifier,
@"adjust-cursor-thickness": ?Metrics.Modifier,
@"adjust-cursor-height": ?Metrics.Modifier,
@"adjust-box-thickness": ?Metrics.Modifier,
@"freetype-load-flags": font.face.FreetypeLoadFlags,
@ -468,6 +469,7 @@ pub const DerivedConfig = struct {
.@"adjust-overline-position" = config.@"adjust-overline-position",
.@"adjust-overline-thickness" = config.@"adjust-overline-thickness",
.@"adjust-cursor-thickness" = config.@"adjust-cursor-thickness",
.@"adjust-cursor-height" = config.@"adjust-cursor-height",
.@"adjust-box-thickness" = config.@"adjust-box-thickness",
.@"freetype-load-flags" = if (font.face.FreetypeLoadFlags != void) config.@"freetype-load-flags" else {},
@ -613,6 +615,7 @@ pub const Key = struct {
if (config.@"adjust-overline-position") |m| try set.put(alloc, .overline_position, m);
if (config.@"adjust-overline-thickness") |m| try set.put(alloc, .overline_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-box-thickness") |m| try set.put(alloc, .box_thickness, m);
break :set set;
};

View File

@ -32,10 +32,12 @@ box_thickness: u32,
/// because it is not determined by fonts but rather by user configuration.
cursor_thickness: u32 = 1,
/// Original cell width and height. These are used to render the cursor
/// in the original cell size after modification.
/// 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,
original_cell_height: ?u32 = null,
/// Minimum acceptable values for some fields to prevent modifiers
/// from being able to, for example, cause 0-thickness underlines.
@ -47,6 +49,7 @@ const Minimums = struct {
const overline_thickness = 1;
const box_thickness = 1;
const cursor_thickness = 1;
const cursor_height = 1;
};
const CalcOpts = struct {
@ -167,6 +170,7 @@ pub fn calc(opts: CalcOpts) Metrics {
.overline_position = 0,
.overline_thickness = @intFromFloat(underline_thickness),
.box_thickness = @intFromFloat(underline_thickness),
.cursor_height = @intFromFloat(cell_height),
};
// Ensure all metrics are within their allowable range.
@ -192,10 +196,9 @@ 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 and height if not set.
// Preserve the original cell width 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
@ -410,6 +413,7 @@ fn init() Metrics {
.overline_position = 0,
.overline_thickness = 0,
.box_thickness = 0,
.cursor_height = 0,
};
}
@ -440,12 +444,14 @@ test "Metrics: adjust cell height smaller" {
m.underline_position = 55;
m.strikethrough_position = 30;
m.cell_height = 100;
m.cursor_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.?);
// Cursor height is separate from cell height and does not follow it.
try testing.expectEqual(@as(u32, 100), m.cursor_height);
}
test "Metrics: adjust cell height larger" {
@ -461,12 +467,14 @@ test "Metrics: adjust cell height larger" {
m.underline_position = 55;
m.strikethrough_position = 30;
m.cell_height = 100;
m.cursor_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.?);
// Cursor height is separate from cell height and does not follow it.
try testing.expectEqual(@as(u32, 100), m.cursor_height);
}
test "Modifier: parse absolute" {

View File

@ -1045,6 +1045,7 @@ test "coretext: metrics" {
.overline_position = 0,
.overline_thickness = 1,
.box_thickness = 1,
.cursor_height = 17,
}, ct_font.metrics);
// Resize should change metrics
@ -1060,5 +1061,6 @@ test "coretext: metrics" {
.overline_position = 0,
.overline_thickness = 2,
.box_thickness = 2,
.cursor_height = 34,
}, ct_font.metrics);
}

View File

@ -906,6 +906,7 @@ test "color emoji" {
.overline_position = 0,
.overline_thickness = 0,
.box_thickness = 0,
.cursor_height = 0,
},
});
try testing.expectEqual(@as(u32, 24), glyph.height);
@ -952,6 +953,7 @@ test "metrics" {
.overline_position = 0,
.overline_thickness = 1,
.box_thickness = 1,
.cursor_height = 17,
}, ft_font.metrics);
// Resize should change metrics
@ -967,6 +969,7 @@ test "metrics" {
.overline_position = 0,
.overline_thickness = 2,
.box_thickness = 2,
.cursor_height = 34,
}, ft_font.metrics);
}

View File

@ -126,29 +126,20 @@ pub fn renderGlyph(
},
.cursor => cursor: {
// Cursors should be drawn with the original cell height if
// it has been adjusted larger, so they don't get stretched.
const height, const dy = adjust: {
const h = metrics.cell_height;
if (metrics.original_cell_height) |original| {
if (h > original) {
break :adjust .{ original, (h - original) / 2 };
}
}
break :adjust .{ h, 0 };
};
var g = try cursor.renderGlyph(
alloc,
atlas,
@enumFromInt(cp),
width,
height,
metrics.cursor_height,
metrics.cursor_thickness,
);
// Keep the cursor centered in the cell if it's shorter.
g.offset_y += @intCast(dy);
// Cursors are drawn at their specified height
// and are centered vertically within the cell.
const cursor_height: i32 = @intCast(metrics.cursor_height);
const cell_height: i32 = @intCast(metrics.cell_height);
g.offset_y += @divTrunc(cell_height - cursor_height, 2);
break :cursor g;
},