Compare commits
6 commits
f7496654ae
...
5cc6724a07
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5cc6724a07 | ||
|
|
921f094509 | ||
|
|
2790dcfd11 | ||
|
|
05b87b1406 | ||
| 8278a080af | |||
| a9d4fed205 |
6 changed files with 111 additions and 29 deletions
|
|
@ -224,6 +224,17 @@ pub const font_test_text: []const u8 =
|
||||||
\\🙂↔
|
\\🙂↔
|
||||||
\\
|
\\
|
||||||
\\
|
\\
|
||||||
|
\\你好世界 "Hello World"
|
||||||
|
\\一二三四五六七八九十 "123456789"
|
||||||
|
\\龍鳳麟龜 (dragon, phoenix, qilin, turtle)
|
||||||
|
\\Fullwidth numbers: 1234567890
|
||||||
|
\\Fullwidth letters: ABCDEFG abcdefg
|
||||||
|
\\Fullwidth punctuation: !@#$%^&*()
|
||||||
|
\\Half-width (normal): ABC 123
|
||||||
|
\\Full-width (double): ABC 123
|
||||||
|
\\Punctuation: 。,、;:「」『』
|
||||||
|
\\Symbols: ○●□■△▲☆★◇◆
|
||||||
|
\\
|
||||||
\\ recommended fonts for terminals with no nerdfont fallback support (e.g. flow-gui):
|
\\ recommended fonts for terminals with no nerdfont fallback support (e.g. flow-gui):
|
||||||
\\
|
\\
|
||||||
\\ "IosevkaTerm Nerd Font" => https://github.com/ryanoasis/nerd-fonts/releases/download/v3.3.0/IosevkaTerm.zip
|
\\ "IosevkaTerm Nerd Font" => https://github.com/ryanoasis/nerd-fonts/releases/download/v3.3.0/IosevkaTerm.zip
|
||||||
|
|
|
||||||
|
|
@ -75,6 +75,7 @@ pub fn render(
|
||||||
self: *const DwriteRenderer,
|
self: *const DwriteRenderer,
|
||||||
font: Font,
|
font: Font,
|
||||||
utf8: []const u8,
|
utf8: []const u8,
|
||||||
|
double_width: bool,
|
||||||
) void {
|
) void {
|
||||||
var utf16_buf: [10]u16 = undefined;
|
var utf16_buf: [10]u16 = undefined;
|
||||||
const utf16_len = std.unicode.utf8ToUtf16Le(&utf16_buf, utf8) catch unreachable;
|
const utf16_len = std.unicode.utf8ToUtf16Le(&utf16_buf, utf8) catch unreachable;
|
||||||
|
|
@ -85,7 +86,10 @@ pub fn render(
|
||||||
const rect: win32.D2D_RECT_F = .{
|
const rect: win32.D2D_RECT_F = .{
|
||||||
.left = 0,
|
.left = 0,
|
||||||
.top = 0,
|
.top = 0,
|
||||||
.right = @floatFromInt(font.cell_size.x),
|
.right = if (double_width)
|
||||||
|
@as(f32, @floatFromInt(font.cell_size.x)) * 2
|
||||||
|
else
|
||||||
|
@as(f32, @floatFromInt(font.cell_size.x)),
|
||||||
.bottom = @floatFromInt(font.cell_size.y),
|
.bottom = @floatFromInt(font.cell_size.y),
|
||||||
};
|
};
|
||||||
self.render_target.BeginDraw();
|
self.render_target.BeginDraw();
|
||||||
|
|
@ -96,7 +100,7 @@ pub fn render(
|
||||||
self.render_target.DrawText(
|
self.render_target.DrawText(
|
||||||
@ptrCast(utf16.ptr),
|
@ptrCast(utf16.ptr),
|
||||||
@intCast(utf16.len),
|
@intCast(utf16.len),
|
||||||
font.text_format,
|
if (double_width) font.text_format_double else font.text_format_single,
|
||||||
&rect,
|
&rect,
|
||||||
&self.white_brush.ID2D1Brush,
|
&self.white_brush.ID2D1Brush,
|
||||||
.{},
|
.{},
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,15 @@ const Node = struct {
|
||||||
prev: ?u32,
|
prev: ?u32,
|
||||||
next: ?u32,
|
next: ?u32,
|
||||||
codepoint: ?u21,
|
codepoint: ?u21,
|
||||||
|
right_half: ?bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
map: std.AutoHashMapUnmanaged(u21, u32) = .{},
|
const MapKey = struct {
|
||||||
|
codepoint: u21,
|
||||||
|
right_half: bool,
|
||||||
|
};
|
||||||
|
|
||||||
|
map: std.AutoHashMapUnmanaged(MapKey, u32) = .{},
|
||||||
nodes: []Node,
|
nodes: []Node,
|
||||||
front: u32,
|
front: u32,
|
||||||
back: u32,
|
back: u32,
|
||||||
|
|
@ -25,13 +31,14 @@ pub fn init(allocator: std.mem.Allocator, capacity: u32) error{OutOfMemory}!Glyp
|
||||||
|
|
||||||
pub fn clearRetainingCapacity(self: *GlyphIndexCache) void {
|
pub fn clearRetainingCapacity(self: *GlyphIndexCache) void {
|
||||||
self.map.clearRetainingCapacity();
|
self.map.clearRetainingCapacity();
|
||||||
self.nodes[0] = .{ .prev = null, .next = 1, .codepoint = null };
|
self.nodes[0] = .{ .prev = null, .next = 1, .codepoint = null, .right_half = null };
|
||||||
self.nodes[self.nodes.len - 1] = .{ .prev = @intCast(self.nodes.len - 2), .next = null, .codepoint = null };
|
self.nodes[self.nodes.len - 1] = .{ .prev = @intCast(self.nodes.len - 2), .next = null, .codepoint = null, .right_half = null };
|
||||||
for (self.nodes[1 .. self.nodes.len - 1], 1..) |*node, index| {
|
for (self.nodes[1 .. self.nodes.len - 1], 1..) |*node, index| {
|
||||||
node.* = .{
|
node.* = .{
|
||||||
.prev = @intCast(index - 1),
|
.prev = @intCast(index - 1),
|
||||||
.next = @intCast(index + 1),
|
.next = @intCast(index + 1),
|
||||||
.codepoint = null,
|
.codepoint = null,
|
||||||
|
.right_half = null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
self.front = 0;
|
self.front = 0;
|
||||||
|
|
@ -51,12 +58,12 @@ const Reserved = struct {
|
||||||
index: u32,
|
index: u32,
|
||||||
replaced: ?u21,
|
replaced: ?u21,
|
||||||
};
|
};
|
||||||
pub fn reserve(self: *GlyphIndexCache, allocator: std.mem.Allocator, codepoint: u21) error{OutOfMemory}!union(enum) {
|
pub fn reserve(self: *GlyphIndexCache, allocator: std.mem.Allocator, codepoint: u21, right_half: bool) error{OutOfMemory}!union(enum) {
|
||||||
newly_reserved: Reserved,
|
newly_reserved: Reserved,
|
||||||
already_reserved: u32,
|
already_reserved: u32,
|
||||||
} {
|
} {
|
||||||
{
|
{
|
||||||
const entry = try self.map.getOrPut(allocator, codepoint);
|
const entry = try self.map.getOrPut(allocator, .{ .codepoint = codepoint, .right_half = right_half });
|
||||||
if (entry.found_existing) {
|
if (entry.found_existing) {
|
||||||
self.moveToBack(entry.value_ptr.*);
|
self.moveToBack(entry.value_ptr.*);
|
||||||
return .{ .already_reserved = entry.value_ptr.* };
|
return .{ .already_reserved = entry.value_ptr.* };
|
||||||
|
|
@ -69,7 +76,7 @@ pub fn reserve(self: *GlyphIndexCache, allocator: std.mem.Allocator, codepoint:
|
||||||
const replaced = self.nodes[self.front].codepoint;
|
const replaced = self.nodes[self.front].codepoint;
|
||||||
self.nodes[self.front].codepoint = codepoint;
|
self.nodes[self.front].codepoint = codepoint;
|
||||||
if (replaced) |r| {
|
if (replaced) |r| {
|
||||||
const removed = self.map.remove(r);
|
const removed = self.map.remove(.{ .codepoint = r, .right_half = self.nodes[self.front].right_half orelse false });
|
||||||
std.debug.assert(removed);
|
std.debug.assert(removed);
|
||||||
}
|
}
|
||||||
const save_front = self.front;
|
const save_front = self.front;
|
||||||
|
|
|
||||||
|
|
@ -149,7 +149,7 @@ pub const WindowState = struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: this should take a utf8 graphme instead
|
// TODO: this should take a utf8 graphme instead
|
||||||
pub fn generateGlyph(state: *WindowState, font: Font, codepoint: u21) u32 {
|
pub fn generateGlyph(state: *WindowState, font: Font, codepoint: u21, kind: enum { single, left, right }) u32 {
|
||||||
// for now we'll just use 1 texture and leverage the entire thing
|
// for now we'll just use 1 texture and leverage the entire thing
|
||||||
const texture_cell_count: XY(u16) = getD3d11TextureMaxCellCount(font.cell_size);
|
const texture_cell_count: XY(u16) = getD3d11TextureMaxCellCount(font.cell_size);
|
||||||
const texture_cell_count_total: u32 =
|
const texture_cell_count_total: u32 =
|
||||||
|
|
@ -184,16 +184,27 @@ pub const WindowState = struct {
|
||||||
break :blk &(state.glyph_index_cache.?);
|
break :blk &(state.glyph_index_cache.?);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const right_half: bool = switch (kind) {
|
||||||
|
.single, .left => false,
|
||||||
|
.right => true,
|
||||||
|
};
|
||||||
|
|
||||||
switch (glyph_index_cache.reserve(
|
switch (glyph_index_cache.reserve(
|
||||||
global.glyph_cache_arena.allocator(),
|
global.glyph_cache_arena.allocator(),
|
||||||
codepoint,
|
codepoint,
|
||||||
|
right_half,
|
||||||
) catch |e| oom(e)) {
|
) catch |e| oom(e)) {
|
||||||
.newly_reserved => |reserved| {
|
.newly_reserved => |reserved| {
|
||||||
// var render_success = false;
|
// var render_success = false;
|
||||||
// defer if (!render_success) state.glyph_index_cache.remove(reserved.index);
|
// defer if (!render_success) state.glyph_index_cache.remove(reserved.index);
|
||||||
const pos: XY(u16) = cellPosFromIndex(reserved.index, texture_cell_count.x);
|
const pos: XY(u16) = cellPosFromIndex(reserved.index, texture_cell_count.x);
|
||||||
const coord = coordFromCellPos(font.cell_size, pos);
|
const coord = coordFromCellPos(font.cell_size, pos);
|
||||||
const staging = global.staging_texture.update(font.cell_size);
|
const staging_size: XY(u16) = .{
|
||||||
|
// twice the width to handle double-wide glyphs
|
||||||
|
.x = font.cell_size.x * 2,
|
||||||
|
.y = font.cell_size.y,
|
||||||
|
};
|
||||||
|
const staging = global.staging_texture.update(staging_size);
|
||||||
var utf8_buf: [7]u8 = undefined;
|
var utf8_buf: [7]u8 = undefined;
|
||||||
const utf8_len: u3 = std.unicode.utf8Encode(codepoint, &utf8_buf) catch |e| std.debug.panic(
|
const utf8_len: u3 = std.unicode.utf8Encode(codepoint, &utf8_buf) catch |e| std.debug.panic(
|
||||||
"todo: handle invalid codepoint {} (0x{0x}) ({s})",
|
"todo: handle invalid codepoint {} (0x{0x}) ({s})",
|
||||||
|
|
@ -202,12 +213,16 @@ pub const WindowState = struct {
|
||||||
staging.text_renderer.render(
|
staging.text_renderer.render(
|
||||||
font,
|
font,
|
||||||
utf8_buf[0..utf8_len],
|
utf8_buf[0..utf8_len],
|
||||||
|
switch (kind) {
|
||||||
|
.single => false,
|
||||||
|
.left, .right => true,
|
||||||
|
},
|
||||||
);
|
);
|
||||||
const box: win32.D3D11_BOX = .{
|
const box: win32.D3D11_BOX = .{
|
||||||
.left = 0,
|
.left = if (right_half) font.cell_size.x else 0,
|
||||||
.top = 0,
|
.top = 0,
|
||||||
.front = 0,
|
.front = 0,
|
||||||
.right = font.cell_size.x,
|
.right = if (right_half) font.cell_size.x * 2 else font.cell_size.x,
|
||||||
.bottom = font.cell_size.y,
|
.bottom = font.cell_size.y,
|
||||||
.back = 1,
|
.back = 1,
|
||||||
};
|
};
|
||||||
|
|
@ -289,7 +304,7 @@ pub fn paint(
|
||||||
}
|
}
|
||||||
|
|
||||||
const copy_col_count: u16 = @min(col_count, shader_col_count);
|
const copy_col_count: u16 = @min(col_count, shader_col_count);
|
||||||
const blank_space_glyph_index = state.generateGlyph(font, ' ');
|
const blank_space_glyph_index = state.generateGlyph(font, ' ', .single);
|
||||||
|
|
||||||
const cell_count: u32 = @as(u32, shader_col_count) * @as(u32, shader_row_count);
|
const cell_count: u32 = @as(u32, shader_col_count) * @as(u32, shader_row_count);
|
||||||
state.shader_cells.updateCount(cell_count);
|
state.shader_cells.updateCount(cell_count);
|
||||||
|
|
|
||||||
|
|
@ -23,11 +23,13 @@ pub fn init() void {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const Font = struct {
|
pub const Font = struct {
|
||||||
text_format: *win32.IDWriteTextFormat,
|
text_format_single: *win32.IDWriteTextFormat,
|
||||||
|
text_format_double: *win32.IDWriteTextFormat,
|
||||||
cell_size: XY(u16),
|
cell_size: XY(u16),
|
||||||
|
|
||||||
pub fn init(dpi: u32, size: f32, face: *const FontFace) Font {
|
pub fn init(dpi: u32, size: f32, face: *const FontFace) Font {
|
||||||
var text_format: *win32.IDWriteTextFormat = undefined;
|
var text_format_single: *win32.IDWriteTextFormat = undefined;
|
||||||
|
|
||||||
{
|
{
|
||||||
const hr = global.dwrite_factory.CreateTextFormat(
|
const hr = global.dwrite_factory.CreateTextFormat(
|
||||||
face.ptr(),
|
face.ptr(),
|
||||||
|
|
@ -37,14 +39,43 @@ pub const Font = struct {
|
||||||
.NORMAL, // stretch
|
.NORMAL, // stretch
|
||||||
win32.scaleDpi(f32, size, dpi),
|
win32.scaleDpi(f32, size, dpi),
|
||||||
win32.L(""), // locale
|
win32.L(""), // locale
|
||||||
&text_format,
|
&text_format_single,
|
||||||
);
|
);
|
||||||
if (hr < 0) std.debug.panic(
|
if (hr < 0) std.debug.panic(
|
||||||
"CreateTextFormat '{}' height {d} failed, hresult=0x{x}",
|
"CreateTextFormat '{}' height {d} failed, hresult=0x{x}",
|
||||||
.{ std.unicode.fmtUtf16Le(face.slice()), size, @as(u32, @bitCast(hr)) },
|
.{ std.unicode.fmtUtf16Le(face.slice()), size, @as(u32, @bitCast(hr)) },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
errdefer _ = text_format.IUnknown.Release();
|
errdefer _ = text_format_single.IUnknown.Release();
|
||||||
|
|
||||||
|
var text_format_double: *win32.IDWriteTextFormat = undefined;
|
||||||
|
{
|
||||||
|
const hr = global.dwrite_factory.CreateTextFormat(
|
||||||
|
face.ptr(),
|
||||||
|
null,
|
||||||
|
.NORMAL, //weight
|
||||||
|
.NORMAL, // style
|
||||||
|
.NORMAL, // stretch
|
||||||
|
win32.scaleDpi(f32, size, dpi),
|
||||||
|
win32.L(""), // locale
|
||||||
|
&text_format_double,
|
||||||
|
);
|
||||||
|
if (hr < 0) std.debug.panic(
|
||||||
|
"CreateTextFormat '{}' height {d} failed, hresult=0x{x}",
|
||||||
|
.{ std.unicode.fmtUtf16Le(face.slice()), size, @as(u32, @bitCast(hr)) },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
errdefer _ = text_format_double.IUnknown.Release();
|
||||||
|
|
||||||
|
{
|
||||||
|
const hr = text_format_double.SetTextAlignment(win32.DWRITE_TEXT_ALIGNMENT_CENTER);
|
||||||
|
if (hr < 0) fatalHr("SetTextAlignment", hr);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const hr = text_format_double.SetParagraphAlignment(win32.DWRITE_PARAGRAPH_ALIGNMENT_CENTER);
|
||||||
|
if (hr < 0) fatalHr("SetParagraphAlignment", hr);
|
||||||
|
}
|
||||||
|
|
||||||
const cell_size: XY(u16) = blk: {
|
const cell_size: XY(u16) = blk: {
|
||||||
var text_layout: *win32.IDWriteTextLayout = undefined;
|
var text_layout: *win32.IDWriteTextLayout = undefined;
|
||||||
|
|
@ -52,7 +83,7 @@ pub const Font = struct {
|
||||||
const hr = global.dwrite_factory.CreateTextLayout(
|
const hr = global.dwrite_factory.CreateTextLayout(
|
||||||
win32.L("█"),
|
win32.L("█"),
|
||||||
1,
|
1,
|
||||||
text_format,
|
text_format_single,
|
||||||
std.math.floatMax(f32),
|
std.math.floatMax(f32),
|
||||||
std.math.floatMax(f32),
|
std.math.floatMax(f32),
|
||||||
&text_layout,
|
&text_layout,
|
||||||
|
|
@ -73,13 +104,15 @@ pub const Font = struct {
|
||||||
};
|
};
|
||||||
|
|
||||||
return .{
|
return .{
|
||||||
.text_format = text_format,
|
.text_format_single = text_format_single,
|
||||||
|
.text_format_double = text_format_double,
|
||||||
.cell_size = cell_size,
|
.cell_size = cell_size,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(self: *Font) void {
|
pub fn deinit(self: *Font) void {
|
||||||
_ = self.text_format.IUnknown.Release();
|
_ = self.text_format_single.IUnknown.Release();
|
||||||
|
_ = self.text_format_double.IUnknown.Release();
|
||||||
self.* = undefined;
|
self.* = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1081,19 +1081,31 @@ fn WndProc(
|
||||||
global.render_cells_arena.allocator(),
|
global.render_cells_arena.allocator(),
|
||||||
global.screen.buf.len,
|
global.screen.buf.len,
|
||||||
) catch |e| oom(e);
|
) catch |e| oom(e);
|
||||||
|
var prev_width: usize = 1;
|
||||||
|
var prev_cell: render.Cell = undefined;
|
||||||
|
var prev_codepoint: u21 = undefined;
|
||||||
for (global.screen.buf, global.render_cells.items) |*screen_cell, *render_cell| {
|
for (global.screen.buf, global.render_cells.items) |*screen_cell, *render_cell| {
|
||||||
const codepoint = if (std.unicode.utf8ValidateSlice(screen_cell.char.grapheme))
|
const width = screen_cell.char.width;
|
||||||
|
// temporary workaround, ignore multi-codepoint graphemes
|
||||||
|
const codepoint = if (screen_cell.char.grapheme.len > 4)
|
||||||
|
std.unicode.replacement_character
|
||||||
|
else if (std.unicode.utf8ValidateSlice(screen_cell.char.grapheme))
|
||||||
std.unicode.wtf8Decode(screen_cell.char.grapheme) catch std.unicode.replacement_character
|
std.unicode.wtf8Decode(screen_cell.char.grapheme) catch std.unicode.replacement_character
|
||||||
else
|
else
|
||||||
std.unicode.replacement_character;
|
std.unicode.replacement_character;
|
||||||
render_cell.* = .{
|
if (prev_width > 1) {
|
||||||
.glyph_index = state.render_state.generateGlyph(
|
render_cell.* = prev_cell;
|
||||||
font,
|
render_cell.glyph_index = state.render_state.generateGlyph(font, prev_codepoint, .right);
|
||||||
codepoint,
|
} else {
|
||||||
),
|
render_cell.* = .{
|
||||||
.background = renderColorFromVaxis(screen_cell.style.bg),
|
.glyph_index = state.render_state.generateGlyph(font, codepoint, if (width == 1) .single else .left),
|
||||||
.foreground = renderColorFromVaxis(screen_cell.style.fg),
|
.background = renderColorFromVaxis(screen_cell.style.bg),
|
||||||
};
|
.foreground = renderColorFromVaxis(screen_cell.style.fg),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
prev_width = width;
|
||||||
|
prev_cell = render_cell.*;
|
||||||
|
prev_codepoint = codepoint;
|
||||||
}
|
}
|
||||||
render.paint(
|
render.paint(
|
||||||
&state.render_state,
|
&state.render_state,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue