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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 s;({xZAnM8n~VQA0ybood5s; delta 7484 zcmeHLeQ;FO75A;Xd(X++B%6e6Q1Wp%u!%sFEq(+ckeK3h2!!m6t)Qqxu#!Rwj4+BV z>UPMCbYM*0h4!IQQ6drn_1oEh_Q>0O=6Y?l{KM3+Sh)fz~gWIX&7g%OU zNj(h$>HM!9PC647*Bi2cDMf}HLT6WKKt&OeOI6#h&NlHV_`0aI{D@(iaOE_nQ`$8o7_JBZY_lt-H`AtHv7a25Vs~F3%#iOrsI5TN_zr$n5 zo2aKgIq=`42L7=qH)Ip%`0~UHRIyQ1CVcTymF667$Xoa?(8Nt*6z_nb!c8KNjH1dZ;sz@;&VH=PTvq4k+)ZYAyQuXuN17q0 zTb-81#a_b$GXC}rJTBaE+#t-drl0Yv(4HF zUKVg0j85g|8}dHOujH*hBUWkL<6aJQKx8Gnv!KTRP~4aZDp~pCizxfJ=5$ptQ+#i5 z<%=QvxQo)&qdk|j4Vht5nc_|T z$)i32-c)WL`KQ7k40)~P*Uel|Zd$ci9Qa>&Q)hak{7XftwKHZ5U-3NL5kp?~ts~Ef zAus>dk!LaTuvqH_#Wmk@%4f&m&9TuW@-(i!4CN0;qo~WYC#XZaBUmy*vkU=*c;dK`nQqxJjeHhhU!e zxzsY=nfvt4_XO2HiNkqL7OEvp@YK~T{S{n8f`xh;Yu#ouAwwrIlQKI+F5eS#IC2lk z$@mZjPJ;-Qg4ZJX$v+2l>e-I@sg|PniGZgioJt|KzyXJn&w@KGBDa}73pTEkJPm$M z@&q&Uy6SremZnAXzn~hkNp1u^BtYkUxNWNC6m)A5`I+DVvQ7V#YzMb3;W#aRAKkGW zp5q)w%Oo4jm{q`q4{0P__z@@DZROtd$210-^1lW9(_*XxP|go>yO}BFKMzhV9?200 z<78STIbH=$!%2+-Wmutlim=@tkq7L9MstpR?l}jKv29?HT(0p%d5`B^I1!QmG(#;j z_#Md~4T7&iI1y~pF115{ETTFPB*&ro0uIN*f7>(@4?mcWVQTq4{*rVb=+I;_*^qZyq9|RcO#O#3(~1>AJ=lXr&o+KSNvHZn+=cSPX`X_t;Ny-c{%?uo}wE@p_BevjUn=Hh7I4;;?FLl zcaL9^E%PHu4uy+h7ReRZWoG;9B`aa#9=dZW&Y_y6=%VH)aYoEoo6Q>D^JvCmyq|`> z??76;3h(~1L<7xi#Cob}#0;8LX3L=U%{Yx-YQ<8^$WT?&fIRMsp_~l}YFvkv^vS;+ ziSylZYYmnpyT6pt;s^2owJa50hqq|7y#>oGnW3s?8J^ajZk-}R3&2Ai zw_<@6b(RXhXlkrRugY1DMYapdZ8qgrvRn|0Y{_?q^i=P&RQR{1a1#qReWmaYQ#i=N zE7^nK)WkvjWz#R2(#u(TwdwDV3d~qTC5Xv!z07jEilIR3R_+!owuRQ&yi|3_?j9)l zN=&kl#`&EtmA?ij+4w1toow#4xR6$-vYy2$UzT`1EC-Lfp)a z!@T@A)&bJL6lT)rin(vZxgYH1ojicswTK*RqYw7+@-Vh*5jOfkCwl4KPSjOl4@R^I zdvNMa^wRM+QCCH8W7wpLF}-81x~jREUO0q9X#F8{((ywq@E23SXLByV+O{;e*o8W?9l)K