diff --git a/pkg/macos/foundation/number.zig b/pkg/macos/foundation/number.zig index eec9f076a..88bf9edf6 100644 --- a/pkg/macos/foundation/number.zig +++ b/pkg/macos/foundation/number.zig @@ -15,7 +15,7 @@ pub const Number = opaque { )))) orelse Allocator.Error.OutOfMemory; } - pub fn getValue(self: *Number, comptime t: NumberType, ptr: *t.ValueType()) bool { + pub fn getValue(self: *const Number, comptime t: NumberType, ptr: *t.ValueType()) bool { return c.CFNumberGetValue( @ptrCast(self), @intFromEnum(t), diff --git a/pkg/macos/text/font.zig b/pkg/macos/text/font.zig index 7d8ca243c..c08e8ee14 100644 --- a/pkg/macos/text/font.zig +++ b/pkg/macos/text/font.zig @@ -145,7 +145,7 @@ pub const Font = opaque { ); } - pub fn copyAttribute(self: *Font, comptime attr: text.FontAttribute) attr.Value() { + pub fn copyAttribute(self: *Font, comptime attr: text.FontAttribute) ?attr.Value() { return @ptrFromInt(@intFromPtr(c.CTFontCopyAttribute( @ptrCast(self), @ptrCast(attr.key()), diff --git a/pkg/macos/text/font_descriptor.zig b/pkg/macos/text/font_descriptor.zig index fa4fba1ea..3b4928a6e 100644 --- a/pkg/macos/text/font_descriptor.zig +++ b/pkg/macos/text/font_descriptor.zig @@ -54,7 +54,7 @@ pub const FontDescriptor = opaque { c.CFRelease(self); } - pub fn copyAttribute(self: *const FontDescriptor, comptime attr: FontAttribute) attr.Value() { + pub fn copyAttribute(self: *const FontDescriptor, comptime attr: FontAttribute) ?attr.Value() { return @ptrFromInt(@intFromPtr(c.CTFontDescriptorCopyAttribute( @ptrCast(self), @ptrCast(attr.key()), @@ -153,7 +153,7 @@ pub const FontAttribute = enum { .enabled => *foundation.Number, .downloadable => *anyopaque, // CFBoolean .downloaded => *anyopaque, // CFBoolean - .variation_axes => ?*foundation.Array, + .variation_axes => *foundation.Array, }; } }; diff --git a/src/apprt/embedded.zig b/src/apprt/embedded.zig index d3b2d6f29..0115c0ca5 100644 --- a/src/apprt/embedded.zig +++ b/src/apprt/embedded.zig @@ -1981,7 +1981,8 @@ pub const CAPI = struct { // at 1x but callers of this should be using scaled or apply // scale themselves. const size: f32 = size: { - const num = face.font.copyAttribute(.size); + const num = face.font.copyAttribute(.size) orelse + break :size 12; defer num.release(); var v: f32 = 12; _ = num.getValue(.float, &v); diff --git a/src/font/DeferredFace.zig b/src/font/DeferredFace.zig index 84b5700fe..db9e23623 100644 --- a/src/font/DeferredFace.zig +++ b/src/font/DeferredFace.zig @@ -107,7 +107,8 @@ pub fn familyName(self: DeferredFace, buf: []u8) ![]const u8 { .coretext_harfbuzz, .coretext_noshape, => if (self.ct) |ct| { - const family_name = ct.font.copyAttribute(.family_name); + const family_name = ct.font.copyAttribute(.family_name) orelse + return "unknown"; return family_name.cstringPtr(.utf8) orelse unsupported: { break :unsupported family_name.cstring(buf, .utf8) orelse return error.OutOfMemory; @@ -204,7 +205,8 @@ fn loadCoreTextFreetype( const ct = self.ct.?; // Get the URL for the font so we can get the filepath - const url = ct.font.copyAttribute(.url); + const url = ct.font.copyAttribute(.url) orelse + return error.FontHasNoFile; defer url.release(); // Get the path from the URL @@ -229,10 +231,50 @@ fn loadCoreTextFreetype( // the end for a zero so we set that up here. buf[path_slice.len] = 0; - // TODO: face index 0 is not correct long term and we should switch - // to using CoreText for rendering, too. + // Face index 0 is not always correct. We don't ship this configuration + // in a release build. Users should use the pure CoreText builds. //std.log.warn("path={s}", .{path_slice}); - return 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(); + + // 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; } fn loadWebCanvas( diff --git a/src/font/discovery.zig b/src/font/discovery.zig index de17a3fb6..6f43fcb7d 100644 --- a/src/font/discovery.zig +++ b/src/font/discovery.zig @@ -613,7 +613,7 @@ pub const CoreText = struct { // Get our symbolic traits for the descriptor so we can compare // boolean attributes like bold, monospace, etc. const symbolic_traits: macos.text.FontSymbolicTraits = traits: { - const traits = ct_desc.copyAttribute(.traits); + const traits = ct_desc.copyAttribute(.traits) orelse break :traits .{}; defer traits.release(); const key = macos.text.FontTraitKey.symbolic.key(); @@ -626,7 +626,8 @@ pub const CoreText = struct { score_acc.monospace = symbolic_traits.monospace; score_acc.style = style: { - const style = ct_desc.copyAttribute(.style_name); + const style = ct_desc.copyAttribute(.style_name) orelse + break :style .unmatched; defer style.release(); // If we have a specific desired style, attempt to search for that.