font/coretext: tiny shaper improvements

Reduce potential allocation while processing glyphs by ensuring capacity
in the buffer ahead of time and also using CTRunGet*Ptr functions first
and only allocating for those if that didn't work (it should almost
always work in practice.)
pull/9002/head
Qwerasd 2025-10-02 14:06:58 -06:00
parent 9c8d2e577e
commit d6063428bd
2 changed files with 41 additions and 12 deletions

View File

@ -15,10 +15,13 @@ pub const Run = opaque {
return @intCast(c.CTRunGetGlyphCount(@ptrCast(self)));
}
pub fn getGlyphsPtr(self: *Run) []const graphics.Glyph {
pub fn getGlyphsPtr(self: *Run) ?[]const graphics.Glyph {
const len = self.getGlyphCount();
if (len == 0) return &.{};
const ptr = c.CTRunGetGlyphsPtr(@ptrCast(self)) orelse &.{};
const ptr: [*c]const graphics.Glyph = @ptrCast(
c.CTRunGetGlyphsPtr(@ptrCast(self)),
);
if (ptr == null) return null;
return ptr[0..len];
}
@ -34,10 +37,13 @@ pub const Run = opaque {
return ptr;
}
pub fn getPositionsPtr(self: *Run) []const graphics.Point {
pub fn getPositionsPtr(self: *Run) ?[]const graphics.Point {
const len = self.getGlyphCount();
if (len == 0) return &.{};
const ptr = c.CTRunGetPositionsPtr(@ptrCast(self)) orelse &.{};
const ptr: [*c]const graphics.Point = @ptrCast(
c.CTRunGetPositionsPtr(@ptrCast(self)),
);
if (ptr == null) return null;
return ptr[0..len];
}
@ -53,10 +59,13 @@ pub const Run = opaque {
return ptr;
}
pub fn getAdvancesPtr(self: *Run) []const graphics.Size {
pub fn getAdvancesPtr(self: *Run) ?[]const graphics.Size {
const len = self.getGlyphCount();
if (len == 0) return &.{};
const ptr = c.CTRunGetAdvancesPtr(@ptrCast(self)) orelse &.{};
const ptr: [*c]const graphics.Size = @ptrCast(
c.CTRunGetAdvancesPtr(@ptrCast(self)),
);
if (ptr == null) return null;
return ptr[0..len];
}
@ -72,10 +81,13 @@ pub const Run = opaque {
return ptr;
}
pub fn getStringIndicesPtr(self: *Run) []const usize {
pub fn getStringIndicesPtr(self: *Run) ?[]const usize {
const len = self.getGlyphCount();
if (len == 0) return &.{};
const ptr = c.CTRunGetStringIndicesPtr(@ptrCast(self)) orelse &.{};
const ptr: [*c]const usize = @ptrCast(
c.CTRunGetStringIndicesPtr(@ptrCast(self)),
);
if (ptr == null) return null;
return ptr[0..len];
}
@ -90,4 +102,16 @@ pub const Run = opaque {
);
return ptr;
}
pub fn getStatus(self: *Run) Status {
return @bitCast(c.CTRunGetStatus(@ptrCast(self)));
}
};
/// https://developer.apple.com/documentation/coretext/ctrunstatus?language=objc
pub const Status = packed struct(u32) {
right_to_left: bool,
non_monotonic: bool,
has_non_identity_matrix: bool,
_pad: u29 = 0,
};

View File

@ -369,7 +369,12 @@ pub const Shaper = struct {
x: f64 = 0,
y: f64 = 0,
} = .{};
// Clear our cell buf and make sure we have enough room for the whole
// line of glyphs, so that we can just assume capacity when appending
// instead of maybe allocating.
self.cell_buf.clearRetainingCapacity();
try self.cell_buf.ensureTotalCapacity(self.alloc, line.getGlyphCount());
// CoreText may generate multiple runs even though our input to
// CoreText is already split into runs by our own run iterator.
@ -381,9 +386,9 @@ pub const Shaper = struct {
const ctrun = runs.getValueAtIndex(macos.text.Run, i);
// Get our glyphs and positions
const glyphs = try ctrun.getGlyphs(alloc);
const advances = try ctrun.getAdvances(alloc);
const indices = try ctrun.getStringIndices(alloc);
const glyphs = ctrun.getGlyphsPtr() orelse try ctrun.getGlyphs(alloc);
const advances = ctrun.getAdvancesPtr() orelse try ctrun.getAdvances(alloc);
const indices = ctrun.getStringIndicesPtr() orelse try ctrun.getStringIndices(alloc);
assert(glyphs.len == advances.len);
assert(glyphs.len == indices.len);
@ -406,7 +411,7 @@ pub const Shaper = struct {
cell_offset = .{ .cluster = cluster };
}
try self.cell_buf.append(self.alloc, .{
self.cell_buf.appendAssumeCapacity(.{
.x = @intCast(cluster),
.x_offset = @intFromFloat(@round(cell_offset.x)),
.y_offset = @intFromFloat(@round(cell_offset.y)),