mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-16 16:56:09 +03:00
Merge pull request #671 from mitchellh/sprite-width
Fix cursor width, underline width with wide chars
This commit is contained in:
@ -515,6 +515,7 @@ pub fn renderGlyph(
|
|||||||
alloc,
|
alloc,
|
||||||
atlas,
|
atlas,
|
||||||
glyph_index,
|
glyph_index,
|
||||||
|
opts,
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -41,6 +41,7 @@ const CodepointKey = struct {
|
|||||||
const GlyphKey = struct {
|
const GlyphKey = struct {
|
||||||
index: Group.FontIndex,
|
index: Group.FontIndex,
|
||||||
glyph: u32,
|
glyph: u32,
|
||||||
|
opts: font.face.RenderOptions,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// The GroupCache takes ownership of Group and will free it.
|
/// The GroupCache takes ownership of Group and will free it.
|
||||||
@ -124,7 +125,7 @@ pub fn renderGlyph(
|
|||||||
glyph_index: u32,
|
glyph_index: u32,
|
||||||
opts: font.face.RenderOptions,
|
opts: font.face.RenderOptions,
|
||||||
) !Glyph {
|
) !Glyph {
|
||||||
const key: GlyphKey = .{ .index = index, .glyph = glyph_index };
|
const key: GlyphKey = .{ .index = index, .glyph = glyph_index, .opts = opts };
|
||||||
const gop = try self.glyphs.getOrPut(alloc, key);
|
const gop = try self.glyphs.getOrPut(alloc, key);
|
||||||
|
|
||||||
// If it is in the cache, use it.
|
// If it is in the cache, use it.
|
||||||
|
@ -76,6 +76,10 @@ pub const RenderOptions = struct {
|
|||||||
/// is typically naive, but ultimately up to the rasterizer.
|
/// is typically naive, but ultimately up to the rasterizer.
|
||||||
max_height: ?u16 = null,
|
max_height: ?u16 = null,
|
||||||
|
|
||||||
|
/// The number of grid cells this glyph will take up. This can be used
|
||||||
|
/// optionally by the rasterizer to better layout the glyph.
|
||||||
|
cell_width: ?u2 = null,
|
||||||
|
|
||||||
/// Thicken the glyph. This draws the glyph with a thicker stroke width.
|
/// Thicken the glyph. This draws the glyph with a thicker stroke width.
|
||||||
/// This is purely an aesthetic setting.
|
/// This is purely an aesthetic setting.
|
||||||
///
|
///
|
||||||
|
@ -49,6 +49,7 @@ pub fn renderGlyph(
|
|||||||
alloc: Allocator,
|
alloc: Allocator,
|
||||||
atlas: *font.Atlas,
|
atlas: *font.Atlas,
|
||||||
cp: u32,
|
cp: u32,
|
||||||
|
opts: font.face.RenderOptions,
|
||||||
) !font.Glyph {
|
) !font.Glyph {
|
||||||
if (std.debug.runtime_safety) {
|
if (std.debug.runtime_safety) {
|
||||||
if (!self.hasCodepoint(cp, null)) {
|
if (!self.hasCodepoint(cp, null)) {
|
||||||
@ -57,11 +58,17 @@ pub fn renderGlyph(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We adjust our sprite width based on the cell width.
|
||||||
|
const width = switch (opts.cell_width orelse 1) {
|
||||||
|
0, 1 => self.width,
|
||||||
|
else => |width| self.width * width,
|
||||||
|
};
|
||||||
|
|
||||||
// Safe to ".?" because of the above assertion.
|
// Safe to ".?" because of the above assertion.
|
||||||
return switch (Kind.init(cp).?) {
|
return switch (Kind.init(cp).?) {
|
||||||
.box => box: {
|
.box => box: {
|
||||||
const f: Box = .{
|
const f: Box = .{
|
||||||
.width = self.width,
|
.width = width,
|
||||||
.height = self.height,
|
.height = self.height,
|
||||||
.thickness = self.thickness,
|
.thickness = self.thickness,
|
||||||
};
|
};
|
||||||
@ -73,7 +80,7 @@ pub fn renderGlyph(
|
|||||||
alloc,
|
alloc,
|
||||||
atlas,
|
atlas,
|
||||||
@enumFromInt(cp),
|
@enumFromInt(cp),
|
||||||
self.width,
|
width,
|
||||||
self.height,
|
self.height,
|
||||||
self.underline_position,
|
self.underline_position,
|
||||||
self.thickness,
|
self.thickness,
|
||||||
|
@ -1364,7 +1364,7 @@ pub fn updateCell(
|
|||||||
self.alloc,
|
self.alloc,
|
||||||
font.sprite_index,
|
font.sprite_index,
|
||||||
@intFromEnum(sprite),
|
@intFromEnum(sprite),
|
||||||
.{},
|
.{ .cell_width = if (cell.attrs.wide) 2 else 1 },
|
||||||
);
|
);
|
||||||
|
|
||||||
const color = if (cell.attrs.underline_color) cell.underline_fg else colors.fg;
|
const color = if (cell.attrs.underline_color) cell.underline_fg else colors.fg;
|
||||||
@ -1397,12 +1397,26 @@ fn addCursor(
|
|||||||
screen: *terminal.Screen,
|
screen: *terminal.Screen,
|
||||||
cursor_style: renderer.CursorStyle,
|
cursor_style: renderer.CursorStyle,
|
||||||
) ?*const mtl_shaders.Cell {
|
) ?*const mtl_shaders.Cell {
|
||||||
// Add the cursor
|
// Add the cursor. We render the cursor over the wide character if
|
||||||
const cell = screen.getCell(
|
// we're on the wide characer tail.
|
||||||
.active,
|
const cell, const x = cell: {
|
||||||
screen.cursor.y,
|
// The cursor goes over the screen cursor position.
|
||||||
screen.cursor.x,
|
const cell = screen.getCell(
|
||||||
);
|
.active,
|
||||||
|
screen.cursor.y,
|
||||||
|
screen.cursor.x,
|
||||||
|
);
|
||||||
|
if (!cell.attrs.wide_spacer_tail or screen.cursor.x == 0)
|
||||||
|
break :cell .{ cell, screen.cursor.x };
|
||||||
|
|
||||||
|
// If we're part of a wide character, we move the cursor back to
|
||||||
|
// the actual character.
|
||||||
|
break :cell .{ screen.getCell(
|
||||||
|
.active,
|
||||||
|
screen.cursor.y,
|
||||||
|
screen.cursor.x - 1,
|
||||||
|
), screen.cursor.x - 1 };
|
||||||
|
};
|
||||||
|
|
||||||
const color = self.config.cursor_color orelse terminal.color.RGB{
|
const color = self.config.cursor_color orelse terminal.color.RGB{
|
||||||
.r = 0xFF,
|
.r = 0xFF,
|
||||||
@ -1421,7 +1435,7 @@ fn addCursor(
|
|||||||
self.alloc,
|
self.alloc,
|
||||||
font.sprite_index,
|
font.sprite_index,
|
||||||
@intFromEnum(sprite),
|
@intFromEnum(sprite),
|
||||||
.{},
|
.{ .cell_width = if (cell.attrs.wide) 2 else 1 },
|
||||||
) catch |err| {
|
) catch |err| {
|
||||||
log.warn("error rendering cursor glyph err={}", .{err});
|
log.warn("error rendering cursor glyph err={}", .{err});
|
||||||
return null;
|
return null;
|
||||||
@ -1430,7 +1444,7 @@ fn addCursor(
|
|||||||
self.cells.appendAssumeCapacity(.{
|
self.cells.appendAssumeCapacity(.{
|
||||||
.mode = .fg,
|
.mode = .fg,
|
||||||
.grid_pos = .{
|
.grid_pos = .{
|
||||||
@as(f32, @floatFromInt(screen.cursor.x)),
|
@as(f32, @floatFromInt(x)),
|
||||||
@as(f32, @floatFromInt(screen.cursor.y)),
|
@as(f32, @floatFromInt(screen.cursor.y)),
|
||||||
},
|
},
|
||||||
.cell_width = if (cell.attrs.wide) 2 else 1,
|
.cell_width = if (cell.attrs.wide) 2 else 1,
|
||||||
|
@ -848,12 +848,26 @@ fn addCursor(
|
|||||||
screen: *terminal.Screen,
|
screen: *terminal.Screen,
|
||||||
cursor_style: renderer.CursorStyle,
|
cursor_style: renderer.CursorStyle,
|
||||||
) ?*const GPUCell {
|
) ?*const GPUCell {
|
||||||
// Add the cursor
|
// Add the cursor. We render the cursor over the wide character if
|
||||||
const cell = screen.getCell(
|
// we're on the wide characer tail.
|
||||||
.active,
|
const cell, const x = cell: {
|
||||||
screen.cursor.y,
|
// The cursor goes over the screen cursor position.
|
||||||
screen.cursor.x,
|
const cell = screen.getCell(
|
||||||
);
|
.active,
|
||||||
|
screen.cursor.y,
|
||||||
|
screen.cursor.x,
|
||||||
|
);
|
||||||
|
if (!cell.attrs.wide_spacer_tail or screen.cursor.x == 0)
|
||||||
|
break :cell .{ cell, screen.cursor.x };
|
||||||
|
|
||||||
|
// If we're part of a wide character, we move the cursor back to
|
||||||
|
// the actual character.
|
||||||
|
break :cell .{ screen.getCell(
|
||||||
|
.active,
|
||||||
|
screen.cursor.y,
|
||||||
|
screen.cursor.x - 1,
|
||||||
|
), screen.cursor.x - 1 };
|
||||||
|
};
|
||||||
|
|
||||||
const color = self.config.cursor_color orelse terminal.color.RGB{
|
const color = self.config.cursor_color orelse terminal.color.RGB{
|
||||||
.r = 0xFF,
|
.r = 0xFF,
|
||||||
@ -872,7 +886,7 @@ fn addCursor(
|
|||||||
self.alloc,
|
self.alloc,
|
||||||
font.sprite_index,
|
font.sprite_index,
|
||||||
@intFromEnum(sprite),
|
@intFromEnum(sprite),
|
||||||
.{},
|
.{ .cell_width = if (cell.attrs.wide) 2 else 1 },
|
||||||
) catch |err| {
|
) catch |err| {
|
||||||
log.warn("error rendering cursor glyph err={}", .{err});
|
log.warn("error rendering cursor glyph err={}", .{err});
|
||||||
return null;
|
return null;
|
||||||
@ -880,7 +894,7 @@ fn addCursor(
|
|||||||
|
|
||||||
self.cells.appendAssumeCapacity(.{
|
self.cells.appendAssumeCapacity(.{
|
||||||
.mode = .fg,
|
.mode = .fg,
|
||||||
.grid_col = @intCast(screen.cursor.x),
|
.grid_col = @intCast(x),
|
||||||
.grid_row = @intCast(screen.cursor.y),
|
.grid_row = @intCast(screen.cursor.y),
|
||||||
.grid_width = if (cell.attrs.wide) 2 else 1,
|
.grid_width = if (cell.attrs.wide) 2 else 1,
|
||||||
.fg_r = color.r,
|
.fg_r = color.r,
|
||||||
@ -1136,7 +1150,7 @@ pub fn updateCell(
|
|||||||
self.alloc,
|
self.alloc,
|
||||||
font.sprite_index,
|
font.sprite_index,
|
||||||
@intFromEnum(sprite),
|
@intFromEnum(sprite),
|
||||||
.{},
|
.{ .cell_width = if (cell.attrs.wide) 2 else 1 },
|
||||||
);
|
);
|
||||||
|
|
||||||
const color = if (cell.attrs.underline_color) cell.underline_fg else colors.fg;
|
const color = if (cell.attrs.underline_color) cell.underline_fg else colors.fg;
|
||||||
|
@ -821,9 +821,10 @@ fn printCell(self: *Terminal, unmapped_c: u21) *Screen.Cell {
|
|||||||
const x = self.screen.cursor.x - 1;
|
const x = self.screen.cursor.x - 1;
|
||||||
|
|
||||||
const wide_cell = row.getCellPtr(x);
|
const wide_cell = row.getCellPtr(x);
|
||||||
|
wide_cell.char = 0;
|
||||||
wide_cell.attrs.wide = false;
|
wide_cell.attrs.wide = false;
|
||||||
|
|
||||||
if (self.screen.cursor.x <= 1) {
|
if (self.screen.cursor.y > 0 and self.screen.cursor.x <= 1) {
|
||||||
self.clearWideSpacerHead();
|
self.clearWideSpacerHead();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2013,6 +2014,47 @@ test "Terminal: print over wide char at 0,0" {
|
|||||||
|
|
||||||
try testing.expectEqual(@as(usize, 0), t.screen.cursor.y);
|
try testing.expectEqual(@as(usize, 0), t.screen.cursor.y);
|
||||||
try testing.expectEqual(@as(usize, 1), t.screen.cursor.x);
|
try testing.expectEqual(@as(usize, 1), t.screen.cursor.x);
|
||||||
|
|
||||||
|
const row = t.screen.getRow(.{ .screen = 0 });
|
||||||
|
{
|
||||||
|
const cell = row.getCell(0);
|
||||||
|
try testing.expectEqual(@as(u32, 'A'), cell.char);
|
||||||
|
try testing.expect(!cell.attrs.wide);
|
||||||
|
try testing.expectEqual(@as(usize, 1), row.codepointLen(0));
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const cell = row.getCell(1);
|
||||||
|
try testing.expect(!cell.attrs.wide_spacer_tail);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test "Terminal: print over wide spacer tail" {
|
||||||
|
var t = try init(testing.allocator, 5, 5);
|
||||||
|
defer t.deinit(testing.allocator);
|
||||||
|
|
||||||
|
try t.print('橋');
|
||||||
|
t.setCursorPos(1, 2);
|
||||||
|
try t.print('X');
|
||||||
|
|
||||||
|
{
|
||||||
|
var str = try t.plainString(testing.allocator);
|
||||||
|
defer testing.allocator.free(str);
|
||||||
|
try testing.expectEqualStrings(" X", str);
|
||||||
|
}
|
||||||
|
|
||||||
|
const row = t.screen.getRow(.{ .screen = 0 });
|
||||||
|
{
|
||||||
|
const cell = row.getCell(0);
|
||||||
|
try testing.expectEqual(@as(u32, 0), cell.char);
|
||||||
|
try testing.expect(!cell.attrs.wide);
|
||||||
|
try testing.expectEqual(@as(usize, 1), row.codepointLen(0));
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const cell = row.getCell(1);
|
||||||
|
try testing.expectEqual(@as(u32, 'X'), cell.char);
|
||||||
|
try testing.expect(!cell.attrs.wide_spacer_tail);
|
||||||
|
try testing.expectEqual(@as(usize, 1), row.codepointLen(1));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
test "Terminal: print multicodepoint grapheme, disabled mode 2027" {
|
test "Terminal: print multicodepoint grapheme, disabled mode 2027" {
|
||||||
|
Reference in New Issue
Block a user