Merge pull request #1711 from mitchellh/strikesprite

Strikethrough should be sprite-rendered
This commit is contained in:
Mitchell Hashimoto
2024-04-22 10:42:20 -07:00
committed by GitHub
11 changed files with 208 additions and 208 deletions

View File

@ -129,6 +129,7 @@ fn reloadMetrics(self: *SharedGrid, thicken: bool) !void {
.thickness = self.metrics.underline_thickness *
@as(u32, if (thicken) 2 else 1),
.underline_position = self.metrics.underline_position,
.strikethrough_position = self.metrics.strikethrough_position,
};
}

View File

@ -20,6 +20,8 @@ pub const Sprite = enum(u32) {
underline_dashed,
underline_curly,
strikethrough,
cursor_rect,
cursor_hollow_rect,
cursor_bar,

View File

@ -30,11 +30,17 @@ height: u32,
/// Base thickness value for lines of sprites. This is in pixels. If you
/// want to do any DPI scaling, it is expected to be done earlier.
thickness: u32,
thickness: u32 = 1,
/// The position of the underline.
underline_position: u32 = 0,
/// The position of the strikethrough.
// NOTE(mitchellh): We don't use a dedicated strikethrough thickness
// setting yet but fonts can in theory set this. If this becomes an
// issue in practice we can add it here.
strikethrough_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
@ -113,6 +119,16 @@ pub fn renderGlyph(
self.thickness,
),
.strikethrough => try underline.renderGlyph(
alloc,
atlas,
@enumFromInt(cp),
width,
self.height,
self.strikethrough_position,
self.thickness,
),
.powerline => powerline: {
const f: Powerline = .{
.width = width,
@ -129,6 +145,7 @@ pub fn renderGlyph(
const Kind = enum {
box,
underline,
strikethrough,
powerline,
pub fn init(cp: u32) ?Kind {
@ -141,6 +158,9 @@ const Kind = enum {
.underline_curly,
=> .underline,
.strikethrough,
=> .strikethrough,
.cursor_rect,
.cursor_hollow_rect,
.cursor_bar,
@ -189,3 +209,7 @@ const Kind = enum {
};
}
};
test {
@import("std").testing.refAllDecls(@This());
}

View File

@ -7,6 +7,9 @@
//! 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.
//!
//! This also renders strikethrough, so its really more generally a
//! "horizontal line" renderer.
const std = @import("std");
const builtin = @import("builtin");
const assert = std.debug.assert;
@ -71,6 +74,7 @@ const Draw = struct {
.underline_dotted => self.drawDotted(canvas),
.underline_dashed => self.drawDashed(canvas),
.underline_curly => self.drawCurly(canvas),
.strikethrough => self.drawSingle(canvas),
else => unreachable,
}
}
@ -225,6 +229,24 @@ test "single" {
);
}
test "strikethrough" {
const testing = std.testing;
const alloc = testing.allocator;
var atlas_greyscale = try font.Atlas.init(alloc, 512, .greyscale);
defer atlas_greyscale.deinit(alloc);
_ = try renderGlyph(
alloc,
&atlas_greyscale,
.strikethrough,
36,
18,
9,
2,
);
}
test "single large thickness" {
const testing = std.testing;
const alloc = testing.allocator;

View File

@ -452,8 +452,6 @@ pub fn init(alloc: Allocator, options: renderer.Options) !Metal {
.uniforms = .{
.projection_matrix = undefined,
.cell_size = undefined,
.strikethrough_position = @floatFromInt(font_critical.metrics.strikethrough_position),
.strikethrough_thickness = @floatFromInt(font_critical.metrics.strikethrough_thickness),
.min_contrast = options.config.min_contrast,
},
@ -584,8 +582,6 @@ pub fn setFontGrid(self: *Metal, grid: *font.SharedGrid) void {
@floatFromInt(metrics.cell_width),
@floatFromInt(metrics.cell_height),
},
.strikethrough_position = @floatFromInt(metrics.strikethrough_position),
.strikethrough_thickness = @floatFromInt(metrics.strikethrough_thickness),
.min_contrast = self.uniforms.min_contrast,
};
}
@ -1436,8 +1432,6 @@ pub fn setScreenSize(
@floatFromInt(self.grid_metrics.cell_width),
@floatFromInt(self.grid_metrics.cell_height),
},
.strikethrough_position = old.strikethrough_position,
.strikethrough_thickness = old.strikethrough_thickness,
.min_contrast = old.min_contrast,
};
@ -1914,12 +1908,25 @@ fn updateCell(
}
if (style.flags.strikethrough) {
const render = try self.font_grid.renderGlyph(
self.alloc,
font.sprite_index,
@intFromEnum(font.Sprite.strikethrough),
.{
.cell_width = if (cell.wide == .wide) 2 else 1,
.grid_metrics = self.grid_metrics,
},
);
self.cells.appendAssumeCapacity(.{
.mode = .strikethrough,
.mode = .fg,
.grid_pos = .{ @as(f32, @floatFromInt(x)), @as(f32, @floatFromInt(y)) },
.cell_width = cell.gridWidth(),
.color = .{ colors.fg.r, colors.fg.g, colors.fg.b, alpha },
.bg_color = bg,
.glyph_pos = .{ render.glyph.atlas_x, render.glyph.atlas_y },
.glyph_size = .{ render.glyph.width, render.glyph.height },
.glyph_offset = .{ render.glyph.offset_x, render.glyph.offset_y },
});
}

View File

@ -207,17 +207,6 @@ const SetFontSize = struct {
},
);
}
const bind = try gl_state.cell_program.program.use();
defer bind.unbind();
try gl_state.cell_program.program.setUniform(
"strikethrough_position",
@as(f32, @floatFromInt(self.metrics.strikethrough_position)),
);
try gl_state.cell_program.program.setUniform(
"strikethrough_thickness",
@as(f32, @floatFromInt(self.metrics.strikethrough_thickness)),
);
}
};
@ -1491,17 +1480,27 @@ fn updateCell(
}
if (style.flags.strikethrough) {
const render = try self.font_grid.renderGlyph(
self.alloc,
font.sprite_index,
@intFromEnum(font.Sprite.strikethrough),
.{
.cell_width = if (cell.wide == .wide) 2 else 1,
.grid_metrics = self.grid_metrics,
},
);
self.cells.appendAssumeCapacity(.{
.mode = .strikethrough,
.mode = .fg,
.grid_col = @intCast(x),
.grid_row = @intCast(y),
.grid_width = cell.gridWidth(),
.glyph_x = 0,
.glyph_y = 0,
.glyph_width = 0,
.glyph_height = 0,
.glyph_offset_x = 0,
.glyph_offset_y = 0,
.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 = colors.fg.r,
.g = colors.fg.g,
.b = colors.fg.b,

View File

@ -103,7 +103,6 @@ pub const Cell = extern struct {
fg = 2,
fg_constrained = 3,
fg_color = 7,
strikethrough = 8,
};
};
@ -124,10 +123,6 @@ pub const Uniforms = extern struct {
/// Size of a single cell in pixels, unscaled.
cell_size: [2]f32,
/// Metrics for underline/strikethrough
strikethrough_position: f32,
strikethrough_thickness: f32,
/// The minimum contrast ratio for text. The contrast ratio is calculated
/// according to the WCAG 2.0 spec.
min_contrast: f32,

View File

@ -53,7 +53,6 @@ pub const CellMode = enum(u8) {
fg = 2,
fg_constrained = 3,
fg_color = 7,
strikethrough = 8,
// Non-exhaustive because masks change it
_,

View File

@ -28,7 +28,6 @@ const uint MODE_BG = 1u;
const uint MODE_FG = 2u;
const uint MODE_FG_CONSTRAINED = 3u;
const uint MODE_FG_COLOR = 7u;
const uint MODE_STRIKETHROUGH = 8u;
void main() {
float a;
@ -48,9 +47,5 @@ void main() {
case MODE_FG_COLOR:
out_FragColor = texture(text_color, glyph_tex_coords);
break;
case MODE_STRIKETHROUGH:
out_FragColor = color;
break;
}
}

View File

@ -6,14 +6,11 @@ enum Mode : uint8_t {
MODE_FG = 2u,
MODE_FG_CONSTRAINED = 3u,
MODE_FG_COLOR = 7u,
MODE_STRIKETHROUGH = 8u,
};
struct Uniforms {
float4x4 projection_matrix;
float2 cell_size;
float strikethrough_position;
float strikethrough_thickness;
float min_contrast;
};
@ -110,11 +107,9 @@ float4 contrasted_color(float min, float4 fg, float4 bg) {
//-------------------------------------------------------------------
#pragma mark - Terminal Grid Cell Shader
vertex VertexOut uber_vertex(
unsigned int vid [[ vertex_id ]],
vertex VertexOut uber_vertex(unsigned int vid [[vertex_id]],
VertexIn input [[stage_in]],
constant Uniforms &uniforms [[ buffer(1) ]]
) {
constant Uniforms& uniforms [[buffer(1)]]) {
// Convert the grid x,y into world space x, y by accounting for cell size
float2 cell_pos = uniforms.cell_size * input.grid_pos;
@ -147,7 +142,8 @@ vertex VertexOut uber_vertex(
// one cell up and to the left. (Do the math to verify yourself)
cell_pos = cell_pos + cell_size_scaled * position;
out.position = uniforms.projection_matrix * float4(cell_pos.x, cell_pos.y, 0.0f, 1.0f);
out.position = uniforms.projection_matrix *
float4(cell_pos.x, cell_pos.y, 0.0f, 1.0f);
break;
case MODE_FG:
@ -176,49 +172,33 @@ vertex VertexOut uber_vertex(
// Calculate the final position of the cell which uses our glyph size
// and glyph offset to create the correct bounding box for the glyph.
cell_pos = cell_pos + glyph_size * position + glyph_offset;
out.position = uniforms.projection_matrix * float4(cell_pos.x, cell_pos.y, 0.0f, 1.0f);
out.position = uniforms.projection_matrix *
float4(cell_pos.x, cell_pos.y, 0.0f, 1.0f);
// Calculate the texture coordinate in pixels. This is NOT normalized
// (between 0.0 and 1.0) and must be done in the fragment shader.
out.tex_coord = float2(input.glyph_pos) + float2(input.glyph_size) * position;
out.tex_coord =
float2(input.glyph_pos) + float2(input.glyph_size) * position;
// If we have a minimum contrast, we need to check if we need to
// change the color of the text to ensure it has enough contrast
// with the background.
if (uniforms.min_contrast > 1.0f && input.mode == MODE_FG) {
float4 bg_color = float4(input.bg_color) / 255.0f;
out.color = contrasted_color(uniforms.min_contrast, out.color, bg_color);
out.color =
contrasted_color(uniforms.min_contrast, out.color, bg_color);
}
break;
}
case MODE_STRIKETHROUGH: {
// Strikethrough Y value is just our thickness
float2 strikethrough_size = float2(cell_size_scaled.x, uniforms.strikethrough_thickness);
// Position the strikethrough where we are told to
float2 strikethrough_offset = float2(cell_size_scaled.x, uniforms.strikethrough_position);
// Go to the bottom of the cell, take away the size of the
// strikethrough, and that is our position. We also float it slightly
// above the bottom.
cell_pos = cell_pos + strikethrough_offset - (strikethrough_size * position);
out.position = uniforms.projection_matrix * float4(cell_pos, 0.0f, 1.0);
break;
}
}
return out;
}
fragment float4 uber_fragment(
VertexOut in [[ stage_in ]],
fragment float4 uber_fragment(VertexOut in [[stage_in]],
texture2d<float> textureGreyscale [[texture(0)]],
texture2d<float> textureColor [[ texture(1) ]]
) {
texture2d<float> textureColor [[texture(1)]]) {
constexpr sampler textureSampler(address::clamp_to_edge, filter::linear);
switch (in.mode) {
@ -228,7 +208,8 @@ fragment float4 uber_fragment(
case MODE_FG_CONSTRAINED:
case MODE_FG: {
// Normalize the texture coordinates to [0,1]
float2 size = float2(textureGreyscale.get_width(), textureGreyscale.get_height());
float2 size =
float2(textureGreyscale.get_width(), textureGreyscale.get_height());
float2 coord = in.tex_coord / size;
// We premult the alpha to our whole color since our blend function
@ -247,9 +228,6 @@ fragment float4 uber_fragment(
float2 coord = in.tex_coord / size;
return textureColor.sample(textureSampler, coord);
}
case MODE_STRIKETHROUGH:
return in.color;
}
}
@ -279,12 +257,10 @@ struct ImageVertexOut {
float2 tex_coord;
};
vertex ImageVertexOut image_vertex(
unsigned int vid [[ vertex_id ]],
vertex ImageVertexOut image_vertex(unsigned int vid [[vertex_id]],
ImageVertexIn input [[stage_in]],
texture2d<uint> image [[texture(0)]],
constant Uniforms &uniforms [[ buffer(1) ]]
) {
constant Uniforms& uniforms [[buffer(1)]]) {
// The size of the image in pixels
float2 image_size = float2(image.get_width(), image.get_height());
@ -315,15 +291,14 @@ vertex ImageVertexOut image_vertex(
float2 image_pos = (uniforms.cell_size * input.grid_pos) + input.cell_offset;
image_pos += input.dest_size * position;
out.position = uniforms.projection_matrix * float4(image_pos.x, image_pos.y, 0.0f, 1.0f);
out.position =
uniforms.projection_matrix * float4(image_pos.x, image_pos.y, 0.0f, 1.0f);
out.tex_coord = tex_coord;
return out;
}
fragment float4 image_fragment(
ImageVertexOut in [[ stage_in ]],
texture2d<uint> image [[ texture(0) ]]
) {
fragment float4 image_fragment(ImageVertexOut in [[stage_in]],
texture2d<uint> image [[texture(0)]]) {
constexpr sampler textureSampler(address::clamp_to_edge, filter::linear);
// Ehhhhh our texture is in RGBA8Uint but our color attachment is

View File

@ -8,7 +8,6 @@ const uint MODE_BG = 1u;
const uint MODE_FG = 2u;
const uint MODE_FG_CONSTRAINED = 3u;
const uint MODE_FG_COLOR = 7u;
const uint MODE_STRIKETHROUGH = 8u;
// The grid coordinates (x, y) where x < columns and y < rows
layout (location = 0) in vec2 grid_coord;
@ -57,8 +56,6 @@ uniform sampler2D text;
uniform sampler2D text_color;
uniform vec2 cell_size;
uniform mat4 projection;
uniform float strikethrough_position;
uniform float strikethrough_thickness;
uniform float min_contrast;
/********************************************************************
@ -233,21 +230,5 @@ void main() {
}
color = color_final;
break;
case MODE_STRIKETHROUGH:
// Strikethrough Y value is just our thickness
vec2 strikethrough_size = vec2(cell_size_scaled.x, strikethrough_thickness);
// Position the strikethrough where we are told to
vec2 strikethrough_offset = vec2(cell_size_scaled.x, strikethrough_position) ;
// Go to the bottom of the cell, take away the size of the
// strikethrough, and that is our position. We also float it slightly
// above the bottom.
cell_pos = cell_pos + strikethrough_offset - (strikethrough_size * position);
gl_Position = projection * vec4(cell_pos, cell_z, 1.0);
color = color_in / 255.0;
break;
}
}