From 584149121dd34a87f1755983a16a903336582bcb Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 23 Nov 2022 09:10:19 -0800 Subject: [PATCH 01/11] use enum for underline styles --- src/renderer/Metal.zig | 2 +- src/renderer/OpenGL.zig | 4 ++-- src/terminal/Screen.zig | 5 +++-- src/terminal/Terminal.zig | 6 +++--- src/terminal/sgr.zig | 13 +++++++++++-- 5 files changed, 20 insertions(+), 10 deletions(-) diff --git a/src/renderer/Metal.zig b/src/renderer/Metal.zig index 0b8da73ba..33eadeb24 100644 --- a/src/renderer/Metal.zig +++ b/src/renderer/Metal.zig @@ -911,7 +911,7 @@ pub fn updateCell( }); } - if (cell.attrs.underline) { + if (cell.attrs.underline != .none) { self.cells.appendAssumeCapacity(.{ .mode = .underline, .grid_pos = .{ @intToFloat(f32, x), @intToFloat(f32, y) }, diff --git a/src/renderer/OpenGL.zig b/src/renderer/OpenGL.zig index 662963e2c..2771b1a65 100644 --- a/src/renderer/OpenGL.zig +++ b/src/renderer/OpenGL.zig @@ -928,7 +928,7 @@ pub fn updateCell( var i: usize = 0; if (colors.bg != null) i += 1; if (!cell.empty()) i += 1; - if (cell.attrs.underline) i += 1; + if (cell.attrs.underline != .none) i += 1; if (cell.attrs.strikethrough) i += 1; break :needed i; }; @@ -1002,7 +1002,7 @@ pub fn updateCell( }); } - if (cell.attrs.underline) { + if (cell.attrs.underline != .none) { self.cells.appendAssumeCapacity(.{ .mode = .underline, .grid_col = @intCast(u16, x), diff --git a/src/terminal/Screen.zig b/src/terminal/Screen.zig index 993e0a4e0..4b9161077 100644 --- a/src/terminal/Screen.zig +++ b/src/terminal/Screen.zig @@ -56,6 +56,7 @@ const Allocator = std.mem.Allocator; const utf8proc = @import("utf8proc"); const trace = @import("tracy").trace; +const sgr = @import("sgr.zig"); const color = @import("color.zig"); const point = @import("point.zig"); const CircBuf = @import("circ_buf.zig").CircBuf; @@ -167,10 +168,10 @@ pub const Cell = struct { bold: bool = false, italic: bool = false, faint: bool = false, - underline: bool = false, blink: bool = false, inverse: bool = false, strikethrough: bool = false, + underline: sgr.Attribute.Underline = .none, /// True if this is a wide character. This char takes up /// two cells. The following cell ALWAYS is a space. @@ -241,7 +242,7 @@ pub const Cell = struct { } test { - //log.warn("CELL={} {}", .{ @sizeOf(Cell), @alignOf(Cell) }); + //log.warn("CELL={} bits={} {}", .{ @sizeOf(Cell), @bitSizeOf(Cell), @alignOf(Cell) }); try std.testing.expectEqual(12, @sizeOf(Cell)); } }; diff --git a/src/terminal/Terminal.zig b/src/terminal/Terminal.zig index 91b1ac21d..90867da0c 100644 --- a/src/terminal/Terminal.zig +++ b/src/terminal/Terminal.zig @@ -379,12 +379,12 @@ pub fn setAttribute(self: *Terminal, attr: sgr.Attribute) !void { self.screen.cursor.pen.attrs.faint = true; }, - .underline => { - self.screen.cursor.pen.attrs.underline = true; + .underline => |v| { + self.screen.cursor.pen.attrs.underline = v; }, .reset_underline => { - self.screen.cursor.pen.attrs.underline = false; + self.screen.cursor.pen.attrs.underline = .none; }, .blink => { diff --git a/src/terminal/sgr.zig b/src/terminal/sgr.zig index 835694092..8d031c4a1 100644 --- a/src/terminal/sgr.zig +++ b/src/terminal/sgr.zig @@ -31,7 +31,7 @@ pub const Attribute = union(enum) { faint: void, /// Underline the text - underline: void, + underline: Underline, reset_underline: void, /// Blink the text @@ -75,6 +75,15 @@ pub const Attribute = union(enum) { g: u8, b: u8, }; + + pub const Underline = enum(u3) { + none = 0, + single = 1, + double = 2, + curly = 3, + dotted = 4, + dashed = 5, + }; }; /// Parser parses the attributes from a list of SGR parameters. @@ -107,7 +116,7 @@ pub const Parser = struct { 3 => return Attribute{ .italic = {} }, - 4 => return Attribute{ .underline = {} }, + 4 => return Attribute{ .underline = .single }, 5 => return Attribute{ .blink = {} }, From 278668c953ffcba960c1c9fb7a4a8fed4ba614c9 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 27 Nov 2022 15:14:49 -0800 Subject: [PATCH 02/11] font: draw single and double underlines as sprites --- src/Window.zig | 7 -- src/font/main.zig | 4 ++ src/font/sprite.zig | 5 +- src/font/sprite/Canvas.zig | 6 +- src/font/sprite/Face.zig | 30 +++++++-- src/font/sprite/underline.zig | 119 ++++++++++++++++++++++++++++++++++ 6 files changed, 155 insertions(+), 16 deletions(-) create mode 100644 src/font/sprite/underline.zig diff --git a/src/Window.zig b/src/Window.zig index 82bd511ef..ebe3b67c7 100644 --- a/src/Window.zig +++ b/src/Window.zig @@ -289,13 +289,6 @@ pub fn create(alloc: Allocator, app: *App, config: *const Config) !*Window { // Pre-calculate our initial cell size ourselves. const cell_size = try renderer.CellSize.init(alloc, font_group); - // Setup our box font - font_group.group.sprite = font.sprite.Face{ - .width = @floatToInt(u32, cell_size.width), - .height = @floatToInt(u32, cell_size.height), - .thickness = 2, - }; - // Convert our padding from points to pixels const padding_x = (@intToFloat(f32, config.@"window-padding-x") * x_dpi) / 72; const padding_y = (@intToFloat(f32, config.@"window-padding-y") * y_dpi) / 72; diff --git a/src/font/main.zig b/src/font/main.zig index 4bcc74ff4..ddcfe551f 100644 --- a/src/font/main.zig +++ b/src/font/main.zig @@ -11,6 +11,7 @@ pub const Glyph = @import("Glyph.zig"); pub const Library = @import("Library.zig"); pub const Shaper = @import("Shaper.zig"); pub const sprite = @import("sprite.zig"); +pub const Sprite = sprite.Sprite; pub const Descriptor = discovery.Descriptor; pub const Discover = discovery.Discover; @@ -60,6 +61,9 @@ pub const Presentation = enum(u1) { emoji = 1, // U+FEOF }; +/// A FontIndex that can be used to use the sprite font directly. +pub const sprite_index = Group.FontIndex.initSpecial(.sprite); + test { @import("std").testing.refAllDecls(@This()); } diff --git a/src/font/sprite.zig b/src/font/sprite.zig index e548cd4e9..dc3843de6 100644 --- a/src/font/sprite.zig +++ b/src/font/sprite.zig @@ -11,10 +11,11 @@ pub const Face = @import("sprite/Face.zig"); /// Area of Unicode. pub const Sprite = enum(u32) { // Start 1 above the maximum Unicode codepoint. - const start: u32 = std.math.maxInt(u21) + 1; - const end: u32 = std.math.maxInt(u32); + pub const start: u32 = std.math.maxInt(u21) + 1; + pub const end: u32 = std.math.maxInt(u32); underline = start, + underline_double = start + 1, // Note: we don't currently put the box drawing glyphs in here because // there are a LOT and I'm lazy. What I want to do is spend more time diff --git a/src/font/sprite/Canvas.zig b/src/font/sprite/Canvas.zig index 05ef1e5a1..208ad3593 100644 --- a/src/font/sprite/Canvas.zig +++ b/src/font/sprite/Canvas.zig @@ -117,13 +117,13 @@ pub fn writeAtlas(self: *Canvas, alloc: Allocator, atlas: *Atlas) !Atlas.Region /// 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: pixman.Color) void { +pub fn rect(self: *Canvas, v: Rect, color: Color) void { const boxes = &[_]pixman.Box32{ .{ .x1 = @intCast(i32, v.x), .y1 = @intCast(i32, v.y), - .x2 = @intCast(i32, v.width), - .y2 = @intCast(i32, v.height), + .x2 = @intCast(i32, v.x + v.width), + .y2 = @intCast(i32, v.y + v.height), }, }; diff --git a/src/font/sprite/Face.zig b/src/font/sprite/Face.zig index cb4643833..d42b3ef0b 100644 --- a/src/font/sprite/Face.zig +++ b/src/font/sprite/Face.zig @@ -17,8 +17,10 @@ const builtin = @import("builtin"); const assert = std.debug.assert; const Allocator = std.mem.Allocator; const font = @import("../main.zig"); +const Sprite = font.sprite.Sprite; const Atlas = @import("../../Atlas.zig"); const Box = @import("Box.zig"); +const underline = @import("underline.zig"); /// The cell width and height. width: u32, @@ -28,6 +30,9 @@ height: u32, /// want to do any DPI scaling, it is expected to be done earlier. thickness: u32, +/// The position fo the underline. +underline_position: u32 = 0, + /// Returns true if the codepoint exists in our sprite font. pub fn hasCodepoint(self: Face, cp: u32, p: ?font.Presentation) bool { // We ignore presentation. No matter what presentation is requested @@ -47,25 +52,42 @@ pub fn renderGlyph( if (std.debug.runtime_safety) assert(self.hasCodepoint(cp, null)); // Safe to ".?" because of the above assertion. - switch (Kind.init(cp).?) { - .box => { + return switch (Kind.init(cp).?) { + .box => box: { const f: Box = .{ .width = self.width, .height = self.height, .thickness = self.thickness, }; - return try f.renderGlyph(alloc, atlas, cp); + break :box try f.renderGlyph(alloc, atlas, cp); }, - } + + .underline => try underline.renderGlyph( + alloc, + atlas, + @intToEnum(Sprite, cp), + self.width, + self.height, + self.underline_position, + self.thickness, + ), + }; } /// Kind of sprites we have. Drawing is implemented separately for each kind. const Kind = enum { box, + underline, pub fn init(cp: u32) ?Kind { return switch (cp) { + Sprite.start...Sprite.end => switch (@intToEnum(Sprite, cp)) { + .underline, + .underline_double, + => .underline, + }, + // Box fonts 0x2500...0x257F, // "Box Drawing" block 0x2580...0x259F, // "Block Elements" block diff --git a/src/font/sprite/underline.zig b/src/font/sprite/underline.zig new file mode 100644 index 000000000..5f6d2f9af --- /dev/null +++ b/src/font/sprite/underline.zig @@ -0,0 +1,119 @@ +//! This file renders underline sprites. To draw underlines, we render the +//! full cell-width as a sprite and then draw it as a separate pass to the +//! text. +//! +//! We used to render the underlines directly in the GPU shaders but its +//! annoying to support multiple types of underlines and its also annoying +//! to maintain and debug another set of shaders for each renderer instead of +//! just relying on the glyph system we already need to support for text +//! anyways. +const std = @import("std"); +const builtin = @import("builtin"); +const assert = std.debug.assert; +const Allocator = std.mem.Allocator; +const font = @import("../main.zig"); +const Sprite = font.sprite.Sprite; +const Atlas = @import("../../Atlas.zig"); + +/// Draw an underline. +pub fn renderGlyph( + alloc: Allocator, + atlas: *Atlas, + sprite: Sprite, + width: u32, + height: u32, + line_pos: u32, + line_thickness: u32, +) !font.Glyph { + // Create the canvas we'll use to draw. We draw the underline in + // a full cell size and position it according to "pos". + var canvas = try font.sprite.Canvas.init(alloc, width, height); + defer canvas.deinit(alloc); + + // Perform the actual drawing + (Draw{ + .width = width, + .height = height, + .pos = line_pos, + .thickness = line_thickness, + }).draw(&canvas, sprite); + + // Write the drawing to the atlas + const region = try canvas.writeAtlas(alloc, atlas); + + // Our coordinates start at the BOTTOM for our renderers so we have to + // specify an offset of the full height because we rendered a full size + // cell. + const offset_y = @intCast(i32, height); + + return font.Glyph{ + .width = width, + .height = height, + .offset_x = 0, + .offset_y = offset_y, + .atlas_x = region.x, + .atlas_y = region.y, + .advance_x = @intToFloat(f32, width), + }; +} + +/// Stores drawing state. +const Draw = struct { + width: u32, + height: u32, + pos: u32, + thickness: u32, + + /// Draw a specific underline sprite to the canvas. + fn draw(self: Draw, canvas: *font.sprite.Canvas, sprite: Sprite) void { + switch (sprite) { + .underline => self.drawSingle(canvas), + .underline_double => self.drawDouble(canvas), + } + } + + /// Draw a single underline. + fn drawSingle(self: Draw, canvas: *font.sprite.Canvas) void { + canvas.rect(.{ + .x = 0, + .y = self.pos, + .width = self.width, + .height = self.thickness, + }, .on); + } + + /// Draw a double underline. + fn drawDouble(self: Draw, canvas: *font.sprite.Canvas) void { + canvas.rect(.{ + .x = 0, + .y = self.pos, + .width = self.width, + .height = self.thickness, + }, .on); + + canvas.rect(.{ + .x = 0, + .y = self.pos + (self.thickness * 2), + .width = self.width, + .height = self.thickness, + }, .on); + } +}; + +test "single" { + const testing = std.testing; + const alloc = testing.allocator; + + var atlas_greyscale = try Atlas.init(alloc, 512, .greyscale); + defer atlas_greyscale.deinit(alloc); + + _ = try renderGlyph( + alloc, + &atlas_greyscale, + .underline, + 36, + 18, + 9, + 2, + ); +} From 3a248f6051d3340710786c5259cfcc4014a2dc63 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 27 Nov 2022 15:18:03 -0800 Subject: [PATCH 03/11] opengl: render underlines using sprite system, not shaders --- src/renderer/OpenGL.zig | 41 ++++++++++++++++++++++++++++------------- 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/src/renderer/OpenGL.zig b/src/renderer/OpenGL.zig index 2771b1a65..18b2aa959 100644 --- a/src/renderer/OpenGL.zig +++ b/src/renderer/OpenGL.zig @@ -501,12 +501,6 @@ pub fn setFontSize(self: *OpenGL, size: font.face.DesiredSize) !void { if (std.meta.eql(self.cell_size, new_cell_size)) return; self.cell_size = new_cell_size; - // Set the cell size of the box font - if (self.font_group.group.sprite) |*sprite| { - sprite.width = @floatToInt(u32, self.cell_size.width); - sprite.height = @floatToInt(u32, self.cell_size.height); - } - // Notify the window that the cell size changed. _ = self.window_mailbox.push(.{ .cell_size = new_cell_size, @@ -530,6 +524,14 @@ fn resetFontMetrics( }; log.debug("cell dimensions={}", .{metrics}); + // Set details for our sprite font + font_group.group.sprite = font.sprite.Face{ + .width = @floatToInt(u32, metrics.cell_width), + .height = @floatToInt(u32, metrics.cell_height), + .thickness = 2, + .underline_position = @floatToInt(u32, metrics.underline_position), + }; + // Set our uniforms that rely on metrics const pbind = try program.use(); defer pbind.unbind(); @@ -1003,17 +1005,30 @@ pub fn updateCell( } if (cell.attrs.underline != .none) { + const sprite: font.Sprite = switch (cell.attrs.underline) { + .none => unreachable, + .single => .underline, + .double => .underline_double, + else => .underline, + }; + + const underline_glyph = try self.font_group.renderGlyph( + self.alloc, + font.sprite_index, + @enumToInt(sprite), + null, + ); self.cells.appendAssumeCapacity(.{ - .mode = .underline, + .mode = .fg, .grid_col = @intCast(u16, x), .grid_row = @intCast(u16, y), .grid_width = cell.widthLegacy(), - .glyph_x = 0, - .glyph_y = 0, - .glyph_width = 0, - .glyph_height = 0, - .glyph_offset_x = 0, - .glyph_offset_y = 0, + .glyph_x = underline_glyph.atlas_x, + .glyph_y = underline_glyph.atlas_y, + .glyph_width = underline_glyph.width, + .glyph_height = underline_glyph.height, + .glyph_offset_x = underline_glyph.offset_x, + .glyph_offset_y = underline_glyph.offset_y, .fg_r = colors.fg.r, .fg_g = colors.fg.g, .fg_b = colors.fg.b, From d7fe6a1c47880efe989aa3c3f601e7447de469cf Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 27 Nov 2022 15:30:02 -0800 Subject: [PATCH 04/11] fix sgr parsing for underline styles --- src/terminal/Parser.zig | 9 +++++++ src/terminal/sgr.zig | 57 ++++++++++++++++++++++++++++++++++++++++- src/terminal/stream.zig | 2 +- 3 files changed, 66 insertions(+), 2 deletions(-) diff --git a/src/terminal/Parser.zig b/src/terminal/Parser.zig index 9773da63f..69087dc58 100644 --- a/src/terminal/Parser.zig +++ b/src/terminal/Parser.zig @@ -79,6 +79,10 @@ pub const Action = union(enum) { intermediates: []u8, params: []u16, final: u8, + sep: Sep, + + /// The separator used for CSI params. + pub const Sep = enum { semicolon, colon }; // Implement formatter for logging pub fn format( @@ -392,6 +396,11 @@ fn doAction(self: *Parser, action: TransitionAction, c: u8) ?Action { .intermediates = self.intermediates[0..self.intermediates_idx], .params = self.params[0..self.params_idx], .final = c, + .sep = switch (self.params_sep) { + .none, .semicolon => .semicolon, + .colon => .colon, + .mixed => unreachable, + }, }, }; }, diff --git a/src/terminal/sgr.zig b/src/terminal/sgr.zig index 8d031c4a1..16620e822 100644 --- a/src/terminal/sgr.zig +++ b/src/terminal/sgr.zig @@ -91,6 +91,9 @@ pub const Parser = struct { params: []const u16, idx: usize = 0, + /// True if the separator is a colon + colon: bool = false, + /// Next returns the next attribute or null if there are no more attributes. pub fn next(self: *Parser) ?Attribute { if (self.idx > self.params.len) return null; @@ -116,7 +119,35 @@ pub const Parser = struct { 3 => return Attribute{ .italic = {} }, - 4 => return Attribute{ .underline = .single }, + 4 => blk: { + if (self.colon) { + switch (slice.len) { + // 0 is unreachable because we're here and we read + // an element to get here. + 0 => unreachable, + + // 1 is unreachable because we can't have a colon + // separator if there are no separators. + 1 => unreachable, + + // 2 means we have a specific underline style. + 2 => { + self.idx += 1; + switch (slice[1]) { + 0 => return Attribute{ .reset_underline = {} }, + 1 => return Attribute{ .underline = .single }, + 2 => return Attribute{ .underline = .double }, + else => break :blk, + } + }, + + // Colon-separated must only be 2. + else => break :blk, + } + } + + return Attribute{ .underline = .single }; + }, 5 => return Attribute{ .blink = {} }, @@ -215,6 +246,11 @@ fn testParse(params: []const u16) Attribute { return p.next().?; } +fn testParseColon(params: []const u16) Attribute { + var p: Parser = .{ .params = params, .colon = true }; + return p.next().?; +} + test "sgr: Parser" { try testing.expect(testParse(&[_]u16{}) == .unset); try testing.expect(testParse(&[_]u16{0}) == .unset); @@ -284,6 +320,25 @@ test "sgr: underline" { } } +test "sgr: underline styles" { + { + const v = testParseColon(&[_]u16{ 4, 2 }); + try testing.expect(v == .underline); + try testing.expect(v.underline == .double); + } + + { + const v = testParseColon(&[_]u16{ 4, 0 }); + try testing.expect(v == .reset_underline); + } + + { + const v = testParseColon(&[_]u16{ 4, 1 }); + try testing.expect(v == .underline); + try testing.expect(v.underline == .single); + } +} + test "sgr: blink" { { const v = testParse(&[_]u16{5}); diff --git a/src/terminal/stream.zig b/src/terminal/stream.zig index cdde8addb..eb1a73998 100644 --- a/src/terminal/stream.zig +++ b/src/terminal/stream.zig @@ -383,7 +383,7 @@ pub fn Stream(comptime Handler: type) type { // SGR - Select Graphic Rendition 'm' => if (@hasDecl(T, "setAttribute")) { - var p: sgr.Parser = .{ .params = action.params }; + var p: sgr.Parser = .{ .params = action.params, .colon = action.sep == .colon }; while (p.next()) |attr| try self.handler.setAttribute(attr); } else log.warn("unimplemented CSI callback: {}", .{action}), From 5045e51b99cff8c8091279059e3f1992e075fc76 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 27 Nov 2022 15:30:49 -0800 Subject: [PATCH 05/11] unknown underline styles render a single underline --- src/terminal/sgr.zig | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/terminal/sgr.zig b/src/terminal/sgr.zig index 16620e822..895d5bbeb 100644 --- a/src/terminal/sgr.zig +++ b/src/terminal/sgr.zig @@ -137,7 +137,10 @@ pub const Parser = struct { 0 => return Attribute{ .reset_underline = {} }, 1 => return Attribute{ .underline = .single }, 2 => return Attribute{ .underline = .double }, - else => break :blk, + + // For unknown underline styles, just render + // a single underline. + else => return Attribute{ .underline = .single }, } }, From c2d08c30713aab1e28f19f2f62a47799cee88bc3 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 27 Nov 2022 15:39:12 -0800 Subject: [PATCH 06/11] terminal: parse all underline styles --- src/terminal/sgr.zig | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/terminal/sgr.zig b/src/terminal/sgr.zig index 895d5bbeb..a68eed90e 100644 --- a/src/terminal/sgr.zig +++ b/src/terminal/sgr.zig @@ -137,6 +137,8 @@ pub const Parser = struct { 0 => return Attribute{ .reset_underline = {} }, 1 => return Attribute{ .underline = .single }, 2 => return Attribute{ .underline = .double }, + 4 => return Attribute{ .underline = .dotted }, + 5 => return Attribute{ .underline = .dashed }, // For unknown underline styles, just render // a single underline. @@ -340,6 +342,18 @@ test "sgr: underline styles" { try testing.expect(v == .underline); try testing.expect(v.underline == .single); } + + { + const v = testParseColon(&[_]u16{ 4, 4 }); + try testing.expect(v == .underline); + try testing.expect(v.underline == .dotted); + } + + { + const v = testParseColon(&[_]u16{ 4, 5 }); + try testing.expect(v == .underline); + try testing.expect(v.underline == .dashed); + } } test "sgr: blink" { From 6a32a30a16dc8c9158821a27e02ef280a9a5fa74 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 27 Nov 2022 15:39:18 -0800 Subject: [PATCH 07/11] font: render dotted underlines --- src/font/sprite.zig | 1 + src/font/sprite/Face.zig | 1 + src/font/sprite/underline.zig | 16 ++++++++++++++++ src/renderer/OpenGL.zig | 1 + 4 files changed, 19 insertions(+) diff --git a/src/font/sprite.zig b/src/font/sprite.zig index dc3843de6..f5b232061 100644 --- a/src/font/sprite.zig +++ b/src/font/sprite.zig @@ -16,6 +16,7 @@ pub const Sprite = enum(u32) { underline = start, underline_double = start + 1, + underline_dotted = start + 2, // Note: we don't currently put the box drawing glyphs in here because // there are a LOT and I'm lazy. What I want to do is spend more time diff --git a/src/font/sprite/Face.zig b/src/font/sprite/Face.zig index d42b3ef0b..829cecc09 100644 --- a/src/font/sprite/Face.zig +++ b/src/font/sprite/Face.zig @@ -85,6 +85,7 @@ const Kind = enum { Sprite.start...Sprite.end => switch (@intToEnum(Sprite, cp)) { .underline, .underline_double, + .underline_dotted, => .underline, }, diff --git a/src/font/sprite/underline.zig b/src/font/sprite/underline.zig index 5f6d2f9af..4ce2aad31 100644 --- a/src/font/sprite/underline.zig +++ b/src/font/sprite/underline.zig @@ -69,6 +69,7 @@ const Draw = struct { switch (sprite) { .underline => self.drawSingle(canvas), .underline_double => self.drawDouble(canvas), + .underline_dotted => self.drawDotted(canvas), } } @@ -98,6 +99,21 @@ const Draw = struct { .height = self.thickness, }, .on); } + + /// Draw a dotted underline. + fn drawDotted(self: Draw, canvas: *font.sprite.Canvas) void { + const dot_width = @max(self.thickness, 3); + const dot_count = self.width / dot_width; + var i: u32 = 0; + while (i < dot_count) : (i += 2) { + canvas.rect(.{ + .x = i * dot_width, + .y = self.pos, + .width = dot_width, + .height = self.thickness, + }, .on); + } + } }; test "single" { diff --git a/src/renderer/OpenGL.zig b/src/renderer/OpenGL.zig index 18b2aa959..35659490d 100644 --- a/src/renderer/OpenGL.zig +++ b/src/renderer/OpenGL.zig @@ -1009,6 +1009,7 @@ pub fn updateCell( .none => unreachable, .single => .underline, .double => .underline_double, + .dotted => .underline_dotted, else => .underline, }; From fee681ac78eb7438bb749bbff870d4255e32a551 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 27 Nov 2022 15:44:12 -0800 Subject: [PATCH 08/11] dashed underlines --- src/font/sprite.zig | 1 + src/font/sprite/Face.zig | 1 + src/font/sprite/underline.zig | 16 ++++++++++++++++ src/renderer/OpenGL.zig | 1 + 4 files changed, 19 insertions(+) diff --git a/src/font/sprite.zig b/src/font/sprite.zig index f5b232061..16c307554 100644 --- a/src/font/sprite.zig +++ b/src/font/sprite.zig @@ -17,6 +17,7 @@ pub const Sprite = enum(u32) { underline = start, underline_double = start + 1, underline_dotted = start + 2, + underline_dashed = start + 3, // Note: we don't currently put the box drawing glyphs in here because // there are a LOT and I'm lazy. What I want to do is spend more time diff --git a/src/font/sprite/Face.zig b/src/font/sprite/Face.zig index 829cecc09..60c356548 100644 --- a/src/font/sprite/Face.zig +++ b/src/font/sprite/Face.zig @@ -86,6 +86,7 @@ const Kind = enum { .underline, .underline_double, .underline_dotted, + .underline_dashed, => .underline, }, diff --git a/src/font/sprite/underline.zig b/src/font/sprite/underline.zig index 4ce2aad31..87c25a9d5 100644 --- a/src/font/sprite/underline.zig +++ b/src/font/sprite/underline.zig @@ -70,6 +70,7 @@ const Draw = struct { .underline => self.drawSingle(canvas), .underline_double => self.drawDouble(canvas), .underline_dotted => self.drawDotted(canvas), + .underline_dashed => self.drawDashed(canvas), } } @@ -114,6 +115,21 @@ const Draw = struct { }, .on); } } + + /// Draw a dashed underline. + fn drawDashed(self: Draw, canvas: *font.sprite.Canvas) void { + const dash_width = self.width / 3 + 1; + const dash_count = (self.width / dash_width) + 1; + var i: u32 = 0; + while (i < dash_count) : (i += 2) { + canvas.rect(.{ + .x = i * dash_width, + .y = self.pos, + .width = dash_width, + .height = self.thickness, + }, .on); + } + } }; test "single" { diff --git a/src/renderer/OpenGL.zig b/src/renderer/OpenGL.zig index 35659490d..b83f746cb 100644 --- a/src/renderer/OpenGL.zig +++ b/src/renderer/OpenGL.zig @@ -1010,6 +1010,7 @@ pub fn updateCell( .single => .underline, .double => .underline_double, .dotted => .underline_dotted, + .dashed => .underline_dashed, else => .underline, }; From bfc657395ac0373cf1d93c7ba75d04ee19b4932a Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 27 Nov 2022 16:11:15 -0800 Subject: [PATCH 09/11] curly underlines --- src/font/sprite.zig | 1 + src/font/sprite/Face.zig | 1 + src/font/sprite/underline.zig | 54 +++++++++++++++++++++++++++++++++++ src/renderer/OpenGL.zig | 2 +- src/terminal/sgr.zig | 1 + 5 files changed, 58 insertions(+), 1 deletion(-) diff --git a/src/font/sprite.zig b/src/font/sprite.zig index 16c307554..027456b7c 100644 --- a/src/font/sprite.zig +++ b/src/font/sprite.zig @@ -18,6 +18,7 @@ pub const Sprite = enum(u32) { underline_double = start + 1, underline_dotted = start + 2, underline_dashed = start + 3, + underline_curly = start + 4, // Note: we don't currently put the box drawing glyphs in here because // there are a LOT and I'm lazy. What I want to do is spend more time diff --git a/src/font/sprite/Face.zig b/src/font/sprite/Face.zig index 60c356548..b8829e4f5 100644 --- a/src/font/sprite/Face.zig +++ b/src/font/sprite/Face.zig @@ -87,6 +87,7 @@ const Kind = enum { .underline_double, .underline_dotted, .underline_dashed, + .underline_curly, => .underline, }, diff --git a/src/font/sprite/underline.zig b/src/font/sprite/underline.zig index 87c25a9d5..5e1deddef 100644 --- a/src/font/sprite/underline.zig +++ b/src/font/sprite/underline.zig @@ -71,6 +71,7 @@ const Draw = struct { .underline_double => self.drawDouble(canvas), .underline_dotted => self.drawDotted(canvas), .underline_dashed => self.drawDashed(canvas), + .underline_curly => self.drawCurly(canvas), } } @@ -130,6 +131,41 @@ const Draw = struct { }, .on); } } + + /// Draw a curly underline. Thanks to Wez Furlong for providing + /// the basic math structure for this since I was lazy with the + /// geometry. + fn drawCurly(self: Draw, canvas: *font.sprite.Canvas) void { + // This is the lowest that the curl can go. + const y_max = self.height - 1; + + // The full heightof the wave can be from the bottom to the + // underline position. We also calculate our starting y which is + // slightly below our descender since our wave will move about that. + const wave_height = @intToFloat(f64, y_max - self.pos); + const half_height = wave_height / 4; + const y = self.pos + @floatToInt(u32, half_height); + + const x_factor = (2 * std.math.pi) / @intToFloat(f64, self.width); + var x: u32 = 0; + while (x < self.width) : (x += 1) { + const vertical = @floatToInt( + u32, + (-1 * half_height) * @sin(@intToFloat(f64, x) * x_factor) + half_height, + ); + + var row: u32 = 0; + while (row < self.thickness) : (row += 1) { + const y1 = @min(row + y + vertical, y_max); + canvas.rect(.{ + .x = x, + .y = y1, + .width = 1, + .height = 1, + }, .on); + } + } + } }; test "single" { @@ -149,3 +185,21 @@ test "single" { 2, ); } + +test "curly" { + const testing = std.testing; + const alloc = testing.allocator; + + var atlas_greyscale = try Atlas.init(alloc, 512, .greyscale); + defer atlas_greyscale.deinit(alloc); + + _ = try renderGlyph( + alloc, + &atlas_greyscale, + .underline_curly, + 36, + 18, + 9, + 2, + ); +} diff --git a/src/renderer/OpenGL.zig b/src/renderer/OpenGL.zig index b83f746cb..fa4877a1a 100644 --- a/src/renderer/OpenGL.zig +++ b/src/renderer/OpenGL.zig @@ -1011,7 +1011,7 @@ pub fn updateCell( .double => .underline_double, .dotted => .underline_dotted, .dashed => .underline_dashed, - else => .underline, + .curly => .underline_curly, }; const underline_glyph = try self.font_group.renderGlyph( diff --git a/src/terminal/sgr.zig b/src/terminal/sgr.zig index a68eed90e..eda08ac8c 100644 --- a/src/terminal/sgr.zig +++ b/src/terminal/sgr.zig @@ -137,6 +137,7 @@ pub const Parser = struct { 0 => return Attribute{ .reset_underline = {} }, 1 => return Attribute{ .underline = .single }, 2 => return Attribute{ .underline = .double }, + 3 => return Attribute{ .underline = .curly }, 4 => return Attribute{ .underline = .dotted }, 5 => return Attribute{ .underline = .dashed }, From 2b9a47edb23fc91990fb2aaf9d1568fe2a287899 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 27 Nov 2022 16:15:22 -0800 Subject: [PATCH 10/11] metal: underline styles --- src/renderer/Metal.zig | 41 +++++++++++++++++++++++++++++++++++------ 1 file changed, 35 insertions(+), 6 deletions(-) diff --git a/src/renderer/Metal.zig b/src/renderer/Metal.zig index 33eadeb24..3d4893c22 100644 --- a/src/renderer/Metal.zig +++ b/src/renderer/Metal.zig @@ -171,6 +171,14 @@ pub fn init(alloc: Allocator, options: renderer.Options) !Metal { }; log.debug("cell dimensions={}", .{metrics}); + // Set the sprite font up + options.font_group.group.sprite = font.sprite.Face{ + .width = @floatToInt(u32, metrics.cell_width), + .height = @floatToInt(u32, metrics.cell_height), + .thickness = 2, + .underline_position = @floatToInt(u32, metrics.underline_position), + }; + // Create the font shaper. We initially create a shaper that can support // a width of 160 which is a common width for modern screens to help // avoid allocations later. @@ -408,11 +416,13 @@ pub fn setFontSize(self: *Metal, size: font.face.DesiredSize) !void { if (std.meta.eql(self.cell_size, new_cell_size)) return; self.cell_size = new_cell_size; - // Set the cell size of the box font - if (self.font_group.group.sprite) |*sprite| { - sprite.width = @floatToInt(u32, self.cell_size.width); - sprite.height = @floatToInt(u32, self.cell_size.height); - } + // Set the sprite font up + self.font_group.group.sprite = font.sprite.Face{ + .width = @floatToInt(u32, self.cell_size.width), + .height = @floatToInt(u32, self.cell_size.height), + .thickness = 2, + .underline_position = @floatToInt(u32, metrics.underline_position), + }; // Notify the window that the cell size changed. _ = self.window_mailbox.push(.{ @@ -912,11 +922,30 @@ pub fn updateCell( } if (cell.attrs.underline != .none) { + const sprite: font.Sprite = switch (cell.attrs.underline) { + .none => unreachable, + .single => .underline, + .double => .underline_double, + .dotted => .underline_dotted, + .dashed => .underline_dashed, + .curly => .underline_curly, + }; + + const glyph = try self.font_group.renderGlyph( + self.alloc, + font.sprite_index, + @enumToInt(sprite), + null, + ); + self.cells.appendAssumeCapacity(.{ - .mode = .underline, + .mode = .fg, .grid_pos = .{ @intToFloat(f32, x), @intToFloat(f32, y) }, .cell_width = cell.widthLegacy(), .color = .{ colors.fg.r, colors.fg.g, colors.fg.b, alpha }, + .glyph_pos = .{ glyph.atlas_x, glyph.atlas_y }, + .glyph_size = .{ glyph.width, glyph.height }, + .glyph_offset = .{ glyph.offset_x, glyph.offset_y }, }); } From 379072566fc0472436352880044d00fcfd5d2099 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 27 Nov 2022 16:18:00 -0800 Subject: [PATCH 11/11] remove underline support from shaders since we now use sprites --- src/renderer/Metal.zig | 9 --------- src/renderer/OpenGL.zig | 5 ----- src/renderer/shaders/cell.f.glsl | 5 ----- src/renderer/shaders/cell.metal | 22 ---------------------- src/renderer/shaders/cell.v.glsl | 19 ------------------- 5 files changed, 60 deletions(-) diff --git a/src/renderer/Metal.zig b/src/renderer/Metal.zig index 3d4893c22..aa4375179 100644 --- a/src/renderer/Metal.zig +++ b/src/renderer/Metal.zig @@ -102,8 +102,6 @@ const GPUUniforms = extern struct { cell_size: [2]f32, /// Metrics for underline/strikethrough - underline_position: f32, - underline_thickness: f32, strikethrough_position: f32, strikethrough_thickness: f32, }; @@ -115,7 +113,6 @@ const GPUCellMode = enum(u8) { cursor_rect = 3, cursor_rect_hollow = 4, cursor_bar = 5, - underline = 6, strikethrough = 8, pub fn fromCursor(cursor: renderer.CursorStyle) GPUCellMode { @@ -267,8 +264,6 @@ pub fn init(alloc: Allocator, options: renderer.Options) !Metal { .uniforms = .{ .projection_matrix = undefined, .cell_size = undefined, - .underline_position = metrics.underline_position, - .underline_thickness = metrics.underline_thickness, .strikethrough_position = metrics.strikethrough_position, .strikethrough_thickness = metrics.strikethrough_thickness, }, @@ -405,8 +400,6 @@ pub fn setFontSize(self: *Metal, size: font.face.DesiredSize) !void { self.uniforms = .{ .projection_matrix = self.uniforms.projection_matrix, .cell_size = .{ new_cell_size.width, new_cell_size.height }, - .underline_position = metrics.underline_position, - .underline_thickness = metrics.underline_thickness, .strikethrough_position = metrics.strikethrough_position, .strikethrough_thickness = metrics.strikethrough_thickness, }; @@ -717,8 +710,6 @@ pub fn setScreenSize(self: *Metal, _: renderer.ScreenSize) !void { -1 * padding.top, ), .cell_size = .{ self.cell_size.width, self.cell_size.height }, - .underline_position = old.underline_position, - .underline_thickness = old.underline_thickness, .strikethrough_position = old.strikethrough_position, .strikethrough_thickness = old.strikethrough_thickness, }; diff --git a/src/renderer/OpenGL.zig b/src/renderer/OpenGL.zig index fa4877a1a..f01debb2f 100644 --- a/src/renderer/OpenGL.zig +++ b/src/renderer/OpenGL.zig @@ -137,7 +137,6 @@ const GPUCellMode = enum(u8) { cursor_rect = 3, cursor_rect_hollow = 4, cursor_bar = 5, - underline = 6, strikethrough = 8, // Non-exhaustive because masks change it @@ -180,8 +179,6 @@ pub fn init(alloc: Allocator, options: renderer.Options) !OpenGL { const pbind = try program.use(); defer pbind.unbind(); try program.setUniform("cell_size", @Vector(2, f32){ metrics.cell_width, metrics.cell_height }); - try program.setUniform("underline_position", metrics.underline_position); - try program.setUniform("underline_thickness", metrics.underline_thickness); try program.setUniform("strikethrough_position", metrics.strikethrough_position); try program.setUniform("strikethrough_thickness", metrics.strikethrough_thickness); @@ -536,8 +533,6 @@ fn resetFontMetrics( const pbind = try program.use(); defer pbind.unbind(); try program.setUniform("cell_size", @Vector(2, f32){ metrics.cell_width, metrics.cell_height }); - try program.setUniform("underline_position", metrics.underline_position); - try program.setUniform("underline_thickness", metrics.underline_thickness); try program.setUniform("strikethrough_position", metrics.strikethrough_position); try program.setUniform("strikethrough_thickness", metrics.strikethrough_thickness); diff --git a/src/renderer/shaders/cell.f.glsl b/src/renderer/shaders/cell.f.glsl index 79ab8186c..4a01e6958 100644 --- a/src/renderer/shaders/cell.f.glsl +++ b/src/renderer/shaders/cell.f.glsl @@ -30,7 +30,6 @@ const uint MODE_FG_COLOR = 7u; const uint MODE_CURSOR_RECT = 3u; const uint MODE_CURSOR_RECT_HOLLOW = 4u; const uint MODE_CURSOR_BAR = 5u; -const uint MODE_UNDERLINE = 6u; const uint MODE_STRIKETHROUGH = 8u; void main() { @@ -94,10 +93,6 @@ void main() { out_FragColor = color; break; - case MODE_UNDERLINE: - out_FragColor = color; - break; - case MODE_STRIKETHROUGH: out_FragColor = color; break; diff --git a/src/renderer/shaders/cell.metal b/src/renderer/shaders/cell.metal index 763e93ddf..f92b7e529 100644 --- a/src/renderer/shaders/cell.metal +++ b/src/renderer/shaders/cell.metal @@ -8,15 +8,12 @@ enum Mode : uint8_t { MODE_CURSOR_RECT = 3u, MODE_CURSOR_RECT_HOLLOW = 4u, MODE_CURSOR_BAR = 5u, - MODE_UNDERLINE = 6u, MODE_STRIKETHROUGH = 8u, }; struct Uniforms { float4x4 projection_matrix; float2 cell_size; - float underline_position; - float underline_thickness; float strikethrough_position; float strikethrough_thickness; }; @@ -154,22 +151,6 @@ vertex VertexOut uber_vertex( break; } - case MODE_UNDERLINE: { - // Underline Y value is just our thickness - float2 underline_size = float2(cell_size_scaled.x, uniforms.underline_thickness); - - // Position the underline where we are told to - float2 underline_offset = float2(cell_size_scaled.x, uniforms.underline_position); - - // Go to the bottom of the cell, take away the size of the - // underline, and that is our position. We also float it slightly - // above the bottom. - cell_pos = cell_pos + underline_offset - (underline_size * position); - - out.position = uniforms.projection_matrix * float4(cell_pos, 0.0f, 1.0); - break; - } - case MODE_STRIKETHROUGH: { // Strikethrough Y value is just our thickness float2 strikethrough_size = float2(cell_size_scaled.x, uniforms.strikethrough_thickness); @@ -259,9 +240,6 @@ fragment float4 uber_fragment( case MODE_CURSOR_BAR: return in.color; - case MODE_UNDERLINE: - return in.color; - case MODE_STRIKETHROUGH: return in.color; } diff --git a/src/renderer/shaders/cell.v.glsl b/src/renderer/shaders/cell.v.glsl index b0c94a31f..f20b3dfc5 100644 --- a/src/renderer/shaders/cell.v.glsl +++ b/src/renderer/shaders/cell.v.glsl @@ -10,7 +10,6 @@ const uint MODE_FG_COLOR = 7u; const uint MODE_CURSOR_RECT = 3u; const uint MODE_CURSOR_RECT_HOLLOW = 4u; const uint MODE_CURSOR_BAR = 5u; -const uint MODE_UNDERLINE = 6u; const uint MODE_STRIKETHROUGH = 8u; // The grid coordinates (x, y) where x < columns and y < rows @@ -58,8 +57,6 @@ uniform sampler2D text; uniform sampler2D text_color; uniform vec2 cell_size; uniform mat4 projection; -uniform float underline_position; -uniform float underline_thickness; uniform float strikethrough_position; uniform float strikethrough_thickness; @@ -200,22 +197,6 @@ void main() { color = bg_color_in / 255.0; break; - case MODE_UNDERLINE: - // Underline Y value is just our thickness - vec2 underline_size = vec2(cell_size_scaled.x, underline_thickness); - - // Position the underline where we are told to - vec2 underline_offset = vec2(cell_size_scaled.x, underline_position) ; - - // Go to the bottom of the cell, take away the size of the - // underline, and that is our position. We also float it slightly - // above the bottom. - cell_pos = cell_pos + underline_offset - (underline_size * position); - - gl_Position = projection * vec4(cell_pos, cell_z, 1.0); - color = fg_color_in / 255.0; - break; - case MODE_STRIKETHROUGH: // Strikethrough Y value is just our thickness vec2 strikethrough_size = vec2(cell_size_scaled.x, strikethrough_thickness);