mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-17 09:16:11 +03:00
Merge pull request #58 from mitchellh/underline-styles
Underline styles: singe, double, dashed, dotted, curly
This commit is contained in:
@ -289,13 +289,6 @@ pub fn create(alloc: Allocator, app: *App, config: *const Config) !*Window {
|
|||||||
// Pre-calculate our initial cell size ourselves.
|
// Pre-calculate our initial cell size ourselves.
|
||||||
const cell_size = try renderer.CellSize.init(alloc, font_group);
|
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
|
// Convert our padding from points to pixels
|
||||||
const padding_x = (@intToFloat(f32, config.@"window-padding-x") * x_dpi) / 72;
|
const padding_x = (@intToFloat(f32, config.@"window-padding-x") * x_dpi) / 72;
|
||||||
const padding_y = (@intToFloat(f32, config.@"window-padding-y") * y_dpi) / 72;
|
const padding_y = (@intToFloat(f32, config.@"window-padding-y") * y_dpi) / 72;
|
||||||
|
@ -11,6 +11,7 @@ pub const Glyph = @import("Glyph.zig");
|
|||||||
pub const Library = @import("Library.zig");
|
pub const Library = @import("Library.zig");
|
||||||
pub const Shaper = @import("Shaper.zig");
|
pub const Shaper = @import("Shaper.zig");
|
||||||
pub const sprite = @import("sprite.zig");
|
pub const sprite = @import("sprite.zig");
|
||||||
|
pub const Sprite = sprite.Sprite;
|
||||||
pub const Descriptor = discovery.Descriptor;
|
pub const Descriptor = discovery.Descriptor;
|
||||||
pub const Discover = discovery.Discover;
|
pub const Discover = discovery.Discover;
|
||||||
|
|
||||||
@ -60,6 +61,9 @@ pub const Presentation = enum(u1) {
|
|||||||
emoji = 1, // U+FEOF
|
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 {
|
test {
|
||||||
@import("std").testing.refAllDecls(@This());
|
@import("std").testing.refAllDecls(@This());
|
||||||
}
|
}
|
||||||
|
@ -11,10 +11,14 @@ pub const Face = @import("sprite/Face.zig");
|
|||||||
/// Area of Unicode.
|
/// Area of Unicode.
|
||||||
pub const Sprite = enum(u32) {
|
pub const Sprite = enum(u32) {
|
||||||
// Start 1 above the maximum Unicode codepoint.
|
// Start 1 above the maximum Unicode codepoint.
|
||||||
const start: u32 = std.math.maxInt(u21) + 1;
|
pub const start: u32 = std.math.maxInt(u21) + 1;
|
||||||
const end: u32 = std.math.maxInt(u32);
|
pub const end: u32 = std.math.maxInt(u32);
|
||||||
|
|
||||||
underline = start,
|
underline = start,
|
||||||
|
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
|
// 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
|
// there are a LOT and I'm lazy. What I want to do is spend more time
|
||||||
|
@ -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
|
/// Draw and fill a rectangle. This is the main primitive for drawing
|
||||||
/// lines as well (which are just generally skinny rectangles...)
|
/// 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{
|
const boxes = &[_]pixman.Box32{
|
||||||
.{
|
.{
|
||||||
.x1 = @intCast(i32, v.x),
|
.x1 = @intCast(i32, v.x),
|
||||||
.y1 = @intCast(i32, v.y),
|
.y1 = @intCast(i32, v.y),
|
||||||
.x2 = @intCast(i32, v.width),
|
.x2 = @intCast(i32, v.x + v.width),
|
||||||
.y2 = @intCast(i32, v.height),
|
.y2 = @intCast(i32, v.y + v.height),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -17,8 +17,10 @@ const builtin = @import("builtin");
|
|||||||
const assert = std.debug.assert;
|
const assert = std.debug.assert;
|
||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
const font = @import("../main.zig");
|
const font = @import("../main.zig");
|
||||||
|
const Sprite = font.sprite.Sprite;
|
||||||
const Atlas = @import("../../Atlas.zig");
|
const Atlas = @import("../../Atlas.zig");
|
||||||
const Box = @import("Box.zig");
|
const Box = @import("Box.zig");
|
||||||
|
const underline = @import("underline.zig");
|
||||||
|
|
||||||
/// The cell width and height.
|
/// The cell width and height.
|
||||||
width: u32,
|
width: u32,
|
||||||
@ -28,6 +30,9 @@ height: u32,
|
|||||||
/// want to do any DPI scaling, it is expected to be done earlier.
|
/// want to do any DPI scaling, it is expected to be done earlier.
|
||||||
thickness: u32,
|
thickness: u32,
|
||||||
|
|
||||||
|
/// The position fo the underline.
|
||||||
|
underline_position: u32 = 0,
|
||||||
|
|
||||||
/// Returns true if the codepoint exists in our sprite font.
|
/// Returns true if the codepoint exists in our sprite font.
|
||||||
pub fn hasCodepoint(self: Face, cp: u32, p: ?font.Presentation) bool {
|
pub fn hasCodepoint(self: Face, cp: u32, p: ?font.Presentation) bool {
|
||||||
// We ignore presentation. No matter what presentation is requested
|
// We ignore presentation. No matter what presentation is requested
|
||||||
@ -47,25 +52,45 @@ pub fn renderGlyph(
|
|||||||
if (std.debug.runtime_safety) assert(self.hasCodepoint(cp, null));
|
if (std.debug.runtime_safety) assert(self.hasCodepoint(cp, null));
|
||||||
|
|
||||||
// Safe to ".?" because of the above assertion.
|
// Safe to ".?" because of the above assertion.
|
||||||
switch (Kind.init(cp).?) {
|
return switch (Kind.init(cp).?) {
|
||||||
.box => {
|
.box => box: {
|
||||||
const f: Box = .{
|
const f: Box = .{
|
||||||
.width = self.width,
|
.width = self.width,
|
||||||
.height = self.height,
|
.height = self.height,
|
||||||
.thickness = self.thickness,
|
.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.
|
/// Kind of sprites we have. Drawing is implemented separately for each kind.
|
||||||
const Kind = enum {
|
const Kind = enum {
|
||||||
box,
|
box,
|
||||||
|
underline,
|
||||||
|
|
||||||
pub fn init(cp: u32) ?Kind {
|
pub fn init(cp: u32) ?Kind {
|
||||||
return switch (cp) {
|
return switch (cp) {
|
||||||
|
Sprite.start...Sprite.end => switch (@intToEnum(Sprite, cp)) {
|
||||||
|
.underline,
|
||||||
|
.underline_double,
|
||||||
|
.underline_dotted,
|
||||||
|
.underline_dashed,
|
||||||
|
.underline_curly,
|
||||||
|
=> .underline,
|
||||||
|
},
|
||||||
|
|
||||||
// Box fonts
|
// Box fonts
|
||||||
0x2500...0x257F, // "Box Drawing" block
|
0x2500...0x257F, // "Box Drawing" block
|
||||||
0x2580...0x259F, // "Block Elements" block
|
0x2580...0x259F, // "Block Elements" block
|
||||||
|
205
src/font/sprite/underline.zig
Normal file
205
src/font/sprite/underline.zig
Normal file
@ -0,0 +1,205 @@
|
|||||||
|
//! 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),
|
||||||
|
.underline_dotted => self.drawDotted(canvas),
|
||||||
|
.underline_dashed => self.drawDashed(canvas),
|
||||||
|
.underline_curly => self.drawCurly(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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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" {
|
||||||
|
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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
);
|
||||||
|
}
|
@ -102,8 +102,6 @@ const GPUUniforms = extern struct {
|
|||||||
cell_size: [2]f32,
|
cell_size: [2]f32,
|
||||||
|
|
||||||
/// Metrics for underline/strikethrough
|
/// Metrics for underline/strikethrough
|
||||||
underline_position: f32,
|
|
||||||
underline_thickness: f32,
|
|
||||||
strikethrough_position: f32,
|
strikethrough_position: f32,
|
||||||
strikethrough_thickness: f32,
|
strikethrough_thickness: f32,
|
||||||
};
|
};
|
||||||
@ -115,7 +113,6 @@ const GPUCellMode = enum(u8) {
|
|||||||
cursor_rect = 3,
|
cursor_rect = 3,
|
||||||
cursor_rect_hollow = 4,
|
cursor_rect_hollow = 4,
|
||||||
cursor_bar = 5,
|
cursor_bar = 5,
|
||||||
underline = 6,
|
|
||||||
strikethrough = 8,
|
strikethrough = 8,
|
||||||
|
|
||||||
pub fn fromCursor(cursor: renderer.CursorStyle) GPUCellMode {
|
pub fn fromCursor(cursor: renderer.CursorStyle) GPUCellMode {
|
||||||
@ -171,6 +168,14 @@ pub fn init(alloc: Allocator, options: renderer.Options) !Metal {
|
|||||||
};
|
};
|
||||||
log.debug("cell dimensions={}", .{metrics});
|
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
|
// 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
|
// a width of 160 which is a common width for modern screens to help
|
||||||
// avoid allocations later.
|
// avoid allocations later.
|
||||||
@ -259,8 +264,6 @@ pub fn init(alloc: Allocator, options: renderer.Options) !Metal {
|
|||||||
.uniforms = .{
|
.uniforms = .{
|
||||||
.projection_matrix = undefined,
|
.projection_matrix = undefined,
|
||||||
.cell_size = undefined,
|
.cell_size = undefined,
|
||||||
.underline_position = metrics.underline_position,
|
|
||||||
.underline_thickness = metrics.underline_thickness,
|
|
||||||
.strikethrough_position = metrics.strikethrough_position,
|
.strikethrough_position = metrics.strikethrough_position,
|
||||||
.strikethrough_thickness = metrics.strikethrough_thickness,
|
.strikethrough_thickness = metrics.strikethrough_thickness,
|
||||||
},
|
},
|
||||||
@ -397,8 +400,6 @@ pub fn setFontSize(self: *Metal, size: font.face.DesiredSize) !void {
|
|||||||
self.uniforms = .{
|
self.uniforms = .{
|
||||||
.projection_matrix = self.uniforms.projection_matrix,
|
.projection_matrix = self.uniforms.projection_matrix,
|
||||||
.cell_size = .{ new_cell_size.width, new_cell_size.height },
|
.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_position = metrics.strikethrough_position,
|
||||||
.strikethrough_thickness = metrics.strikethrough_thickness,
|
.strikethrough_thickness = metrics.strikethrough_thickness,
|
||||||
};
|
};
|
||||||
@ -408,11 +409,13 @@ pub fn setFontSize(self: *Metal, size: font.face.DesiredSize) !void {
|
|||||||
if (std.meta.eql(self.cell_size, new_cell_size)) return;
|
if (std.meta.eql(self.cell_size, new_cell_size)) return;
|
||||||
self.cell_size = new_cell_size;
|
self.cell_size = new_cell_size;
|
||||||
|
|
||||||
// Set the cell size of the box font
|
// Set the sprite font up
|
||||||
if (self.font_group.group.sprite) |*sprite| {
|
self.font_group.group.sprite = font.sprite.Face{
|
||||||
sprite.width = @floatToInt(u32, self.cell_size.width);
|
.width = @floatToInt(u32, self.cell_size.width),
|
||||||
sprite.height = @floatToInt(u32, self.cell_size.height);
|
.height = @floatToInt(u32, self.cell_size.height),
|
||||||
}
|
.thickness = 2,
|
||||||
|
.underline_position = @floatToInt(u32, metrics.underline_position),
|
||||||
|
};
|
||||||
|
|
||||||
// Notify the window that the cell size changed.
|
// Notify the window that the cell size changed.
|
||||||
_ = self.window_mailbox.push(.{
|
_ = self.window_mailbox.push(.{
|
||||||
@ -707,8 +710,6 @@ pub fn setScreenSize(self: *Metal, _: renderer.ScreenSize) !void {
|
|||||||
-1 * padding.top,
|
-1 * padding.top,
|
||||||
),
|
),
|
||||||
.cell_size = .{ self.cell_size.width, self.cell_size.height },
|
.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_position = old.strikethrough_position,
|
||||||
.strikethrough_thickness = old.strikethrough_thickness,
|
.strikethrough_thickness = old.strikethrough_thickness,
|
||||||
};
|
};
|
||||||
@ -911,12 +912,31 @@ pub fn updateCell(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cell.attrs.underline) {
|
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(.{
|
self.cells.appendAssumeCapacity(.{
|
||||||
.mode = .underline,
|
.mode = .fg,
|
||||||
.grid_pos = .{ @intToFloat(f32, x), @intToFloat(f32, y) },
|
.grid_pos = .{ @intToFloat(f32, x), @intToFloat(f32, y) },
|
||||||
.cell_width = cell.widthLegacy(),
|
.cell_width = cell.widthLegacy(),
|
||||||
.color = .{ colors.fg.r, colors.fg.g, colors.fg.b, alpha },
|
.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 },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -137,7 +137,6 @@ const GPUCellMode = enum(u8) {
|
|||||||
cursor_rect = 3,
|
cursor_rect = 3,
|
||||||
cursor_rect_hollow = 4,
|
cursor_rect_hollow = 4,
|
||||||
cursor_bar = 5,
|
cursor_bar = 5,
|
||||||
underline = 6,
|
|
||||||
strikethrough = 8,
|
strikethrough = 8,
|
||||||
|
|
||||||
// Non-exhaustive because masks change it
|
// Non-exhaustive because masks change it
|
||||||
@ -180,8 +179,6 @@ pub fn init(alloc: Allocator, options: renderer.Options) !OpenGL {
|
|||||||
const pbind = try program.use();
|
const pbind = try program.use();
|
||||||
defer pbind.unbind();
|
defer pbind.unbind();
|
||||||
try program.setUniform("cell_size", @Vector(2, f32){ metrics.cell_width, metrics.cell_height });
|
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_position", metrics.strikethrough_position);
|
||||||
try program.setUniform("strikethrough_thickness", metrics.strikethrough_thickness);
|
try program.setUniform("strikethrough_thickness", metrics.strikethrough_thickness);
|
||||||
|
|
||||||
@ -501,12 +498,6 @@ pub fn setFontSize(self: *OpenGL, size: font.face.DesiredSize) !void {
|
|||||||
if (std.meta.eql(self.cell_size, new_cell_size)) return;
|
if (std.meta.eql(self.cell_size, new_cell_size)) return;
|
||||||
self.cell_size = new_cell_size;
|
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.
|
// Notify the window that the cell size changed.
|
||||||
_ = self.window_mailbox.push(.{
|
_ = self.window_mailbox.push(.{
|
||||||
.cell_size = new_cell_size,
|
.cell_size = new_cell_size,
|
||||||
@ -530,12 +521,18 @@ fn resetFontMetrics(
|
|||||||
};
|
};
|
||||||
log.debug("cell dimensions={}", .{metrics});
|
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
|
// Set our uniforms that rely on metrics
|
||||||
const pbind = try program.use();
|
const pbind = try program.use();
|
||||||
defer pbind.unbind();
|
defer pbind.unbind();
|
||||||
try program.setUniform("cell_size", @Vector(2, f32){ metrics.cell_width, metrics.cell_height });
|
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_position", metrics.strikethrough_position);
|
||||||
try program.setUniform("strikethrough_thickness", metrics.strikethrough_thickness);
|
try program.setUniform("strikethrough_thickness", metrics.strikethrough_thickness);
|
||||||
|
|
||||||
@ -928,7 +925,7 @@ pub fn updateCell(
|
|||||||
var i: usize = 0;
|
var i: usize = 0;
|
||||||
if (colors.bg != null) i += 1;
|
if (colors.bg != null) i += 1;
|
||||||
if (!cell.empty()) 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;
|
if (cell.attrs.strikethrough) i += 1;
|
||||||
break :needed i;
|
break :needed i;
|
||||||
};
|
};
|
||||||
@ -1002,18 +999,33 @@ pub fn updateCell(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cell.attrs.underline) {
|
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 underline_glyph = try self.font_group.renderGlyph(
|
||||||
|
self.alloc,
|
||||||
|
font.sprite_index,
|
||||||
|
@enumToInt(sprite),
|
||||||
|
null,
|
||||||
|
);
|
||||||
self.cells.appendAssumeCapacity(.{
|
self.cells.appendAssumeCapacity(.{
|
||||||
.mode = .underline,
|
.mode = .fg,
|
||||||
.grid_col = @intCast(u16, x),
|
.grid_col = @intCast(u16, x),
|
||||||
.grid_row = @intCast(u16, y),
|
.grid_row = @intCast(u16, y),
|
||||||
.grid_width = cell.widthLegacy(),
|
.grid_width = cell.widthLegacy(),
|
||||||
.glyph_x = 0,
|
.glyph_x = underline_glyph.atlas_x,
|
||||||
.glyph_y = 0,
|
.glyph_y = underline_glyph.atlas_y,
|
||||||
.glyph_width = 0,
|
.glyph_width = underline_glyph.width,
|
||||||
.glyph_height = 0,
|
.glyph_height = underline_glyph.height,
|
||||||
.glyph_offset_x = 0,
|
.glyph_offset_x = underline_glyph.offset_x,
|
||||||
.glyph_offset_y = 0,
|
.glyph_offset_y = underline_glyph.offset_y,
|
||||||
.fg_r = colors.fg.r,
|
.fg_r = colors.fg.r,
|
||||||
.fg_g = colors.fg.g,
|
.fg_g = colors.fg.g,
|
||||||
.fg_b = colors.fg.b,
|
.fg_b = colors.fg.b,
|
||||||
|
@ -30,7 +30,6 @@ const uint MODE_FG_COLOR = 7u;
|
|||||||
const uint MODE_CURSOR_RECT = 3u;
|
const uint MODE_CURSOR_RECT = 3u;
|
||||||
const uint MODE_CURSOR_RECT_HOLLOW = 4u;
|
const uint MODE_CURSOR_RECT_HOLLOW = 4u;
|
||||||
const uint MODE_CURSOR_BAR = 5u;
|
const uint MODE_CURSOR_BAR = 5u;
|
||||||
const uint MODE_UNDERLINE = 6u;
|
|
||||||
const uint MODE_STRIKETHROUGH = 8u;
|
const uint MODE_STRIKETHROUGH = 8u;
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
@ -94,10 +93,6 @@ void main() {
|
|||||||
out_FragColor = color;
|
out_FragColor = color;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MODE_UNDERLINE:
|
|
||||||
out_FragColor = color;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case MODE_STRIKETHROUGH:
|
case MODE_STRIKETHROUGH:
|
||||||
out_FragColor = color;
|
out_FragColor = color;
|
||||||
break;
|
break;
|
||||||
|
@ -8,15 +8,12 @@ enum Mode : uint8_t {
|
|||||||
MODE_CURSOR_RECT = 3u,
|
MODE_CURSOR_RECT = 3u,
|
||||||
MODE_CURSOR_RECT_HOLLOW = 4u,
|
MODE_CURSOR_RECT_HOLLOW = 4u,
|
||||||
MODE_CURSOR_BAR = 5u,
|
MODE_CURSOR_BAR = 5u,
|
||||||
MODE_UNDERLINE = 6u,
|
|
||||||
MODE_STRIKETHROUGH = 8u,
|
MODE_STRIKETHROUGH = 8u,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Uniforms {
|
struct Uniforms {
|
||||||
float4x4 projection_matrix;
|
float4x4 projection_matrix;
|
||||||
float2 cell_size;
|
float2 cell_size;
|
||||||
float underline_position;
|
|
||||||
float underline_thickness;
|
|
||||||
float strikethrough_position;
|
float strikethrough_position;
|
||||||
float strikethrough_thickness;
|
float strikethrough_thickness;
|
||||||
};
|
};
|
||||||
@ -154,22 +151,6 @@ vertex VertexOut uber_vertex(
|
|||||||
break;
|
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: {
|
case MODE_STRIKETHROUGH: {
|
||||||
// Strikethrough Y value is just our thickness
|
// Strikethrough Y value is just our thickness
|
||||||
float2 strikethrough_size = float2(cell_size_scaled.x, uniforms.strikethrough_thickness);
|
float2 strikethrough_size = float2(cell_size_scaled.x, uniforms.strikethrough_thickness);
|
||||||
@ -259,9 +240,6 @@ fragment float4 uber_fragment(
|
|||||||
case MODE_CURSOR_BAR:
|
case MODE_CURSOR_BAR:
|
||||||
return in.color;
|
return in.color;
|
||||||
|
|
||||||
case MODE_UNDERLINE:
|
|
||||||
return in.color;
|
|
||||||
|
|
||||||
case MODE_STRIKETHROUGH:
|
case MODE_STRIKETHROUGH:
|
||||||
return in.color;
|
return in.color;
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,6 @@ const uint MODE_FG_COLOR = 7u;
|
|||||||
const uint MODE_CURSOR_RECT = 3u;
|
const uint MODE_CURSOR_RECT = 3u;
|
||||||
const uint MODE_CURSOR_RECT_HOLLOW = 4u;
|
const uint MODE_CURSOR_RECT_HOLLOW = 4u;
|
||||||
const uint MODE_CURSOR_BAR = 5u;
|
const uint MODE_CURSOR_BAR = 5u;
|
||||||
const uint MODE_UNDERLINE = 6u;
|
|
||||||
const uint MODE_STRIKETHROUGH = 8u;
|
const uint MODE_STRIKETHROUGH = 8u;
|
||||||
|
|
||||||
// The grid coordinates (x, y) where x < columns and y < rows
|
// The grid coordinates (x, y) where x < columns and y < rows
|
||||||
@ -58,8 +57,6 @@ uniform sampler2D text;
|
|||||||
uniform sampler2D text_color;
|
uniform sampler2D text_color;
|
||||||
uniform vec2 cell_size;
|
uniform vec2 cell_size;
|
||||||
uniform mat4 projection;
|
uniform mat4 projection;
|
||||||
uniform float underline_position;
|
|
||||||
uniform float underline_thickness;
|
|
||||||
uniform float strikethrough_position;
|
uniform float strikethrough_position;
|
||||||
uniform float strikethrough_thickness;
|
uniform float strikethrough_thickness;
|
||||||
|
|
||||||
@ -200,22 +197,6 @@ void main() {
|
|||||||
color = bg_color_in / 255.0;
|
color = bg_color_in / 255.0;
|
||||||
break;
|
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:
|
case MODE_STRIKETHROUGH:
|
||||||
// Strikethrough Y value is just our thickness
|
// Strikethrough Y value is just our thickness
|
||||||
vec2 strikethrough_size = vec2(cell_size_scaled.x, strikethrough_thickness);
|
vec2 strikethrough_size = vec2(cell_size_scaled.x, strikethrough_thickness);
|
||||||
|
@ -79,6 +79,10 @@ pub const Action = union(enum) {
|
|||||||
intermediates: []u8,
|
intermediates: []u8,
|
||||||
params: []u16,
|
params: []u16,
|
||||||
final: u8,
|
final: u8,
|
||||||
|
sep: Sep,
|
||||||
|
|
||||||
|
/// The separator used for CSI params.
|
||||||
|
pub const Sep = enum { semicolon, colon };
|
||||||
|
|
||||||
// Implement formatter for logging
|
// Implement formatter for logging
|
||||||
pub fn format(
|
pub fn format(
|
||||||
@ -392,6 +396,11 @@ fn doAction(self: *Parser, action: TransitionAction, c: u8) ?Action {
|
|||||||
.intermediates = self.intermediates[0..self.intermediates_idx],
|
.intermediates = self.intermediates[0..self.intermediates_idx],
|
||||||
.params = self.params[0..self.params_idx],
|
.params = self.params[0..self.params_idx],
|
||||||
.final = c,
|
.final = c,
|
||||||
|
.sep = switch (self.params_sep) {
|
||||||
|
.none, .semicolon => .semicolon,
|
||||||
|
.colon => .colon,
|
||||||
|
.mixed => unreachable,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
@ -56,6 +56,7 @@ const Allocator = std.mem.Allocator;
|
|||||||
|
|
||||||
const utf8proc = @import("utf8proc");
|
const utf8proc = @import("utf8proc");
|
||||||
const trace = @import("tracy").trace;
|
const trace = @import("tracy").trace;
|
||||||
|
const sgr = @import("sgr.zig");
|
||||||
const color = @import("color.zig");
|
const color = @import("color.zig");
|
||||||
const point = @import("point.zig");
|
const point = @import("point.zig");
|
||||||
const CircBuf = @import("circ_buf.zig").CircBuf;
|
const CircBuf = @import("circ_buf.zig").CircBuf;
|
||||||
@ -167,10 +168,10 @@ pub const Cell = struct {
|
|||||||
bold: bool = false,
|
bold: bool = false,
|
||||||
italic: bool = false,
|
italic: bool = false,
|
||||||
faint: bool = false,
|
faint: bool = false,
|
||||||
underline: bool = false,
|
|
||||||
blink: bool = false,
|
blink: bool = false,
|
||||||
inverse: bool = false,
|
inverse: bool = false,
|
||||||
strikethrough: bool = false,
|
strikethrough: bool = false,
|
||||||
|
underline: sgr.Attribute.Underline = .none,
|
||||||
|
|
||||||
/// True if this is a wide character. This char takes up
|
/// True if this is a wide character. This char takes up
|
||||||
/// two cells. The following cell ALWAYS is a space.
|
/// two cells. The following cell ALWAYS is a space.
|
||||||
@ -241,7 +242,7 @@ pub const Cell = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
test {
|
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));
|
try std.testing.expectEqual(12, @sizeOf(Cell));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -379,12 +379,12 @@ pub fn setAttribute(self: *Terminal, attr: sgr.Attribute) !void {
|
|||||||
self.screen.cursor.pen.attrs.faint = true;
|
self.screen.cursor.pen.attrs.faint = true;
|
||||||
},
|
},
|
||||||
|
|
||||||
.underline => {
|
.underline => |v| {
|
||||||
self.screen.cursor.pen.attrs.underline = true;
|
self.screen.cursor.pen.attrs.underline = v;
|
||||||
},
|
},
|
||||||
|
|
||||||
.reset_underline => {
|
.reset_underline => {
|
||||||
self.screen.cursor.pen.attrs.underline = false;
|
self.screen.cursor.pen.attrs.underline = .none;
|
||||||
},
|
},
|
||||||
|
|
||||||
.blink => {
|
.blink => {
|
||||||
|
@ -31,7 +31,7 @@ pub const Attribute = union(enum) {
|
|||||||
faint: void,
|
faint: void,
|
||||||
|
|
||||||
/// Underline the text
|
/// Underline the text
|
||||||
underline: void,
|
underline: Underline,
|
||||||
reset_underline: void,
|
reset_underline: void,
|
||||||
|
|
||||||
/// Blink the text
|
/// Blink the text
|
||||||
@ -75,6 +75,15 @@ pub const Attribute = union(enum) {
|
|||||||
g: u8,
|
g: u8,
|
||||||
b: 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.
|
/// Parser parses the attributes from a list of SGR parameters.
|
||||||
@ -82,6 +91,9 @@ pub const Parser = struct {
|
|||||||
params: []const u16,
|
params: []const u16,
|
||||||
idx: usize = 0,
|
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.
|
/// Next returns the next attribute or null if there are no more attributes.
|
||||||
pub fn next(self: *Parser) ?Attribute {
|
pub fn next(self: *Parser) ?Attribute {
|
||||||
if (self.idx > self.params.len) return null;
|
if (self.idx > self.params.len) return null;
|
||||||
@ -107,7 +119,41 @@ pub const Parser = struct {
|
|||||||
|
|
||||||
3 => return Attribute{ .italic = {} },
|
3 => return Attribute{ .italic = {} },
|
||||||
|
|
||||||
4 => return Attribute{ .underline = {} },
|
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 },
|
||||||
|
3 => return Attribute{ .underline = .curly },
|
||||||
|
4 => return Attribute{ .underline = .dotted },
|
||||||
|
5 => return Attribute{ .underline = .dashed },
|
||||||
|
|
||||||
|
// For unknown underline styles, just render
|
||||||
|
// a single underline.
|
||||||
|
else => return Attribute{ .underline = .single },
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Colon-separated must only be 2.
|
||||||
|
else => break :blk,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Attribute{ .underline = .single };
|
||||||
|
},
|
||||||
|
|
||||||
5 => return Attribute{ .blink = {} },
|
5 => return Attribute{ .blink = {} },
|
||||||
|
|
||||||
@ -206,6 +252,11 @@ fn testParse(params: []const u16) Attribute {
|
|||||||
return p.next().?;
|
return p.next().?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn testParseColon(params: []const u16) Attribute {
|
||||||
|
var p: Parser = .{ .params = params, .colon = true };
|
||||||
|
return p.next().?;
|
||||||
|
}
|
||||||
|
|
||||||
test "sgr: Parser" {
|
test "sgr: Parser" {
|
||||||
try testing.expect(testParse(&[_]u16{}) == .unset);
|
try testing.expect(testParse(&[_]u16{}) == .unset);
|
||||||
try testing.expect(testParse(&[_]u16{0}) == .unset);
|
try testing.expect(testParse(&[_]u16{0}) == .unset);
|
||||||
@ -275,6 +326,37 @@ 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
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" {
|
test "sgr: blink" {
|
||||||
{
|
{
|
||||||
const v = testParse(&[_]u16{5});
|
const v = testParse(&[_]u16{5});
|
||||||
|
@ -383,7 +383,7 @@ pub fn Stream(comptime Handler: type) type {
|
|||||||
|
|
||||||
// SGR - Select Graphic Rendition
|
// SGR - Select Graphic Rendition
|
||||||
'm' => if (@hasDecl(T, "setAttribute")) {
|
'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);
|
while (p.next()) |attr| try self.handler.setAttribute(attr);
|
||||||
} else log.warn("unimplemented CSI callback: {}", .{action}),
|
} else log.warn("unimplemented CSI callback: {}", .{action}),
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user