Merge pull request #2146 from ghostty-org/ct-ft

font: coretext_freetype backend supports font variations
This commit is contained in:
Mitchell Hashimoto
2024-08-24 21:03:28 -07:00
committed by GitHub
6 changed files with 56 additions and 12 deletions

View File

@ -15,7 +15,7 @@ pub const Number = opaque {
)))) orelse Allocator.Error.OutOfMemory; )))) 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( return c.CFNumberGetValue(
@ptrCast(self), @ptrCast(self),
@intFromEnum(t), @intFromEnum(t),

View File

@ -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( return @ptrFromInt(@intFromPtr(c.CTFontCopyAttribute(
@ptrCast(self), @ptrCast(self),
@ptrCast(attr.key()), @ptrCast(attr.key()),

View File

@ -54,7 +54,7 @@ pub const FontDescriptor = opaque {
c.CFRelease(self); 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( return @ptrFromInt(@intFromPtr(c.CTFontDescriptorCopyAttribute(
@ptrCast(self), @ptrCast(self),
@ptrCast(attr.key()), @ptrCast(attr.key()),
@ -153,7 +153,7 @@ pub const FontAttribute = enum {
.enabled => *foundation.Number, .enabled => *foundation.Number,
.downloadable => *anyopaque, // CFBoolean .downloadable => *anyopaque, // CFBoolean
.downloaded => *anyopaque, // CFBoolean .downloaded => *anyopaque, // CFBoolean
.variation_axes => ?*foundation.Array, .variation_axes => *foundation.Array,
}; };
} }
}; };

View File

@ -1981,7 +1981,8 @@ pub const CAPI = struct {
// at 1x but callers of this should be using scaled or apply // at 1x but callers of this should be using scaled or apply
// scale themselves. // scale themselves.
const size: f32 = size: { const size: f32 = size: {
const num = face.font.copyAttribute(.size); const num = face.font.copyAttribute(.size) orelse
break :size 12;
defer num.release(); defer num.release();
var v: f32 = 12; var v: f32 = 12;
_ = num.getValue(.float, &v); _ = num.getValue(.float, &v);

View File

@ -107,7 +107,8 @@ pub fn familyName(self: DeferredFace, buf: []u8) ![]const u8 {
.coretext_harfbuzz, .coretext_harfbuzz,
.coretext_noshape, .coretext_noshape,
=> if (self.ct) |ct| { => 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: { return family_name.cstringPtr(.utf8) orelse unsupported: {
break :unsupported family_name.cstring(buf, .utf8) orelse break :unsupported family_name.cstring(buf, .utf8) orelse
return error.OutOfMemory; return error.OutOfMemory;
@ -204,7 +205,8 @@ fn loadCoreTextFreetype(
const ct = self.ct.?; const ct = self.ct.?;
// Get the URL for the font so we can get the filepath // 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(); defer url.release();
// Get the path from the URL // Get the path from the URL
@ -229,10 +231,50 @@ fn loadCoreTextFreetype(
// the end for a zero so we set that up here. // the end for a zero so we set that up here.
buf[path_slice.len] = 0; buf[path_slice.len] = 0;
// TODO: face index 0 is not correct long term and we should switch // Face index 0 is not always correct. We don't ship this configuration
// to using CoreText for rendering, too. // in a release build. Users should use the pure CoreText builds.
//std.log.warn("path={s}", .{path_slice}); //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( fn loadWebCanvas(

View File

@ -613,7 +613,7 @@ pub const CoreText = struct {
// Get our symbolic traits for the descriptor so we can compare // Get our symbolic traits for the descriptor so we can compare
// boolean attributes like bold, monospace, etc. // boolean attributes like bold, monospace, etc.
const symbolic_traits: macos.text.FontSymbolicTraits = traits: { 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(); defer traits.release();
const key = macos.text.FontTraitKey.symbolic.key(); const key = macos.text.FontTraitKey.symbolic.key();
@ -626,7 +626,8 @@ pub const CoreText = struct {
score_acc.monospace = symbolic_traits.monospace; score_acc.monospace = symbolic_traits.monospace;
score_acc.style = style: { 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(); defer style.release();
// If we have a specific desired style, attempt to search for that. // If we have a specific desired style, attempt to search for that.