From 4dbf404dc330d6af610519abeecfc7eb2d8b4617 Mon Sep 17 00:00:00 2001 From: Qwerasd Date: Wed, 6 Nov 2024 18:27:22 -0500 Subject: [PATCH] font/sprite: cleanup branch drawing character impl, implement fade-out lines --- src/font/sprite/Box.zig | 619 ++++++++++++++++++------------- src/font/sprite/Face.zig | 22 +- src/font/sprite/testdata/Box.ppm | Bin 1048593 -> 1048593 bytes 3 files changed, 371 insertions(+), 270 deletions(-) diff --git a/src/font/sprite/Box.zig b/src/font/sprite/Box.zig index 80369d8fd..382aa4206 100644 --- a/src/font/sprite/Box.zig +++ b/src/font/sprite/Box.zig @@ -79,13 +79,15 @@ const Quads = packed struct(u4) { br: bool = false, }; -/// Specification of a git branch node, which can have any of -/// 4 lines connecting to it in vertical or horizontal alignment -const NodeAlign = packed struct(u4) { +/// Specification of a branch drawing node, which consists of a +/// circle which is either empty or filled, and lines connecting +/// optionally between the circle and each of the 4 edges. +const BranchNode = packed struct(u5) { up: bool = false, + right: bool = false, down: bool = false, left: bool = false, - right: bool = false, + filled: bool = false, }; /// Alignment of a figure within a cell @@ -483,14 +485,14 @@ fn draw(self: Box, alloc: Allocator, canvas: *font.sprite.Canvas, cp: u32) !void // '╬' 0x256c => self.draw_lines(canvas, .{ .up = .double, .down = .double, .left = .double, .right = .double }), // '╭' - 0x256d => try self.draw_light_arc(canvas, .br), + 0x256d => try self.draw_arc(canvas, .br, .light), // '╮' - 0x256e => try self.draw_light_arc(canvas, .bl), + 0x256e => try self.draw_arc(canvas, .bl, .light), // '╯' - 0x256f => try self.draw_light_arc(canvas, .tl), + 0x256f => try self.draw_arc(canvas, .tl, .light), // '╰' - 0x2570 => try self.draw_light_arc(canvas, .tr), + 0x2570 => try self.draw_arc(canvas, .tr, .light), // '╱' 0x2571 => self.draw_light_diagonal_upper_right_to_lower_left(canvas), // '╲' @@ -1311,319 +1313,329 @@ fn draw(self: Box, alloc: Allocator, canvas: *font.sprite.Canvas, cp: u32) !void // '🯯' 0x1fbef => self.draw_circle(canvas, Alignment.top_left, true), + // (Below:) + // Branch drawing character set, used for drawing git-like + // graphs in the terminal. Originally implemented in Kitty. + // Ref: + // - https://github.com/kovidgoyal/kitty/pull/7681 + // - https://github.com/kovidgoyal/kitty/pull/7805 + // NOTE: Kitty is GPL licensed, and its code was not referenced + // for these characters, only the loose specification of + // the character set in the pull request descriptions. + // + // TODO(qwerasd): This should be in another file, but really the + // general organization of the sprite font code + // needs to be reworked eventually. + // + //           + //                     + //                     + //             + // '' - 0x0f5d0 => self.hline_middle(canvas, Thickness.light), - + 0x0f5d0 => self.hline_middle(canvas, .light), // '' - 0x0f5d1 => self.vline_middle(canvas, Thickness.light), - + 0x0f5d1 => self.vline_middle(canvas, .light), // '' - // 0x0f5d2 => self.draw_dash_fading(canvas, 4, Direction.LEFT, Thickness.light.height(self.thickness)), - + 0x0f5d2 => self.draw_fading_line(canvas, .right, .light), // '' - // 0x0f5d3 => self.draw_dash_fading(canvas, 4, Direction.RIGHT, Thickness.light.height(self.thickness)), - + 0x0f5d3 => self.draw_fading_line(canvas, .left, .light), // '' - // 0x0f5d4 => self.draw_dash_fading(canvas, 5, Direction.UP, Thickness.light.height(self.thickness)), - + 0x0f5d4 => self.draw_fading_line(canvas, .bottom, .light), // '' - // 0x0f5d5 => self.draw_dash_fading(canvas, 5, Direction.DOWN, Thickness.light.height(self.thickness)), - + 0x0f5d5 => self.draw_fading_line(canvas, .top, .light), // '' - 0x0f5d6 => try self.draw_light_arc(canvas, .br), - + 0x0f5d6 => try self.draw_arc(canvas, .br, .light), // '' - 0x0f5d7 => try self.draw_light_arc(canvas, .bl), - + 0x0f5d7 => try self.draw_arc(canvas, .bl, .light), // '' - 0x0f5d8 => try self.draw_light_arc(canvas, .tr), - + 0x0f5d8 => try self.draw_arc(canvas, .tr, .light), // '' - 0x0f5d9 => try self.draw_light_arc(canvas, .tl), - + 0x0f5d9 => try self.draw_arc(canvas, .tl, .light), // '' 0x0f5da => { - self.vline_middle(canvas, Thickness.light); - try self.draw_light_arc(canvas, .tr); + self.vline_middle(canvas, .light); + try self.draw_arc(canvas, .tr, .light); }, - // '' 0x0f5db => { - self.vline_middle(canvas, Thickness.light); - try self.draw_light_arc(canvas, .br); + self.vline_middle(canvas, .light); + try self.draw_arc(canvas, .br, .light); }, - // '' 0x0f5dc => { - try self.draw_light_arc(canvas, .tr); - try self.draw_light_arc(canvas, .br); + try self.draw_arc(canvas, .tr, .light); + try self.draw_arc(canvas, .br, .light); }, - // '' 0x0f5dd => { - self.vline_middle(canvas, Thickness.light); - try self.draw_light_arc(canvas, .tl); + self.vline_middle(canvas, .light); + try self.draw_arc(canvas, .tl, .light); }, - // '' 0x0f5de => { - self.vline_middle(canvas, Thickness.light); - try self.draw_light_arc(canvas, .bl); + self.vline_middle(canvas, .light); + try self.draw_arc(canvas, .bl, .light); }, - // '' 0x0f5df => { - try self.draw_light_arc(canvas, .tl); - try self.draw_light_arc(canvas, .bl); + try self.draw_arc(canvas, .tl, .light); + try self.draw_arc(canvas, .bl, .light); }, // '' 0x0f5e0 => { - try self.draw_light_arc(canvas, .bl); - self.hline_middle(canvas, Thickness.light); + try self.draw_arc(canvas, .bl, .light); + self.hline_middle(canvas, .light); }, - // '' 0x0f5e1 => { - try self.draw_light_arc(canvas, .br); - self.hline_middle(canvas, Thickness.light); + try self.draw_arc(canvas, .br, .light); + self.hline_middle(canvas, .light); }, - // '' 0x0f5e2 => { - try self.draw_light_arc(canvas, .br); - try self.draw_light_arc(canvas, .bl); + try self.draw_arc(canvas, .br, .light); + try self.draw_arc(canvas, .bl, .light); }, - // '' 0x0f5e3 => { - try self.draw_light_arc(canvas, .tl); - self.hline_middle(canvas, Thickness.light); + try self.draw_arc(canvas, .tl, .light); + self.hline_middle(canvas, .light); }, - // '' 0x0f5e4 => { - try self.draw_light_arc(canvas, .tr); - self.hline_middle(canvas, Thickness.light); + try self.draw_arc(canvas, .tr, .light); + self.hline_middle(canvas, .light); }, - // '' 0x0f5e5 => { - try self.draw_light_arc(canvas, .tr); - try self.draw_light_arc(canvas, .tl); + try self.draw_arc(canvas, .tr, .light); + try self.draw_arc(canvas, .tl, .light); }, - // '' 0x0f5e6 => { - self.vline_middle(canvas, Thickness.light); - try self.draw_light_arc(canvas, .tl); - try self.draw_light_arc(canvas, .tr); + self.vline_middle(canvas, .light); + try self.draw_arc(canvas, .tl, .light); + try self.draw_arc(canvas, .tr, .light); }, - // '' 0x0f5e7 => { - self.vline_middle(canvas, Thickness.light); - try self.draw_light_arc(canvas, .bl); - try self.draw_light_arc(canvas, .br); + self.vline_middle(canvas, .light); + try self.draw_arc(canvas, .bl, .light); + try self.draw_arc(canvas, .br, .light); }, - // '' 0x0f5e8 => { - self.hline_middle(canvas, Thickness.light); - try self.draw_light_arc(canvas, .bl); - try self.draw_light_arc(canvas, .tl); + self.hline_middle(canvas, .light); + try self.draw_arc(canvas, .bl, .light); + try self.draw_arc(canvas, .tl, .light); }, - // '' 0x0f5e9 => { - self.hline_middle(canvas, Thickness.light); - try self.draw_light_arc(canvas, .tr); - try self.draw_light_arc(canvas, .br); + self.hline_middle(canvas, .light); + try self.draw_arc(canvas, .tr, .light); + try self.draw_arc(canvas, .br, .light); }, - // '' 0x0f5ea => { - self.vline_middle(canvas, Thickness.light); - try self.draw_light_arc(canvas, .tl); - try self.draw_light_arc(canvas, .br); + self.vline_middle(canvas, .light); + try self.draw_arc(canvas, .tl, .light); + try self.draw_arc(canvas, .br, .light); }, - // '' 0x0f5eb => { - self.vline_middle(canvas, Thickness.light); - try self.draw_light_arc(canvas, .tr); - try self.draw_light_arc(canvas, .bl); + self.vline_middle(canvas, .light); + try self.draw_arc(canvas, .tr, .light); + try self.draw_arc(canvas, .bl, .light); }, - // '' 0x0f5ec => { - self.hline_middle(canvas, Thickness.light); - try self.draw_light_arc(canvas, .tl); - try self.draw_light_arc(canvas, .br); + self.hline_middle(canvas, .light); + try self.draw_arc(canvas, .tl, .light); + try self.draw_arc(canvas, .br, .light); }, - // '' 0x0f5ed => { - self.hline_middle(canvas, Thickness.light); - try self.draw_light_arc(canvas, .tr); - try self.draw_light_arc(canvas, .bl); + self.hline_middle(canvas, .light); + try self.draw_arc(canvas, .tr, .light); + try self.draw_arc(canvas, .bl, .light); }, - // '' - 0x0f5ee => self.draw_git_node(canvas, .{}, Thickness.light, true), - + 0x0f5ee => self.draw_branch_node(canvas, .{ .filled = true }, .light), // '' - 0x0f5ef => self.draw_git_node(canvas, .{}, Thickness.light, false), + 0x0f5ef => self.draw_branch_node(canvas, .{}, .light), // '' - 0x0f5f0 => { - self.draw_git_node(canvas, .{ .right = true }, Thickness.light, true); - }, - + 0x0f5f0 => self.draw_branch_node(canvas, .{ + .right = true, + .filled = true, + }, .light), // '' - 0x0f5f1 => { - self.draw_git_node(canvas, .{ .right = true }, Thickness.light, false); - }, - + 0x0f5f1 => self.draw_branch_node(canvas, .{ + .right = true, + }, .light), // '' - 0x0f5f2 => { - self.draw_git_node(canvas, .{ .left = true }, Thickness.light, true); - }, - + 0x0f5f2 => self.draw_branch_node(canvas, .{ + .left = true, + .filled = true, + }, .light), // '' - 0x0f5f3 => { - self.draw_git_node(canvas, .{ .left = true }, Thickness.light, false); - }, - + 0x0f5f3 => self.draw_branch_node(canvas, .{ + .left = true, + }, .light), // '' - 0x0f5f4 => { - self.draw_git_node(canvas, .{ .left = true, .right = true }, Thickness.light, true); - }, - + 0x0f5f4 => self.draw_branch_node(canvas, .{ + .left = true, + .right = true, + .filled = true, + }, .light), // '' - 0x0f5f5 => { - self.draw_git_node(canvas, .{ .left = true, .right = true }, Thickness.light, false); - }, - + 0x0f5f5 => self.draw_branch_node(canvas, .{ + .left = true, + .right = true, + }, .light), // '' - 0x0f5f6 => { - self.draw_git_node(canvas, .{ .down = true }, Thickness.light, true); - }, - + 0x0f5f6 => self.draw_branch_node(canvas, .{ + .down = true, + .filled = true, + }, .light), // '' - 0x0f5f7 => { - self.draw_git_node(canvas, .{ .down = true }, Thickness.light, false); - }, - + 0x0f5f7 => self.draw_branch_node(canvas, .{ + .down = true, + }, .light), // '' - 0x0f5f8 => { - self.draw_git_node(canvas, .{ .up = true }, Thickness.light, true); - }, - + 0x0f5f8 => self.draw_branch_node(canvas, .{ + .up = true, + .filled = true, + }, .light), // '' - 0x0f5f9 => { - self.draw_git_node(canvas, .{ .up = true }, Thickness.light, false); - }, - + 0x0f5f9 => self.draw_branch_node(canvas, .{ + .up = true, + }, .light), // '' - 0x0f5fa => { - self.draw_git_node(canvas, .{ .up = true, .down = true }, Thickness.light, true); - }, - + 0x0f5fa => self.draw_branch_node(canvas, .{ + .up = true, + .down = true, + .filled = true, + }, .light), // '' - 0x0f5fb => { - self.draw_git_node(canvas, .{ .up = true, .down = true }, Thickness.light, false); - }, - + 0x0f5fb => self.draw_branch_node(canvas, .{ + .up = true, + .down = true, + }, .light), // '' - 0x0f5fc => { - self.draw_git_node(canvas, .{ .right = true, .down = true }, Thickness.light, true); - }, - + 0x0f5fc => self.draw_branch_node(canvas, .{ + .right = true, + .down = true, + .filled = true, + }, .light), // '' - 0x0f5fd => { - self.draw_git_node(canvas, .{ .right = true, .down = true }, Thickness.light, false); - }, - + 0x0f5fd => self.draw_branch_node(canvas, .{ + .right = true, + .down = true, + }, .light), // '' - 0x0f5fe => { - self.draw_git_node(canvas, .{ .left = true, .down = true }, Thickness.light, true); - }, - + 0x0f5fe => self.draw_branch_node(canvas, .{ + .left = true, + .down = true, + .filled = true, + }, .light), // '' - 0x0f5ff => { - self.draw_git_node(canvas, .{ .left = true, .down = true }, Thickness.light, false); - }, + 0x0f5ff => self.draw_branch_node(canvas, .{ + .left = true, + .down = true, + }, .light), // '' - 0x0f600 => { - self.draw_git_node(canvas, .{ .up = true, .right = true }, Thickness.light, true); - }, - + 0x0f600 => self.draw_branch_node(canvas, .{ + .up = true, + .right = true, + .filled = true, + }, .light), // '' - 0x0f601 => { - self.draw_git_node(canvas, .{ .up = true, .right = true }, Thickness.light, false); - }, - + 0x0f601 => self.draw_branch_node(canvas, .{ + .up = true, + .right = true, + }, .light), // '' - 0x0f602 => { - self.draw_git_node(canvas, .{ .up = true, .left = true }, Thickness.light, true); - }, - + 0x0f602 => self.draw_branch_node(canvas, .{ + .up = true, + .left = true, + .filled = true, + }, .light), // '' - 0x0f603 => { - self.draw_git_node(canvas, .{ .up = true, .left = true }, Thickness.light, false); - }, - + 0x0f603 => self.draw_branch_node(canvas, .{ + .up = true, + .left = true, + }, .light), // '' - 0x0f604 => { - self.draw_git_node(canvas, .{ .up = true, .down = true, .right = true }, Thickness.light, true); - }, - + 0x0f604 => self.draw_branch_node(canvas, .{ + .up = true, + .down = true, + .right = true, + .filled = true, + }, .light), // '' - 0x0f605 => { - self.draw_git_node(canvas, .{ .up = true, .down = true, .right = true }, Thickness.light, false); - }, - + 0x0f605 => self.draw_branch_node(canvas, .{ + .up = true, + .down = true, + .right = true, + }, .light), // '' - 0x0f606 => { - self.draw_git_node(canvas, .{ .up = true, .down = true, .left = true }, Thickness.light, true); - }, - + 0x0f606 => self.draw_branch_node(canvas, .{ + .up = true, + .down = true, + .left = true, + .filled = true, + }, .light), // '' - 0x0f607 => { - self.draw_git_node(canvas, .{ .up = true, .down = true, .left = true }, Thickness.light, false); - }, - + 0x0f607 => self.draw_branch_node(canvas, .{ + .up = true, + .down = true, + .left = true, + }, .light), // '' - 0x0f608 => { - self.draw_git_node(canvas, .{ .down = true, .left = true, .right = true }, Thickness.light, true); - }, - + 0x0f608 => self.draw_branch_node(canvas, .{ + .down = true, + .left = true, + .right = true, + .filled = true, + }, .light), // '' - 0x0f609 => { - self.draw_git_node(canvas, .{ .down = true, .left = true, .right = true }, Thickness.light, false); - }, - + 0x0f609 => self.draw_branch_node(canvas, .{ + .down = true, + .left = true, + .right = true, + }, .light), // '' - 0x0f60a => { - self.draw_git_node(canvas, .{ .up = true, .left = true, .right = true }, Thickness.light, true); - }, - + 0x0f60a => self.draw_branch_node(canvas, .{ + .up = true, + .left = true, + .right = true, + .filled = true, + }, .light), // '' - 0x0f60b => { - self.draw_git_node(canvas, .{ .up = true, .left = true, .right = true }, Thickness.light, false); - }, - + 0x0f60b => self.draw_branch_node(canvas, .{ + .up = true, + .left = true, + .right = true, + }, .light), // '' - 0x0f60c => { - self.draw_git_node(canvas, .{ .up = true, .down = true, .left = true, .right = true }, Thickness.light, true); - }, - + 0x0f60c => self.draw_branch_node(canvas, .{ + .up = true, + .down = true, + .left = true, + .right = true, + .filled = true, + }, .light), // '' - 0x0f60d => { - self.draw_git_node(canvas, .{ .up = true, .down = true, .left = true, .right = true }, Thickness.light, false); - }, + 0x0f60d => self.draw_branch_node(canvas, .{ + .up = true, + .down = true, + .left = true, + .right = true, + }, .light), // Not official box characters but special characters we hide // in the high bits of a unicode codepoint. @@ -2116,21 +2128,98 @@ fn draw_cell_diagonal( ) catch {}; } -fn draw_git_node( +fn draw_fading_line( self: Box, canvas: *font.sprite.Canvas, - comptime nodes: NodeAlign, + comptime to: Edge, comptime thickness: Thickness, - comptime filled: bool, ) void { + const thick_px = thickness.height(self.thickness); const float_width: f64 = @floatFromInt(self.width); const float_height: f64 = @floatFromInt(self.height); - const x: f64 = float_width / 2; - const y: f64 = float_height / 2; + // Top of horizontal strokes + const h_top = (self.height -| thick_px) / 2; + // Bottom of horizontal strokes + const h_bottom = h_top +| thick_px; + // Left of vertical strokes + const v_left = (self.width -| thick_px) / 2; + // Right of vertical strokes + const v_right = v_left +| thick_px; - // we need at least 1px leeway to see node + right/left line - const r: f64 = 0.47 * @min(float_width, float_height); + // If we're fading to the top or left, we start with 0.0 + // and increment up as we progress, otherwise we start + // at 255.0 and increment down (negative). + var color: f64 = switch (to) { + .top, .left => 0.0, + .bottom, .right => 255.0, + }; + const inc: f64 = 255.0 / switch (to) { + .top => float_height, + .bottom => -float_height, + .left => float_width, + .right => -float_width, + }; + + switch (to) { + .top, .bottom => { + for (0..self.height) |y| { + for (v_left..v_right) |x| { + canvas.pixel( + @intCast(x), + @intCast(y), + @enumFromInt(@as(u8, @intFromFloat(@round(color)))), + ); + } + color += inc; + } + }, + .left, .right => { + for (0..self.width) |x| { + for (h_top..h_bottom) |y| { + canvas.pixel( + @intCast(x), + @intCast(y), + @enumFromInt(@as(u8, @intFromFloat(@round(color)))), + ); + } + color += inc; + } + }, + } +} + +fn draw_branch_node( + self: Box, + canvas: *font.sprite.Canvas, + node: BranchNode, + comptime thickness: Thickness, +) void { + const thick_px = thickness.height(self.thickness); + const float_width: f64 = @floatFromInt(self.width); + const float_height: f64 = @floatFromInt(self.height); + const float_thick: f64 = @floatFromInt(thick_px); + + // Top of horizontal strokes + const h_top = (self.height -| thick_px) / 2; + // Bottom of horizontal strokes + const h_bottom = h_top +| thick_px; + // Left of vertical strokes + const v_left = (self.width -| thick_px) / 2; + // Right of vertical strokes + const v_right = v_left +| thick_px; + + // We calculate the center of the circle this way + // to ensure it aligns with box drawing characters + // since the lines are sometimes off center to + // make sure they aren't split between pixels. + const cx: f64 = @as(f64, @floatFromInt(v_left)) + float_thick / 2; + const cy: f64 = @as(f64, @floatFromInt(h_top)) + float_thick / 2; + // The radius needs to be the smallest distance from the center to an edge. + const r: f64 = @min( + @min(cx, cy), + @min(float_width - cx, float_height - cy), + ); var ctx: z2d.Context = .{ .surface = canvas.sfc, @@ -2139,29 +2228,30 @@ fn draw_git_node( .pixel = .{ .alpha8 = .{ .a = @intFromEnum(Shade.on) } }, }, }, - .line_width = @floatFromInt(thickness.height(self.thickness)), + .line_width = float_thick, }; var path = z2d.Path.init(canvas.alloc); defer path.deinit(); - const int_radius: u32 = @intFromFloat(r); + // These @intFromFloat casts shouldn't ever fail since r can never + // be greater than cx or cy, so when subtracting it from them the + // result can never be negative. + if (node.up) + self.rect(canvas, v_left, 0, v_right, @intFromFloat(@ceil(cy - r))); + if (node.right) + self.rect(canvas, @intFromFloat(@floor(cx + r)), h_top, self.width, h_bottom); + if (node.down) + self.rect(canvas, v_left, @intFromFloat(@floor(cy + r)), v_right, self.height); + if (node.left) + self.rect(canvas, 0, h_top, @intFromFloat(@ceil(cx - r)), h_bottom); - if (nodes.up) - self.vline_middle_xy(canvas, 0, (self.height / 2) - int_radius, self.width, thickness); - if (nodes.down) - self.vline_middle_xy(canvas, (self.height / 2) + int_radius, self.height, self.width, thickness); - if (nodes.left) - self.hline_middle_xy(canvas, 0, (self.width / 2) - int_radius, self.height, thickness); - if (nodes.right) - self.hline_middle_xy(canvas, (self.width / 2) + int_radius, self.width, self.height, thickness); - - if (filled) { - path.arc(x, y, r, 0, std.math.pi * 2, false, null) catch return; + if (node.filled) { + path.arc(cx, cy, r, 0, std.math.pi * 2, false, null) catch return; path.close() catch return; ctx.fill(canvas.alloc, path) catch return; } else { - path.arc(x, y, r - ctx.line_width / 2, 0, std.math.pi * 2, false, null) catch return; + path.arc(cx, cy, r - float_thick / 2, 0, std.math.pi * 2, false, null) catch return; path.close() catch return; ctx.stroke(canvas.alloc, path) catch return; } @@ -2492,12 +2582,13 @@ fn draw_edge_triangle( try ctx.fill(canvas.alloc, path); } -fn draw_light_arc( +fn draw_arc( self: Box, canvas: *font.sprite.Canvas, comptime corner: Corner, + comptime thickness: Thickness, ) !void { - const thick_px = Thickness.light.height(self.thickness); + const thick_px = thickness.height(self.thickness); const float_width: f64 = @floatFromInt(self.width); const float_height: f64 = @floatFromInt(self.height); const float_thick: f64 = @floatFromInt(thick_px); @@ -2751,16 +2842,6 @@ fn draw_cursor_bar(self: Box, canvas: *font.sprite.Canvas) void { self.vline(canvas, 0, self.height, 0, thick_px); } -fn vline_middle_xy(self: Box, canvas: *font.sprite.Canvas, y: u32, y2: u32, x: u32, thickness: Thickness) void { - const thick_px = thickness.height(self.thickness); - self.vline(canvas, y, y2, (x -| thick_px) / 2, thick_px); -} - -fn hline_middle_xy(self: Box, canvas: *font.sprite.Canvas, x: u32, x2: u32, y: u32, thickness: Thickness) void { - const thick_px = thickness.height(self.thickness); - self.hline(canvas, x, x2, (y -| thick_px) / 2, thick_px); -} - fn vline_middle(self: Box, canvas: *font.sprite.Canvas, thickness: Thickness) void { const thick_px = thickness.height(self.thickness); self.vline(canvas, 0, self.height, (self.width -| thick_px) / 2, thick_px); @@ -2864,20 +2945,6 @@ fn testRenderAll(self: Box, alloc: Allocator, atlas: *font.Atlas) !void { ); } - // Git Branch characters - cp = 0xf5d0; - while (cp <= 0xf60d) : (cp += 1) { - switch (cp) { - 0xf5d0...0xf60d, - => _ = try self.renderGlyph( - alloc, - atlas, - cp, - ), - else => {}, - } - } - // Symbols for Legacy Computing. cp = 0x1fb00; while (cp <= 0x1fbef) : (cp += 1) { @@ -2932,6 +2999,32 @@ fn testRenderAll(self: Box, alloc: Allocator, atlas: *font.Atlas) !void { else => {}, } } + + // Branch drawing character set, used for drawing git-like + // graphs in the terminal. Originally implemented in Kitty. + // Ref: + // - https://github.com/kovidgoyal/kitty/pull/7681 + // - https://github.com/kovidgoyal/kitty/pull/7805 + // NOTE: Kitty is GPL licensed, and its code was not referenced + // for these characters, only the loose specification of + // the character set in the pull request descriptions. + // + // TODO(qwerasd): This should be in another file, but really the + // general organization of the sprite font code + // needs to be reworked eventually. + // + //           + //                     + //                     + //             + cp = 0xf5d0; + while (cp <= 0xf60d) : (cp += 1) { + _ = try self.renderGlyph( + alloc, + atlas, + cp, + ); + } } test "render all sprites" { diff --git a/src/font/sprite/Face.zig b/src/font/sprite/Face.zig index 650bd2a5f..ca0ed96e8 100644 --- a/src/font/sprite/Face.zig +++ b/src/font/sprite/Face.zig @@ -263,6 +263,21 @@ const Kind = enum { 0x1FBCE...0x1FBEF, => .box, + // Branch drawing character set, used for drawing git-like + // graphs in the terminal. Originally implemented in Kitty. + // Ref: + // - https://github.com/kovidgoyal/kitty/pull/7681 + // - https://github.com/kovidgoyal/kitty/pull/7805 + // NOTE: Kitty is GPL licensed, and its code was not referenced + // for these characters, only the loose specification of + // the character set in the pull request descriptions. + // + //           + //                     + //                     + //             + 0xF5D0...0xF60D => .box, + // Powerline fonts 0xE0B0, 0xE0B4, @@ -276,13 +291,6 @@ const Kind = enum { 0xE0D4, => .powerline, - // (Git Branch) - //           - //                     - //                     - //             - 0xF5D0...0xF60D => .box, - else => null, }; } diff --git a/src/font/sprite/testdata/Box.ppm b/src/font/sprite/testdata/Box.ppm index 29ae539e709deba5b723a5e3fba111c1914a0cd7..1301a4299816fec222672eb24f327db221007b21 100644 GIT binary patch delta 21898 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 delta 7484 zcmbQ(;4rblp`nGbg{g(Pg{6hHg>4IauIBUy{v2G>p0G*6BNK*tftfBs&T?r`yh9w?p_?X*$^OT@LI^rXM)TDYiY}7W)pyY4_Rr zrwjOSuum6=<=~zkAjh$LdO#Kj-*kRgcDC&@F6`!v)8!wta8FN2;NYIFkk837{rpKz z_U!@p*mp2aKj6zLFx_5>;|xefV7kK;c58%%s!+b%^bP*(EYoeLvTIGZ_hi?Ddkqx& z&`iby&pF(n+yqYEJkuBaWaXNu;0HVK+v23R&|zJepNOX^wllO$56kcFp)~Rt4oE-tBfV?D`1J zw2elU?FI?#w#c@DOI}2&Kw&ab*=~@+ZqK+;;ULp?gA6EZ`hmZ!VxUq$5h?aQ^fPKq zzmUV;JUtuRW9$5T}O@F1$!9D%HIEU#(1wODS zNJJ5p5TE{lokI~-R(!gFH^IMn7Ey4xHLvRN9M^%~eE=^W(oQ0o`4b8ok4VHalvr9Hms_tQBzrsuJ9 zh)q8!$00razW?e=LLD;TH86|l>K@&~^wJE9bonW(@6GEQK6cMtm+P=$af z01&04E+pFo9A@X3{@0s>Z~7~BP=-_I(3!q1i9>jLdlCoV^a)X%tdkc!;F*3`j)NWI zY&|PZZjkA0(;bpH+_#_1;?QNBsDP-q#J2lSVCP`OZ|$@v?2sHjO^1UUo~Om9KM?0I z0r>}1)4_$onI0{xi%&Pm=QuV|L36vq6i_HXSOjTWf-6IGPg29SVvud%>WXbTsHO+i zPdXD7%t2KYSi~3Z#XfKuXt zzpNb7|HgA1}6lj z^MlHc4NEw*r(Y1}GT5k~j-n%jV?X0Yg>x`5Zw{yFu;M~=doh059FDz=(+?U=BA-0cO|%M3E*oJwcU|XQF}-T$MIV)p>BaD+($Y zV9wFrUR=SkpAppNh4lt~L2XZdknQZ-6*h2~FisD6$pNnIMW;`&ViTRb{vP}EgmzA4 zP&p~Ly|{&=j~S$zfb2vC4!DaPLAo57Kv`03+7nLx>HLS;MM3REVUW+orYpE`7EKR$ z!NERFokI~WWH8+ziGzFkg|(bAs3t+|M|ju=WIPtZ2^=R_w=2xx5a8OVum$jukNaMXg#<^U-X1*LIB^0orS!sCq!U%_70nW!KJ zaZkfujtvOUBE`}kj=xYJ`E#&OSFqp&_kcG*bIBF3F);5p@N#aLKEau@b9%#LPFavP z9aPf|KtZq<5)s?8oj9!-w;Sx^n85+*=^aqztlTc3%BjgXT|kwSd)j?=R!~olZCW}9 zH%KIAqrx>tqLq3=@&?HJ;Czl21_IlAbU4>i!6r~P0C!XmsB$JxSBU03HvO~1{6UJ*r)(XG_f3N)BHG%rho9|beeXb zU1j=(`|K8A9%xuV2;^%E(9i+v^!vV?>S*Eezl`(wbUiIjw&^#WIHyfFSjMp!>?R6K ztfHdp>c}z+JV+uueS;E5^mK)0&ST)vCM~cKM#M}HE8$F#5*;!8mz=C;PN)4zcM6EIDQ2z82iRVG&qI52xaG z;TlePCY{feD;SOw;BuDNc7d%_4xVau-yk z+;j^AHlFP=Q#kFIKwSYLWaIY16fyd57o5TAiK*fcOvUsaeXKm&W#)j&DTRDav55*A zpcW$6^s;%Jrl7PYxP8MhxMGg?tUS{tm$7nhS6{%%&IAfdtU69{nlnz{eVS8Xqrw)Z z?W@mm+A>bxbDooLqrz6E?W-?w+A&UlIiFK;`v3Wy+}jn`aIRyVsPJO@>T4j0$Z`(u z>0cIeicSB&7$m!aa~Vce*o%ryPkVNS*Aw-0l2 zZ