From c66042d6e06aeb004f346255bb5587b2b2af865f Mon Sep 17 00:00:00 2001 From: Qwerasd Date: Tue, 15 Oct 2024 11:59:52 -0400 Subject: [PATCH] font/sprite: address PR review feedback - Make canvas geometry primitives generic, use `Rect(u32)` for `rect` function, so that we don't have to worry about negatives or rounding. - Make `Quads` struct packed just in case it gets non-comptime use in the future. - Clarify comment on why we're discarding out of range pixels + runtime unreachable for any other type of error which we shouldn't ever see. - Move z2d import above in-tree imports. --- src/font/sprite/Box.zig | 62 ++++++++--------- src/font/sprite/Powerline.zig | 8 +-- src/font/sprite/canvas.zig | 124 ++++++++++++++++++++-------------- src/font/sprite/underline.zig | 26 +++---- 4 files changed, 120 insertions(+), 100 deletions(-) diff --git a/src/font/sprite/Box.zig b/src/font/sprite/Box.zig index 72568ffab..477708c99 100644 --- a/src/font/sprite/Box.zig +++ b/src/font/sprite/Box.zig @@ -20,11 +20,11 @@ const std = @import("std"); const assert = std.debug.assert; const Allocator = std.mem.Allocator; +const z2d = @import("z2d"); + const font = @import("../main.zig"); const Sprite = @import("../sprite.zig").Sprite; -const z2d = @import("z2d"); - const log = std.log.scoped(.box_font); /// The cell width and height because the boxes are fit perfectly @@ -72,7 +72,7 @@ const Lines = packed struct(u8) { /// Specification of a quadrants char, which has each of the /// 4 quadrants of the character cell either filled or empty. -const Quads = struct { +const Quads = packed struct(u4) { tl: bool = false, tr: bool = false, bl: bool = false, @@ -1621,10 +1621,10 @@ fn draw_block_shade( }; canvas.rect(.{ - .x = @floatFromInt(x), - .y = @floatFromInt(y), - .width = @floatFromInt(w), - .height = @floatFromInt(h), + .x = x, + .y = y, + .width = w, + .height = h, }, @as(font.sprite.Color, @enumFromInt(@intFromEnum(shade)))); } @@ -1671,10 +1671,10 @@ fn draw_checkerboard_fill(self: Box, canvas: *font.sprite.Canvas, parity: u1) vo const y1 = (self.height * (y + 1)) / y_size; if ((x + y) % 2 == parity) { canvas.rect(.{ - .x = @floatFromInt(x0), - .y = @floatFromInt(y0), - .width = @floatFromInt(x1 -| x0), - .height = @floatFromInt(y1 -| y0), + .x = @intCast(x0), + .y = @intCast(y0), + .width = @intCast(x1 -| x0), + .height = @intCast(y1 -| y0), }, .on); } } @@ -1841,8 +1841,8 @@ fn draw_circle( fn draw_line( self: Box, canvas: *font.sprite.Canvas, - p0: font.sprite.Point, - p1: font.sprite.Point, + p0: font.sprite.Point(f64), + p1: font.sprite.Point(f64), comptime thickness: Thickness, ) !void { canvas.line( @@ -1853,11 +1853,11 @@ fn draw_line( } fn draw_shade(self: Box, canvas: *font.sprite.Canvas, v: u16) void { - canvas.rect((font.sprite.Box{ + canvas.rect((font.sprite.Box(u32){ .p0 = .{ .x = 0, .y = 0 }, .p1 = .{ - .x = @floatFromInt(self.width), - .y = @floatFromInt(self.height), + .x = self.width, + .y = self.height, }, }).rect(), @as(font.sprite.Color, @enumFromInt(v))); } @@ -2395,12 +2395,12 @@ fn vline( x: u32, thickness_px: u32, ) void { - canvas.rect((font.sprite.Box{ .p0 = .{ - .x = @floatFromInt(@min(@max(x, 0), self.width)), - .y = @floatFromInt(@min(@max(y1, 0), self.height)), + canvas.rect((font.sprite.Box(u32){ .p0 = .{ + .x = @min(@max(x, 0), self.width), + .y = @min(@max(y1, 0), self.height), }, .p1 = .{ - .x = @floatFromInt(@min(@max(x + thickness_px, 0), self.width)), - .y = @floatFromInt(@min(@max(y2, 0), self.height)), + .x = @min(@max(x + thickness_px, 0), self.width), + .y = @min(@max(y2, 0), self.height), } }).rect(), .on); } @@ -2412,12 +2412,12 @@ fn hline( y: u32, thickness_px: u32, ) void { - canvas.rect((font.sprite.Box{ .p0 = .{ - .x = @floatFromInt(@min(@max(x1, 0), self.width)), - .y = @floatFromInt(@min(@max(y, 0), self.height)), + canvas.rect((font.sprite.Box(u32){ .p0 = .{ + .x = @min(@max(x1, 0), self.width), + .y = @min(@max(y, 0), self.height), }, .p1 = .{ - .x = @floatFromInt(@min(@max(x2, 0), self.width)), - .y = @floatFromInt(@min(@max(y + thickness_px, 0), self.height)), + .x = @min(@max(x2, 0), self.width), + .y = @min(@max(y + thickness_px, 0), self.height), } }).rect(), .on); } @@ -2429,12 +2429,12 @@ fn rect( x2: u32, y2: u32, ) void { - canvas.rect((font.sprite.Box{ .p0 = .{ - .x = @floatFromInt(@min(@max(x1, 0), self.width)), - .y = @floatFromInt(@min(@max(y1, 0), self.height)), + canvas.rect((font.sprite.Box(u32){ .p0 = .{ + .x = @min(@max(x1, 0), self.width), + .y = @min(@max(y1, 0), self.height), }, .p1 = .{ - .x = @floatFromInt(@min(@max(x2, 0), self.width)), - .y = @floatFromInt(@min(@max(y2, 0), self.height)), + .x = @min(@max(x2, 0), self.width), + .y = @min(@max(y2, 0), self.height), } }).rect(), .on); } diff --git a/src/font/sprite/Powerline.zig b/src/font/sprite/Powerline.zig index ba56eb38a..fdb13870b 100644 --- a/src/font/sprite/Powerline.zig +++ b/src/font/sprite/Powerline.zig @@ -391,8 +391,8 @@ fn draw_half_circle(self: Powerline, alloc: Allocator, canvas: *font.sprite.Canv const average = @as(u8, @intCast(@min(total / (supersample * supersample), 0xFF))); canvas.rect( .{ - .x = @floatFromInt(c), - .y = @floatFromInt(r), + .x = @intCast(c), + .y = @intCast(r), .width = 1, .height = 1, }, @@ -404,7 +404,7 @@ fn draw_half_circle(self: Powerline, alloc: Allocator, canvas: *font.sprite.Canv } fn draw_trapezoid_top_bottom(self: Powerline, canvas: *font.sprite.Canvas, cp: u32) !void { - const t_top: Quad = if (cp == 0xE0D4) + const t_top: Quad(f64) = if (cp == 0xE0D4) .{ .p0 = .{ .x = 0, @@ -443,7 +443,7 @@ fn draw_trapezoid_top_bottom(self: Powerline, canvas: *font.sprite.Canvas, cp: u }, }; - const t_bottom: Quad = if (cp == 0xE0D4) + const t_bottom: Quad(f64) = if (cp == 0xE0D4) .{ .p0 = .{ .x = @floatFromInt(self.width - self.width / 3), diff --git a/src/font/sprite/canvas.zig b/src/font/sprite/canvas.zig index a3792fe23..14caddea3 100644 --- a/src/font/sprite/canvas.zig +++ b/src/font/sprite/canvas.zig @@ -6,53 +6,66 @@ const Allocator = std.mem.Allocator; const z2d = @import("z2d"); const font = @import("../main.zig"); -pub const Point = struct { - x: f64, - y: f64, -}; +pub fn Point(comptime T: type) type { + return struct { + x: T, + y: T, + }; +} -pub const Line = struct { - p0: Point, - p1: Point, -}; +pub fn Line(comptime T: type) type { + return struct { + p0: Point(T), + p1: Point(T), + }; +} -pub const Box = struct { - p0: Point, - p1: Point, +pub fn Box(comptime T: type) type { + return struct { + p0: Point(T), + p1: Point(T), - pub fn rect(self: Box) Rect { - const tl_x = @min(self.p0.x, self.p1.x); - const tl_y = @min(self.p0.y, self.p1.y); - const br_x = @max(self.p0.x, self.p1.x); - const br_y = @max(self.p0.y, self.p1.y); - return .{ - .x = tl_x, - .y = tl_y, - .width = br_x - tl_x, - .height = br_y - tl_y, - }; - } -}; + pub fn rect(self: Box(T)) Rect(T) { + const tl_x = @min(self.p0.x, self.p1.x); + const tl_y = @min(self.p0.y, self.p1.y); + const br_x = @max(self.p0.x, self.p1.x); + const br_y = @max(self.p0.y, self.p1.y); -pub const Rect = struct { - x: f64, - y: f64, - width: f64, - height: f64, -}; + return .{ + .x = tl_x, + .y = tl_y, + .width = br_x - tl_x, + .height = br_y - tl_y, + }; + } + }; +} -pub const Triangle = struct { - p0: Point, - p1: Point, - p2: Point, -}; +pub fn Rect(comptime T: type) type { + return struct { + x: T, + y: T, + width: T, + height: T, + }; +} -pub const Quad = struct { - p0: Point, - p1: Point, - p2: Point, - p3: Point, -}; +pub fn Triangle(comptime T: type) type { + return struct { + p0: Point(T), + p1: Point(T), + p2: Point(T), + }; +} + +pub fn Quad(comptime T: type) type { + return struct { + p0: Point(T), + p1: Point(T), + p2: Point(T), + p3: Point(T), + }; +} /// We only use alpha-channel so a pixel can only be "on" or "off". pub const Color = enum(u8) { @@ -137,19 +150,26 @@ pub const Canvas = struct { @intCast(x), @intCast(y), .{ .alpha8 = .{ .a = @intFromEnum(color) } }, - ) catch { - // If we try to set out of range this will fail. - // We just silently ignore that. + ) catch |e| switch (e) { + error.OutOfRange => { + // If we try to set out of range this will fail. We just silently + // ignore it, so that this method (and `rect` which uses it) have + // implicit bounds clipping. + }, + else => { + std.log.err("Wtf? err={}", .{e}); + unreachable; // This shouldn't be possible. + }, }; } /// Draw and fill a rectangle. This is the main primitive for drawing /// lines as well (which are just generally skinny rectangles...) - pub fn rect(self: *Canvas, v: Rect, color: Color) void { - const x0: usize = @intFromFloat(v.x); - const x1: usize = @intFromFloat(v.x + v.width); - const y0: usize = @intFromFloat(v.y); - const y1: usize = @intFromFloat(v.y + v.height); + pub fn rect(self: *Canvas, v: Rect(u32), color: Color) void { + const x0 = v.x; + const x1 = v.x + v.width; + const y0 = v.y; + const y1 = v.y + v.height; for (y0..y1) |y| { for (x0..x1) |x| { @@ -163,7 +183,7 @@ pub const Canvas = struct { } /// Draw and fill a quad. - pub fn quad(self: *Canvas, q: Quad, color: Color) !void { + pub fn quad(self: *Canvas, q: Quad(f64), color: Color) !void { var ctx: z2d.Context = .{ .surface = self.sfc, .pattern = .{ @@ -186,7 +206,7 @@ pub const Canvas = struct { } /// Draw and fill a triangle. - pub fn triangle(self: *Canvas, t: Triangle, color: Color) !void { + pub fn triangle(self: *Canvas, t: Triangle(f64), color: Color) !void { var ctx: z2d.Context = .{ .surface = self.sfc, .pattern = .{ @@ -208,7 +228,7 @@ pub const Canvas = struct { } /// Stroke a line. - pub fn line(self: *Canvas, l: Line, thickness: f64, color: Color) !void { + pub fn line(self: *Canvas, l: Line(f64), thickness: f64, color: Color) !void { var ctx: z2d.Context = .{ .surface = self.sfc, .pattern = .{ diff --git a/src/font/sprite/underline.zig b/src/font/sprite/underline.zig index cd580d141..10e3e82f9 100644 --- a/src/font/sprite/underline.zig +++ b/src/font/sprite/underline.zig @@ -70,8 +70,8 @@ fn drawSingle(alloc: Allocator, width: u32, thickness: u32) !CanvasAndOffset { canvas.rect(.{ .x = 0, .y = 0, - .width = @floatFromInt(width), - .height = @floatFromInt(thickness), + .width = width, + .height = thickness, }, .on); const offset_y: i32 = 0; @@ -91,15 +91,15 @@ fn drawDouble(alloc: Allocator, width: u32, thickness: u32) !CanvasAndOffset { canvas.rect(.{ .x = 0, .y = 0, - .width = @floatFromInt(width), - .height = @floatFromInt(thickness), + .width = width, + .height = thickness, }, .on); canvas.rect(.{ .x = 0, - .y = @floatFromInt(thickness * 2), - .width = @floatFromInt(width), - .height = @floatFromInt(thickness), + .y = thickness * 2, + .width = width, + .height = thickness, }, .on); const offset_y: i32 = -@as(i32, @intCast(thickness)); @@ -121,10 +121,10 @@ fn drawDotted(alloc: Allocator, width: u32, thickness: u32) !CanvasAndOffset { const x = @min(i * (dot_width + gap_width), width - 1); const rect_width = @min(width - x, dot_width); canvas.rect(.{ - .x = @floatFromInt(x), + .x = @intCast(x), .y = 0, - .width = @floatFromInt(rect_width), - .height = @floatFromInt(thickness), + .width = rect_width, + .height = thickness, }, .on); } @@ -146,10 +146,10 @@ fn drawDashed(alloc: Allocator, width: u32, thickness: u32) !CanvasAndOffset { const x = @min(i * dash_width, width - 1); const rect_width = @min(width - x, dash_width); canvas.rect(.{ - .x = @floatFromInt(x), + .x = @intCast(x), .y = 0, - .width = @floatFromInt(rect_width), - .height = @floatFromInt(thickness), + .width = rect_width, + .height = thickness, }, .on); }