From ab4491a45d618c1dbf29df2fc2afd105107a8a0e Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 30 Sep 2022 14:03:37 -0700 Subject: [PATCH 01/15] pkg/macos: start core foundation bindings --- build.zig | 2 ++ pkg/macos/build.zig | 24 +++++++++++++++ pkg/macos/foundation.zig | 6 ++++ pkg/macos/foundation/string.zig | 54 +++++++++++++++++++++++++++++++++ pkg/macos/foundation/type.zig | 1 + pkg/macos/main.zig | 5 +++ 6 files changed, 92 insertions(+) create mode 100644 pkg/macos/build.zig create mode 100644 pkg/macos/foundation.zig create mode 100644 pkg/macos/foundation/string.zig create mode 100644 pkg/macos/foundation/type.zig create mode 100644 pkg/macos/main.zig diff --git a/build.zig b/build.zig index fbd4a9a76..c99995eb8 100644 --- a/build.zig +++ b/build.zig @@ -9,6 +9,7 @@ const harfbuzz = @import("pkg/harfbuzz/build.zig"); const libxml2 = @import("vendor/zig-libxml2/libxml2.zig"); const libuv = @import("pkg/libuv/build.zig"); const libpng = @import("pkg/libpng/build.zig"); +const macos = @import("pkg/macos/build.zig"); const utf8proc = @import("pkg/utf8proc/build.zig"); const zlib = @import("pkg/zlib/build.zig"); const tracylib = @import("pkg/tracy/build.zig"); @@ -183,6 +184,7 @@ fn addDeps( step.addPackage(glfw.pkg); step.addPackage(libuv.pkg); step.addPackage(utf8proc.pkg); + if (step.target.isDarwin()) step.addPackage(macos.pkg); // We always statically compile glad step.addIncludePath("vendor/glad/include/"); diff --git a/pkg/macos/build.zig b/pkg/macos/build.zig new file mode 100644 index 000000000..9f1eb1796 --- /dev/null +++ b/pkg/macos/build.zig @@ -0,0 +1,24 @@ +const std = @import("std"); +const builtin = @import("builtin"); + +pub const pkg = std.build.Pkg{ + .name = "macos", + .source = .{ .path = thisDir() ++ "/main.zig" }, +}; + +fn thisDir() []const u8 { + return std.fs.path.dirname(@src().file) orelse "."; +} + +pub const Options = struct {}; + +pub fn link( + b: *std.build.Builder, + step: *std.build.LibExeObjStep, + opt: Options, +) !*std.build.LibExeObjStep { + _ = opt; + const lib = b.addStaticLibrary("macos", null); + step.linkFramework("CoreFoundation"); + return lib; +} diff --git a/pkg/macos/foundation.zig b/pkg/macos/foundation.zig new file mode 100644 index 000000000..f2abe233a --- /dev/null +++ b/pkg/macos/foundation.zig @@ -0,0 +1,6 @@ +pub usingnamespace @import("foundation/string.zig"); +pub usingnamespace @import("foundation/type.zig"); + +test { + @import("std").testing.refAllDecls(@This()); +} diff --git a/pkg/macos/foundation/string.zig b/pkg/macos/foundation/string.zig new file mode 100644 index 000000000..60feb19b2 --- /dev/null +++ b/pkg/macos/foundation/string.zig @@ -0,0 +1,54 @@ +const std = @import("std"); +const Allocator = std.mem.Allocator; +const cftype = @import("type.zig"); + +pub const String = opaque { + pub fn createWithBytes( + bs: []const u8, + encoding: StringEncoding, + external: bool, + ) Allocator.Error!*String { + return CFStringCreateWithBytes( + null, + bs.ptr, + bs.len, + @enumToInt(encoding), + external, + ) orelse Allocator.Error.OutOfMemory; + } + + pub fn release(self: *String) void { + cftype.CFRelease(self); + } + + pub extern "c" fn CFStringCreateWithBytes( + allocator: ?*anyopaque, + bytes: [*]const u8, + numBytes: usize, + encooding: u32, + is_external: bool, + ) ?*String; +}; + +/// https://developer.apple.com/documentation/corefoundation/cfstringencoding?language=objc +pub const StringEncoding = enum(u32) { + invalid = 0xffffffff, + mac_roman = 0, + windows_latin1 = 0x0500, + iso_latin1 = 0x0201, + nextstep_latin = 0x0B01, + ascii = 0x0600, + unicode = 0x0100, + utf8 = 0x08000100, + non_lossy_ascii = 0x0BFF, + utf16_be = 0x10000100, + utf16_le = 0x14000100, + utf32 = 0x0c000100, + utf32_be = 0x18000100, + utf32_le = 0x1c000100, +}; + +test "string" { + const str = try String.createWithBytes("hello world", .ascii, false); + defer str.release(); +} diff --git a/pkg/macos/foundation/type.zig b/pkg/macos/foundation/type.zig new file mode 100644 index 000000000..e3ee150f2 --- /dev/null +++ b/pkg/macos/foundation/type.zig @@ -0,0 +1 @@ +pub extern "c" fn CFRelease(*anyopaque) void; diff --git a/pkg/macos/main.zig b/pkg/macos/main.zig new file mode 100644 index 000000000..e651c0103 --- /dev/null +++ b/pkg/macos/main.zig @@ -0,0 +1,5 @@ +pub const foundation = @import("foundation.zig"); + +test { + @import("std").testing.refAllDecls(@This()); +} From b0d993324982706c284a412649721a6ec6b239a0 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 30 Sep 2022 14:12:15 -0700 Subject: [PATCH 02/15] macos: add another string API to test it really works --- pkg/macos/foundation/string.zig | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/pkg/macos/foundation/string.zig b/pkg/macos/foundation/string.zig index 60feb19b2..a0f0380c3 100644 --- a/pkg/macos/foundation/string.zig +++ b/pkg/macos/foundation/string.zig @@ -21,6 +21,10 @@ pub const String = opaque { cftype.CFRelease(self); } + pub fn hasPrefix(self: *String, prefix: *String) bool { + return CFStringHasPrefix(self, prefix) == 1; + } + pub extern "c" fn CFStringCreateWithBytes( allocator: ?*anyopaque, bytes: [*]const u8, @@ -28,6 +32,7 @@ pub const String = opaque { encooding: u32, is_external: bool, ) ?*String; + pub extern "c" fn CFStringHasPrefix(*String, *String) u8; }; /// https://developer.apple.com/documentation/corefoundation/cfstringencoding?language=objc @@ -49,6 +54,13 @@ pub const StringEncoding = enum(u32) { }; test "string" { + const testing = std.testing; + const str = try String.createWithBytes("hello world", .ascii, false); defer str.release(); + + const prefix = try String.createWithBytes("hello", .ascii, false); + defer prefix.release(); + + try testing.expect(str.hasPrefix(prefix)); } From f9e11273174756a474ef08808dbd0948c0942cff Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 30 Sep 2022 14:48:06 -0700 Subject: [PATCH 03/15] pkg/macos: add CoreText --- build.zig | 7 +++++- pkg/macos/build.zig | 1 + pkg/macos/foundation.zig | 1 + pkg/macos/foundation/dictionary.zig | 37 +++++++++++++++++++++++++++++ pkg/macos/main.zig | 1 + pkg/macos/text.zig | 5 ++++ pkg/macos/text/font_collection.zig | 22 +++++++++++++++++ 7 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 pkg/macos/foundation/dictionary.zig create mode 100644 pkg/macos/text.zig create mode 100644 pkg/macos/text/font_collection.zig diff --git a/build.zig b/build.zig index c99995eb8..f227ee657 100644 --- a/build.zig +++ b/build.zig @@ -184,7 +184,12 @@ fn addDeps( step.addPackage(glfw.pkg); step.addPackage(libuv.pkg); step.addPackage(utf8proc.pkg); - if (step.target.isDarwin()) step.addPackage(macos.pkg); + + // Mac Stuff + if (step.target.isDarwin()) { + step.addPackage(macos.pkg); + _ = try macos.link(b, step, .{}); + } // We always statically compile glad step.addIncludePath("vendor/glad/include/"); diff --git a/pkg/macos/build.zig b/pkg/macos/build.zig index 9f1eb1796..78df21ee8 100644 --- a/pkg/macos/build.zig +++ b/pkg/macos/build.zig @@ -20,5 +20,6 @@ pub fn link( _ = opt; const lib = b.addStaticLibrary("macos", null); step.linkFramework("CoreFoundation"); + step.linkFramework("CoreText"); return lib; } diff --git a/pkg/macos/foundation.zig b/pkg/macos/foundation.zig index f2abe233a..9f23a611b 100644 --- a/pkg/macos/foundation.zig +++ b/pkg/macos/foundation.zig @@ -1,3 +1,4 @@ +pub usingnamespace @import("foundation/dictionary.zig"); pub usingnamespace @import("foundation/string.zig"); pub usingnamespace @import("foundation/type.zig"); diff --git a/pkg/macos/foundation/dictionary.zig b/pkg/macos/foundation/dictionary.zig new file mode 100644 index 000000000..495accd50 --- /dev/null +++ b/pkg/macos/foundation/dictionary.zig @@ -0,0 +1,37 @@ +const std = @import("std"); +const Allocator = std.mem.Allocator; +const cftype = @import("type.zig"); + +pub const Dictionary = opaque { + pub fn create() Allocator.Error!*Dictionary { + return CFDictionaryCreate( + null, + null, + null, + 0, + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks, + ) orelse Allocator.Error.OutOfMemory; + } + + pub fn release(self: *Dictionary) void { + cftype.CFRelease(self); + } + + pub extern "c" fn CFDictionaryCreate( + allocator: ?*anyopaque, + keys: ?[*]*const anyopaque, + values: ?[*]*const anyopaque, + num_values: usize, + key_callbacks: *const anyopaque, + value_callbacks: *const anyopaque, + ) ?*Dictionary; + + extern "c" var kCFTypeDictionaryKeyCallBacks: anyopaque; + extern "c" var kCFTypeDictionaryValueCallBacks: anyopaque; +}; + +test "dictionary" { + const dict = try Dictionary.create(); + defer dict.release(); +} diff --git a/pkg/macos/main.zig b/pkg/macos/main.zig index e651c0103..d938a633d 100644 --- a/pkg/macos/main.zig +++ b/pkg/macos/main.zig @@ -1,4 +1,5 @@ pub const foundation = @import("foundation.zig"); +pub const text = @import("text.zig"); test { @import("std").testing.refAllDecls(@This()); diff --git a/pkg/macos/text.zig b/pkg/macos/text.zig new file mode 100644 index 000000000..7455fde5e --- /dev/null +++ b/pkg/macos/text.zig @@ -0,0 +1,5 @@ +pub usingnamespace @import("text/font_collection.zig"); + +test { + @import("std").testing.refAllDecls(@This()); +} diff --git a/pkg/macos/text/font_collection.zig b/pkg/macos/text/font_collection.zig new file mode 100644 index 000000000..7de026f29 --- /dev/null +++ b/pkg/macos/text/font_collection.zig @@ -0,0 +1,22 @@ +const std = @import("std"); +const Allocator = std.mem.Allocator; +const foundation = @import("../foundation.zig"); + +pub const FontCollection = opaque { + pub fn createFromAvailableFonts() Allocator.Error!*FontCollection { + return CTFontCollectionCreateFromAvailableFonts(null) orelse Allocator.Error.OutOfMemory; + } + + pub fn release(self: *FontCollection) void { + foundation.CFRelease(self); + } + + pub extern "c" fn CTFontCollectionCreateFromAvailableFonts( + options: ?*foundation.Dictionary, + ) ?*FontCollection; +}; + +test "collection" { + const v = try FontCollection.createFromAvailableFonts(); + defer v.release(); +} From 93f2a99b64477437af160317b990b71e4133c599 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 30 Sep 2022 15:10:11 -0700 Subject: [PATCH 04/15] macos: CFArray --- pkg/macos/foundation.zig | 1 + pkg/macos/foundation/array.zig | 52 ++++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+) create mode 100644 pkg/macos/foundation/array.zig diff --git a/pkg/macos/foundation.zig b/pkg/macos/foundation.zig index 9f23a611b..6dff4b8b5 100644 --- a/pkg/macos/foundation.zig +++ b/pkg/macos/foundation.zig @@ -1,3 +1,4 @@ +pub usingnamespace @import("foundation/array.zig"); pub usingnamespace @import("foundation/dictionary.zig"); pub usingnamespace @import("foundation/string.zig"); pub usingnamespace @import("foundation/type.zig"); diff --git a/pkg/macos/foundation/array.zig b/pkg/macos/foundation/array.zig new file mode 100644 index 000000000..927269627 --- /dev/null +++ b/pkg/macos/foundation/array.zig @@ -0,0 +1,52 @@ +const std = @import("std"); +const Allocator = std.mem.Allocator; +const cftype = @import("type.zig"); + +pub const Array = opaque { + pub fn create(comptime T: type, values: []*const T) Allocator.Error!*Array { + return CFArrayCreate( + null, + @ptrCast([*]*const anyopaque, values.ptr), + @intCast(usize, values.len), + null, + ) orelse error.OutOfMemory; + } + + pub fn release(self: *Array) void { + cftype.CFRelease(self); + } + + pub fn getCount(self: *Array) usize { + return CFArrayGetCount(self); + } + + pub fn getValueAtIndex(self: *Array, comptime T: type, idx: usize) *const T { + return @ptrCast(*const T, CFArrayGetValueAtIndex(self, idx)); + } + + pub extern "c" fn CFArrayCreate( + allocator: ?*anyopaque, + values: [*]*const anyopaque, + num_values: usize, + callbacks: ?*const anyopaque, + ) ?*Array; + pub extern "c" fn CFArrayGetCount(*Array) usize; + pub extern "c" fn CFArrayGetValueAtIndex(*Array, usize) *const anyopaque; + extern "c" var kCFTypeArrayCallBacks: anyopaque; +}; + +test "array" { + const testing = std.testing; + + const str = "hello"; + var values = [_]*const u8{ &str[0], &str[1] }; + const arr = try Array.create(u8, &values); + defer arr.release(); + + try testing.expectEqual(@as(usize, 2), arr.getCount()); + + { + const ch = arr.getValueAtIndex(u8, 0); + try testing.expectEqual(@as(u8, 'h'), ch.*); + } +} From d79c8fab39e735c9cc2563a37f44af374ef04783 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 30 Sep 2022 15:13:29 -0700 Subject: [PATCH 05/15] macos/text: matching font descriptors --- pkg/macos/text/font_collection.zig | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/pkg/macos/text/font_collection.zig b/pkg/macos/text/font_collection.zig index 7de026f29..9335c7658 100644 --- a/pkg/macos/text/font_collection.zig +++ b/pkg/macos/text/font_collection.zig @@ -11,12 +11,26 @@ pub const FontCollection = opaque { foundation.CFRelease(self); } + pub fn createMatchingFontDescriptors(self: *FontCollection) *foundation.Array { + return CTFontCollectionCreateMatchingFontDescriptors(self); + } + pub extern "c" fn CTFontCollectionCreateFromAvailableFonts( options: ?*foundation.Dictionary, ) ?*FontCollection; + pub extern "c" fn CTFontCollectionCreateMatchingFontDescriptors( + collection: *FontCollection, + ) *foundation.Array; }; test "collection" { + const testing = std.testing; + const v = try FontCollection.createFromAvailableFonts(); defer v.release(); + + const list = v.createMatchingFontDescriptors(); + defer list.release(); + + try testing.expect(list.getCount() > 0); } From 2440b0ec67edd7b8fa380f4423bb910a5c5d64bb Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 1 Oct 2022 09:53:48 -0700 Subject: [PATCH 06/15] pkg/macos: dictionary create --- pkg/macos/foundation/dictionary.zig | 41 ++++++++++++++++++++++++----- 1 file changed, 34 insertions(+), 7 deletions(-) diff --git a/pkg/macos/foundation/dictionary.zig b/pkg/macos/foundation/dictionary.zig index 495accd50..6d9c6bd9e 100644 --- a/pkg/macos/foundation/dictionary.zig +++ b/pkg/macos/foundation/dictionary.zig @@ -1,21 +1,35 @@ const std = @import("std"); +const assert = std.debug.assert; const Allocator = std.mem.Allocator; -const cftype = @import("type.zig"); +const foundation = @import("../foundation.zig"); pub const Dictionary = opaque { - pub fn create() Allocator.Error!*Dictionary { + pub fn create( + keys: ?[]*const anyopaque, + values: ?[]*const anyopaque, + ) Allocator.Error!*Dictionary { + if (keys != null or values != null) { + assert(keys != null); + assert(values != null); + assert(keys.?.len == values.?.len); + } + return CFDictionaryCreate( null, - null, - null, - 0, + if (keys) |slice| slice.ptr else null, + if (values) |slice| slice.ptr else null, + if (keys) |slice| slice.len else 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks, ) orelse Allocator.Error.OutOfMemory; } pub fn release(self: *Dictionary) void { - cftype.CFRelease(self); + foundation.CFRelease(self); + } + + pub fn getCount(self: *Dictionary) usize { + return CFDictionaryGetCount(self); } pub extern "c" fn CFDictionaryCreate( @@ -26,12 +40,25 @@ pub const Dictionary = opaque { key_callbacks: *const anyopaque, value_callbacks: *const anyopaque, ) ?*Dictionary; + pub extern "c" fn CFDictionaryGetCount(*Dictionary) usize; extern "c" var kCFTypeDictionaryKeyCallBacks: anyopaque; extern "c" var kCFTypeDictionaryValueCallBacks: anyopaque; }; +// Just used for a test +extern "c" var kCFURLIsPurgeableKey: *const anyopaque; + test "dictionary" { - const dict = try Dictionary.create(); + const testing = std.testing; + + const str = try foundation.String.createWithBytes("hello", .unicode, false); + defer str.release(); + + var keys = [_]*const anyopaque{kCFURLIsPurgeableKey}; + var values = [_]*const anyopaque{str}; + const dict = try Dictionary.create(&keys, &values); defer dict.release(); + + try testing.expectEqual(@as(usize, 1), dict.getCount()); } From 92d564a246da4cfa5c1425e7604ffc95f4ea4e8c Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 1 Oct 2022 10:32:54 -0700 Subject: [PATCH 07/15] macos/foundation: more string funcs --- pkg/macos/foundation.zig | 1 + pkg/macos/foundation/base.zig | 5 +++++ pkg/macos/foundation/string.zig | 35 +++++++++++++++++++++++++++++++-- 3 files changed, 39 insertions(+), 2 deletions(-) create mode 100644 pkg/macos/foundation/base.zig diff --git a/pkg/macos/foundation.zig b/pkg/macos/foundation.zig index 6dff4b8b5..ce969293e 100644 --- a/pkg/macos/foundation.zig +++ b/pkg/macos/foundation.zig @@ -1,4 +1,5 @@ pub usingnamespace @import("foundation/array.zig"); +pub usingnamespace @import("foundation/base.zig"); pub usingnamespace @import("foundation/dictionary.zig"); pub usingnamespace @import("foundation/string.zig"); pub usingnamespace @import("foundation/type.zig"); diff --git a/pkg/macos/foundation/base.zig b/pkg/macos/foundation/base.zig new file mode 100644 index 000000000..b48d6df38 --- /dev/null +++ b/pkg/macos/foundation/base.zig @@ -0,0 +1,5 @@ +pub const ComparisonResult = enum(c_int) { + less = -1, + equal = 0, + greater = 1, +}; diff --git a/pkg/macos/foundation/string.zig b/pkg/macos/foundation/string.zig index a0f0380c3..9414bec4c 100644 --- a/pkg/macos/foundation/string.zig +++ b/pkg/macos/foundation/string.zig @@ -1,6 +1,6 @@ const std = @import("std"); const Allocator = std.mem.Allocator; -const cftype = @import("type.zig"); +const foundation = @import("../foundation.zig"); pub const String = opaque { pub fn createWithBytes( @@ -18,13 +18,24 @@ pub const String = opaque { } pub fn release(self: *String) void { - cftype.CFRelease(self); + foundation.CFRelease(self); } pub fn hasPrefix(self: *String, prefix: *String) bool { return CFStringHasPrefix(self, prefix) == 1; } + pub fn compare( + self: *String, + other: *String, + options: StringComparison, + ) foundation.ComparisonResult { + return @intToEnum( + foundation.ComparisonResult, + CFStringCompare(self, other, @bitCast(c_int, options)), + ); + } + pub extern "c" fn CFStringCreateWithBytes( allocator: ?*anyopaque, bytes: [*]const u8, @@ -33,6 +44,25 @@ pub const String = opaque { is_external: bool, ) ?*String; pub extern "c" fn CFStringHasPrefix(*String, *String) u8; + pub extern "c" fn CFStringCompare(*String, *String, c_int) c_int; +}; + +pub const StringComparison = packed struct { + case_insensitive: bool = false, + _unused_2: bool = false, + backwards: bool = false, + anchored: bool = false, + nonliteral: bool = false, + localized: bool = false, + numerically: bool = false, + diacritic_insensitive: bool = false, + width_insensitive: bool = false, + forced_ordering: bool = false, + _padding: u22 = 0, + + test { + try std.testing.expectEqual(@bitSizeOf(c_int), @bitSizeOf(StringComparison)); + } }; /// https://developer.apple.com/documentation/corefoundation/cfstringencoding?language=objc @@ -63,4 +93,5 @@ test "string" { defer prefix.release(); try testing.expect(str.hasPrefix(prefix)); + try testing.expectEqual(foundation.ComparisonResult.equal, str.compare(str, .{})); } From cb02bab89edf71be0e591c16e95e4d7a5873ddbb Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 1 Oct 2022 10:50:09 -0700 Subject: [PATCH 08/15] macos/text: font descriptors --- pkg/macos/foundation/string.zig | 25 +++++ pkg/macos/text.zig | 1 + pkg/macos/text/font_descriptor.zig | 157 +++++++++++++++++++++++++++++ 3 files changed, 183 insertions(+) create mode 100644 pkg/macos/text/font_descriptor.zig diff --git a/pkg/macos/foundation/string.zig b/pkg/macos/foundation/string.zig index 9414bec4c..a08ae5b85 100644 --- a/pkg/macos/foundation/string.zig +++ b/pkg/macos/foundation/string.zig @@ -36,6 +36,22 @@ pub const String = opaque { ); } + pub fn cstring(self: *String, buf: []u8, encoding: StringEncoding) ?[]const u8 { + if (CFStringGetCString( + self, + buf.ptr, + buf.len, + @enumToInt(encoding), + ) == 0) return null; + return std.mem.sliceTo(buf, 0); + } + + pub fn cstringPtr(self: *String, encoding: StringEncoding) ?[:0]const u8 { + const ptr = CFStringGetCStringPtr(self, @enumToInt(encoding)); + if (ptr == null) return null; + return std.mem.sliceTo(ptr, 0); + } + pub extern "c" fn CFStringCreateWithBytes( allocator: ?*anyopaque, bytes: [*]const u8, @@ -45,6 +61,8 @@ pub const String = opaque { ) ?*String; pub extern "c" fn CFStringHasPrefix(*String, *String) u8; pub extern "c" fn CFStringCompare(*String, *String, c_int) c_int; + pub extern "c" fn CFStringGetCString(*String, [*]u8, usize, u32) u8; + pub extern "c" fn CFStringGetCStringPtr(*String, u32) [*c]const u8; }; pub const StringComparison = packed struct { @@ -94,4 +112,11 @@ test "string" { try testing.expect(str.hasPrefix(prefix)); try testing.expectEqual(foundation.ComparisonResult.equal, str.compare(str, .{})); + try testing.expectEqualStrings("hello world", str.cstringPtr(.ascii).?); + + { + var buf: [128]u8 = undefined; + const cstr = str.cstring(&buf, .ascii).?; + try testing.expectEqualStrings("hello world", cstr); + } } diff --git a/pkg/macos/text.zig b/pkg/macos/text.zig index 7455fde5e..4954e0afc 100644 --- a/pkg/macos/text.zig +++ b/pkg/macos/text.zig @@ -1,4 +1,5 @@ pub usingnamespace @import("text/font_collection.zig"); +pub usingnamespace @import("text/font_descriptor.zig"); test { @import("std").testing.refAllDecls(@This()); diff --git a/pkg/macos/text/font_descriptor.zig b/pkg/macos/text/font_descriptor.zig new file mode 100644 index 000000000..17fd404c9 --- /dev/null +++ b/pkg/macos/text/font_descriptor.zig @@ -0,0 +1,157 @@ +const std = @import("std"); +const Allocator = std.mem.Allocator; +const foundation = @import("../foundation.zig"); + +pub const FontDescriptor = opaque { + pub fn createWithNameAndSize(name: *foundation.String, size: f64) Allocator.Error!*FontDescriptor { + return CTFontDescriptorCreateWithNameAndSize(name, size) orelse Allocator.Error.OutOfMemory; + } + + pub fn release(self: *FontDescriptor) void { + foundation.CFRelease(self); + } + + pub fn copyAttribute(self: *FontDescriptor, comptime attr: FontAttribute) attr.Value() { + const T = attr.Value(); + return @ptrCast(T, CTFontDescriptorCopyAttribute(self, attr.key())); + } + + pub extern "c" fn CTFontDescriptorCreateWithNameAndSize( + name: *foundation.String, + size: f64, + ) ?*FontDescriptor; + pub extern "c" fn CTFontDescriptorCopyAttribute( + *FontDescriptor, + *foundation.String, + ) ?*anyopaque; +}; + +pub const FontAttribute = enum { + url, + name, + display_name, + family_name, + style_name, + traits, + variation, + size, + matrix, + cascade_list, + character_set, + languages, + baseline_adjust, + macintosh_encodings, + features, + feature_settings, + fixed_advance, + orientation, + format, + registration_scope, + priority, + enabled, + downloadable, + downloaded, + + pub fn key(self: FontAttribute) *foundation.String { + return switch (self) { + .url => kCTFontURLAttribute, + .name => kCTFontNameAttribute, + .display_name => kCTFontDisplayNameAttribute, + .family_name => kCTFontFamilyNameAttribute, + .style_name => kCTFontStyleNameAttribute, + .traits => kCTFontTraitsAttribute, + .variation => kCTFontVariationAttribute, + .size => kCTFontSizeAttribute, + .matrix => kCTFontMatrixAttribute, + .cascade_list => kCTFontCascadeListAttribute, + .character_set => kCTFontCharacterSetAttribute, + .languages => kCTFontLanguagesAttribute, + .baseline_adjust => kCTFontBaselineAdjustAttribute, + .macintosh_encodings => kCTFontMacintoshEncodingsAttribute, + .features => kCTFontFeaturesAttribute, + .feature_settings => kCTFontFeatureSettingsAttribute, + .fixed_advance => kCTFontFixedAdvanceAttribute, + .orientation => kCTFontOrientationAttribute, + .format => kCTFontFormatAttribute, + .registration_scope => kCTFontRegistrationScopeAttribute, + .priority => kCTFontPriorityAttribute, + .enabled => kCTFontEnabledAttribute, + .downloadable => kCTFontDownloadableAttribute, + .downloaded => kCTFontDownloadedAttribute, + }; + } + + pub fn Value(self: FontAttribute) type { + return switch (self) { + .url => *anyopaque, // CFUrl + .name => *foundation.String, + .display_name => *foundation.String, + .family_name => *foundation.String, + .style_name => *foundation.String, + .traits => *foundation.Dictionary, + .variation => *foundation.Dictionary, + .size => *anyopaque, // CFNumber + .matrix => *anyopaque, // CFDataRef + .cascade_list => *foundation.Array, + .character_set => *anyopaque, // CFCharacterSetRef + .languages => *foundation.Array, + .baseline_adjust => *anyopaque, // CFNumber + .macintosh_encodings => *anyopaque, // CFNumber + .features => *foundation.Array, + .feature_settings => *foundation.Array, + .fixed_advance => *anyopaque, // CFNumber + .orientation => *anyopaque, // CFNumber + .format => *anyopaque, // CFNumber + .registration_scope => *anyopaque, // CFNumber + .priority => *anyopaque, // CFNumber + .enabled => *anyopaque, // CFNumber + .downloadable => *anyopaque, // CFBoolean + .downloaded => *anyopaque, // CFBoolean + }; + } + + extern "c" const kCTFontURLAttribute: *foundation.String; + extern "c" const kCTFontNameAttribute: *foundation.String; + extern "c" const kCTFontDisplayNameAttribute: *foundation.String; + extern "c" const kCTFontFamilyNameAttribute: *foundation.String; + extern "c" const kCTFontStyleNameAttribute: *foundation.String; + extern "c" const kCTFontTraitsAttribute: *foundation.String; + extern "c" const kCTFontVariationAttribute: *foundation.String; + extern "c" const kCTFontVariationAxesAttribute: *foundation.String; + extern "c" const kCTFontSizeAttribute: *foundation.String; + extern "c" const kCTFontMatrixAttribute: *foundation.String; + extern "c" const kCTFontCascadeListAttribute: *foundation.String; + extern "c" const kCTFontCharacterSetAttribute: *foundation.String; + extern "c" const kCTFontLanguagesAttribute: *foundation.String; + extern "c" const kCTFontBaselineAdjustAttribute: *foundation.String; + extern "c" const kCTFontMacintoshEncodingsAttribute: *foundation.String; + extern "c" const kCTFontFeaturesAttribute: *foundation.String; + extern "c" const kCTFontFeatureSettingsAttribute: *foundation.String; + extern "c" const kCTFontFixedAdvanceAttribute: *foundation.String; + extern "c" const kCTFontOrientationAttribute: *foundation.String; + extern "c" const kCTFontFormatAttribute: *foundation.String; + extern "c" const kCTFontRegistrationScopeAttribute: *foundation.String; + extern "c" const kCTFontPriorityAttribute: *foundation.String; + extern "c" const kCTFontEnabledAttribute: *foundation.String; + extern "c" const kCTFontDownloadableAttribute: *foundation.String; + extern "c" const kCTFontDownloadedAttribute: *foundation.String; +}; + +test "descriptor" { + const testing = std.testing; + + const name = try foundation.String.createWithBytes("foo", .utf8, false); + defer name.release(); + + const v = try FontDescriptor.createWithNameAndSize(name, 12); + defer v.release(); + + const copy_name = v.copyAttribute(.name); + defer copy_name.release(); + + { + var buf: [128]u8 = undefined; + const cstr = copy_name.cstring(&buf, .utf8).?; + try testing.expectEqualStrings("foo", cstr); + } +} From 49c9c21d523f01ccd4c6493cb8e590f72295cf54 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 1 Oct 2022 11:06:07 -0700 Subject: [PATCH 09/15] macos: test listing fonts --- pkg/macos/foundation/array.zig | 9 ++++++--- pkg/macos/text/font_collection.zig | 13 +++++++++++++ 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/pkg/macos/foundation/array.zig b/pkg/macos/foundation/array.zig index 927269627..9ab5fa293 100644 --- a/pkg/macos/foundation/array.zig +++ b/pkg/macos/foundation/array.zig @@ -20,8 +20,11 @@ pub const Array = opaque { return CFArrayGetCount(self); } - pub fn getValueAtIndex(self: *Array, comptime T: type, idx: usize) *const T { - return @ptrCast(*const T, CFArrayGetValueAtIndex(self, idx)); + /// Note the return type is actually a `*const T` but we strip the + /// constness so that further API calls work correctly. The Foundation + /// API doesn't properly mark things const/non-const. + pub fn getValueAtIndex(self: *Array, comptime T: type, idx: usize) *T { + return @ptrCast(*T, CFArrayGetValueAtIndex(self, idx)); } pub extern "c" fn CFArrayCreate( @@ -31,7 +34,7 @@ pub const Array = opaque { callbacks: ?*const anyopaque, ) ?*Array; pub extern "c" fn CFArrayGetCount(*Array) usize; - pub extern "c" fn CFArrayGetValueAtIndex(*Array, usize) *const anyopaque; + pub extern "c" fn CFArrayGetValueAtIndex(*Array, usize) *anyopaque; extern "c" var kCFTypeArrayCallBacks: anyopaque; }; diff --git a/pkg/macos/text/font_collection.zig b/pkg/macos/text/font_collection.zig index 9335c7658..1b6168719 100644 --- a/pkg/macos/text/font_collection.zig +++ b/pkg/macos/text/font_collection.zig @@ -1,6 +1,7 @@ const std = @import("std"); const Allocator = std.mem.Allocator; const foundation = @import("../foundation.zig"); +const text = @import("../text.zig"); pub const FontCollection = opaque { pub fn createFromAvailableFonts() Allocator.Error!*FontCollection { @@ -33,4 +34,16 @@ test "collection" { defer list.release(); try testing.expect(list.getCount() > 0); + + // var i: usize = 0; + // while (i < list.getCount()) : (i += 1) { + // const desc = list.getValueAtIndex(text.FontDescriptor, i); + // { + // var buf: [128]u8 = undefined; + // const name = desc.copyAttribute(.name); + // defer name.release(); + // const cstr = name.cstring(&buf, .utf8).?; + // std.log.warn("i={d} v={s}", .{ i, cstr }); + // } + // } } From 88ae3be9efd59933f2a024929dae5f7365861c8d Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 1 Oct 2022 11:13:44 -0700 Subject: [PATCH 10/15] pkg/macos: CFURL --- pkg/macos/foundation.zig | 1 + pkg/macos/foundation/url.zig | 47 ++++++++++++++++++++++++++++++ pkg/macos/text/font_descriptor.zig | 2 +- 3 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 pkg/macos/foundation/url.zig diff --git a/pkg/macos/foundation.zig b/pkg/macos/foundation.zig index ce969293e..ed18b5dfd 100644 --- a/pkg/macos/foundation.zig +++ b/pkg/macos/foundation.zig @@ -3,6 +3,7 @@ pub usingnamespace @import("foundation/base.zig"); pub usingnamespace @import("foundation/dictionary.zig"); pub usingnamespace @import("foundation/string.zig"); pub usingnamespace @import("foundation/type.zig"); +pub usingnamespace @import("foundation/url.zig"); test { @import("std").testing.refAllDecls(@This()); diff --git a/pkg/macos/foundation/url.zig b/pkg/macos/foundation/url.zig new file mode 100644 index 000000000..3917beff3 --- /dev/null +++ b/pkg/macos/foundation/url.zig @@ -0,0 +1,47 @@ +const std = @import("std"); +const Allocator = std.mem.Allocator; +const foundation = @import("../foundation.zig"); + +pub const URL = opaque { + pub fn createWithString(str: *foundation.String, base: ?*URL) Allocator.Error!*URL { + return CFURLCreateWithString( + null, + str, + base, + ) orelse error.OutOfMemory; + } + + pub fn release(self: *URL) void { + foundation.CFRelease(self); + } + + pub fn copyPath(self: *URL) ?*foundation.String { + return CFURLCopyPath(self); + } + + pub extern "c" fn CFURLCreateWithString( + allocator: ?*anyopaque, + url_string: *const anyopaque, + base_url: ?*const anyopaque, + ) ?*URL; + pub extern "c" fn CFURLCopyPath(*URL) ?*foundation.String; +}; + +test { + const testing = std.testing; + + const str = try foundation.String.createWithBytes("http://www.example.com/foo", .utf8, false); + defer str.release(); + + const url = try URL.createWithString(str, null); + defer url.release(); + + { + const path = url.copyPath().?; + defer path.release(); + + var buf: [128]u8 = undefined; + const cstr = path.cstring(&buf, .utf8).?; + try testing.expectEqualStrings("/foo", cstr); + } +} diff --git a/pkg/macos/text/font_descriptor.zig b/pkg/macos/text/font_descriptor.zig index 17fd404c9..8e8ce33e5 100644 --- a/pkg/macos/text/font_descriptor.zig +++ b/pkg/macos/text/font_descriptor.zig @@ -83,7 +83,7 @@ pub const FontAttribute = enum { pub fn Value(self: FontAttribute) type { return switch (self) { - .url => *anyopaque, // CFUrl + .url => *foundation.URL, .name => *foundation.String, .display_name => *foundation.String, .family_name => *foundation.String, From 57c1d8d329ff7f8bc612b4119d479c27a8961173 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 1 Oct 2022 11:25:21 -0700 Subject: [PATCH 11/15] macos/text: more APIs --- pkg/macos/foundation/url.zig | 16 ++++++++++++++++ pkg/macos/text/font_collection.zig | 23 ++++++++++++++++++++++- 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/pkg/macos/foundation/url.zig b/pkg/macos/foundation/url.zig index 3917beff3..f0d71e26a 100644 --- a/pkg/macos/foundation/url.zig +++ b/pkg/macos/foundation/url.zig @@ -11,6 +11,17 @@ pub const URL = opaque { ) orelse error.OutOfMemory; } + pub fn createStringByReplacingPercentEscapes( + str: *foundation.String, + escape: *foundation.String, + ) Allocator.Error!*foundation.String { + return CFURLCreateStringByReplacingPercentEscapes( + null, + str, + escape, + ) orelse return error.OutOfMemory; + } + pub fn release(self: *URL) void { foundation.CFRelease(self); } @@ -25,6 +36,11 @@ pub const URL = opaque { base_url: ?*const anyopaque, ) ?*URL; pub extern "c" fn CFURLCopyPath(*URL) ?*foundation.String; + pub extern "c" fn CFURLCreateStringByReplacingPercentEscapes( + allocator: ?*anyopaque, + original: *const anyopaque, + escape: *const anyopaque, + ) ?*foundation.String; }; test { diff --git a/pkg/macos/text/font_collection.zig b/pkg/macos/text/font_collection.zig index 1b6168719..11ff499af 100644 --- a/pkg/macos/text/font_collection.zig +++ b/pkg/macos/text/font_collection.zig @@ -43,7 +43,28 @@ test "collection" { // const name = desc.copyAttribute(.name); // defer name.release(); // const cstr = name.cstring(&buf, .utf8).?; - // std.log.warn("i={d} v={s}", .{ i, cstr }); + // + // var buf2: [128]u8 = undefined; + // const url = desc.copyAttribute(.url); + // defer url.release(); + // const path = path: { + // const blank = try foundation.String.createWithBytes("", .utf8, false); + // defer blank.release(); + // + // const path = url.copyPath() orelse break :path ""; + // defer path.release(); + // + // const decoded = try foundation.URL.createStringByReplacingPercentEscapes( + // path, + // blank, + // ); + // defer decoded.release(); + // + // break :path decoded.cstring(&buf2, .utf8) orelse + // ""; + // }; + // + // std.log.warn("i={d} name={s} path={s}", .{ i, cstr, path }); // } // } } From 3eeef743159bb0052abef2d2a05d8b7a2eba850e Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 1 Oct 2022 15:24:22 -0700 Subject: [PATCH 12/15] pkg/macos: import headers instead of doing externs manually --- pkg/macos/foundation.zig | 1 + pkg/macos/foundation/string.zig | 45 +++++++------ pkg/macos/text/font_collection.zig | 22 ++++--- pkg/macos/text/font_descriptor.zig | 101 ++++++++++------------------- 4 files changed, 71 insertions(+), 98 deletions(-) diff --git a/pkg/macos/foundation.zig b/pkg/macos/foundation.zig index ed18b5dfd..ca0536b8c 100644 --- a/pkg/macos/foundation.zig +++ b/pkg/macos/foundation.zig @@ -1,3 +1,4 @@ +pub const c = @import("foundation/c.zig"); pub usingnamespace @import("foundation/array.zig"); pub usingnamespace @import("foundation/base.zig"); pub usingnamespace @import("foundation/dictionary.zig"); diff --git a/pkg/macos/foundation/string.zig b/pkg/macos/foundation/string.zig index a08ae5b85..fb322c8b3 100644 --- a/pkg/macos/foundation/string.zig +++ b/pkg/macos/foundation/string.zig @@ -1,6 +1,7 @@ const std = @import("std"); const Allocator = std.mem.Allocator; const foundation = @import("../foundation.zig"); +const c = @import("c.zig"); pub const String = opaque { pub fn createWithBytes( @@ -8,21 +9,24 @@ pub const String = opaque { encoding: StringEncoding, external: bool, ) Allocator.Error!*String { - return CFStringCreateWithBytes( + return @intToPtr(?*String, @ptrToInt(c.CFStringCreateWithBytes( null, bs.ptr, - bs.len, + @intCast(c_long, bs.len), @enumToInt(encoding), - external, - ) orelse Allocator.Error.OutOfMemory; + @boolToInt(external), + ))) orelse Allocator.Error.OutOfMemory; } pub fn release(self: *String) void { - foundation.CFRelease(self); + c.CFRelease(self); } pub fn hasPrefix(self: *String, prefix: *String) bool { - return CFStringHasPrefix(self, prefix) == 1; + return c.CFStringHasPrefix( + @ptrCast(c.CFStringRef, self), + @ptrCast(c.CFStringRef, prefix), + ) == 1; } pub fn compare( @@ -32,37 +36,32 @@ pub const String = opaque { ) foundation.ComparisonResult { return @intToEnum( foundation.ComparisonResult, - CFStringCompare(self, other, @bitCast(c_int, options)), + c.CFStringCompare( + @ptrCast(c.CFStringRef, self), + @ptrCast(c.CFStringRef, other), + @intCast(c_ulong, @bitCast(c_int, options)), + ), ); } pub fn cstring(self: *String, buf: []u8, encoding: StringEncoding) ?[]const u8 { - if (CFStringGetCString( - self, + if (c.CFStringGetCString( + @ptrCast(c.CFStringRef, self), buf.ptr, - buf.len, + @intCast(c_long, buf.len), @enumToInt(encoding), ) == 0) return null; return std.mem.sliceTo(buf, 0); } pub fn cstringPtr(self: *String, encoding: StringEncoding) ?[:0]const u8 { - const ptr = CFStringGetCStringPtr(self, @enumToInt(encoding)); + const ptr = c.CFStringGetCStringPtr( + @ptrCast(c.CFStringRef, self), + @enumToInt(encoding), + ); if (ptr == null) return null; return std.mem.sliceTo(ptr, 0); } - - pub extern "c" fn CFStringCreateWithBytes( - allocator: ?*anyopaque, - bytes: [*]const u8, - numBytes: usize, - encooding: u32, - is_external: bool, - ) ?*String; - pub extern "c" fn CFStringHasPrefix(*String, *String) u8; - pub extern "c" fn CFStringCompare(*String, *String, c_int) c_int; - pub extern "c" fn CFStringGetCString(*String, [*]u8, usize, u32) u8; - pub extern "c" fn CFStringGetCStringPtr(*String, u32) [*c]const u8; }; pub const StringComparison = packed struct { diff --git a/pkg/macos/text/font_collection.zig b/pkg/macos/text/font_collection.zig index 11ff499af..b1e8411f3 100644 --- a/pkg/macos/text/font_collection.zig +++ b/pkg/macos/text/font_collection.zig @@ -2,26 +2,28 @@ const std = @import("std"); const Allocator = std.mem.Allocator; const foundation = @import("../foundation.zig"); const text = @import("../text.zig"); +const c = @import("c.zig"); pub const FontCollection = opaque { pub fn createFromAvailableFonts() Allocator.Error!*FontCollection { - return CTFontCollectionCreateFromAvailableFonts(null) orelse Allocator.Error.OutOfMemory; + return @intToPtr( + ?*FontCollection, + @ptrToInt(c.CTFontCollectionCreateFromAvailableFonts(null)), + ) orelse Allocator.Error.OutOfMemory; } pub fn release(self: *FontCollection) void { - foundation.CFRelease(self); + c.CFRelease(self); } pub fn createMatchingFontDescriptors(self: *FontCollection) *foundation.Array { - return CTFontCollectionCreateMatchingFontDescriptors(self); + return @intToPtr( + *foundation.Array, + @ptrToInt(c.CTFontCollectionCreateMatchingFontDescriptors( + @ptrCast(c.CTFontCollectionRef, self), + )), + ); } - - pub extern "c" fn CTFontCollectionCreateFromAvailableFonts( - options: ?*foundation.Dictionary, - ) ?*FontCollection; - pub extern "c" fn CTFontCollectionCreateMatchingFontDescriptors( - collection: *FontCollection, - ) *foundation.Array; }; test "collection" { diff --git a/pkg/macos/text/font_descriptor.zig b/pkg/macos/text/font_descriptor.zig index 8e8ce33e5..1f679a617 100644 --- a/pkg/macos/text/font_descriptor.zig +++ b/pkg/macos/text/font_descriptor.zig @@ -1,29 +1,26 @@ const std = @import("std"); const Allocator = std.mem.Allocator; const foundation = @import("../foundation.zig"); +const c = @import("c.zig"); pub const FontDescriptor = opaque { pub fn createWithNameAndSize(name: *foundation.String, size: f64) Allocator.Error!*FontDescriptor { - return CTFontDescriptorCreateWithNameAndSize(name, size) orelse Allocator.Error.OutOfMemory; + return @intToPtr( + ?*FontDescriptor, + @ptrToInt(c.CTFontDescriptorCreateWithNameAndSize(@ptrCast(c.CFStringRef, name), size)), + ) orelse Allocator.Error.OutOfMemory; } pub fn release(self: *FontDescriptor) void { - foundation.CFRelease(self); + c.CFRelease(self); } pub fn copyAttribute(self: *FontDescriptor, comptime attr: FontAttribute) attr.Value() { - const T = attr.Value(); - return @ptrCast(T, CTFontDescriptorCopyAttribute(self, attr.key())); + return @intToPtr(attr.Value(), @ptrToInt(c.CTFontDescriptorCopyAttribute( + @ptrCast(c.CTFontDescriptorRef, self), + @ptrCast(c.CFStringRef, attr.key()), + ))); } - - pub extern "c" fn CTFontDescriptorCreateWithNameAndSize( - name: *foundation.String, - size: f64, - ) ?*FontDescriptor; - pub extern "c" fn CTFontDescriptorCopyAttribute( - *FontDescriptor, - *foundation.String, - ) ?*anyopaque; }; pub const FontAttribute = enum { @@ -53,32 +50,32 @@ pub const FontAttribute = enum { downloaded, pub fn key(self: FontAttribute) *foundation.String { - return switch (self) { - .url => kCTFontURLAttribute, - .name => kCTFontNameAttribute, - .display_name => kCTFontDisplayNameAttribute, - .family_name => kCTFontFamilyNameAttribute, - .style_name => kCTFontStyleNameAttribute, - .traits => kCTFontTraitsAttribute, - .variation => kCTFontVariationAttribute, - .size => kCTFontSizeAttribute, - .matrix => kCTFontMatrixAttribute, - .cascade_list => kCTFontCascadeListAttribute, - .character_set => kCTFontCharacterSetAttribute, - .languages => kCTFontLanguagesAttribute, - .baseline_adjust => kCTFontBaselineAdjustAttribute, - .macintosh_encodings => kCTFontMacintoshEncodingsAttribute, - .features => kCTFontFeaturesAttribute, - .feature_settings => kCTFontFeatureSettingsAttribute, - .fixed_advance => kCTFontFixedAdvanceAttribute, - .orientation => kCTFontOrientationAttribute, - .format => kCTFontFormatAttribute, - .registration_scope => kCTFontRegistrationScopeAttribute, - .priority => kCTFontPriorityAttribute, - .enabled => kCTFontEnabledAttribute, - .downloadable => kCTFontDownloadableAttribute, - .downloaded => kCTFontDownloadedAttribute, - }; + return @intToPtr(*foundation.String, @ptrToInt(switch (self) { + .url => c.kCTFontURLAttribute, + .name => c.kCTFontNameAttribute, + .display_name => c.kCTFontDisplayNameAttribute, + .family_name => c.kCTFontFamilyNameAttribute, + .style_name => c.kCTFontStyleNameAttribute, + .traits => c.kCTFontTraitsAttribute, + .variation => c.kCTFontVariationAttribute, + .size => c.kCTFontSizeAttribute, + .matrix => c.kCTFontMatrixAttribute, + .cascade_list => c.kCTFontCascadeListAttribute, + .character_set => c.kCTFontCharacterSetAttribute, + .languages => c.kCTFontLanguagesAttribute, + .baseline_adjust => c.kCTFontBaselineAdjustAttribute, + .macintosh_encodings => c.kCTFontMacintoshEncodingsAttribute, + .features => c.kCTFontFeaturesAttribute, + .feature_settings => c.kCTFontFeatureSettingsAttribute, + .fixed_advance => c.kCTFontFixedAdvanceAttribute, + .orientation => c.kCTFontOrientationAttribute, + .format => c.kCTFontFormatAttribute, + .registration_scope => c.kCTFontRegistrationScopeAttribute, + .priority => c.kCTFontPriorityAttribute, + .enabled => c.kCTFontEnabledAttribute, + .downloadable => c.kCTFontDownloadableAttribute, + .downloaded => c.kCTFontDownloadedAttribute, + })); } pub fn Value(self: FontAttribute) type { @@ -109,32 +106,6 @@ pub const FontAttribute = enum { .downloaded => *anyopaque, // CFBoolean }; } - - extern "c" const kCTFontURLAttribute: *foundation.String; - extern "c" const kCTFontNameAttribute: *foundation.String; - extern "c" const kCTFontDisplayNameAttribute: *foundation.String; - extern "c" const kCTFontFamilyNameAttribute: *foundation.String; - extern "c" const kCTFontStyleNameAttribute: *foundation.String; - extern "c" const kCTFontTraitsAttribute: *foundation.String; - extern "c" const kCTFontVariationAttribute: *foundation.String; - extern "c" const kCTFontVariationAxesAttribute: *foundation.String; - extern "c" const kCTFontSizeAttribute: *foundation.String; - extern "c" const kCTFontMatrixAttribute: *foundation.String; - extern "c" const kCTFontCascadeListAttribute: *foundation.String; - extern "c" const kCTFontCharacterSetAttribute: *foundation.String; - extern "c" const kCTFontLanguagesAttribute: *foundation.String; - extern "c" const kCTFontBaselineAdjustAttribute: *foundation.String; - extern "c" const kCTFontMacintoshEncodingsAttribute: *foundation.String; - extern "c" const kCTFontFeaturesAttribute: *foundation.String; - extern "c" const kCTFontFeatureSettingsAttribute: *foundation.String; - extern "c" const kCTFontFixedAdvanceAttribute: *foundation.String; - extern "c" const kCTFontOrientationAttribute: *foundation.String; - extern "c" const kCTFontFormatAttribute: *foundation.String; - extern "c" const kCTFontRegistrationScopeAttribute: *foundation.String; - extern "c" const kCTFontPriorityAttribute: *foundation.String; - extern "c" const kCTFontEnabledAttribute: *foundation.String; - extern "c" const kCTFontDownloadableAttribute: *foundation.String; - extern "c" const kCTFontDownloadedAttribute: *foundation.String; }; test "descriptor" { From 7eb466959aa2fae6a828d38bad6b2c8dee8323d7 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 1 Oct 2022 15:49:05 -0700 Subject: [PATCH 13/15] macos/foundation: number, dict get value --- pkg/macos/foundation.zig | 1 + pkg/macos/foundation/c.zig | 3 ++ pkg/macos/foundation/dictionary.zig | 48 ++++++++---------- pkg/macos/foundation/number.zig | 79 +++++++++++++++++++++++++++++ 4 files changed, 104 insertions(+), 27 deletions(-) create mode 100644 pkg/macos/foundation/c.zig create mode 100644 pkg/macos/foundation/number.zig diff --git a/pkg/macos/foundation.zig b/pkg/macos/foundation.zig index ca0536b8c..8b29ccfb3 100644 --- a/pkg/macos/foundation.zig +++ b/pkg/macos/foundation.zig @@ -2,6 +2,7 @@ pub const c = @import("foundation/c.zig"); pub usingnamespace @import("foundation/array.zig"); pub usingnamespace @import("foundation/base.zig"); pub usingnamespace @import("foundation/dictionary.zig"); +pub usingnamespace @import("foundation/number.zig"); pub usingnamespace @import("foundation/string.zig"); pub usingnamespace @import("foundation/type.zig"); pub usingnamespace @import("foundation/url.zig"); diff --git a/pkg/macos/foundation/c.zig b/pkg/macos/foundation/c.zig new file mode 100644 index 000000000..9bd571adb --- /dev/null +++ b/pkg/macos/foundation/c.zig @@ -0,0 +1,3 @@ +pub usingnamespace @cImport({ + @cInclude("CoreFoundation/CoreFoundation.h"); +}); diff --git a/pkg/macos/foundation/dictionary.zig b/pkg/macos/foundation/dictionary.zig index 6d9c6bd9e..8d74a90f1 100644 --- a/pkg/macos/foundation/dictionary.zig +++ b/pkg/macos/foundation/dictionary.zig @@ -2,11 +2,12 @@ const std = @import("std"); const assert = std.debug.assert; const Allocator = std.mem.Allocator; const foundation = @import("../foundation.zig"); +const c = @import("c.zig"); pub const Dictionary = opaque { pub fn create( - keys: ?[]*const anyopaque, - values: ?[]*const anyopaque, + keys: ?[]?*const anyopaque, + values: ?[]?*const anyopaque, ) Allocator.Error!*Dictionary { if (keys != null or values != null) { assert(keys != null); @@ -14,14 +15,14 @@ pub const Dictionary = opaque { assert(keys.?.len == values.?.len); } - return CFDictionaryCreate( + return @intToPtr(?*Dictionary, @ptrToInt(c.CFDictionaryCreate( null, - if (keys) |slice| slice.ptr else null, - if (values) |slice| slice.ptr else null, - if (keys) |slice| slice.len else 0, - &kCFTypeDictionaryKeyCallBacks, - &kCFTypeDictionaryValueCallBacks, - ) orelse Allocator.Error.OutOfMemory; + @ptrCast([*c]?*const anyopaque, if (keys) |slice| slice.ptr else null), + @ptrCast([*c]?*const anyopaque, if (values) |slice| slice.ptr else null), + @intCast(c.CFIndex, if (keys) |slice| slice.len else 0), + &c.kCFTypeDictionaryKeyCallBacks, + &c.kCFTypeDictionaryValueCallBacks, + ))) orelse Allocator.Error.OutOfMemory; } pub fn release(self: *Dictionary) void { @@ -29,36 +30,29 @@ pub const Dictionary = opaque { } pub fn getCount(self: *Dictionary) usize { - return CFDictionaryGetCount(self); + return @intCast(usize, c.CFDictionaryGetCount(@ptrCast(c.CFDictionaryRef, self))); } - pub extern "c" fn CFDictionaryCreate( - allocator: ?*anyopaque, - keys: ?[*]*const anyopaque, - values: ?[*]*const anyopaque, - num_values: usize, - key_callbacks: *const anyopaque, - value_callbacks: *const anyopaque, - ) ?*Dictionary; - pub extern "c" fn CFDictionaryGetCount(*Dictionary) usize; - - extern "c" var kCFTypeDictionaryKeyCallBacks: anyopaque; - extern "c" var kCFTypeDictionaryValueCallBacks: anyopaque; + pub fn getValue(self: *Dictionary, comptime V: type, key: ?*const anyopaque) ?*V { + return @intToPtr(?*V, @ptrToInt(c.CFDictionaryGetValue( + @ptrCast(c.CFDictionaryRef, self), + key, + ))); + } }; -// Just used for a test -extern "c" var kCFURLIsPurgeableKey: *const anyopaque; - test "dictionary" { const testing = std.testing; const str = try foundation.String.createWithBytes("hello", .unicode, false); defer str.release(); - var keys = [_]*const anyopaque{kCFURLIsPurgeableKey}; - var values = [_]*const anyopaque{str}; + var keys = [_]?*const anyopaque{c.kCFURLIsPurgeableKey}; + var values = [_]?*const anyopaque{str}; const dict = try Dictionary.create(&keys, &values); defer dict.release(); try testing.expectEqual(@as(usize, 1), dict.getCount()); + try testing.expect(dict.getValue(foundation.String, c.kCFURLIsPurgeableKey) != null); + try testing.expect(dict.getValue(foundation.String, c.kCFURLIsVolumeKey) == null); } diff --git a/pkg/macos/foundation/number.zig b/pkg/macos/foundation/number.zig new file mode 100644 index 000000000..289c9e8f1 --- /dev/null +++ b/pkg/macos/foundation/number.zig @@ -0,0 +1,79 @@ +const std = @import("std"); +const Allocator = std.mem.Allocator; +const foundation = @import("../foundation.zig"); +const c = @import("c.zig"); + +pub const Number = opaque { + pub fn create( + comptime type_: NumberType, + value: *const type_.ValueType(), + ) Allocator.Error!*Number { + return @intToPtr(?*Number, @ptrToInt(c.CFNumberCreate( + null, + @enumToInt(type_), + value, + ))) orelse Allocator.Error.OutOfMemory; + } + + pub fn getValue(self: *Number, comptime t: NumberType, ptr: *t.ValueType()) bool { + return c.CFNumberGetValue( + @ptrCast(c.CFNumberRef, self), + @enumToInt(t), + ptr, + ) == 1; + } + + pub fn release(self: *Number) void { + c.CFRelease(self); + } +}; + +pub const NumberType = enum(c.CFNumberType) { + sint8 = c.kCFNumberSInt8Type, + sint16 = c.kCFNumberSInt16Type, + sint32 = c.kCFNumberSInt32Type, + sint64 = c.kCFNumberSInt64Type, + float32 = c.kCFNumberFloat32Type, + float64 = c.kCFNumberFloat64Type, + char = c.kCFNumberCharType, + short = c.kCFNumberShortType, + int = c.kCFNumberIntType, + long = c.kCFNumberLongType, + long_long = c.kCFNumberLongLongType, + float = c.kCFNumberFloatType, + double = c.kCFNumberDoubleType, + cf_index = c.kCFNumberCFIndexType, + ns_integer = c.kCFNumberNSIntegerType, + cg_float = c.kCFNumberCGFloatType, + + pub fn ValueType(self: NumberType) type { + return switch (self) { + .sint8 => i8, + .sint16 => i16, + .sint32 => i32, + .sint64 => i64, + .float32 => f32, + .float64 => f64, + .char => u8, + .short => c_short, + .int => c_int, + .long => c_long, + .long_long => c_longlong, + .float => f32, + .double => f64, + else => unreachable, // TODO + }; + } +}; + +test { + const testing = std.testing; + + const inner: i8 = 42; + const v = try Number.create(.sint8, &inner); + defer v.release(); + + var result: i8 = undefined; + try testing.expect(v.getValue(.sint8, &result)); + try testing.expectEqual(result, inner); +} From 623e7f5916dd7acea2655c3828d77d042a25345e Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 1 Oct 2022 16:00:52 -0700 Subject: [PATCH 14/15] macos/text: FontTraits --- pkg/macos/text/font_descriptor.zig | 95 +++++++++++++++++++++++++++--- 1 file changed, 86 insertions(+), 9 deletions(-) diff --git a/pkg/macos/text/font_descriptor.zig b/pkg/macos/text/font_descriptor.zig index 1f679a617..4de03a259 100644 --- a/pkg/macos/text/font_descriptor.zig +++ b/pkg/macos/text/font_descriptor.zig @@ -87,27 +87,104 @@ pub const FontAttribute = enum { .style_name => *foundation.String, .traits => *foundation.Dictionary, .variation => *foundation.Dictionary, - .size => *anyopaque, // CFNumber + .size => *foundation.Number, .matrix => *anyopaque, // CFDataRef .cascade_list => *foundation.Array, .character_set => *anyopaque, // CFCharacterSetRef .languages => *foundation.Array, - .baseline_adjust => *anyopaque, // CFNumber - .macintosh_encodings => *anyopaque, // CFNumber + .baseline_adjust => *foundation.Number, + .macintosh_encodings => *foundation.Number, .features => *foundation.Array, .feature_settings => *foundation.Array, - .fixed_advance => *anyopaque, // CFNumber - .orientation => *anyopaque, // CFNumber - .format => *anyopaque, // CFNumber - .registration_scope => *anyopaque, // CFNumber - .priority => *anyopaque, // CFNumber - .enabled => *anyopaque, // CFNumber + .fixed_advance => *foundation.Number, + .orientation => *foundation.Number, + .format => *foundation.Number, + .registration_scope => *foundation.Number, + .priority => *foundation.Number, + .enabled => *foundation.Number, .downloadable => *anyopaque, // CFBoolean .downloaded => *anyopaque, // CFBoolean }; } }; +pub const FontTraitKey = enum { + symbolic, + weight, + width, + slant, + + pub fn key(self: FontTraitKey) *foundation.String { + return @intToPtr(*foundation.String, @ptrToInt(switch (self) { + .symbolic => c.kCTFontSymbolicTrait, + .weight => c.kCTFontWeightTrait, + .width => c.kCTFontWidthTrait, + .slant => c.kCTFontFontSlantTrait, + })); + } + + pub fn Value(self: FontTraitKey) type { + return switch (self) { + .symbolic => *foundation.Number, + .weight => *foundation.Number, + .width => *foundation.Number, + .slant => *foundation.Number, + }; + } +}; + +pub const FontSymbolicTraits = packed struct { + italic: bool = false, + bold: bool = false, + _unused1: u3 = 0, + expanded: bool = false, + condensed: bool = false, + _unused2: u3 = 0, + monospace: bool = false, + vertical: bool = false, + ui_optimized: bool = false, + color_glyphs: bool = false, + composite: bool = false, + _padding: u17 = 0, + + pub fn init(num: *foundation.Number) FontSymbolicTraits { + var raw: i32 = undefined; + _ = num.getValue(.sint32, &raw); + return @bitCast(FontSymbolicTraits, raw); + } + + test { + try std.testing.expectEqual( + @bitSizeOf(c.CTFontSymbolicTraits), + @bitSizeOf(FontSymbolicTraits), + ); + } + + test "bitcast" { + const actual: c.CTFontSymbolicTraits = c.kCTFontTraitMonoSpace | c.kCTFontTraitExpanded; + const expected: FontSymbolicTraits = .{ + .monospace = true, + .expanded = true, + }; + + try std.testing.expectEqual(actual, @bitCast(c.CTFontSymbolicTraits, expected)); + } + + test "number" { + const raw: i32 = c.kCTFontTraitMonoSpace | c.kCTFontTraitExpanded; + const num = try foundation.Number.create(.sint32, &raw); + defer num.release(); + + const expected: FontSymbolicTraits = .{ .monospace = true, .expanded = true }; + const actual = FontSymbolicTraits.init(num); + try std.testing.expect(std.meta.eql(expected, actual)); + } +}; + +test { + @import("std").testing.refAllDecls(@This()); +} + test "descriptor" { const testing = std.testing; From c6dc2a3529ec5d3e5698ab56e572ded1c4e85b11 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 1 Oct 2022 16:10:50 -0700 Subject: [PATCH 15/15] macos/text: create collection from descriptors --- pkg/macos/text/font_collection.zig | 104 ++++++++++++++++++++--------- 1 file changed, 71 insertions(+), 33 deletions(-) diff --git a/pkg/macos/text/font_collection.zig b/pkg/macos/text/font_collection.zig index b1e8411f3..686db2be2 100644 --- a/pkg/macos/text/font_collection.zig +++ b/pkg/macos/text/font_collection.zig @@ -12,6 +12,16 @@ pub const FontCollection = opaque { ) orelse Allocator.Error.OutOfMemory; } + pub fn createWithFontDescriptors(descs: *foundation.Array) Allocator.Error!*FontCollection { + return @intToPtr( + ?*FontCollection, + @ptrToInt(c.CTFontCollectionCreateWithFontDescriptors( + @ptrCast(c.CFArrayRef, descs), + null, + )), + ) orelse Allocator.Error.OutOfMemory; + } + pub fn release(self: *FontCollection) void { c.CFRelease(self); } @@ -26,6 +36,41 @@ pub const FontCollection = opaque { } }; +fn debugDumpList(list: *foundation.Array) !void { + var i: usize = 0; + while (i < list.getCount()) : (i += 1) { + const desc = list.getValueAtIndex(text.FontDescriptor, i); + { + var buf: [128]u8 = undefined; + const name = desc.copyAttribute(.name); + defer name.release(); + const cstr = name.cstring(&buf, .utf8).?; + + var buf2: [128]u8 = undefined; + const url = desc.copyAttribute(.url); + defer url.release(); + const path = path: { + const blank = try foundation.String.createWithBytes("", .utf8, false); + defer blank.release(); + + const path = url.copyPath() orelse break :path ""; + defer path.release(); + + const decoded = try foundation.URL.createStringByReplacingPercentEscapes( + path, + blank, + ); + defer decoded.release(); + + break :path decoded.cstring(&buf2, .utf8) orelse + ""; + }; + + std.log.warn("i={d} name={s} path={s}", .{ i, cstr, path }); + } + } +} + test "collection" { const testing = std.testing; @@ -36,37 +81,30 @@ test "collection" { defer list.release(); try testing.expect(list.getCount() > 0); - - // var i: usize = 0; - // while (i < list.getCount()) : (i += 1) { - // const desc = list.getValueAtIndex(text.FontDescriptor, i); - // { - // var buf: [128]u8 = undefined; - // const name = desc.copyAttribute(.name); - // defer name.release(); - // const cstr = name.cstring(&buf, .utf8).?; - // - // var buf2: [128]u8 = undefined; - // const url = desc.copyAttribute(.url); - // defer url.release(); - // const path = path: { - // const blank = try foundation.String.createWithBytes("", .utf8, false); - // defer blank.release(); - // - // const path = url.copyPath() orelse break :path ""; - // defer path.release(); - // - // const decoded = try foundation.URL.createStringByReplacingPercentEscapes( - // path, - // blank, - // ); - // defer decoded.release(); - // - // break :path decoded.cstring(&buf2, .utf8) orelse - // ""; - // }; - // - // std.log.warn("i={d} name={s} path={s}", .{ i, cstr, path }); - // } - // } +} + +test "from descriptors" { + const testing = std.testing; + + const name = try foundation.String.createWithBytes("AppleColorEmoji", .utf8, false); + defer name.release(); + + const desc = try text.FontDescriptor.createWithNameAndSize(name, 12); + defer desc.release(); + + const arr = try foundation.Array.create( + text.FontDescriptor, + &[_]*const text.FontDescriptor{desc}, + ); + defer arr.release(); + + const v = try FontCollection.createWithFontDescriptors(arr); + defer v.release(); + + const list = v.createMatchingFontDescriptors(); + defer list.release(); + + try testing.expect(list.getCount() > 0); + + //try debugDumpList(list); }