From 39376feae02b4bd8931c7eb1046bbffd9ea56dc6 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 29 Aug 2022 20:15:29 -0700 Subject: [PATCH] font: shape API, calls shape and outputs some debug --- pkg/harfbuzz/buffer.zig | 5 +++- src/font/Shaper.zig | 58 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 61 insertions(+), 2 deletions(-) diff --git a/pkg/harfbuzz/buffer.zig b/pkg/harfbuzz/buffer.zig index 5b494579c..b48fc985b 100644 --- a/pkg/harfbuzz/buffer.zig +++ b/pkg/harfbuzz/buffer.zig @@ -192,7 +192,10 @@ pub const Buffer = struct { /// long as buffer contents are not modified. pub fn getGlyphInfos(self: Buffer) []GlyphInfo { 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]; } diff --git a/src/font/Shaper.zig b/src/font/Shaper.zig index 9ef3b56fc..f0bd78182 100644 --- a/src/font/Shaper.zig +++ b/src/font/Shaper.zig @@ -2,6 +2,7 @@ const Shaper = @This(); const std = @import("std"); +const assert = std.debug.assert; const Allocator = std.mem.Allocator; const harfbuzz = @import("harfbuzz"); const Atlas = @import("../Atlas.zig"); @@ -12,6 +13,8 @@ const Library = @import("main.zig").Library; const Style = @import("main.zig").Style; const terminal = @import("../terminal/main.zig"); +const log = std.log.scoped(.font_shaper); + /// The font group to use under the covers group: *GroupCache, @@ -37,6 +40,28 @@ pub fn runIterator(self: *Shaper, row: terminal.Screen.Row) RunIterator { 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 /// until the next run is created. pub const TextRun = struct { @@ -74,13 +99,14 @@ pub const RunIterator = struct { // Determine the font for this cell const font_idx_opt = try self.shaper.group.indexForCodepoint(alloc, style, cell.char); const font_idx = font_idx_opt.?; + //log.warn("char={x} idx={}", .{ cell.char, font_idx }); if (j == self.i) current_font = font_idx; // If our fonts are not equal, then we're done with our run. if (font_idx.int() != current_font.int()) break; // 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 @@ -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 { alloc: Allocator, shaper: Shaper,