mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-16 16:56:09 +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.
|
||||
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;
|
||||
|
@ -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());
|
||||
}
|
||||
|
@ -11,10 +11,14 @@ 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,
|
||||
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
|
||||
|
@ -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),
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -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,45 @@ 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_dotted,
|
||||
.underline_dashed,
|
||||
.underline_curly,
|
||||
=> .underline,
|
||||
},
|
||||
|
||||
// Box fonts
|
||||
0x2500...0x257F, // "Box Drawing" 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,
|
||||
|
||||
/// 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 {
|
||||
@ -171,6 +168,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.
|
||||
@ -259,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,
|
||||
},
|
||||
@ -397,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,
|
||||
};
|
||||
@ -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;
|
||||
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(.{
|
||||
@ -707,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,
|
||||
};
|
||||
@ -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(.{
|
||||
.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 },
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
||||
@ -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;
|
||||
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,12 +521,18 @@ 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();
|
||||
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);
|
||||
|
||||
@ -928,7 +925,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,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(.{
|
||||
.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,
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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,
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
|
@ -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));
|
||||
}
|
||||
};
|
||||
|
@ -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 => {
|
||||
|
@ -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.
|
||||
@ -82,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;
|
||||
@ -107,7 +119,41 @@ pub const Parser = struct {
|
||||
|
||||
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 = {} },
|
||||
|
||||
@ -206,6 +252,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);
|
||||
@ -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" {
|
||||
{
|
||||
const v = testParse(&[_]u16{5});
|
||||
|
@ -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}),
|
||||
|
||||
|
Reference in New Issue
Block a user