mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-10-09 19:36:45 +00:00
Compare commits
4 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
7071a22cb5 | ||
![]() |
a586b47dc9 | ||
![]() |
c8efb2a8c9 | ||
![]() |
62ed472d9e |
@@ -1,6 +1,6 @@
|
|||||||
.{
|
.{
|
||||||
.name = .ghostty,
|
.name = .ghostty,
|
||||||
.version = "1.2.1",
|
.version = "1.2.2",
|
||||||
.paths = .{""},
|
.paths = .{""},
|
||||||
.fingerprint = 0x64407a2a0b4147e5,
|
.fingerprint = 0x64407a2a0b4147e5,
|
||||||
.minimum_zig_version = "0.14.1",
|
.minimum_zig_version = "0.14.1",
|
||||||
|
@@ -52,8 +52,8 @@
|
|||||||
|
|
||||||
<releases>
|
<releases>
|
||||||
<!-- TODO: Generate this automatically -->
|
<!-- TODO: Generate this automatically -->
|
||||||
<release version="1.2.1" date="2025-10-06">
|
<release version="1.2.2" date="2025-10-08">
|
||||||
<url type="details">https://ghostty.org/docs/install/release-notes/1-2-1</url>
|
<url type="details">https://ghostty.org/docs/install/release-notes/1-2-2</url>
|
||||||
</release>
|
</release>
|
||||||
</releases>
|
</releases>
|
||||||
</component>
|
</component>
|
||||||
|
@@ -40,7 +40,7 @@
|
|||||||
in
|
in
|
||||||
stdenv.mkDerivation (finalAttrs: {
|
stdenv.mkDerivation (finalAttrs: {
|
||||||
pname = "ghostty";
|
pname = "ghostty";
|
||||||
version = "1.2.1";
|
version = "1.2.2";
|
||||||
|
|
||||||
# We limit source like this to try and reduce the amount of rebuilds as possible
|
# We limit source like this to try and reduce the amount of rebuilds as possible
|
||||||
# thus we only provide the source that is needed for the build
|
# thus we only provide the source that is needed for the build
|
||||||
|
@@ -20,7 +20,7 @@ const GitVersion = @import("GitVersion.zig");
|
|||||||
/// TODO: When Zig 0.14 is released, derive this from build.zig.zon directly.
|
/// TODO: When Zig 0.14 is released, derive this from build.zig.zon directly.
|
||||||
/// Until then this MUST match build.zig.zon and should always be the
|
/// Until then this MUST match build.zig.zon and should always be the
|
||||||
/// _next_ version to release.
|
/// _next_ version to release.
|
||||||
const app_version: std.SemanticVersion = .{ .major = 1, .minor = 2, .patch = 1 };
|
const app_version: std.SemanticVersion = .{ .major = 1, .minor = 2, .patch = 2 };
|
||||||
|
|
||||||
/// Standard build configuration options.
|
/// Standard build configuration options.
|
||||||
optimize: std.builtin.OptimizeMode,
|
optimize: std.builtin.OptimizeMode,
|
||||||
|
147
src/datastruct/comparison.zig
Normal file
147
src/datastruct/comparison.zig
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
// The contents of this file is largely based on testing.zig from the Zig 0.15.1
|
||||||
|
// stdlib, distributed under the MIT license, copyright (c) Zig contributors
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
/// Generic, recursive equality testing utility using approximate comparison for
|
||||||
|
/// floats and equality for everything else
|
||||||
|
///
|
||||||
|
/// Based on `std.testing.expectEqual` and `std.testing.expectEqualSlices`.
|
||||||
|
///
|
||||||
|
/// The relative tolerance is currently hardcoded to `sqrt(eps(float_type))`.
|
||||||
|
pub inline fn expectApproxEqual(expected: anytype, actual: anytype) !void {
|
||||||
|
const T = @TypeOf(expected, actual);
|
||||||
|
return expectApproxEqualInner(T, expected, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expectApproxEqualInner(comptime T: type, expected: T, actual: T) !void {
|
||||||
|
switch (@typeInfo(T)) {
|
||||||
|
// check approximate equality for floats
|
||||||
|
.float => {
|
||||||
|
const sqrt_eps = comptime std.math.sqrt(std.math.floatEps(T));
|
||||||
|
if (!std.math.approxEqRel(T, expected, actual, sqrt_eps)) {
|
||||||
|
print("expected approximately {any}, found {any}\n", .{ expected, actual });
|
||||||
|
return error.TestExpectedApproxEqual;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// recurse into containers
|
||||||
|
.array => {
|
||||||
|
const diff_index: usize = diff_index: {
|
||||||
|
const shortest = @min(expected.len, actual.len);
|
||||||
|
var index: usize = 0;
|
||||||
|
while (index < shortest) : (index += 1) {
|
||||||
|
expectApproxEqual(actual[index], expected[index]) catch break :diff_index index;
|
||||||
|
}
|
||||||
|
break :diff_index if (expected.len == actual.len) return else shortest;
|
||||||
|
};
|
||||||
|
print("slices not approximately equal. first significant difference occurs at index {d} (0x{X})\n", .{ diff_index, diff_index });
|
||||||
|
return error.TestExpectedApproxEqual;
|
||||||
|
},
|
||||||
|
.vector => |info| {
|
||||||
|
var i: usize = 0;
|
||||||
|
while (i < info.len) : (i += 1) {
|
||||||
|
expectApproxEqual(expected[i], actual[i]) catch {
|
||||||
|
print("index {d} incorrect. expected approximately {any}, found {any}\n", .{
|
||||||
|
i, expected[i], actual[i],
|
||||||
|
});
|
||||||
|
return error.TestExpectedApproxEqual;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.@"struct" => |structType| {
|
||||||
|
inline for (structType.fields) |field| {
|
||||||
|
try expectApproxEqual(@field(expected, field.name), @field(actual, field.name));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// unwrap unions, optionals, and error unions
|
||||||
|
.@"union" => |union_info| {
|
||||||
|
if (union_info.tag_type == null) {
|
||||||
|
// untagged unions can only be compared bitwise,
|
||||||
|
// so expectEqual is all we need
|
||||||
|
std.testing.expectEqual(expected, actual) catch {
|
||||||
|
return error.TestExpectedApproxEqual;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const Tag = std.meta.Tag(@TypeOf(expected));
|
||||||
|
|
||||||
|
const expectedTag = @as(Tag, expected);
|
||||||
|
const actualTag = @as(Tag, actual);
|
||||||
|
|
||||||
|
std.testing.expectEqual(expectedTag, actualTag) catch {
|
||||||
|
return error.TestExpectedApproxEqual;
|
||||||
|
};
|
||||||
|
|
||||||
|
// we only reach this switch if the tags are equal
|
||||||
|
switch (expected) {
|
||||||
|
inline else => |val, tag| try expectApproxEqual(val, @field(actual, @tagName(tag))),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.optional, .error_union => {
|
||||||
|
if (expected) |expected_payload| if (actual) |actual_payload| {
|
||||||
|
return expectApproxEqual(expected_payload, actual_payload);
|
||||||
|
};
|
||||||
|
// we only reach this point if there's at least one null or error,
|
||||||
|
// in which case expectEqual is all we need
|
||||||
|
std.testing.expectEqual(expected, actual) catch {
|
||||||
|
return error.TestExpectedApproxEqual;
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
// fall back to expectEqual for everything else
|
||||||
|
else => std.testing.expectEqual(expected, actual) catch {
|
||||||
|
return error.TestExpectedApproxEqual;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Copy of std.testing.print (not public)
|
||||||
|
fn print(comptime fmt: []const u8, args: anytype) void {
|
||||||
|
if (@inComptime()) {
|
||||||
|
@compileError(std.fmt.comptimePrint(fmt, args));
|
||||||
|
} else if (std.testing.backend_can_print) {
|
||||||
|
std.debug.print(fmt, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tests based on the `expectEqual` tests in the Zig stdlib
|
||||||
|
test "expectApproxEqual.union(enum)" {
|
||||||
|
const T = union(enum) {
|
||||||
|
a: i32,
|
||||||
|
b: f32,
|
||||||
|
};
|
||||||
|
|
||||||
|
const b10 = T{ .b = 10.0 };
|
||||||
|
const b10plus = T{ .b = 10.000001 };
|
||||||
|
|
||||||
|
try expectApproxEqual(b10, b10plus);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "expectApproxEqual nested array" {
|
||||||
|
const a = [2][2]f32{
|
||||||
|
[_]f32{ 1.0, 0.0 },
|
||||||
|
[_]f32{ 0.0, 1.0 },
|
||||||
|
};
|
||||||
|
|
||||||
|
const b = [2][2]f32{
|
||||||
|
[_]f32{ 1.000001, 0.0 },
|
||||||
|
[_]f32{ 0.0, 0.999999 },
|
||||||
|
};
|
||||||
|
|
||||||
|
try expectApproxEqual(a, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "expectApproxEqual vector" {
|
||||||
|
const a: @Vector(4, f32) = @splat(4.0);
|
||||||
|
const b: @Vector(4, f32) = @splat(4.000001);
|
||||||
|
|
||||||
|
try expectApproxEqual(a, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "expectApproxEqual struct" {
|
||||||
|
const a = .{ 1, @as(f32, 1.0) };
|
||||||
|
const b = .{ 1, @as(f32, 0.999999) };
|
||||||
|
|
||||||
|
try expectApproxEqual(a, b);
|
||||||
|
}
|
@@ -19,6 +19,7 @@ const std = @import("std");
|
|||||||
const assert = std.debug.assert;
|
const assert = std.debug.assert;
|
||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
const config = @import("../config.zig");
|
const config = @import("../config.zig");
|
||||||
|
const comparison = @import("../datastruct/comparison.zig");
|
||||||
const font = @import("main.zig");
|
const font = @import("main.zig");
|
||||||
const options = font.options;
|
const options = font.options;
|
||||||
const DeferredFace = font.DeferredFace;
|
const DeferredFace = font.DeferredFace;
|
||||||
@@ -1199,7 +1200,7 @@ test "metrics" {
|
|||||||
|
|
||||||
try c.updateMetrics();
|
try c.updateMetrics();
|
||||||
|
|
||||||
try std.testing.expectEqual(font.Metrics{
|
try comparison.expectApproxEqual(font.Metrics{
|
||||||
.cell_width = 8,
|
.cell_width = 8,
|
||||||
// The cell height is 17 px because the calculation is
|
// The cell height is 17 px because the calculation is
|
||||||
//
|
//
|
||||||
@@ -1229,12 +1230,12 @@ test "metrics" {
|
|||||||
.icon_height = 12.24,
|
.icon_height = 12.24,
|
||||||
.face_width = 8.0,
|
.face_width = 8.0,
|
||||||
.face_height = 16.784,
|
.face_height = 16.784,
|
||||||
.face_y = @round(3.04) - @as(f64, 3.04), // use f64, not comptime float, for exact match with runtime value
|
.face_y = -0.04,
|
||||||
}, c.metrics);
|
}, c.metrics);
|
||||||
|
|
||||||
// Resize should change metrics
|
// Resize should change metrics
|
||||||
try c.setSize(.{ .points = 24, .xdpi = 96, .ydpi = 96 });
|
try c.setSize(.{ .points = 24, .xdpi = 96, .ydpi = 96 });
|
||||||
try std.testing.expectEqual(font.Metrics{
|
try comparison.expectApproxEqual(font.Metrics{
|
||||||
.cell_width = 16,
|
.cell_width = 16,
|
||||||
.cell_height = 34,
|
.cell_height = 34,
|
||||||
.cell_baseline = 6,
|
.cell_baseline = 6,
|
||||||
@@ -1249,7 +1250,7 @@ test "metrics" {
|
|||||||
.icon_height = 24.48,
|
.icon_height = 24.48,
|
||||||
.face_width = 16.0,
|
.face_width = 16.0,
|
||||||
.face_height = 33.568,
|
.face_height = 33.568,
|
||||||
.face_y = @round(6.08) - @as(f64, 6.08), // use f64, not comptime float, for exact match with runtime value
|
.face_y = -0.08,
|
||||||
}, c.metrics);
|
}, c.metrics);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1493,29 +1494,7 @@ test "face metrics" {
|
|||||||
.{ narrowMetricsExpected, wideMetricsExpected },
|
.{ narrowMetricsExpected, wideMetricsExpected },
|
||||||
.{ narrowMetrics, wideMetrics },
|
.{ narrowMetrics, wideMetrics },
|
||||||
) |metricsExpected, metricsActual| {
|
) |metricsExpected, metricsActual| {
|
||||||
inline for (@typeInfo(font.Metrics.FaceMetrics).@"struct".fields) |field| {
|
try comparison.expectApproxEqual(metricsExpected, metricsActual);
|
||||||
const expected = @field(metricsExpected, field.name);
|
|
||||||
const actual = @field(metricsActual, field.name);
|
|
||||||
// Unwrap optional fields
|
|
||||||
const expectedValue, const actualValue = unwrap: switch (@typeInfo(field.type)) {
|
|
||||||
.optional => {
|
|
||||||
if (expected) |expectedValue| if (actual) |actualValue| {
|
|
||||||
break :unwrap .{ expectedValue, actualValue };
|
|
||||||
};
|
|
||||||
// Null values can be compared directly
|
|
||||||
try std.testing.expectEqual(expected, actual);
|
|
||||||
continue;
|
|
||||||
},
|
|
||||||
else => break :unwrap .{ expected, actual },
|
|
||||||
};
|
|
||||||
// All non-null values are floats
|
|
||||||
const eps = std.math.floatEps(@TypeOf(actualValue - expectedValue));
|
|
||||||
try std.testing.expectApproxEqRel(
|
|
||||||
expectedValue,
|
|
||||||
actualValue,
|
|
||||||
std.math.sqrt(eps),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify estimated metrics. icWidth() should equal the smaller of
|
// Verify estimated metrics. icWidth() should equal the smaller of
|
||||||
|
@@ -507,3 +507,196 @@ test "Variation.Id: slnt should be 1936486004" {
|
|||||||
try testing.expectEqual(@as(u32, 1936486004), @as(u32, @bitCast(id)));
|
try testing.expectEqual(@as(u32, 1936486004), @as(u32, @bitCast(id)));
|
||||||
try testing.expectEqualStrings("slnt", &(id.str()));
|
try testing.expectEqualStrings("slnt", &(id.str()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "Constraints" {
|
||||||
|
const comparison = @import("../datastruct/comparison.zig");
|
||||||
|
const getConstraint = @import("nerd_font_attributes.zig").getConstraint;
|
||||||
|
|
||||||
|
// Hardcoded data matches metrics from CoreText at size 12 and DPI 96.
|
||||||
|
|
||||||
|
// Define grid metrics (matches font-family = JetBrains Mono)
|
||||||
|
const metrics: Metrics = .{
|
||||||
|
.cell_width = 10,
|
||||||
|
.cell_height = 22,
|
||||||
|
.cell_baseline = 5,
|
||||||
|
.underline_position = 19,
|
||||||
|
.underline_thickness = 1,
|
||||||
|
.strikethrough_position = 12,
|
||||||
|
.strikethrough_thickness = 1,
|
||||||
|
.overline_position = 0,
|
||||||
|
.overline_thickness = 1,
|
||||||
|
.box_thickness = 1,
|
||||||
|
.cursor_thickness = 1,
|
||||||
|
.cursor_height = 22,
|
||||||
|
.icon_height = 44.48 / 3.0,
|
||||||
|
.face_width = 9.6,
|
||||||
|
.face_height = 21.12,
|
||||||
|
.face_y = 0.2,
|
||||||
|
};
|
||||||
|
|
||||||
|
// ASCII (no constraint).
|
||||||
|
{
|
||||||
|
const constraint: RenderOptions.Constraint = .none;
|
||||||
|
|
||||||
|
// BBox of 'x' from JetBrains Mono.
|
||||||
|
const glyph_x: GlyphSize = .{
|
||||||
|
.width = 6.784,
|
||||||
|
.height = 15.28,
|
||||||
|
.x = 1.408,
|
||||||
|
.y = 4.84,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Any constraint width: do nothing.
|
||||||
|
inline for (.{ 1, 2 }) |constraint_width| {
|
||||||
|
try comparison.expectApproxEqual(
|
||||||
|
glyph_x,
|
||||||
|
constraint.constrain(glyph_x, metrics, constraint_width),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Symbol (same constraint as hardcoded in Renderer.addGlyph).
|
||||||
|
{
|
||||||
|
const constraint: RenderOptions.Constraint = .{ .size = .fit };
|
||||||
|
|
||||||
|
// BBox of '■' (0x25A0 black square) from Iosevka.
|
||||||
|
// NOTE: This glyph is designed to span two cells.
|
||||||
|
const glyph_25A0: GlyphSize = .{
|
||||||
|
.width = 10.272,
|
||||||
|
.height = 10.272,
|
||||||
|
.x = 2.864,
|
||||||
|
.y = 5.304,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Constraint width 1: scale down and shift to fit a single cell.
|
||||||
|
try comparison.expectApproxEqual(
|
||||||
|
GlyphSize{
|
||||||
|
.width = metrics.face_width,
|
||||||
|
.height = metrics.face_width,
|
||||||
|
.x = 0,
|
||||||
|
.y = 5.64,
|
||||||
|
},
|
||||||
|
constraint.constrain(glyph_25A0, metrics, 1),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Constraint width 2: do nothing.
|
||||||
|
try comparison.expectApproxEqual(
|
||||||
|
glyph_25A0,
|
||||||
|
constraint.constrain(glyph_25A0, metrics, 2),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Emoji (same constraint as hardcoded in SharedGrid.renderGlyph).
|
||||||
|
{
|
||||||
|
const constraint: RenderOptions.Constraint = .{
|
||||||
|
.size = .cover,
|
||||||
|
.align_horizontal = .center,
|
||||||
|
.align_vertical = .center,
|
||||||
|
.pad_left = 0.025,
|
||||||
|
.pad_right = 0.025,
|
||||||
|
};
|
||||||
|
|
||||||
|
// BBox of '🥸' (0x1F978) from Apple Color Emoji.
|
||||||
|
const glyph_1F978: GlyphSize = .{
|
||||||
|
.width = 20,
|
||||||
|
.height = 20,
|
||||||
|
.x = 0.46,
|
||||||
|
.y = 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Constraint width 2: scale to cover two cells with padding, center;
|
||||||
|
try comparison.expectApproxEqual(
|
||||||
|
GlyphSize{
|
||||||
|
.width = 18.72,
|
||||||
|
.height = 18.72,
|
||||||
|
.x = 0.44,
|
||||||
|
.y = 1.4,
|
||||||
|
},
|
||||||
|
constraint.constrain(glyph_1F978, metrics, 2),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nerd Font default.
|
||||||
|
{
|
||||||
|
const constraint = getConstraint(0xea61).?;
|
||||||
|
|
||||||
|
// Verify that this is the constraint we expect.
|
||||||
|
try std.testing.expectEqual(.fit_cover1, constraint.size);
|
||||||
|
try std.testing.expectEqual(.icon, constraint.height);
|
||||||
|
try std.testing.expectEqual(.center1, constraint.align_horizontal);
|
||||||
|
try std.testing.expectEqual(.center1, constraint.align_vertical);
|
||||||
|
|
||||||
|
// BBox of '' (0xEA61 nf-cod-lightbulb) from Symbols Only.
|
||||||
|
// NOTE: This icon is part of a group, so the
|
||||||
|
// constraint applies to a larger bounding box.
|
||||||
|
const glyph_EA61: GlyphSize = .{
|
||||||
|
.width = 9.015625,
|
||||||
|
.height = 13.015625,
|
||||||
|
.x = 3.015625,
|
||||||
|
.y = 3.76525,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Constraint width 1: scale and shift group to fit a single cell.
|
||||||
|
try comparison.expectApproxEqual(
|
||||||
|
GlyphSize{
|
||||||
|
.width = 7.2125,
|
||||||
|
.height = 10.4125,
|
||||||
|
.x = 0.8125,
|
||||||
|
.y = 5.950695224719102,
|
||||||
|
},
|
||||||
|
constraint.constrain(glyph_EA61, metrics, 1),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Constraint width 2: no scaling; left-align and vertically center group.
|
||||||
|
try comparison.expectApproxEqual(
|
||||||
|
GlyphSize{
|
||||||
|
.width = glyph_EA61.width,
|
||||||
|
.height = glyph_EA61.height,
|
||||||
|
.x = 1.015625,
|
||||||
|
.y = 4.7483690308988775,
|
||||||
|
},
|
||||||
|
constraint.constrain(glyph_EA61, metrics, 2),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nerd Font stretch.
|
||||||
|
{
|
||||||
|
const constraint = getConstraint(0xe0c0).?;
|
||||||
|
|
||||||
|
// Verify that this is the constraint we expect.
|
||||||
|
try std.testing.expectEqual(.stretch, constraint.size);
|
||||||
|
try std.testing.expectEqual(.cell, constraint.height);
|
||||||
|
try std.testing.expectEqual(.start, constraint.align_horizontal);
|
||||||
|
try std.testing.expectEqual(.center1, constraint.align_vertical);
|
||||||
|
|
||||||
|
// BBox of ' ' (0xE0C0 nf-ple-flame_thick) from Symbols Only.
|
||||||
|
const glyph_E0C0: GlyphSize = .{
|
||||||
|
.width = 16.796875,
|
||||||
|
.height = 16.46875,
|
||||||
|
.x = -0.796875,
|
||||||
|
.y = 1.7109375,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Constraint width 1: stretch and position to exactly cover one cell.
|
||||||
|
try comparison.expectApproxEqual(
|
||||||
|
GlyphSize{
|
||||||
|
.width = @floatFromInt(metrics.cell_width),
|
||||||
|
.height = @floatFromInt(metrics.cell_height),
|
||||||
|
.x = 0,
|
||||||
|
.y = 0,
|
||||||
|
},
|
||||||
|
constraint.constrain(glyph_E0C0, metrics, 1),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Constraint width 1: stretch and position to exactly cover two cells.
|
||||||
|
try comparison.expectApproxEqual(
|
||||||
|
GlyphSize{
|
||||||
|
.width = @floatFromInt(2 * metrics.cell_width),
|
||||||
|
.height = @floatFromInt(metrics.cell_height),
|
||||||
|
.x = 0,
|
||||||
|
.y = 0,
|
||||||
|
},
|
||||||
|
constraint.constrain(glyph_E0C0, metrics, 2),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -363,7 +363,11 @@ pub const Face = struct {
|
|||||||
// We center all glyphs within the pixel-rounded and adjusted
|
// We center all glyphs within the pixel-rounded and adjusted
|
||||||
// cell width if it's larger than the face width, so that they
|
// cell width if it's larger than the face width, so that they
|
||||||
// aren't weirdly off to the left.
|
// aren't weirdly off to the left.
|
||||||
if (metrics.face_width < cell_width) {
|
//
|
||||||
|
// We don't do this if the glyph has a stretch constraint,
|
||||||
|
// since in that case the position was already calculated with the
|
||||||
|
// new cell width in mind.
|
||||||
|
if ((constraint.size != .stretch) and (metrics.face_width < cell_width)) {
|
||||||
// We add half the difference to re-center.
|
// We add half the difference to re-center.
|
||||||
x += (cell_width - metrics.face_width) / 2;
|
x += (cell_width - metrics.face_width) / 2;
|
||||||
}
|
}
|
||||||
@@ -378,18 +382,6 @@ pub const Face = struct {
|
|||||||
y = @round(y);
|
y = @round(y);
|
||||||
}
|
}
|
||||||
|
|
||||||
// We center all glyphs within the pixel-rounded and adjusted
|
|
||||||
// cell width if it's larger than the face width, so that they
|
|
||||||
// aren't weirdly off to the left.
|
|
||||||
//
|
|
||||||
// We don't do this if the glyph has a stretch constraint,
|
|
||||||
// since in that case the position was already calculated with the
|
|
||||||
// new cell width in mind.
|
|
||||||
if ((constraint.size != .stretch) and (metrics.face_width < cell_width)) {
|
|
||||||
// We add half the difference to re-center.
|
|
||||||
x += (cell_width - metrics.face_width) / 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
// We make an assumption that font smoothing ("thicken")
|
// We make an assumption that font smoothing ("thicken")
|
||||||
// adds no more than 1 extra pixel to any edge. We don't
|
// adds no more than 1 extra pixel to any edge. We don't
|
||||||
// add extra size if it's a sbix color font though, since
|
// add extra size if it's a sbix color font though, since
|
||||||
|
@@ -1177,43 +1177,6 @@ test "color emoji" {
|
|||||||
const glyph_id = ft_font.glyphIndex('🥸').?;
|
const glyph_id = ft_font.glyphIndex('🥸').?;
|
||||||
try testing.expect(ft_font.isColorGlyph(glyph_id));
|
try testing.expect(ft_font.isColorGlyph(glyph_id));
|
||||||
}
|
}
|
||||||
|
|
||||||
// resize
|
|
||||||
// TODO: Comprehensive tests for constraints,
|
|
||||||
// this is just an adapted legacy test.
|
|
||||||
{
|
|
||||||
const glyph = try ft_font.renderGlyph(
|
|
||||||
alloc,
|
|
||||||
&atlas,
|
|
||||||
ft_font.glyphIndex('🥸').?,
|
|
||||||
.{
|
|
||||||
.grid_metrics = .{
|
|
||||||
.cell_width = 13,
|
|
||||||
.cell_height = 24,
|
|
||||||
.cell_baseline = 0,
|
|
||||||
.underline_position = 0,
|
|
||||||
.underline_thickness = 0,
|
|
||||||
.strikethrough_position = 0,
|
|
||||||
.strikethrough_thickness = 0,
|
|
||||||
.overline_position = 0,
|
|
||||||
.overline_thickness = 0,
|
|
||||||
.box_thickness = 0,
|
|
||||||
.cursor_height = 0,
|
|
||||||
.icon_height = 0,
|
|
||||||
.face_width = 13,
|
|
||||||
.face_height = 24,
|
|
||||||
.face_y = 0,
|
|
||||||
},
|
|
||||||
.constraint_width = 2,
|
|
||||||
.constraint = .{
|
|
||||||
.size = .fit,
|
|
||||||
.align_horizontal = .center,
|
|
||||||
.align_vertical = .center,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
try testing.expectEqual(@as(u32, 24), glyph.height);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
test "mono to bgra" {
|
test "mono to bgra" {
|
||||||
|
@@ -95,9 +95,6 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
|
|||||||
/// Allocator that can be used
|
/// Allocator that can be used
|
||||||
alloc: std.mem.Allocator,
|
alloc: std.mem.Allocator,
|
||||||
|
|
||||||
/// MemoryPool for PageList pages which we use when cloning the screen.
|
|
||||||
page_pool: terminal.PageList.MemoryPool,
|
|
||||||
|
|
||||||
/// This mutex must be held whenever any state used in `drawFrame` is
|
/// This mutex must be held whenever any state used in `drawFrame` is
|
||||||
/// being modified, and also when it's being accessed in `drawFrame`.
|
/// being modified, and also when it's being accessed in `drawFrame`.
|
||||||
draw_mutex: std.Thread.Mutex = .{},
|
draw_mutex: std.Thread.Mutex = .{},
|
||||||
@@ -679,19 +676,8 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
|
|||||||
};
|
};
|
||||||
errdefer if (display_link) |v| v.release();
|
errdefer if (display_link) |v| v.release();
|
||||||
|
|
||||||
// We preheat the page pool with 4 pages- this is an arbitrary
|
|
||||||
// choice based on what seems reasonable for the number of pages
|
|
||||||
// used by the viewport area.
|
|
||||||
var page_pool: terminal.PageList.MemoryPool = try .init(
|
|
||||||
alloc,
|
|
||||||
std.heap.page_allocator,
|
|
||||||
4,
|
|
||||||
);
|
|
||||||
errdefer page_pool.deinit();
|
|
||||||
|
|
||||||
var result: Self = .{
|
var result: Self = .{
|
||||||
.alloc = alloc,
|
.alloc = alloc,
|
||||||
.page_pool = page_pool,
|
|
||||||
.config = options.config,
|
.config = options.config,
|
||||||
.surface_mailbox = options.surface_mailbox,
|
.surface_mailbox = options.surface_mailbox,
|
||||||
.grid_metrics = font_critical.metrics,
|
.grid_metrics = font_critical.metrics,
|
||||||
@@ -774,8 +760,6 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(self: *Self) void {
|
pub fn deinit(self: *Self) void {
|
||||||
self.page_pool.deinit();
|
|
||||||
|
|
||||||
self.swap_chain.deinit();
|
self.swap_chain.deinit();
|
||||||
|
|
||||||
if (DisplayLink != void) {
|
if (DisplayLink != void) {
|
||||||
@@ -1108,13 +1092,6 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
|
|||||||
full_rebuild: bool,
|
full_rebuild: bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Empty our page pool, but retain capacity.
|
|
||||||
self.page_pool.reset(.retain_capacity);
|
|
||||||
|
|
||||||
var arena: std.heap.ArenaAllocator = .init(self.alloc);
|
|
||||||
defer arena.deinit();
|
|
||||||
const alloc = arena.allocator();
|
|
||||||
|
|
||||||
// Update all our data as tightly as possible within the mutex.
|
// Update all our data as tightly as possible within the mutex.
|
||||||
var critical: Critical = critical: {
|
var critical: Critical = critical: {
|
||||||
// const start = try std.time.Instant.now();
|
// const start = try std.time.Instant.now();
|
||||||
@@ -1171,12 +1148,12 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
|
|||||||
// We used to share terminal state, but we've since learned through
|
// We used to share terminal state, but we've since learned through
|
||||||
// analysis that it is faster to copy the terminal state than to
|
// analysis that it is faster to copy the terminal state than to
|
||||||
// hold the lock while rebuilding GPU cells.
|
// hold the lock while rebuilding GPU cells.
|
||||||
const screen_copy = try state.terminal.screen.clonePool(
|
var screen_copy = try state.terminal.screen.clone(
|
||||||
alloc,
|
self.alloc,
|
||||||
&self.page_pool,
|
|
||||||
.{ .viewport = .{} },
|
.{ .viewport = .{} },
|
||||||
null,
|
null,
|
||||||
);
|
);
|
||||||
|
errdefer screen_copy.deinit();
|
||||||
|
|
||||||
// Whether to draw our cursor or not.
|
// Whether to draw our cursor or not.
|
||||||
const cursor_style = if (state.terminal.flags.password_input)
|
const cursor_style = if (state.terminal.flags.password_input)
|
||||||
@@ -1192,8 +1169,9 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
|
|||||||
const preedit: ?renderer.State.Preedit = preedit: {
|
const preedit: ?renderer.State.Preedit = preedit: {
|
||||||
if (cursor_style == null) break :preedit null;
|
if (cursor_style == null) break :preedit null;
|
||||||
const p = state.preedit orelse break :preedit null;
|
const p = state.preedit orelse break :preedit null;
|
||||||
break :preedit try p.clone(alloc);
|
break :preedit try p.clone(self.alloc);
|
||||||
};
|
};
|
||||||
|
errdefer if (preedit) |p| p.deinit(self.alloc);
|
||||||
|
|
||||||
// If we have Kitty graphics data, we enter a SLOW SLOW SLOW path.
|
// If we have Kitty graphics data, we enter a SLOW SLOW SLOW path.
|
||||||
// We only do this if the Kitty image state is dirty meaning only if
|
// We only do this if the Kitty image state is dirty meaning only if
|
||||||
@@ -1263,6 +1241,10 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
|
|||||||
.full_rebuild = full_rebuild,
|
.full_rebuild = full_rebuild,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
defer {
|
||||||
|
critical.screen.deinit();
|
||||||
|
if (critical.preedit) |p| p.deinit(self.alloc);
|
||||||
|
}
|
||||||
|
|
||||||
// Build our GPU cells
|
// Build our GPU cells
|
||||||
try self.rebuildCells(
|
try self.rebuildCells(
|
||||||
|
Reference in New Issue
Block a user