font/coretext: coretext shaper is cleaner

This commit is contained in:
Mitchell Hashimoto
2023-12-11 12:56:30 -08:00
parent 62a5fe0236
commit fcd9de0311

View File

@ -22,15 +22,17 @@ pub const Shaper = struct {
alloc: Allocator, alloc: Allocator,
/// The string used for shaping the current run. /// The string used for shaping the current run.
str: ?*macos.foundation.MutableString = null, codepoints: CodepointList = .{},
codepoints: ClusterBuf = .{},
clusters: ClusterBuf = .{},
/// The shared memory used for shaping results. /// The shared memory used for shaping results.
cell_buf: CellBuf, cell_buf: CellBuf,
const CellBuf = std.ArrayListUnmanaged(font.shape.Cell); const CellBuf = std.ArrayListUnmanaged(font.shape.Cell);
const ClusterBuf = std.ArrayListUnmanaged(u32); const CodepointList = std.ArrayListUnmanaged(Codepoint);
const Codepoint = struct {
codepoint: u32,
cluster: u32,
};
// These features are hardcoded to always be on by default. Users // These features are hardcoded to always be on by default. Users
// can turn them off by setting the features to "-liga" for example. // can turn them off by setting the features to "-liga" for example.
@ -51,8 +53,6 @@ pub const Shaper = struct {
pub fn deinit(self: *Shaper) void { pub fn deinit(self: *Shaper) void {
self.cell_buf.deinit(self.alloc); self.cell_buf.deinit(self.alloc);
self.codepoints.deinit(self.alloc); self.codepoints.deinit(self.alloc);
self.clusters.deinit(self.alloc);
if (self.str) |str| str.release();
} }
pub fn runIterator( pub fn runIterator(
@ -79,10 +79,10 @@ pub const Shaper = struct {
if (run.font_index.special() != null) { if (run.font_index.special() != null) {
self.cell_buf.clearRetainingCapacity(); self.cell_buf.clearRetainingCapacity();
try self.cell_buf.ensureTotalCapacity(self.alloc, self.codepoints.items.len); try self.cell_buf.ensureTotalCapacity(self.alloc, self.codepoints.items.len);
for (self.codepoints.items, self.clusters.items) |cp, x| { for (self.codepoints.items) |entry| {
self.cell_buf.appendAssumeCapacity(.{ self.cell_buf.appendAssumeCapacity(.{
.x = @intCast(x), .x = @intCast(entry.cluster),
.glyph_index = @intCast(cp), .glyph_index = @intCast(entry.codepoint),
}); });
} }
@ -94,6 +94,26 @@ pub const Shaper = struct {
defer arena.deinit(); defer arena.deinit();
const alloc = arena.allocator(); const alloc = arena.allocator();
// Build up our string contents
const str = str: {
const str = try macos.foundation.MutableString.create(0);
errdefer str.release();
for (self.codepoints.items) |entry| {
var unichars: [2]u16 = undefined;
const pair = macos.foundation.stringGetSurrogatePairForLongCharacter(
entry.codepoint,
&unichars,
);
const len: usize = if (pair) 2 else 1;
str.appendCharacters(unichars[0..len]);
// log.warn("append codepoint={} unichar_len={}", .{ cp, len });
}
break :str str;
};
defer str.release();
// Get our font and use that get the attributes to set for the // Get our font and use that get the attributes to set for the
// attributed string so the whole string uses the same font. // attributed string so the whole string uses the same font.
const attr_dict = dict: { const attr_dict = dict: {
@ -106,7 +126,7 @@ pub const Shaper = struct {
// Create an attributed string from our string // Create an attributed string from our string
const attr_str = try macos.foundation.AttributedString.create( const attr_str = try macos.foundation.AttributedString.create(
self.str.?.string(), str.string(),
attr_dict, attr_dict,
); );
defer attr_str.release(); defer attr_str.release();
@ -130,7 +150,7 @@ pub const Shaper = struct {
self.cell_buf.clearRetainingCapacity(); self.cell_buf.clearRetainingCapacity();
try self.cell_buf.ensureTotalCapacity(self.alloc, glyphs.len); try self.cell_buf.ensureTotalCapacity(self.alloc, glyphs.len);
for (glyphs, positions, advances, indices) |glyph, pos, advance, index| { for (glyphs, positions, advances, indices) |glyph, pos, advance, index| {
const cluster = self.clusters.items[index]; const cluster = self.codepoints.items[index].cluster;
self.cell_buf.appendAssumeCapacity(.{ self.cell_buf.appendAssumeCapacity(.{
.x = @intCast(cluster), .x = @intCast(cluster),
.glyph_index = glyph, .glyph_index = glyph,
@ -153,20 +173,14 @@ pub const Shaper = struct {
shaper: *Shaper, shaper: *Shaper,
pub fn prepare(self: *RunIteratorHook) !void { pub fn prepare(self: *RunIteratorHook) !void {
if (self.shaper.str) |str| str.release();
self.shaper.str = try macos.foundation.MutableString.create(0);
self.shaper.codepoints.clearRetainingCapacity(); self.shaper.codepoints.clearRetainingCapacity();
self.shaper.clusters.clearRetainingCapacity();
} }
pub fn addCodepoint(self: RunIteratorHook, cp: u32, cluster: u32) !void { pub fn addCodepoint(self: RunIteratorHook, cp: u32, cluster: u32) !void {
var unichars: [2]u16 = undefined; try self.shaper.codepoints.append(self.shaper.alloc, .{
const pair = macos.foundation.stringGetSurrogatePairForLongCharacter(cp, &unichars); .codepoint = cp,
const len: usize = if (pair) 2 else 1; .cluster = cluster,
// log.warn("append codepoint={} unichar_len={}", .{ cp, len }); });
self.shaper.str.?.appendCharacters(unichars[0..len]);
try self.shaper.codepoints.append(self.shaper.alloc, cp);
try self.shaper.clusters.appendNTimes(self.shaper.alloc, cluster, len);
} }
pub fn finalize(self: RunIteratorHook) !void { pub fn finalize(self: RunIteratorHook) !void {
@ -253,7 +267,7 @@ test "run iterator: empty cells with background set" {
// The run should have length 3 because of the two background // The run should have length 3 because of the two background
// cells. // cells.
try testing.expectEqual(@as(usize, 3), shaper.str.?.string().getLength()); try testing.expectEqual(@as(usize, 3), shaper.codepoints.items.len);
const cells = try shaper.shape(run); const cells = try shaper.shape(run);
try testing.expectEqual(@as(usize, 3), cells.len); try testing.expectEqual(@as(usize, 3), cells.len);
} }