coretext: set variations on deferred face load

This commit makes CoreText behave a lot like FreeType where we set the
variation axes on the deferred face load. This fixes a bug where the
`slnt` variation axis could not be set with CoreText with the Monaspace
Argon Variable font.

This was a bug found in Discord. Specifically, with the Monaspace Argon
Variable font, the `slnt` variation axis could not be set with CoreText.
I'm not sure _exactly_ what causes this but I suspect it has to do with
the `slnt` axis being a negative value. I'm not sure if this is a bug
with CoreText or not.

What was happening was that with CoreText, we set the variation axes
during discovery and expect them to be preserved in the resulting
discovered faces. That seems to be true with the `wght` axis but not the
`slnt` axis for whatever reason.
This commit is contained in:
Mitchell Hashimoto
2024-11-05 16:13:53 -08:00
parent fc7ff2a7ef
commit e08eeb2b2a
3 changed files with 22 additions and 40 deletions

View File

@ -57,6 +57,11 @@ pub const CoreText = struct {
/// The initialized font /// The initialized font
font: *macos.text.Font, font: *macos.text.Font,
/// Variations to apply to this font. We apply the variations to the
/// search descriptor but sometimes when the font collection is
/// made the variation axes are reset so we have to reapply them.
variations: []const font.face.Variation,
pub fn deinit(self: *CoreText) void { pub fn deinit(self: *CoreText) void {
self.font.release(); self.font.release();
self.* = undefined; self.* = undefined;
@ -194,7 +199,10 @@ fn loadCoreText(
) !Face { ) !Face {
_ = lib; _ = lib;
const ct = self.ct.?; const ct = self.ct.?;
return try Face.initFontCopy(ct.font, opts); var face = try Face.initFontCopy(ct.font, opts);
errdefer face.deinit();
try face.setVariations(ct.variations, opts);
return face;
} }
fn loadCoreTextFreetype( fn loadCoreTextFreetype(
@ -236,43 +244,7 @@ fn loadCoreTextFreetype(
//std.log.warn("path={s}", .{path_slice}); //std.log.warn("path={s}", .{path_slice});
var face = try Face.initFile(lib, buf[0..path_slice.len :0], 0, opts); var face = try Face.initFile(lib, buf[0..path_slice.len :0], 0, opts);
errdefer face.deinit(); errdefer face.deinit();
try face.setVariations(ct.variations, opts);
// If our ct font has variations, apply them to the face.
if (ct.font.copyAttribute(.variation)) |variations| vars: {
defer variations.release();
if (variations.getCount() == 0) break :vars;
// This configuration is just used for testing so we don't want to
// have to pass a full allocator through so use the stack. We
// shouldn't have a lot of variations and if we do we should use
// another mechanism.
//
// On macOS the default stack size for a thread is 512KB and the main
// thread gets megabytes so 16KB is a safe stack allocation.
var data: [1024 * 16]u8 = undefined;
var fba = std.heap.FixedBufferAllocator.init(&data);
const alloc = fba.allocator();
var face_vars = std.ArrayList(font.face.Variation).init(alloc);
const kav = try variations.getKeysAndValues(alloc);
for (kav.keys, kav.values) |key, value| {
const num: *const macos.foundation.Number = @ptrCast(key.?);
const val: *const macos.foundation.Number = @ptrCast(value.?);
var num_i32: i32 = undefined;
if (!num.getValue(.sint32, &num_i32)) continue;
var val_f64: f64 = undefined;
if (!val.getValue(.float64, &val_f64)) continue;
try face_vars.append(.{
.id = @bitCast(num_i32),
.value = val_f64,
});
}
try face.setVariations(face_vars.items, opts);
}
return face; return face;
} }

View File

@ -79,7 +79,7 @@ pub const Descriptor = struct {
// This is not correct, but we don't currently depend on the // This is not correct, but we don't currently depend on the
// hash value being different based on decimal values of variations. // hash value being different based on decimal values of variations.
autoHash(hasher, @as(u64, @intFromFloat(variation.value))); autoHash(hasher, @as(i64, @intFromFloat(variation.value)));
} }
} }
@ -384,6 +384,7 @@ pub const CoreText = struct {
return DiscoverIterator{ return DiscoverIterator{
.alloc = alloc, .alloc = alloc,
.list = zig_list, .list = zig_list,
.variations = desc.variations,
.i = 0, .i = 0,
}; };
} }
@ -420,6 +421,7 @@ pub const CoreText = struct {
return DiscoverIterator{ return DiscoverIterator{
.alloc = alloc, .alloc = alloc,
.list = list, .list = list,
.variations = desc.variations,
.i = 0, .i = 0,
}; };
} }
@ -443,6 +445,7 @@ pub const CoreText = struct {
return DiscoverIterator{ return DiscoverIterator{
.alloc = alloc, .alloc = alloc,
.list = list, .list = list,
.variations = desc.variations,
.i = 0, .i = 0,
}; };
} }
@ -721,6 +724,7 @@ pub const CoreText = struct {
pub const DiscoverIterator = struct { pub const DiscoverIterator = struct {
alloc: Allocator, alloc: Allocator,
list: []const *macos.text.FontDescriptor, list: []const *macos.text.FontDescriptor,
variations: []const Variation,
i: usize, i: usize,
pub fn deinit(self: *DiscoverIterator) void { pub fn deinit(self: *DiscoverIterator) void {
@ -756,7 +760,10 @@ pub const CoreText = struct {
defer self.i += 1; defer self.i += 1;
return DeferredFace{ return DeferredFace{
.ct = .{ .font = font }, .ct = .{
.font = font,
.variations = self.variations,
},
}; };
} }
}; };

View File

@ -229,6 +229,9 @@ pub const Face = struct {
vs: []const font.face.Variation, vs: []const font.face.Variation,
opts: font.face.Options, opts: font.face.Options,
) !void { ) !void {
// If we have no variations, we don't need to do anything.
if (vs.len == 0) return;
// Create a new font descriptor with all the variations set. // Create a new font descriptor with all the variations set.
var desc = self.font.copyDescriptor(); var desc = self.font.copyDescriptor();
defer desc.release(); defer desc.release();