fix(gui): render block drawing glyphs with no anti aliasing
This commit is contained in:
parent
6954983d40
commit
4d15884402
1 changed files with 104 additions and 10 deletions
|
|
@ -90,6 +90,23 @@ pub fn render(
|
|||
kind: GlyphKind,
|
||||
staging_buf: []u8,
|
||||
) void {
|
||||
// Always use 2*cell_w as the row stride so it matches the staging buffer
|
||||
// width allocated by generateGlyph (which always allocates 2*cell_w wide).
|
||||
const buf_w: i32 = @as(i32, @intCast(font.cell_size.x)) * 2;
|
||||
const buf_h: i32 = @intCast(font.cell_size.y);
|
||||
const x_offset: i32 = switch (kind) {
|
||||
.single, .left => 0,
|
||||
.right => @intCast(font.cell_size.x),
|
||||
};
|
||||
const cw: i32 = @intCast(font.cell_size.x);
|
||||
const ch: i32 = @intCast(font.cell_size.y);
|
||||
|
||||
// Block element characters (U+2580–U+259F) are rasterized geometrically
|
||||
// rather than through the TrueType anti-aliasing path. Anti-aliased edges
|
||||
// produce partial-alpha pixels at cell boundaries, creating visible seams
|
||||
// between adjacent cells when fg ≠ bg.
|
||||
if (renderBlockElement(codepoint, staging_buf, buf_w, buf_h, x_offset, cw, ch)) return;
|
||||
|
||||
var arena = std.heap.ArenaAllocator.init(self.allocator);
|
||||
defer arena.deinit();
|
||||
const alloc = arena.allocator();
|
||||
|
|
@ -103,16 +120,6 @@ pub fn render(
|
|||
|
||||
if (dims.width == 0 or dims.height == 0) return;
|
||||
|
||||
// Always use 2*cell_w as the row stride so it matches the staging buffer
|
||||
// width allocated by generateGlyph (which always allocates 2*cell_w wide).
|
||||
const buf_w: i32 = @as(i32, @intCast(font.cell_size.x)) * 2;
|
||||
const buf_h: i32 = @intCast(font.cell_size.y);
|
||||
|
||||
const x_offset: i32 = switch (kind) {
|
||||
.single, .left => 0,
|
||||
.right => @intCast(font.cell_size.x),
|
||||
};
|
||||
|
||||
for (0..dims.height) |row| {
|
||||
const dst_y: i32 = font.ascent_px + @as(i32, dims.off_y) + @as(i32, @intCast(row));
|
||||
if (dst_y < 0 or dst_y >= buf_h) continue;
|
||||
|
|
@ -130,3 +137,90 @@ pub fn render(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Fill a solid rectangle [x0, x1) × [y0, y1) in the staging buffer.
|
||||
fn fillRect(buf: []u8, buf_w: i32, buf_h: i32, x0: i32, y0: i32, x1: i32, y1: i32) void {
|
||||
const cx0 = @max(0, x0);
|
||||
const cy0 = @max(0, y0);
|
||||
const cx1 = @min(buf_w, x1);
|
||||
const cy1 = @min(buf_h, y1);
|
||||
if (cx0 >= cx1 or cy0 >= cy1) return;
|
||||
var y = cy0;
|
||||
while (y < cy1) : (y += 1) {
|
||||
var x = cx0;
|
||||
while (x < cx1) : (x += 1) {
|
||||
buf[@intCast(y * buf_w + x)] = 255;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Render a block element character (U+2580–U+259F) geometrically.
|
||||
/// Returns true if the character was handled, false if it should fall through
|
||||
/// to the normal TrueType rasterizer.
|
||||
fn renderBlockElement(
|
||||
cp: u21,
|
||||
buf: []u8,
|
||||
buf_w: i32,
|
||||
buf_h: i32,
|
||||
x0: i32,
|
||||
cw: i32,
|
||||
ch: i32,
|
||||
) bool {
|
||||
if (cp < 0x2580 or cp > 0x259F) return false;
|
||||
|
||||
const x1 = x0 + cw;
|
||||
const half_w = @divTrunc(cw, 2);
|
||||
const half_h = @divTrunc(ch, 2);
|
||||
const mid_x = x0 + half_w;
|
||||
|
||||
switch (cp) {
|
||||
0x2580 => fillRect(buf, buf_w, buf_h, x0, 0, x1, half_h), // ▀ upper half
|
||||
0x2581 => fillRect(buf, buf_w, buf_h, x0, ch - @divTrunc(ch, 8), x1, ch), // ▁ lower 1/8
|
||||
0x2582 => fillRect(buf, buf_w, buf_h, x0, ch - @divTrunc(ch, 4), x1, ch), // ▂ lower 1/4
|
||||
0x2583 => fillRect(buf, buf_w, buf_h, x0, ch - @divTrunc(ch * 3, 8), x1, ch), // ▃ lower 3/8
|
||||
0x2584 => fillRect(buf, buf_w, buf_h, x0, ch - half_h, x1, ch), // ▄ lower half
|
||||
0x2585 => fillRect(buf, buf_w, buf_h, x0, ch - @divTrunc(ch * 5, 8), x1, ch), // ▅ lower 5/8
|
||||
0x2586 => fillRect(buf, buf_w, buf_h, x0, ch - @divTrunc(ch * 3, 4), x1, ch), // ▆ lower 3/4
|
||||
0x2587 => fillRect(buf, buf_w, buf_h, x0, ch - @divTrunc(ch * 7, 8), x1, ch), // ▇ lower 7/8
|
||||
0x2588 => fillRect(buf, buf_w, buf_h, x0, 0, x1, ch), // █ full block
|
||||
0x2589 => fillRect(buf, buf_w, buf_h, x0, 0, x0 + @divTrunc(cw * 7, 8), ch), // ▉ left 7/8
|
||||
0x258A => fillRect(buf, buf_w, buf_h, x0, 0, x0 + @divTrunc(cw * 3, 4), ch), // ▊ left 3/4
|
||||
0x258B => fillRect(buf, buf_w, buf_h, x0, 0, x0 + @divTrunc(cw * 5, 8), ch), // ▋ left 5/8
|
||||
0x258C => fillRect(buf, buf_w, buf_h, x0, 0, mid_x, ch), // ▌ left half
|
||||
0x258D => fillRect(buf, buf_w, buf_h, x0, 0, x0 + @divTrunc(cw * 3, 8), ch), // ▍ left 3/8
|
||||
0x258E => fillRect(buf, buf_w, buf_h, x0, 0, x0 + @divTrunc(cw, 4), ch), // ▎ left 1/4
|
||||
0x258F => fillRect(buf, buf_w, buf_h, x0, 0, x0 + @divTrunc(cw, 8), ch), // ▏ left 1/8
|
||||
0x2590 => fillRect(buf, buf_w, buf_h, mid_x, 0, x1, ch), // ▐ right half
|
||||
0x2591 => { // ░ light shade — approximate with alternating pixels
|
||||
var y: i32 = 0;
|
||||
while (y < ch) : (y += 1) {
|
||||
var x = x0 + @mod(y, 2);
|
||||
while (x < x1) : (x += 2) {
|
||||
if (x >= 0 and x < buf_w and y >= 0 and y < buf_h)
|
||||
buf[@intCast(y * buf_w + x)] = 255;
|
||||
}
|
||||
}
|
||||
},
|
||||
0x2592 => { // ▒ medium shade — alternate rows fully/empty
|
||||
var y: i32 = 0;
|
||||
while (y < ch) : (y += 2) {
|
||||
fillRect(buf, buf_w, buf_h, x0, y, x1, y + 1);
|
||||
}
|
||||
},
|
||||
0x2593 => { // ▓ dark shade — fill all except alternating sparse pixels
|
||||
fillRect(buf, buf_w, buf_h, x0, 0, x1, ch);
|
||||
var y: i32 = 0;
|
||||
while (y < ch) : (y += 2) {
|
||||
var x = x0 + @mod(y, 2);
|
||||
while (x < x1) : (x += 2) {
|
||||
if (x >= 0 and x < buf_w and y >= 0 and y < buf_h)
|
||||
buf[@intCast(y * buf_w + x)] = 0;
|
||||
}
|
||||
}
|
||||
},
|
||||
0x2594 => fillRect(buf, buf_w, buf_h, x0, 0, x1, @divTrunc(ch, 8)), // ▔ upper 1/8
|
||||
0x2595 => fillRect(buf, buf_w, buf_h, x1 - @divTrunc(cw, 8), 0, x1, ch), // ▕ right 1/8
|
||||
else => return false,
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue