mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-08-02 14:57:31 +03:00
font: shape API, calls shape and outputs some debug
This commit is contained in:
@ -192,7 +192,10 @@ pub const Buffer = struct {
|
|||||||
/// long as buffer contents are not modified.
|
/// long as buffer contents are not modified.
|
||||||
pub fn getGlyphInfos(self: Buffer) []GlyphInfo {
|
pub fn getGlyphInfos(self: Buffer) []GlyphInfo {
|
||||||
var length: u32 = 0;
|
var length: u32 = 0;
|
||||||
const ptr = c.hb_buffer_get_glyph_infos(self.handle, &length);
|
const ptr = @ptrCast(
|
||||||
|
[*c]GlyphInfo,
|
||||||
|
c.hb_buffer_get_glyph_infos(self.handle, &length),
|
||||||
|
);
|
||||||
return ptr[0..length];
|
return ptr[0..length];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
const Shaper = @This();
|
const Shaper = @This();
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
const assert = std.debug.assert;
|
||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
const harfbuzz = @import("harfbuzz");
|
const harfbuzz = @import("harfbuzz");
|
||||||
const Atlas = @import("../Atlas.zig");
|
const Atlas = @import("../Atlas.zig");
|
||||||
@ -12,6 +13,8 @@ const Library = @import("main.zig").Library;
|
|||||||
const Style = @import("main.zig").Style;
|
const Style = @import("main.zig").Style;
|
||||||
const terminal = @import("../terminal/main.zig");
|
const terminal = @import("../terminal/main.zig");
|
||||||
|
|
||||||
|
const log = std.log.scoped(.font_shaper);
|
||||||
|
|
||||||
/// The font group to use under the covers
|
/// The font group to use under the covers
|
||||||
group: *GroupCache,
|
group: *GroupCache,
|
||||||
|
|
||||||
@ -37,6 +40,28 @@ pub fn runIterator(self: *Shaper, row: terminal.Screen.Row) RunIterator {
|
|||||||
return .{ .shaper = self, .row = row };
|
return .{ .shaper = self, .row = row };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Shape the given text run. The text run must be the immediately previous
|
||||||
|
/// text run that was iterated since the text run does share state with the
|
||||||
|
/// Shaper struct.
|
||||||
|
///
|
||||||
|
/// NOTE: there is no return value here yet because its still WIP
|
||||||
|
pub fn shape(self: Shaper, run: TextRun) void {
|
||||||
|
const face = self.group.group.faceFromIndex(run.font_index);
|
||||||
|
harfbuzz.shape(face.hb_font, self.hb_buf, null);
|
||||||
|
|
||||||
|
const info = self.hb_buf.getGlyphInfos();
|
||||||
|
const pos = self.hb_buf.getGlyphPositions() orelse return;
|
||||||
|
|
||||||
|
// This is perhaps not true somewhere, but we currently assume it is true.
|
||||||
|
// If it isn't true, I'd like to catch it and learn more.
|
||||||
|
assert(info.len == pos.len);
|
||||||
|
|
||||||
|
// log.warn("info={} pos={}", .{ info.len, pos.len });
|
||||||
|
// for (info) |v, i| {
|
||||||
|
// log.warn("info {} = {}", .{ i, v });
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
/// A single text run. A text run is only valid for one Shaper and
|
/// A single text run. A text run is only valid for one Shaper and
|
||||||
/// until the next run is created.
|
/// until the next run is created.
|
||||||
pub const TextRun = struct {
|
pub const TextRun = struct {
|
||||||
@ -74,13 +99,14 @@ pub const RunIterator = struct {
|
|||||||
// Determine the font for this cell
|
// Determine the font for this cell
|
||||||
const font_idx_opt = try self.shaper.group.indexForCodepoint(alloc, style, cell.char);
|
const font_idx_opt = try self.shaper.group.indexForCodepoint(alloc, style, cell.char);
|
||||||
const font_idx = font_idx_opt.?;
|
const font_idx = font_idx_opt.?;
|
||||||
|
//log.warn("char={x} idx={}", .{ cell.char, font_idx });
|
||||||
if (j == self.i) current_font = font_idx;
|
if (j == self.i) current_font = font_idx;
|
||||||
|
|
||||||
// If our fonts are not equal, then we're done with our run.
|
// If our fonts are not equal, then we're done with our run.
|
||||||
if (font_idx.int() != current_font.int()) break;
|
if (font_idx.int() != current_font.int()) break;
|
||||||
|
|
||||||
// Continue with our run
|
// Continue with our run
|
||||||
self.shaper.hb_buf.add(cell.char, @intCast(u32, j - self.i));
|
self.shaper.hb_buf.add(cell.char, @intCast(u32, j));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Finalize our buffer
|
// Finalize our buffer
|
||||||
@ -134,6 +160,36 @@ test "run iterator" {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "shape" {
|
||||||
|
const testing = std.testing;
|
||||||
|
const alloc = testing.allocator;
|
||||||
|
|
||||||
|
var testdata = try testShaper(alloc);
|
||||||
|
defer testdata.deinit();
|
||||||
|
|
||||||
|
var buf: [32]u8 = undefined;
|
||||||
|
var buf_idx: usize = 0;
|
||||||
|
buf_idx += try std.unicode.utf8Encode(0x1F44D, buf[buf_idx..]); // Thumbs up plain
|
||||||
|
buf_idx += try std.unicode.utf8Encode(0x1F44D, buf[buf_idx..]); // Thumbs up plain
|
||||||
|
buf_idx += try std.unicode.utf8Encode(0x1F3FD, buf[buf_idx..]); // Medium skin tone
|
||||||
|
|
||||||
|
// Make a screen with some data
|
||||||
|
var screen = try terminal.Screen.init(alloc, 3, 10, 0);
|
||||||
|
defer screen.deinit(alloc);
|
||||||
|
screen.testWriteString(buf[0..buf_idx]);
|
||||||
|
|
||||||
|
// Get our run iterator
|
||||||
|
var shaper = testdata.shaper;
|
||||||
|
var it = shaper.runIterator(screen.getRow(.{ .screen = 0 }));
|
||||||
|
var count: usize = 0;
|
||||||
|
while (try it.next(alloc)) |run| {
|
||||||
|
count += 1;
|
||||||
|
try testing.expectEqual(@as(u32, 3), shaper.hb_buf.getLength());
|
||||||
|
shaper.shape(run);
|
||||||
|
}
|
||||||
|
try testing.expectEqual(@as(usize, 1), count);
|
||||||
|
}
|
||||||
|
|
||||||
const TestShaper = struct {
|
const TestShaper = struct {
|
||||||
alloc: Allocator,
|
alloc: Allocator,
|
||||||
shaper: Shaper,
|
shaper: Shaper,
|
||||||
|
Reference in New Issue
Block a user