diff --git a/src/font/sprite.zig b/src/font/sprite.zig index d71e777bf..6485d6008 100644 --- a/src/font/sprite.zig +++ b/src/font/sprite.zig @@ -27,6 +27,8 @@ pub const Sprite = enum(u32) { strikethrough, + overline, + cursor_rect, cursor_hollow_rect, cursor_bar, diff --git a/src/font/sprite/Face.zig b/src/font/sprite/Face.zig index f183192dc..af82bb731 100644 --- a/src/font/sprite/Face.zig +++ b/src/font/sprite/Face.zig @@ -150,6 +150,16 @@ pub fn renderGlyph( self.thickness, ), + .overline => try underline.renderGlyph( + alloc, + atlas, + @enumFromInt(cp), + width, + self.height, + 0, + self.thickness, + ), + .powerline => powerline: { const f: Powerline = .{ .width = width, @@ -166,6 +176,7 @@ pub fn renderGlyph( const Kind = enum { box, underline, + overline, strikethrough, powerline, @@ -179,6 +190,9 @@ const Kind = enum { .underline_curly, => .underline, + .overline, + => .overline, + .strikethrough, => .strikethrough, diff --git a/src/font/sprite/underline.zig b/src/font/sprite/underline.zig index 074212151..e54807bc1 100644 --- a/src/font/sprite/underline.zig +++ b/src/font/sprite/underline.zig @@ -34,6 +34,7 @@ pub fn renderGlyph( .underline_dotted => try drawDotted(alloc, width, line_thickness), .underline_dashed => try drawDashed(alloc, width, line_thickness), .underline_curly => try drawCurly(alloc, width, line_thickness), + .overline => try drawSingle(alloc, width, line_thickness), .strikethrough => try drawSingle(alloc, width, line_thickness), else => unreachable, }; diff --git a/src/renderer/Metal.zig b/src/renderer/Metal.zig index 0665c40bc..f586d22b4 100644 --- a/src/renderer/Metal.zig +++ b/src/renderer/Metal.zig @@ -2492,6 +2492,18 @@ fn rebuildCells( ); }; + if (style.flags.overline) self.addOverline( + @intCast(x), + @intCast(y), + fg, + alpha + ) catch |err| { + log.warn( + "error adding overline to cell, will be invalid x={} y={}, err={}", + .{ x, y, err }, + ); + }; + // If we're at or past the end of our shaper run then // we need to get the next run from the run iterator. if (shaper_cells != null and shaper_cells_i >= shaper_cells.?.len) { @@ -2709,6 +2721,38 @@ fn addUnderline( }); } +/// Add a overline decoration to the specified cell +fn addOverline( + self: *Metal, + x: terminal.size.CellCountInt, + y: terminal.size.CellCountInt, + color: terminal.color.RGB, + alpha: u8, +) !void { + const render = try self.font_grid.renderGlyph( + self.alloc, + font.sprite_index, + @intFromEnum(font.Sprite.overline), + .{ + .cell_width = 1, + .grid_metrics = self.grid_metrics, + }, + ); + + try self.cells.add(self.alloc, .overline, .{ + .mode = .fg, + .grid_pos = .{ @intCast(x), @intCast(y) }, + .constraint_width = 1, + .color = .{ color.r, color.g, color.b, alpha }, + .glyph_pos = .{ render.glyph.atlas_x, render.glyph.atlas_y }, + .glyph_size = .{ render.glyph.width, render.glyph.height }, + .bearings = .{ + @intCast(render.glyph.offset_x), + @intCast(render.glyph.offset_y), + }, + }); +} + /// Add a strikethrough decoration to the specified cell fn addStrikethrough( self: *Metal, diff --git a/src/renderer/OpenGL.zig b/src/renderer/OpenGL.zig index 4e3184c28..324fe14b3 100644 --- a/src/renderer/OpenGL.zig +++ b/src/renderer/OpenGL.zig @@ -1586,6 +1586,19 @@ pub fn rebuildCells( ); }; + if (style.flags.overline) self.addOverline( + @intCast(x), + @intCast(y), + fg, + alpha, + bg_color, + ) catch |err| { + log.warn( + "error adding overline to cell, will be invalid x={} y={}, err={}", + .{ x, y, err }, + ); + }; + // If we're at or past the end of our shaper run then // we need to get the next run from the run iterator. if (shaper_cells != null and shaper_cells_i >= shaper_cells.?.len) { @@ -1955,6 +1968,47 @@ fn addUnderline( }); } +/// Add an overline decoration to the specified cell +fn addOverline( + self: *OpenGL, + x: terminal.size.CellCountInt, + y: terminal.size.CellCountInt, + color: terminal.color.RGB, + alpha: u8, + bg: [4]u8, +) !void { + const render = try self.font_grid.renderGlyph( + self.alloc, + font.sprite_index, + @intFromEnum(font.Sprite.overline), + .{ + .cell_width = 1, + .grid_metrics = self.grid_metrics, + }, + ); + + try self.cells.append(self.alloc, .{ + .mode = .fg, + .grid_col = @intCast(x), + .grid_row = @intCast(y), + .grid_width = 1, + .glyph_x = render.glyph.atlas_x, + .glyph_y = render.glyph.atlas_y, + .glyph_width = render.glyph.width, + .glyph_height = render.glyph.height, + .glyph_offset_x = render.glyph.offset_x, + .glyph_offset_y = render.glyph.offset_y, + .r = color.r, + .g = color.g, + .b = color.b, + .a = alpha, + .bg_r = bg[0], + .bg_g = bg[1], + .bg_b = bg[2], + .bg_a = bg[3], + }); +} + /// Add a strikethrough decoration to the specified cell fn addStrikethrough( self: *OpenGL, diff --git a/src/renderer/metal/cell.zig b/src/renderer/metal/cell.zig index 33f781ac1..61b8887fd 100644 --- a/src/renderer/metal/cell.zig +++ b/src/renderer/metal/cell.zig @@ -12,6 +12,7 @@ pub const Key = enum { text, underline, strikethrough, + overline, /// Returns the GPU vertex type for this key. pub fn CellType(self: Key) type { @@ -21,6 +22,7 @@ pub const Key = enum { .text, .underline, .strikethrough, + .overline, => mtl_shaders.CellText, }; } @@ -196,6 +198,7 @@ pub const Contents = struct { .text, .underline, .strikethrough, + .overline, // We have a special list containing the cursor cell at the start // of our fg row pool, so we need to add 1 to the y to get the // correct index. diff --git a/src/terminal/Screen.zig b/src/terminal/Screen.zig index 7927d0343..f1db3dd52 100644 --- a/src/terminal/Screen.zig +++ b/src/terminal/Screen.zig @@ -1618,6 +1618,14 @@ pub fn setAttribute(self: *Screen, attr: sgr.Attribute) !void { self.cursor.style.underline_color = .none; }, + .overline => { + self.cursor.style.flags.overline = true; + }, + + .reset_overline => { + self.cursor.style.flags.overline = false; + }, + .blink => { self.cursor.style.flags.blink = true; }, diff --git a/src/terminal/sgr.zig b/src/terminal/sgr.zig index 67a4c05ea..7d602714c 100644 --- a/src/terminal/sgr.zig +++ b/src/terminal/sgr.zig @@ -37,6 +37,10 @@ pub const Attribute = union(enum) { @"256_underline_color": u8, reset_underline_color: void, + // Overline the text + overline: void, + reset_overline: void, + /// Blink the text blink: void, reset_blink: void, @@ -237,6 +241,9 @@ pub const Parser = struct { 49 => return Attribute{ .reset_bg = {} }, + 53 => return Attribute{ .overline = {} }, + 55 => return Attribute{ .reset_overline = {} }, + 58 => if (slice.len >= 5 and slice[1] == 2) { self.idx += 4; diff --git a/src/terminal/style.zig b/src/terminal/style.zig index 430fca214..0340047e9 100644 --- a/src/terminal/style.zig +++ b/src/terminal/style.zig @@ -35,6 +35,7 @@ pub const Style = struct { inverse: bool = false, invisible: bool = false, strikethrough: bool = false, + overline: bool = false, underline: sgr.Attribute.Underline = .none, } = .{},