From 04419873462c9e78dc6da5e8fba9646b9fad53a3 Mon Sep 17 00:00:00 2001 From: Meili C Date: Fri, 1 Nov 2024 14:54:24 -0800 Subject: [PATCH] font feature: add git branch characters addresses #2561 - adds support for most Git branch drawing characters as specified in ![kitty/7681](https://github.com/kovidgoyal/kitty/pull/7681) except for fading vertical and horizontal lines. Adds git_draw_node function and a new Git node type. Add this range (0xf5d0...0xf60d) for Git branch characters, to tests. adds vline_middle_xy and hline_middle_xy for node connections. add git characters to Face.zig. --- src/font/sprite/Box.zig | 398 +++++++++++++++++++++++++++++++++++++++ src/font/sprite/Face.zig | 7 + 2 files changed, 405 insertions(+) diff --git a/src/font/sprite/Box.zig b/src/font/sprite/Box.zig index 477708c99..80369d8fd 100644 --- a/src/font/sprite/Box.zig +++ b/src/font/sprite/Box.zig @@ -79,6 +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) { + up: bool = false, + down: bool = false, + left: bool = false, + right: bool = false, +}; + /// Alignment of a figure within a cell const Alignment = struct { horizontal: enum { @@ -1302,6 +1311,320 @@ fn draw(self: Box, alloc: Allocator, canvas: *font.sprite.Canvas, cp: u32) !void // '🯯' 0x1fbef => self.draw_circle(canvas, Alignment.top_left, true), + // '' + 0x0f5d0 => self.hline_middle(canvas, Thickness.light), + + // '' + 0x0f5d1 => self.vline_middle(canvas, Thickness.light), + + // '' + // 0x0f5d2 => self.draw_dash_fading(canvas, 4, Direction.LEFT, Thickness.light.height(self.thickness)), + + // '' + // 0x0f5d3 => self.draw_dash_fading(canvas, 4, Direction.RIGHT, Thickness.light.height(self.thickness)), + + // '' + // 0x0f5d4 => self.draw_dash_fading(canvas, 5, Direction.UP, Thickness.light.height(self.thickness)), + + // '' + // 0x0f5d5 => self.draw_dash_fading(canvas, 5, Direction.DOWN, Thickness.light.height(self.thickness)), + + // '' + 0x0f5d6 => try self.draw_light_arc(canvas, .br), + + // '' + 0x0f5d7 => try self.draw_light_arc(canvas, .bl), + + // '' + 0x0f5d8 => try self.draw_light_arc(canvas, .tr), + + // '' + 0x0f5d9 => try self.draw_light_arc(canvas, .tl), + + // '' + 0x0f5da => { + self.vline_middle(canvas, Thickness.light); + try self.draw_light_arc(canvas, .tr); + }, + + // '' + 0x0f5db => { + self.vline_middle(canvas, Thickness.light); + try self.draw_light_arc(canvas, .br); + }, + + // '' + 0x0f5dc => { + try self.draw_light_arc(canvas, .tr); + try self.draw_light_arc(canvas, .br); + }, + + // '' + 0x0f5dd => { + self.vline_middle(canvas, Thickness.light); + try self.draw_light_arc(canvas, .tl); + }, + + // '' + 0x0f5de => { + self.vline_middle(canvas, Thickness.light); + try self.draw_light_arc(canvas, .bl); + }, + + // '' + 0x0f5df => { + try self.draw_light_arc(canvas, .tl); + try self.draw_light_arc(canvas, .bl); + }, + + // '' + 0x0f5e0 => { + try self.draw_light_arc(canvas, .bl); + self.hline_middle(canvas, Thickness.light); + }, + + // '' + 0x0f5e1 => { + try self.draw_light_arc(canvas, .br); + self.hline_middle(canvas, Thickness.light); + }, + + // '' + 0x0f5e2 => { + try self.draw_light_arc(canvas, .br); + try self.draw_light_arc(canvas, .bl); + }, + + // '' + 0x0f5e3 => { + try self.draw_light_arc(canvas, .tl); + self.hline_middle(canvas, Thickness.light); + }, + + // '' + 0x0f5e4 => { + try self.draw_light_arc(canvas, .tr); + self.hline_middle(canvas, Thickness.light); + }, + + // '' + 0x0f5e5 => { + try self.draw_light_arc(canvas, .tr); + try self.draw_light_arc(canvas, .tl); + }, + + // '' + 0x0f5e6 => { + self.vline_middle(canvas, Thickness.light); + try self.draw_light_arc(canvas, .tl); + try self.draw_light_arc(canvas, .tr); + }, + + // '' + 0x0f5e7 => { + self.vline_middle(canvas, Thickness.light); + try self.draw_light_arc(canvas, .bl); + try self.draw_light_arc(canvas, .br); + }, + + // '' + 0x0f5e8 => { + self.hline_middle(canvas, Thickness.light); + try self.draw_light_arc(canvas, .bl); + try self.draw_light_arc(canvas, .tl); + }, + + // '' + 0x0f5e9 => { + self.hline_middle(canvas, Thickness.light); + try self.draw_light_arc(canvas, .tr); + try self.draw_light_arc(canvas, .br); + }, + + // '' + 0x0f5ea => { + self.vline_middle(canvas, Thickness.light); + try self.draw_light_arc(canvas, .tl); + try self.draw_light_arc(canvas, .br); + }, + + // '' + 0x0f5eb => { + self.vline_middle(canvas, Thickness.light); + try self.draw_light_arc(canvas, .tr); + try self.draw_light_arc(canvas, .bl); + }, + + // '' + 0x0f5ec => { + self.hline_middle(canvas, Thickness.light); + try self.draw_light_arc(canvas, .tl); + try self.draw_light_arc(canvas, .br); + }, + + // '' + 0x0f5ed => { + self.hline_middle(canvas, Thickness.light); + try self.draw_light_arc(canvas, .tr); + try self.draw_light_arc(canvas, .bl); + }, + + // '' + 0x0f5ee => self.draw_git_node(canvas, .{}, Thickness.light, true), + + // '' + 0x0f5ef => self.draw_git_node(canvas, .{}, Thickness.light, false), + + // '' + 0x0f5f0 => { + self.draw_git_node(canvas, .{ .right = true }, Thickness.light, true); + }, + + // '' + 0x0f5f1 => { + self.draw_git_node(canvas, .{ .right = true }, Thickness.light, false); + }, + + // '' + 0x0f5f2 => { + self.draw_git_node(canvas, .{ .left = true }, Thickness.light, true); + }, + + // '' + 0x0f5f3 => { + self.draw_git_node(canvas, .{ .left = true }, Thickness.light, false); + }, + + // '' + 0x0f5f4 => { + self.draw_git_node(canvas, .{ .left = true, .right = true }, Thickness.light, true); + }, + + // '' + 0x0f5f5 => { + self.draw_git_node(canvas, .{ .left = true, .right = true }, Thickness.light, false); + }, + + // '' + 0x0f5f6 => { + self.draw_git_node(canvas, .{ .down = true }, Thickness.light, true); + }, + + // '' + 0x0f5f7 => { + self.draw_git_node(canvas, .{ .down = true }, Thickness.light, false); + }, + + // '' + 0x0f5f8 => { + self.draw_git_node(canvas, .{ .up = true }, Thickness.light, true); + }, + + // '' + 0x0f5f9 => { + self.draw_git_node(canvas, .{ .up = true }, Thickness.light, false); + }, + + // '' + 0x0f5fa => { + self.draw_git_node(canvas, .{ .up = true, .down = true }, Thickness.light, true); + }, + + // '' + 0x0f5fb => { + self.draw_git_node(canvas, .{ .up = true, .down = true }, Thickness.light, false); + }, + + // '' + 0x0f5fc => { + self.draw_git_node(canvas, .{ .right = true, .down = true }, Thickness.light, true); + }, + + // '' + 0x0f5fd => { + self.draw_git_node(canvas, .{ .right = true, .down = true }, Thickness.light, false); + }, + + // '' + 0x0f5fe => { + self.draw_git_node(canvas, .{ .left = true, .down = true }, Thickness.light, true); + }, + + // '' + 0x0f5ff => { + self.draw_git_node(canvas, .{ .left = true, .down = true }, Thickness.light, false); + }, + + // '' + 0x0f600 => { + self.draw_git_node(canvas, .{ .up = true, .right = true }, Thickness.light, true); + }, + + // '' + 0x0f601 => { + self.draw_git_node(canvas, .{ .up = true, .right = true }, Thickness.light, false); + }, + + // '' + 0x0f602 => { + self.draw_git_node(canvas, .{ .up = true, .left = true }, Thickness.light, true); + }, + + // '' + 0x0f603 => { + self.draw_git_node(canvas, .{ .up = true, .left = true }, Thickness.light, false); + }, + + // '' + 0x0f604 => { + self.draw_git_node(canvas, .{ .up = true, .down = true, .right = true }, Thickness.light, true); + }, + + // '' + 0x0f605 => { + self.draw_git_node(canvas, .{ .up = true, .down = true, .right = true }, Thickness.light, false); + }, + + // '' + 0x0f606 => { + self.draw_git_node(canvas, .{ .up = true, .down = true, .left = true }, Thickness.light, true); + }, + + // '' + 0x0f607 => { + self.draw_git_node(canvas, .{ .up = true, .down = true, .left = true }, Thickness.light, false); + }, + + // '' + 0x0f608 => { + self.draw_git_node(canvas, .{ .down = true, .left = true, .right = true }, Thickness.light, true); + }, + + // '' + 0x0f609 => { + self.draw_git_node(canvas, .{ .down = true, .left = true, .right = true }, Thickness.light, false); + }, + + // '' + 0x0f60a => { + self.draw_git_node(canvas, .{ .up = true, .left = true, .right = true }, Thickness.light, true); + }, + + // '' + 0x0f60b => { + self.draw_git_node(canvas, .{ .up = true, .left = true, .right = true }, Thickness.light, false); + }, + + // '' + 0x0f60c => { + self.draw_git_node(canvas, .{ .up = true, .down = true, .left = true, .right = true }, Thickness.light, true); + }, + + // '' + 0x0f60d => { + self.draw_git_node(canvas, .{ .up = true, .down = true, .left = true, .right = true }, Thickness.light, false); + }, + // Not official box characters but special characters we hide // in the high bits of a unicode codepoint. @intFromEnum(Sprite.cursor_rect) => self.draw_cursor_rect(canvas), @@ -1793,6 +2116,57 @@ fn draw_cell_diagonal( ) catch {}; } +fn draw_git_node( + self: Box, + canvas: *font.sprite.Canvas, + comptime nodes: NodeAlign, + comptime thickness: Thickness, + comptime filled: bool, +) void { + 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; + + // we need at least 1px leeway to see node + right/left line + const r: f64 = 0.47 * @min(float_width, float_height); + + var ctx: z2d.Context = .{ + .surface = canvas.sfc, + .pattern = .{ + .opaque_pattern = .{ + .pixel = .{ .alpha8 = .{ .a = @intFromEnum(Shade.on) } }, + }, + }, + .line_width = @floatFromInt(thickness.height(self.thickness)), + }; + + var path = z2d.Path.init(canvas.alloc); + defer path.deinit(); + + const int_radius: u32 = @intFromFloat(r); + + 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; + 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.close() catch return; + ctx.stroke(canvas.alloc, path) catch return; + } +} + fn draw_circle( self: Box, canvas: *font.sprite.Canvas, @@ -2377,6 +2751,16 @@ 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); @@ -2480,6 +2864,20 @@ 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) { diff --git a/src/font/sprite/Face.zig b/src/font/sprite/Face.zig index af82bb731..650bd2a5f 100644 --- a/src/font/sprite/Face.zig +++ b/src/font/sprite/Face.zig @@ -276,6 +276,13 @@ const Kind = enum { 0xE0D4, => .powerline, + // (Git Branch) + //           + //                     + //                     + //             + 0xF5D0...0xF60D => .box, + else => null, }; }