mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-09-05 19:08:17 +00:00
font/sprite: remove yHalfs
and friends, use Fraction
Introduces `fill`, which fills between two `Fraction`s, use this instead of `yHalfs` and friends wherever they're used, which also means we can remove `rect`. This commit does change alignment of the vertical/horizontal eighths in certain cell sizes, but the change is for the better IMO. Also changes the center-point alignment of smooth mosaics for odd cell widths, but the change is no more than half a pixel at worst and is probably an improvement ultimately.
This commit is contained in:
@@ -15,9 +15,7 @@ const common = @import("common.zig");
|
||||
const Shade = common.Shade;
|
||||
const Quads = common.Quads;
|
||||
const Alignment = common.Alignment;
|
||||
const xHalfs = common.xHalfs;
|
||||
const yHalfs = common.yHalfs;
|
||||
const rect = common.rect;
|
||||
const fill = common.fill;
|
||||
|
||||
const font = @import("../../main.zig");
|
||||
const Sprite = @import("../../sprite.zig").Sprite;
|
||||
@@ -176,11 +174,8 @@ fn quadrant(
|
||||
canvas: *font.sprite.Canvas,
|
||||
comptime quads: Quads,
|
||||
) void {
|
||||
const x_halfs = xHalfs(metrics);
|
||||
const y_halfs = yHalfs(metrics);
|
||||
|
||||
if (quads.tl) rect(metrics, canvas, 0, 0, x_halfs[0], y_halfs[0]);
|
||||
if (quads.tr) rect(metrics, canvas, x_halfs[1], 0, metrics.cell_width, y_halfs[0]);
|
||||
if (quads.bl) rect(metrics, canvas, 0, y_halfs[1], x_halfs[0], metrics.cell_height);
|
||||
if (quads.br) rect(metrics, canvas, x_halfs[1], y_halfs[1], metrics.cell_width, metrics.cell_height);
|
||||
if (quads.tl) fill(metrics, canvas, .zero, .half, .zero, .half);
|
||||
if (quads.tr) fill(metrics, canvas, .half, .full, .zero, .half);
|
||||
if (quads.bl) fill(metrics, canvas, .zero, .half, .half, .full);
|
||||
if (quads.br) fill(metrics, canvas, .half, .full, .half, .full);
|
||||
}
|
||||
|
@@ -24,7 +24,6 @@ const Quads = common.Quads;
|
||||
const Corner = common.Corner;
|
||||
const Edge = common.Edge;
|
||||
const Alignment = common.Alignment;
|
||||
const rect = common.rect;
|
||||
const hline = common.hline;
|
||||
const vline = common.vline;
|
||||
const hlineMiddle = common.hlineMiddle;
|
||||
@@ -695,20 +694,6 @@ pub fn lightDiagonalCross(
|
||||
lightDiagonalUpperLeftToLowerRight(metrics, canvas);
|
||||
}
|
||||
|
||||
fn quadrant(
|
||||
metrics: font.Metrics,
|
||||
canvas: *font.sprite.Canvas,
|
||||
comptime quads: Quads,
|
||||
) void {
|
||||
const center_x = metrics.cell_width / 2 + metrics.cell_width % 2;
|
||||
const center_y = metrics.cell_height / 2 + metrics.cell_height % 2;
|
||||
|
||||
if (quads.tl) rect(metrics, canvas, 0, 0, center_x, center_y);
|
||||
if (quads.tr) rect(metrics, canvas, center_x, 0, metrics.cell_width, center_y);
|
||||
if (quads.bl) rect(metrics, canvas, 0, center_y, center_x, metrics.cell_height);
|
||||
if (quads.br) rect(metrics, canvas, center_x, center_y, metrics.cell_width, metrics.cell_height);
|
||||
}
|
||||
|
||||
pub fn arc(
|
||||
metrics: font.Metrics,
|
||||
canvas: *font.sprite.Canvas,
|
||||
|
@@ -135,6 +135,7 @@ pub const Fraction = enum {
|
||||
zero,
|
||||
|
||||
// Names based on eighths
|
||||
eighth,
|
||||
one_eighth,
|
||||
two_eighths,
|
||||
three_eighths,
|
||||
@@ -144,17 +145,19 @@ pub const Fraction = enum {
|
||||
seven_eighths,
|
||||
|
||||
// Names based on quarters
|
||||
quarter,
|
||||
one_quarter,
|
||||
two_quarters,
|
||||
three_quarters,
|
||||
|
||||
// Names based on thirds
|
||||
third,
|
||||
one_third,
|
||||
two_thirds,
|
||||
|
||||
// Names based on halves
|
||||
one_half,
|
||||
half,
|
||||
one_half,
|
||||
|
||||
// Alternative names for 1/2
|
||||
center,
|
||||
@@ -167,6 +170,43 @@ pub const Fraction = enum {
|
||||
one,
|
||||
full,
|
||||
|
||||
/// This can be indexed to get the fraction for `i/8`.
|
||||
pub const eighths: [9]Fraction = .{
|
||||
.zero,
|
||||
.one_eighth,
|
||||
.two_eighths,
|
||||
.three_eighths,
|
||||
.four_eighths,
|
||||
.five_eighths,
|
||||
.six_eighths,
|
||||
.seven_eighths,
|
||||
.one,
|
||||
};
|
||||
|
||||
/// This can be indexed to get the fraction for `i/4`.
|
||||
pub const quarters: [5]Fraction = .{
|
||||
.zero,
|
||||
.one_quarter,
|
||||
.two_quarters,
|
||||
.three_quarters,
|
||||
.one,
|
||||
};
|
||||
|
||||
/// This can be indexed to get the fraction for `i/3`.
|
||||
pub const thirds: [4]Fraction = .{
|
||||
.zero,
|
||||
.one_third,
|
||||
.two_thirds,
|
||||
.one,
|
||||
};
|
||||
|
||||
/// This can be indexed to get the fraction for `i/2`.
|
||||
pub const halves: [3]Fraction = .{
|
||||
.zero,
|
||||
.one_half,
|
||||
.one,
|
||||
};
|
||||
|
||||
/// Get the x position for this fraction across a particular
|
||||
/// size (width or height), assuming it will be used as the
|
||||
/// min (left/top) coordinate for a block.
|
||||
@@ -196,6 +236,19 @@ pub const Fraction = enum {
|
||||
return @intFromFloat(@round(self.fraction() * s));
|
||||
}
|
||||
|
||||
/// Get this fraction across a particular size (width/height).
|
||||
/// If you need an integer, use `min` or `max` instead, since
|
||||
/// they contain special logic for consistent alignment. This
|
||||
/// is for when you're drawing with paths and don't care about
|
||||
/// pixel alignment.
|
||||
///
|
||||
/// `size` can be any integer type, since it will be coerced
|
||||
/// with `@floatFromInt`.
|
||||
pub inline fn float(self: Fraction, size: anytype) f64 {
|
||||
return self.fraction() * @as(f64, @floatFromInt(size));
|
||||
}
|
||||
|
||||
/// Get a float for the fraction this represents.
|
||||
pub inline fn fraction(self: Fraction) f64 {
|
||||
return switch (self) {
|
||||
.start,
|
||||
@@ -204,23 +257,26 @@ pub const Fraction = enum {
|
||||
.zero,
|
||||
=> 0.0,
|
||||
|
||||
.eighth,
|
||||
.one_eighth,
|
||||
=> 0.125,
|
||||
|
||||
.quarter,
|
||||
.one_quarter,
|
||||
.two_eighths,
|
||||
=> 0.25,
|
||||
|
||||
.third,
|
||||
.one_third,
|
||||
=> 1.0 / 3.0,
|
||||
|
||||
.three_eighths,
|
||||
=> 0.375,
|
||||
|
||||
.half,
|
||||
.one_half,
|
||||
.two_quarters,
|
||||
.four_eighths,
|
||||
.half,
|
||||
.center,
|
||||
.middle,
|
||||
=> 0.5,
|
||||
@@ -248,52 +304,21 @@ pub const Fraction = enum {
|
||||
}
|
||||
};
|
||||
|
||||
test "sprite font fraction" {
|
||||
const testing = std.testing;
|
||||
|
||||
for (4..64) |s| {
|
||||
const metrics: font.Metrics = .calc(.{
|
||||
.cell_width = @floatFromInt(s),
|
||||
.ascent = @floatFromInt(s),
|
||||
.descent = 0.0,
|
||||
.line_gap = 0.0,
|
||||
.underline_thickness = 2.0,
|
||||
.strikethrough_thickness = 2.0,
|
||||
});
|
||||
|
||||
try testing.expectEqual(@as(i32, @intCast(xHalfs(metrics)[0])), Fraction.half.max(s));
|
||||
try testing.expectEqual(@as(i32, @intCast(xHalfs(metrics)[1])), Fraction.half.min(s));
|
||||
|
||||
try testing.expectEqual(@as(i32, @intCast(yThirds(metrics)[0])), Fraction.one_third.max(s));
|
||||
try testing.expectEqual(@as(i32, @intCast(yThirds(metrics)[1])), Fraction.one_third.min(s));
|
||||
try testing.expectEqual(@as(i32, @intCast(yThirds(metrics)[2])), Fraction.two_thirds.max(s));
|
||||
try testing.expectEqual(@as(i32, @intCast(yThirds(metrics)[3])), Fraction.two_thirds.min(s));
|
||||
|
||||
try testing.expectEqual(@as(i32, @intCast(yQuads(metrics)[0])), Fraction.one_quarter.max(s));
|
||||
try testing.expectEqual(@as(i32, @intCast(yQuads(metrics)[1])), Fraction.one_quarter.min(s));
|
||||
try testing.expectEqual(@as(i32, @intCast(yQuads(metrics)[2])), Fraction.two_quarters.max(s));
|
||||
try testing.expectEqual(@as(i32, @intCast(yQuads(metrics)[3])), Fraction.two_quarters.min(s));
|
||||
try testing.expectEqual(@as(i32, @intCast(yQuads(metrics)[4])), Fraction.three_quarters.max(s));
|
||||
try testing.expectEqual(@as(i32, @intCast(yQuads(metrics)[5])), Fraction.three_quarters.min(s));
|
||||
}
|
||||
}
|
||||
|
||||
/// Fill a rect, clamped to within the cell boundaries.
|
||||
///
|
||||
/// TODO: Eliminate usages of this, prefer `canvas.box`.
|
||||
pub fn rect(
|
||||
/// Fill a section of the cell, specified by a
|
||||
/// horizontal and vertical pair of fraction lines.
|
||||
pub fn fill(
|
||||
metrics: font.Metrics,
|
||||
canvas: *font.sprite.Canvas,
|
||||
x1: u32,
|
||||
y1: u32,
|
||||
x2: u32,
|
||||
y2: u32,
|
||||
x0: Fraction,
|
||||
x1: Fraction,
|
||||
y0: Fraction,
|
||||
y1: Fraction,
|
||||
) void {
|
||||
canvas.box(
|
||||
@intCast(@min(@max(x1, 0), metrics.cell_width)),
|
||||
@intCast(@min(@max(y1, 0), metrics.cell_height)),
|
||||
@intCast(@min(@max(x2, 0), metrics.cell_width)),
|
||||
@intCast(@min(@max(y2, 0), metrics.cell_height)),
|
||||
x0.min(metrics.cell_width),
|
||||
y0.min(metrics.cell_height),
|
||||
x1.max(metrics.cell_width),
|
||||
y1.max(metrics.cell_height),
|
||||
.on,
|
||||
);
|
||||
}
|
||||
@@ -351,58 +376,3 @@ pub fn hline(
|
||||
) void {
|
||||
canvas.box(x1, y, x2, y + @as(i32, @intCast(thickness_px)), .on);
|
||||
}
|
||||
|
||||
/// xHalfs[0] should be used as the right edge of a left-aligned half.
|
||||
/// xHalfs[1] should be used as the left edge of a right-aligned half.
|
||||
pub fn xHalfs(metrics: font.Metrics) [2]u32 {
|
||||
const float_width: f64 = @floatFromInt(metrics.cell_width);
|
||||
const half_width: u32 = @intFromFloat(@round(0.5 * float_width));
|
||||
return .{ half_width, metrics.cell_width - half_width };
|
||||
}
|
||||
|
||||
/// yHalfs[0] should be used as the bottom edge of a top-aligned half.
|
||||
/// yHalfs[1] should be used as the top edge of a bottom-aligned half.
|
||||
pub fn yHalfs(metrics: font.Metrics) [2]u32 {
|
||||
const float_height: f64 = @floatFromInt(metrics.cell_height);
|
||||
const half_height: u32 = @intFromFloat(@round(0.5 * float_height));
|
||||
return .{ half_height, metrics.cell_height - half_height };
|
||||
}
|
||||
|
||||
/// Use these values as such:
|
||||
/// yThirds[0] bottom edge of the first third.
|
||||
/// yThirds[1] top edge of the second third.
|
||||
/// yThirds[2] bottom edge of the second third.
|
||||
/// yThirds[3] top edge of the final third.
|
||||
pub fn yThirds(metrics: font.Metrics) [4]u32 {
|
||||
const float_height: f64 = @floatFromInt(metrics.cell_height);
|
||||
const one_third_height: u32 = @intFromFloat(@round(one_third * float_height));
|
||||
const two_thirds_height: u32 = @intFromFloat(@round(two_thirds * float_height));
|
||||
return .{
|
||||
one_third_height,
|
||||
metrics.cell_height - two_thirds_height,
|
||||
two_thirds_height,
|
||||
metrics.cell_height - one_third_height,
|
||||
};
|
||||
}
|
||||
|
||||
/// Use these values as such:
|
||||
/// yQuads[0] bottom edge of first quarter.
|
||||
/// yQuads[1] top edge of second quarter.
|
||||
/// yQuads[2] bottom edge of second quarter.
|
||||
/// yQuads[3] top edge of third quarter.
|
||||
/// yQuads[4] bottom edge of third quarter
|
||||
/// yQuads[5] top edge of fourth quarter.
|
||||
pub fn yQuads(metrics: font.Metrics) [6]u32 {
|
||||
const float_height: f64 = @floatFromInt(metrics.cell_height);
|
||||
const quarter_height: u32 = @intFromFloat(@round(0.25 * float_height));
|
||||
const half_height: u32 = @intFromFloat(@round(0.50 * float_height));
|
||||
const three_quarters_height: u32 = @intFromFloat(@round(0.75 * float_height));
|
||||
return .{
|
||||
quarter_height,
|
||||
metrics.cell_height - three_quarters_height,
|
||||
half_height,
|
||||
metrics.cell_height - half_height,
|
||||
three_quarters_height,
|
||||
metrics.cell_height - quarter_height,
|
||||
};
|
||||
}
|
||||
|
@@ -28,13 +28,12 @@ const z2d = @import("z2d");
|
||||
const common = @import("common.zig");
|
||||
const Thickness = common.Thickness;
|
||||
const Alignment = common.Alignment;
|
||||
const Fraction = common.Fraction;
|
||||
const Corner = common.Corner;
|
||||
const Quads = common.Quads;
|
||||
const Edge = common.Edge;
|
||||
const Shade = common.Shade;
|
||||
const xHalfs = common.xHalfs;
|
||||
const yThirds = common.yThirds;
|
||||
const rect = common.rect;
|
||||
const fill = common.fill;
|
||||
|
||||
const box = @import("box.zig");
|
||||
const block = @import("block.zig");
|
||||
@@ -121,16 +120,12 @@ pub fn draw1FB00_1FB3B(
|
||||
const sex: Sextants = @bitCast(@as(u6, @intCast(
|
||||
idx + (idx / 0x14) + 1,
|
||||
)));
|
||||
|
||||
const x_halfs = xHalfs(metrics);
|
||||
const y_thirds = yThirds(metrics);
|
||||
|
||||
if (sex.tl) rect(metrics, canvas, 0, 0, x_halfs[0], y_thirds[0]);
|
||||
if (sex.tr) rect(metrics, canvas, x_halfs[1], 0, metrics.cell_width, y_thirds[0]);
|
||||
if (sex.ml) rect(metrics, canvas, 0, y_thirds[1], x_halfs[0], y_thirds[2]);
|
||||
if (sex.mr) rect(metrics, canvas, x_halfs[1], y_thirds[1], metrics.cell_width, y_thirds[2]);
|
||||
if (sex.bl) rect(metrics, canvas, 0, y_thirds[3], x_halfs[0], metrics.cell_height);
|
||||
if (sex.br) rect(metrics, canvas, x_halfs[1], y_thirds[3], metrics.cell_width, metrics.cell_height);
|
||||
if (sex.tl) fill(metrics, canvas, .zero, .half, .zero, .one_third);
|
||||
if (sex.tr) fill(metrics, canvas, .half, .full, .zero, .one_third);
|
||||
if (sex.ml) fill(metrics, canvas, .zero, .half, .one_third, .two_thirds);
|
||||
if (sex.mr) fill(metrics, canvas, .half, .full, .one_third, .two_thirds);
|
||||
if (sex.bl) fill(metrics, canvas, .zero, .half, .two_thirds, .end);
|
||||
if (sex.br) fill(metrics, canvas, .half, .full, .two_thirds, .end);
|
||||
}
|
||||
|
||||
/// Smooth Mosaics
|
||||
@@ -465,17 +460,12 @@ pub fn draw1FB3C_1FB67(
|
||||
else => unreachable,
|
||||
};
|
||||
|
||||
const y_thirds = yThirds(metrics);
|
||||
const top: f64 = 0.0;
|
||||
// We average the edge positions for the y_thirds boundaries here
|
||||
// rather than having to deal with varying alignments depending on
|
||||
// the surrounding pieces. The most this will be off by is half of
|
||||
// a pixel, so hopefully it's not noticeable.
|
||||
const upper: f64 = 0.5 * (@as(f64, @floatFromInt(y_thirds[0])) + @as(f64, @floatFromInt(y_thirds[1])));
|
||||
const lower: f64 = 0.5 * (@as(f64, @floatFromInt(y_thirds[2])) + @as(f64, @floatFromInt(y_thirds[3])));
|
||||
const upper: f64 = Fraction.one_third.float(metrics.cell_height);
|
||||
const lower: f64 = Fraction.two_thirds.float(metrics.cell_height);
|
||||
const bottom: f64 = @floatFromInt(metrics.cell_height);
|
||||
const left: f64 = 0.0;
|
||||
const center: f64 = @round(@as(f64, @floatFromInt(metrics.cell_width)) / 2);
|
||||
const center: f64 = Fraction.half.float(metrics.cell_width);
|
||||
const right: f64 = @floatFromInt(metrics.cell_width);
|
||||
|
||||
var path = canvas.staticPath(12); // nodes.len = 0
|
||||
@@ -571,13 +561,14 @@ pub fn draw1FB70_1FB75(
|
||||
|
||||
const n = cp + 1 - 0x1fb70;
|
||||
|
||||
const x: u32 = @intFromFloat(
|
||||
@round(@as(f64, @floatFromInt(n)) * @as(f64, @floatFromInt(metrics.cell_width)) / 8),
|
||||
fill(
|
||||
metrics,
|
||||
canvas,
|
||||
Fraction.eighths[n],
|
||||
Fraction.eighths[n + 1],
|
||||
.top,
|
||||
.bottom,
|
||||
);
|
||||
const w: u32 = @intFromFloat(
|
||||
@round(@as(f64, @floatFromInt(metrics.cell_width)) / 8),
|
||||
);
|
||||
rect(metrics, canvas, x, 0, x + w, metrics.cell_height);
|
||||
}
|
||||
|
||||
/// Horizontal one eighth blocks
|
||||
@@ -593,21 +584,14 @@ pub fn draw1FB76_1FB7B(
|
||||
|
||||
const n = cp + 1 - 0x1fb76;
|
||||
|
||||
const h = @as(
|
||||
u32,
|
||||
@intFromFloat(@round(@as(f64, @floatFromInt(metrics.cell_height)) / 8)),
|
||||
fill(
|
||||
metrics,
|
||||
canvas,
|
||||
.left,
|
||||
.right,
|
||||
Fraction.eighths[n],
|
||||
Fraction.eighths[n + 1],
|
||||
);
|
||||
const y = @min(
|
||||
metrics.cell_height -| h,
|
||||
@as(
|
||||
u32,
|
||||
@intFromFloat(
|
||||
@round(@as(f64, @floatFromInt(n)) *
|
||||
@as(f64, @floatFromInt(metrics.cell_height)) / 8),
|
||||
),
|
||||
),
|
||||
);
|
||||
rect(metrics, canvas, 0, y, metrics.cell_width, y + h);
|
||||
}
|
||||
|
||||
pub fn draw1FB7C_1FB97(
|
||||
|
@@ -57,9 +57,7 @@ const common = @import("common.zig");
|
||||
const Thickness = common.Thickness;
|
||||
const Corner = common.Corner;
|
||||
const Shade = common.Shade;
|
||||
const xHalfs = common.xHalfs;
|
||||
const yQuads = common.yQuads;
|
||||
const rect = common.rect;
|
||||
const fill = common.fill;
|
||||
|
||||
const box = @import("box.zig");
|
||||
|
||||
@@ -122,17 +120,15 @@ pub fn draw1CD00_1CDE5(
|
||||
break :octants result;
|
||||
};
|
||||
|
||||
const x_halfs = xHalfs(metrics);
|
||||
const y_quads = yQuads(metrics);
|
||||
const oct = octants[cp - octant_min];
|
||||
if (oct.@"1") rect(metrics, canvas, 0, 0, x_halfs[0], y_quads[0]);
|
||||
if (oct.@"2") rect(metrics, canvas, x_halfs[1], 0, metrics.cell_width, y_quads[0]);
|
||||
if (oct.@"3") rect(metrics, canvas, 0, y_quads[1], x_halfs[0], y_quads[2]);
|
||||
if (oct.@"4") rect(metrics, canvas, x_halfs[1], y_quads[1], metrics.cell_width, y_quads[2]);
|
||||
if (oct.@"5") rect(metrics, canvas, 0, y_quads[3], x_halfs[0], y_quads[4]);
|
||||
if (oct.@"6") rect(metrics, canvas, x_halfs[1], y_quads[3], metrics.cell_width, y_quads[4]);
|
||||
if (oct.@"7") rect(metrics, canvas, 0, y_quads[5], x_halfs[0], metrics.cell_height);
|
||||
if (oct.@"8") rect(metrics, canvas, x_halfs[1], y_quads[5], metrics.cell_width, metrics.cell_height);
|
||||
if (oct.@"1") fill(metrics, canvas, .zero, .half, .zero, .one_quarter);
|
||||
if (oct.@"2") fill(metrics, canvas, .half, .full, .zero, .one_quarter);
|
||||
if (oct.@"3") fill(metrics, canvas, .zero, .half, .one_quarter, .two_quarters);
|
||||
if (oct.@"4") fill(metrics, canvas, .half, .full, .one_quarter, .two_quarters);
|
||||
if (oct.@"5") fill(metrics, canvas, .zero, .half, .two_quarters, .three_quarters);
|
||||
if (oct.@"6") fill(metrics, canvas, .half, .full, .two_quarters, .three_quarters);
|
||||
if (oct.@"7") fill(metrics, canvas, .zero, .half, .three_quarters, .end);
|
||||
if (oct.@"8") fill(metrics, canvas, .half, .full, .three_quarters, .end);
|
||||
}
|
||||
|
||||
// Separated Block Quadrants
|
||||
|
@@ -39,8 +39,6 @@ extend-ignore-re = [
|
||||
[default.extend-words]
|
||||
Pn = "Pn"
|
||||
thr = "thr"
|
||||
# Should be "halves", but for now skip it as it would make diff huge
|
||||
halfs = "halfs"
|
||||
# Swift oddities
|
||||
Requestor = "Requestor"
|
||||
iterm = "iterm"
|
||||
|
Reference in New Issue
Block a user