chore: import upstream snapshot with attribution
Test / build-libghostty-vt (x86_64-windows-gnu) (push) Has been cancelled
Test / build-libghostty-vt-macos (aarch64-ios) (push) Has been cancelled
Test / build-libghostty-vt-macos (aarch64-macos) (push) Has been cancelled
Test / build-libghostty-vt-macos (x86_64-macos) (push) Has been cancelled
Test / build-nix (namespace-profile-ghostty-md-arm64) (push) Has been cancelled
Test / build-dist (push) Has been cancelled
Test / GTK x11=true wayland=true (push) Has been cancelled
Test / Build -Dsimd=false (push) Has been cancelled
Test / Build -Dsimd=true (push) Has been cancelled
Test / Build -Dsentry=false (push) Has been cancelled
Test / Build -Dsentry=true (push) Has been cancelled
Test / zig-fmt (push) Has been cancelled
Test / GitHub Actions Pins (push) Has been cancelled
Test / prettier (push) Has been cancelled
Test / swiftlint (push) Has been cancelled
Test / alejandra (push) Has been cancelled
Test / typos (push) Has been cancelled
Nix / Required Checks: Nix (push) Has been cancelled
Nix / check-zig-cache-hash (push) Has been cancelled
Test / skip (push) Has been cancelled
Test / Required Checks: Test (push) Has been cancelled
Test / build-bench (push) Has been cancelled
Test / list-examples (push) Has been cancelled
Test / Example ${{ matrix.dir }} (Windows) (push) Has been cancelled
Test / build-cmake (push) Has been cancelled
Test / test-lib-vt-pkgconfig (push) Has been cancelled
Test / build-flatpak (push) Has been cancelled
Test / build-snap (push) Has been cancelled
Test / build-libghostty-vt (aarch64-linux) (push) Has been cancelled
Test / build-libghostty-vt (aarch64-macos) (push) Has been cancelled
Test / build-libghostty-vt (wasm32-freestanding) (push) Has been cancelled
Test / build-libghostty-vt (x86_64-linux) (push) Has been cancelled
Test / build-libghostty-vt (x86_64-linux-musl) (push) Has been cancelled
Test / build-libghostty-vt (x86_64-macos) (push) Has been cancelled
Test / build-libghostty-vt-android (aarch64-linux-android) (push) Has been cancelled
Test / build-libghostty-vt-android (arm-linux-androideabi) (push) Has been cancelled
Test / build-libghostty-vt-android (x86_64-linux-android) (push) Has been cancelled
Test / build-libghostty-vt-windows (push) Has been cancelled
Test / build-libghostty-windows-gnu (push) Has been cancelled
Test / build-linux (namespace-profile-ghostty-md) (push) Has been cancelled
Test / build-linux (namespace-profile-ghostty-md-arm64) (push) Has been cancelled
Test / build-linux-libghostty (push) Has been cancelled
Test / build-nix (namespace-profile-ghostty-md) (push) Has been cancelled
Test / build-dist-lib-vt (push) Has been cancelled
Test / trigger-snap (push) Has been cancelled
Test / trigger-flatpak (push) Has been cancelled
Test / build-macos (push) Has been cancelled
Test / build-macos-freetype (push) Has been cancelled
Test / test (push) Has been cancelled
Test / test-lib-vt (push) Has been cancelled
Test / GTK x11=false wayland=false (push) Has been cancelled
Test / GTK x11=true wayland=false (push) Has been cancelled
Test / GTK x11=false wayland=true (push) Has been cancelled
Test / test-macos (push) Has been cancelled
Test / test-windows (push) Has been cancelled
Test / Build -Di18n=false (push) Has been cancelled
Test / Build -Di18n=true (push) Has been cancelled
Test / Build test/fuzz-libghostty (push) Has been cancelled
Test / shellcheck (push) Has been cancelled
Test / translations (push) Has been cancelled
Test / blueprint-compiler (push) Has been cancelled
Test / Test pkg/wuffs (push) Has been cancelled
Test / Test build on Debian 13 (push) Has been cancelled
Test / valgrind (push) Has been cancelled
Test / Example ${{ matrix.dir }} (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 12:02:48 +08:00
commit bd44b5aa08
5770 changed files with 506778 additions and 0 deletions
+10
View File
@@ -0,0 +1,10 @@
pub const c = @import("animation/c.zig").c;
/// https://developer.apple.com/documentation/quartzcore/calayer/contents_gravity_values?language=objc
pub extern "c" const kCAGravityTopLeft: *anyopaque;
pub extern "c" const kCAGravityBottomLeft: *anyopaque;
pub extern "c" const kCAGravityCenter: *anyopaque;
test {
@import("std").testing.refAllDecls(@This());
}
+1
View File
@@ -0,0 +1 @@
pub const c = @import("../main.zig").c;
+82
View File
@@ -0,0 +1,82 @@
const std = @import("std");
const builtin = @import("builtin");
const apple_sdk = @import("apple_sdk");
pub fn build(b: *std.Build) !void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const module = b.addModule("macos", .{
.root_source_file = b.path("main.zig"),
.target = target,
.optimize = optimize,
});
const lib = b.addLibrary(.{
.name = "macos",
.root_module = b.createModule(.{
.target = target,
.optimize = optimize,
}),
.linkage = .static,
});
lib.addCSourceFile(.{
.file = b.path("os/zig_macos.c"),
.flags = &.{"-std=c99"},
});
lib.addCSourceFile(.{
.file = b.path("text/ext.c"),
});
lib.linkFramework("CoreFoundation");
lib.linkFramework("CoreGraphics");
lib.linkFramework("CoreText");
lib.linkFramework("CoreVideo");
lib.linkFramework("QuartzCore");
lib.linkFramework("IOSurface");
if (target.result.os.tag == .macos) {
lib.linkFramework("Carbon");
module.linkFramework("Carbon", .{});
}
if (target.result.os.tag.isDarwin()) {
module.linkFramework("CoreFoundation", .{});
module.linkFramework("CoreGraphics", .{});
module.linkFramework("CoreText", .{});
module.linkFramework("CoreVideo", .{});
module.linkFramework("QuartzCore", .{});
module.linkFramework("IOSurface", .{});
try apple_sdk.addPaths(b, lib);
}
b.installArtifact(lib);
{
const test_exe = b.addTest(.{
.name = "test",
.root_module = b.createModule(.{
.root_source_file = b.path("main.zig"),
.target = target,
.optimize = optimize,
}),
});
if (target.result.os.tag.isDarwin()) {
try apple_sdk.addPaths(b, test_exe);
}
test_exe.linkLibrary(lib);
var it = module.import_table.iterator();
while (it.next()) |entry| {
test_exe.root_module.addImport(
entry.key_ptr.*,
entry.value_ptr.*,
);
}
b.installArtifact(test_exe);
const tests_run = b.addRunArtifact(test_exe);
const test_step = b.step("test", "Run tests");
test_step.dependOn(&tests_run.step);
}
}
+9
View File
@@ -0,0 +1,9 @@
.{
.name = .macos,
.version = "0.1.0",
.fingerprint = 0x45e2f6107d5b2b2c,
.paths = .{""},
.dependencies = .{
.apple_sdk = .{ .path = "../apple-sdk" },
},
}
+5
View File
@@ -0,0 +1,5 @@
pub const c = @import("carbon/c.zig").c;
test {
@import("std").testing.refAllDecls(@This());
}
+1
View File
@@ -0,0 +1 @@
pub const c = @import("../main.zig").c;
+18
View File
@@ -0,0 +1,18 @@
pub const c = @import("dispatch/c.zig").c;
pub const data = @import("dispatch/data.zig");
pub const queue = @import("dispatch/queue.zig");
pub const Data = data.Data;
pub extern "c" fn dispatch_sync(
queue: *anyopaque,
block: *anyopaque,
) void;
pub extern "c" fn dispatch_async(
queue: *anyopaque,
block: *anyopaque,
) void;
test {
@import("std").testing.refAllDecls(@This());
}
+1
View File
@@ -0,0 +1 @@
pub const c = @import("../main.zig").c;
+31
View File
@@ -0,0 +1,31 @@
const std = @import("std");
const foundation = @import("../foundation.zig");
const c = @import("c.zig").c;
pub const Data = opaque {
pub const DESTRUCTOR_DEFAULT = c.DISPATCH_DATA_DESTRUCTOR_DEFAULT;
pub fn create(
data: []const u8,
queue: ?*anyopaque,
destructor: ?*anyopaque,
) !*Data {
return dispatch_data_create(
data.ptr,
data.len,
queue,
destructor,
) orelse return error.OutOfMemory;
}
pub fn release(data: *Data) void {
foundation.c.CFRelease(data);
}
};
extern "c" fn dispatch_data_create(
data: [*]const u8,
len: usize,
queue: ?*anyopaque,
destructor: ?*anyopaque,
) ?*Data;
+8
View File
@@ -0,0 +1,8 @@
const std = @import("std");
const c = @import("c.zig").c;
pub const Queue = *anyopaque; // dispatch_queue_t
pub fn getMain() Queue {
return c.dispatch_get_main_queue().?;
}
+37
View File
@@ -0,0 +1,37 @@
const array = @import("foundation/array.zig");
const attributed_string = @import("foundation/attributed_string.zig");
const base = @import("foundation/base.zig");
const character_set = @import("foundation/character_set.zig");
const data = @import("foundation/data.zig");
const dictionary = @import("foundation/dictionary.zig");
const number = @import("foundation/number.zig");
const string = @import("foundation/string.zig");
const typepkg = @import("foundation/type.zig");
const url = @import("foundation/url.zig");
pub const c = @import("foundation/c.zig").c;
pub const Array = array.Array;
pub const MutableArray = array.MutableArray;
pub const AttributedString = attributed_string.AttributedString;
pub const MutableAttributedString = attributed_string.MutableAttributedString;
pub const ComparisonResult = base.ComparisonResult;
pub const Range = base.Range;
pub const FourCharCode = base.FourCharCode;
pub const CharacterSet = character_set.CharacterSet;
pub const Data = data.Data;
pub const Dictionary = dictionary.Dictionary;
pub const MutableDictionary = dictionary.MutableDictionary;
pub const Number = number.Number;
pub const String = string.String;
pub const MutableString = string.MutableString;
pub const StringComparison = string.StringComparison;
pub const StringEncoding = string.StringEncoding;
pub const stringGetSurrogatePairForLongCharacter = string.stringGetSurrogatePairForLongCharacter;
pub const URL = url.URL;
pub const URLPathStyle = url.URLPathStyle;
pub const CFRelease = typepkg.CFRelease;
pub const CFRetain = typepkg.CFRetain;
test {
@import("std").testing.refAllDecls(@This());
}
+176
View File
@@ -0,0 +1,176 @@
const std = @import("std");
const Allocator = std.mem.Allocator;
const base = @import("base.zig");
const c = @import("c.zig").c;
const cftype = @import("type.zig");
const ComparisonResult = base.ComparisonResult;
const Range = base.Range;
pub const Array = opaque {
pub fn create(comptime T: type, values: []*const T) Allocator.Error!*Array {
return CFArrayCreate(
null,
@ptrCast(values.ptr),
@intCast(values.len),
null,
) orelse error.OutOfMemory;
}
pub fn release(self: *Array) void {
cftype.CFRelease(self);
}
pub fn getCount(self: *Array) usize {
return CFArrayGetCount(self);
}
/// 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(@alignCast(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) *anyopaque;
extern "c" var kCFTypeArrayCallBacks: anyopaque;
};
pub const MutableArray = opaque {
pub fn create() Allocator.Error!*MutableArray {
return CFArrayCreateMutable(
null,
0,
&c.kCFTypeArrayCallBacks,
) orelse error.OutOfMemory;
}
pub fn createCopy(array: *Array) Allocator.Error!*MutableArray {
return CFArrayCreateMutableCopy(
null,
0,
array,
) orelse error.OutOfMemory;
}
pub fn release(self: *MutableArray) void {
cftype.CFRelease(self);
}
pub fn appendValue(
self: *MutableArray,
comptime Elem: type,
value: *const Elem,
) void {
CFArrayAppendValue(self, @ptrCast(@constCast(value)));
}
pub fn removeValue(self: *MutableArray, idx: usize) void {
CFArrayRemoveValueAtIndex(self, idx);
}
pub fn sortValues(
self: *MutableArray,
comptime Elem: type,
comptime Context: type,
context: ?*Context,
comptime comparator: ?*const fn (
a: *const Elem,
b: *const Elem,
context: ?*Context,
) callconv(.c) ComparisonResult,
) void {
CFArraySortValues(
self,
Range.init(0, Array.CFArrayGetCount(@ptrCast(self))),
comparator,
context,
);
}
extern "c" fn CFArrayCreateMutable(
allocator: ?*anyopaque,
capacity: usize,
callbacks: ?*const anyopaque,
) ?*MutableArray;
extern "c" fn CFArrayCreateMutableCopy(
allocator: ?*anyopaque,
capacity: usize,
array: *Array,
) ?*MutableArray;
extern "c" fn CFArrayAppendValue(
*MutableArray,
*anyopaque,
) void;
extern "c" fn CFArrayRemoveValueAtIndex(
*MutableArray,
usize,
) void;
extern "c" fn CFArraySortValues(
array: *MutableArray,
range: Range,
comparator: ?*const anyopaque,
context: ?*anyopaque,
) void;
};
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.*);
}
// Can make it mutable
var mut = try MutableArray.createCopy(arr);
defer mut.release();
}
test "array sorting" {
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();
const mut = try MutableArray.createCopy(arr);
defer mut.release();
mut.sortValues(
u8,
void,
null,
struct {
fn compare(a: *const u8, b: *const u8, _: ?*void) callconv(.c) ComparisonResult {
if (a.* > b.*) return .greater;
if (a.* == b.*) return .equal;
return .less;
}
}.compare,
);
{
const mutarr: *Array = @ptrCast(mut);
const ch = mutarr.getValueAtIndex(u8, 0);
try testing.expectEqual(@as(u8, 'e'), ch.*);
}
{
const mutarr: *Array = @ptrCast(mut);
const ch = mutarr.getValueAtIndex(u8, 1);
try testing.expectEqual(@as(u8, 'h'), ch.*);
}
}
+103
View File
@@ -0,0 +1,103 @@
const std = @import("std");
const assert = std.debug.assert;
const Allocator = std.mem.Allocator;
const foundation = @import("../foundation.zig");
const text = @import("../text.zig");
const c = @import("c.zig").c;
pub const AttributedString = opaque {
pub fn create(
str: *foundation.String,
attributes: *foundation.Dictionary,
) Allocator.Error!*AttributedString {
return @ptrCast(@constCast(c.CFAttributedStringCreate(
null,
@ptrCast(str),
@ptrCast(attributes),
) orelse return Allocator.Error.OutOfMemory));
}
pub fn release(self: *AttributedString) void {
foundation.CFRelease(self);
}
pub fn getLength(self: *AttributedString) usize {
return @intCast(c.CFAttributedStringGetLength(@ptrCast(self)));
}
pub fn getString(self: *AttributedString) *foundation.String {
return @ptrFromInt(@intFromPtr(
c.CFAttributedStringGetString(@ptrCast(self)),
));
}
};
pub const MutableAttributedString = opaque {
pub fn create(cap: usize) Allocator.Error!*MutableAttributedString {
return @as(
?*MutableAttributedString,
@ptrFromInt(@intFromPtr(c.CFAttributedStringCreateMutable(
null,
@intCast(cap),
))),
) orelse Allocator.Error.OutOfMemory;
}
pub fn release(self: *MutableAttributedString) void {
foundation.CFRelease(self);
}
pub fn replaceString(
self: *MutableAttributedString,
range: foundation.Range,
replacement: *foundation.String,
) void {
c.CFAttributedStringReplaceString(
@ptrCast(self),
@bitCast(range),
@ptrCast(replacement),
);
}
pub fn setAttribute(
self: *MutableAttributedString,
range: foundation.Range,
key: anytype,
value: ?*anyopaque,
) void {
const T = @TypeOf(key);
const info = @typeInfo(T);
const Key = if (info != .pointer) T else info.pointer.child;
const key_arg = if (@hasDecl(Key, "key"))
key.key()
else
key;
c.CFAttributedStringSetAttribute(
@ptrCast(self),
@bitCast(range),
@ptrCast(key_arg),
value,
);
}
pub fn getLength(self: *MutableAttributedString) usize {
return @intCast(c.CFAttributedStringGetLength(@ptrCast(self)));
}
};
test "mutable attributed string" {
//const testing = std.testing;
const str = try MutableAttributedString.create(0);
defer str.release();
{
const rep = try foundation.String.createWithBytes("hello", .utf8, false);
defer rep.release();
str.replaceString(foundation.Range.init(0, 0), rep);
}
str.setAttribute(foundation.Range.init(0, 0), text.FontAttribute.url, null);
str.setAttribute(foundation.Range.init(0, 0), text.FontAttribute.name.key(), null);
}
+35
View File
@@ -0,0 +1,35 @@
const std = @import("std");
const assert = std.debug.assert;
const c = @import("c.zig").c;
pub const ComparisonResult = enum(c_int) {
less = -1,
equal = 0,
greater = 1,
};
pub const Range = extern struct {
location: c.CFIndex,
length: c.CFIndex,
pub fn init(loc: usize, len: usize) Range {
return @bitCast(c.CFRangeMake(@intCast(loc), @intCast(len)));
}
};
pub const FourCharCode = packed struct(u32) {
d: u8,
c: u8,
b: u8,
a: u8,
pub fn init(v: *const [4]u8) FourCharCode {
return .{ .a = v[0], .b = v[1], .c = v[2], .d = v[3] };
}
/// Converts the ID to a string. The return value is only valid
/// for the lifetime of the self pointer.
pub fn str(self: FourCharCode) [4]u8 {
return .{ self.a, self.b, self.c, self.d };
}
};
+1
View File
@@ -0,0 +1 @@
pub const c = @import("../main.zig").c;
+49
View File
@@ -0,0 +1,49 @@
const std = @import("std");
const assert = std.debug.assert;
const Allocator = std.mem.Allocator;
const foundation = @import("../foundation.zig");
const c = @import("c.zig").c;
pub const CharacterSet = opaque {
pub fn createWithCharactersInString(
str: *foundation.String,
) Allocator.Error!*CharacterSet {
return @as(?*CharacterSet, @ptrFromInt(@intFromPtr(c.CFCharacterSetCreateWithCharactersInString(
null,
@ptrCast(str),
)))) orelse Allocator.Error.OutOfMemory;
}
pub fn createWithCharactersInRange(
range: foundation.Range,
) Allocator.Error!*CharacterSet {
return @as(?*CharacterSet, @ptrFromInt(@intFromPtr(c.CFCharacterSetCreateWithCharactersInRange(
null,
@bitCast(range),
)))) orelse Allocator.Error.OutOfMemory;
}
pub fn release(self: *CharacterSet) void {
c.CFRelease(self);
}
};
test "character set" {
//const testing = std.testing;
const str = try foundation.String.createWithBytes("hello world", .ascii, false);
defer str.release();
const cs = try CharacterSet.createWithCharactersInString(str);
defer cs.release();
}
test "character set range" {
//const testing = std.testing;
const cs = try CharacterSet.createWithCharactersInRange(.{
.location = 'A',
.length = 1,
});
defer cs.release();
}
+38
View File
@@ -0,0 +1,38 @@
const std = @import("std");
const Allocator = std.mem.Allocator;
const foundation = @import("../foundation.zig");
const c = @import("c.zig").c;
pub const Data = opaque {
pub fn createWithBytesNoCopy(data: []const u8) Allocator.Error!*Data {
return @as(
?*Data,
@ptrFromInt(@intFromPtr(c.CFDataCreateWithBytesNoCopy(
null,
data.ptr,
@intCast(data.len),
c.kCFAllocatorNull,
))),
) orelse error.OutOfMemory;
}
pub fn release(self: *Data) void {
foundation.CFRelease(self);
}
pub fn getPointer(self: *Data) [*]const u8 {
return @ptrCast(c.CFDataGetBytePtr(@ptrCast(self)));
}
pub fn getLength(self: *Data) usize {
return @intCast(c.CFDataGetLength(@ptrCast(self)));
}
};
test {
//const testing = std.testing;
const raw = "hello world";
const data = try Data.createWithBytesNoCopy(raw);
defer data.release();
}
+125
View File
@@ -0,0 +1,125 @@
const std = @import("std");
const assert = std.debug.assert;
const Allocator = std.mem.Allocator;
const foundation = @import("../foundation.zig");
const c = @import("c.zig").c;
pub const Dictionary = opaque {
pub fn create(
keys: ?[]const ?*const anyopaque,
values: ?[]const ?*const anyopaque,
) Allocator.Error!*Dictionary {
if (keys != null or values != null) {
assert(keys != null);
assert(values != null);
assert(keys.?.len == values.?.len);
}
return @as(?*Dictionary, @ptrFromInt(@intFromPtr(c.CFDictionaryCreate(
null,
@ptrCast(@constCast(if (keys) |slice| slice.ptr else null)),
@ptrCast(@constCast(if (values) |slice| slice.ptr else null)),
@intCast(if (keys) |slice| slice.len else 0),
&c.kCFTypeDictionaryKeyCallBacks,
&c.kCFTypeDictionaryValueCallBacks,
)))) orelse Allocator.Error.OutOfMemory;
}
pub fn release(self: *Dictionary) void {
foundation.CFRelease(self);
}
pub fn getCount(self: *Dictionary) usize {
return @intCast(c.CFDictionaryGetCount(@ptrCast(self)));
}
pub fn getValue(self: *Dictionary, comptime V: type, key: ?*const anyopaque) ?*V {
return @ptrFromInt(@intFromPtr(c.CFDictionaryGetValue(
@ptrCast(self),
key,
)));
}
pub fn getKeysAndValues(self: *Dictionary, alloc: Allocator) !struct {
keys: []?*const anyopaque,
values: []?*const anyopaque,
} {
const count = self.getCount();
const keys = try alloc.alloc(?*const anyopaque, count);
errdefer alloc.free(keys);
const values = try alloc.alloc(?*const anyopaque, count);
errdefer alloc.free(values);
c.CFDictionaryGetKeysAndValues(
@ptrCast(self),
@ptrCast(keys.ptr),
@ptrCast(values.ptr),
);
return .{ .keys = keys, .values = values };
}
};
pub const MutableDictionary = opaque {
pub fn create(cap: usize) Allocator.Error!*MutableDictionary {
return @as(?*MutableDictionary, @ptrFromInt(@intFromPtr(c.CFDictionaryCreateMutable(
null,
@intCast(cap),
&c.kCFTypeDictionaryKeyCallBacks,
&c.kCFTypeDictionaryValueCallBacks,
)))) orelse Allocator.Error.OutOfMemory;
}
pub fn createMutableCopy(cap: usize, src: *Dictionary) Allocator.Error!*MutableDictionary {
return @as(?*MutableDictionary, @ptrFromInt(@intFromPtr(c.CFDictionaryCreateMutableCopy(
null,
@intCast(cap),
@ptrCast(src),
)))) orelse Allocator.Error.OutOfMemory;
}
pub fn release(self: *MutableDictionary) void {
foundation.CFRelease(self);
}
pub fn setValue(self: *MutableDictionary, key: ?*const anyopaque, value: ?*const anyopaque) void {
c.CFDictionarySetValue(
@ptrCast(self),
key,
value,
);
}
};
test "dictionary" {
const testing = std.testing;
const str = try foundation.String.createWithBytes("hello", .unicode, false);
defer str.release();
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);
}
test "mutable dictionary" {
const testing = std.testing;
const dict = try MutableDictionary.create(0);
defer dict.release();
const str = try foundation.String.createWithBytes("hello", .unicode, false);
defer str.release();
dict.setValue(c.kCFURLIsPurgeableKey, str);
{
const imm = @as(*Dictionary, @ptrCast(dict));
try testing.expectEqual(@as(usize, 1), imm.getCount());
try testing.expect(imm.getValue(foundation.String, c.kCFURLIsPurgeableKey) != null);
try testing.expect(imm.getValue(foundation.String, c.kCFURLIsVolumeKey) == null);
}
}
+79
View File
@@ -0,0 +1,79 @@
const std = @import("std");
const Allocator = std.mem.Allocator;
const foundation = @import("../foundation.zig");
const c = @import("c.zig").c;
pub const Number = opaque {
pub fn create(
comptime type_: NumberType,
value: *const type_.ValueType(),
) Allocator.Error!*Number {
return @as(?*Number, @ptrFromInt(@intFromPtr(c.CFNumberCreate(
null,
@intFromEnum(type_),
value,
)))) orelse Allocator.Error.OutOfMemory;
}
pub fn getValue(self: *const Number, comptime t: NumberType, ptr: *t.ValueType()) bool {
return c.CFNumberGetValue(
@ptrCast(self),
@intFromEnum(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(comptime 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);
}
+174
View File
@@ -0,0 +1,174 @@
const std = @import("std");
const assert = std.debug.assert;
const Allocator = std.mem.Allocator;
const foundation = @import("../foundation.zig");
const c = @import("c.zig").c;
pub const String = opaque {
pub fn createWithBytes(
bs: []const u8,
encoding: StringEncoding,
external: bool,
) Allocator.Error!*String {
return @as(?*String, @ptrFromInt(@intFromPtr(c.CFStringCreateWithBytes(
null,
bs.ptr,
@intCast(bs.len),
@intFromEnum(encoding),
@intFromBool(external),
)))) orelse Allocator.Error.OutOfMemory;
}
pub fn createWithCharactersNoCopy(
unichars: []const u16,
) *String {
return @as(*String, @ptrFromInt(@intFromPtr(c.CFStringCreateWithCharactersNoCopy(
null,
@ptrCast(unichars.ptr),
@intCast(unichars.len),
foundation.c.kCFAllocatorNull,
))));
}
pub fn release(self: *String) void {
c.CFRelease(self);
}
pub fn getLength(self: *String) usize {
return @intCast(c.CFStringGetLength(@ptrCast(self)));
}
pub fn hasPrefix(self: *String, prefix: *String) bool {
return c.CFStringHasPrefix(
@ptrCast(self),
@ptrCast(prefix),
) == 1;
}
pub fn compare(
self: *String,
other: *String,
options: StringComparison,
) foundation.ComparisonResult {
return @enumFromInt(c.CFStringCompare(
@ptrCast(self),
@ptrCast(other),
@intCast(@as(c_int, @bitCast(options))),
));
}
pub fn cstring(self: *String, buf: []u8, encoding: StringEncoding) ?[]const u8 {
if (c.CFStringGetCString(
@ptrCast(self),
buf.ptr,
@intCast(buf.len),
@intFromEnum(encoding),
) == 0) return null;
return std.mem.sliceTo(buf, 0);
}
pub fn cstringPtr(self: *String, encoding: StringEncoding) ?[:0]const u8 {
const ptr = c.CFStringGetCStringPtr(
@ptrCast(self),
@intFromEnum(encoding),
);
if (ptr == null) return null;
return std.mem.sliceTo(ptr, 0);
}
};
pub const MutableString = opaque {
pub fn create(cap: usize) !*MutableString {
return @ptrCast(c.CFStringCreateMutable(
null,
@intCast(cap),
) orelse return Allocator.Error.OutOfMemory);
}
pub fn release(self: *MutableString) void {
foundation.CFRelease(self);
}
pub fn string(self: *MutableString) *String {
return @ptrCast(self);
}
pub fn appendCharacters(self: *MutableString, chars: []const u16) void {
c.CFStringAppendCharacters(
@ptrCast(self),
chars.ptr,
@intCast(chars.len),
);
}
};
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
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,
};
pub fn stringGetSurrogatePairForLongCharacter(
ch: u32,
surrogates: []u16,
) bool {
assert(surrogates.len >= 2);
return c.CFStringGetSurrogatePairForLongCharacter(ch, surrogates.ptr) == 1;
}
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));
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);
}
}
test "unichar" {
const testing = std.testing;
var unichars: [2]u16 = undefined;
try testing.expect(!stringGetSurrogatePairForLongCharacter('A', &unichars));
}
+2
View File
@@ -0,0 +1,2 @@
pub extern "c" fn CFRelease(*anyopaque) void;
pub extern "c" fn CFRetain(*anyopaque) void;
+104
View File
@@ -0,0 +1,104 @@
const std = @import("std");
const Allocator = std.mem.Allocator;
const foundation = @import("../foundation.zig");
const c = @import("c.zig").c;
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 createWithFileSystemPath(
path: *foundation.String,
style: URLPathStyle,
dir: bool,
) Allocator.Error!*URL {
return @as(
?*URL,
@ptrFromInt(@intFromPtr(c.CFURLCreateWithFileSystemPath(
null,
@ptrCast(path),
@intFromEnum(style),
if (dir) 1 else 0,
))),
) 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);
}
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;
pub extern "c" fn CFURLCreateStringByReplacingPercentEscapes(
allocator: ?*anyopaque,
original: *const anyopaque,
escape: *const anyopaque,
) ?*foundation.String;
};
pub const URLPathStyle = enum(c_int) {
posix = c.kCFURLPOSIXPathStyle,
windows = c.kCFURLWindowsPathStyle,
};
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);
}
}
test "path" {
const testing = std.testing;
const str = try foundation.String.createWithBytes("foo/bar.ttf", .utf8, false);
defer str.release();
const url = try URL.createWithFileSystemPath(str, .posix, false);
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/bar.ttf", cstr);
}
}
+24
View File
@@ -0,0 +1,24 @@
const affine_transform = @import("graphics/affine_transform.zig");
const bitmap_context = @import("graphics/bitmap_context.zig");
const color_space = @import("graphics/color_space.zig");
const font = @import("graphics/font.zig");
const geometry = @import("graphics/geometry.zig");
const image = @import("graphics/image.zig");
const path = @import("graphics/path.zig");
pub const c = @import("graphics/c.zig").c;
pub const AffineTransform = affine_transform.AffineTransform;
pub const BitmapContext = bitmap_context.BitmapContext;
pub const ColorSpace = color_space.ColorSpace;
pub const Glyph = font.Glyph;
pub const Point = geometry.Point;
pub const Rect = geometry.Rect;
pub const Size = geometry.Size;
pub const ImageAlphaInfo = image.ImageAlphaInfo;
pub const BitmapInfo = image.BitmapInfo;
pub const Path = path.Path;
pub const MutablePath = path.MutablePath;
test {
@import("std").testing.refAllDecls(@This());
}
+16
View File
@@ -0,0 +1,16 @@
const std = @import("std");
const assert = std.debug.assert;
const c = @import("c.zig").c;
pub const AffineTransform = extern struct {
a: c.CGFloat,
b: c.CGFloat,
c: c.CGFloat,
d: c.CGFloat,
tx: c.CGFloat,
ty: c.CGFloat,
pub fn identity() AffineTransform {
return @bitCast(c.CGAffineTransformIdentity);
}
};
+50
View File
@@ -0,0 +1,50 @@
const std = @import("std");
const assert = std.debug.assert;
const Allocator = std.mem.Allocator;
const graphics = @import("../graphics.zig");
const Context = @import("context.zig").Context;
const c = @import("c.zig").c;
pub const BitmapContext = opaque {
pub const context = Context(BitmapContext);
pub fn create(
data: ?[]u8,
width: usize,
height: usize,
bits_per_component: usize,
bytes_per_row: usize,
space: *graphics.ColorSpace,
opts: c_uint,
) Allocator.Error!*BitmapContext {
return @as(
?*BitmapContext,
@ptrFromInt(@intFromPtr(c.CGBitmapContextCreate(
@ptrCast(if (data) |d| d.ptr else null),
width,
height,
bits_per_component,
bytes_per_row,
@ptrCast(space),
opts,
))),
) orelse Allocator.Error.OutOfMemory;
}
};
test {
//const testing = std.testing;
const cs = try graphics.ColorSpace.createDeviceGray();
defer cs.release();
const ctx = try BitmapContext.create(null, 80, 80, 8, 80, cs, 0);
const context = BitmapContext.context;
defer context.release(ctx);
context.setShouldAntialias(ctx, true);
context.setShouldSmoothFonts(ctx, false);
context.setGrayFillColor(ctx, 1, 1);
context.setGrayStrokeColor(ctx, 1, 1);
context.setTextDrawingMode(ctx, .fill);
context.setTextMatrix(ctx, graphics.AffineTransform.identity());
context.setTextPosition(ctx, 0, 0);
}
+1
View File
@@ -0,0 +1 @@
pub const c = @import("../main.zig").c;
+99
View File
@@ -0,0 +1,99 @@
const std = @import("std");
const assert = std.debug.assert;
const Allocator = std.mem.Allocator;
const c = @import("c.zig").c;
pub const ColorSpace = opaque {
pub fn createDeviceGray() Allocator.Error!*ColorSpace {
return @as(
?*ColorSpace,
@ptrFromInt(@intFromPtr(c.CGColorSpaceCreateDeviceGray())),
) orelse Allocator.Error.OutOfMemory;
}
pub fn createDeviceRGB() Allocator.Error!*ColorSpace {
return @as(
?*ColorSpace,
@ptrFromInt(@intFromPtr(c.CGColorSpaceCreateDeviceRGB())),
) orelse Allocator.Error.OutOfMemory;
}
pub fn createNamed(name: Name) Allocator.Error!*ColorSpace {
return @as(
?*ColorSpace,
@ptrFromInt(@intFromPtr(c.CGColorSpaceCreateWithName(name.cfstring()))),
) orelse Allocator.Error.OutOfMemory;
}
pub fn release(self: *ColorSpace) void {
c.CGColorSpaceRelease(@ptrCast(self));
}
pub const Name = enum {
/// This color space uses the DCI P3 primaries, a D65 white point, and
/// the sRGB transfer function.
displayP3,
/// The Display P3 color space with a linear transfer function and
/// extended-range values.
extendedLinearDisplayP3,
/// The sRGB colorimetry and non-linear transfer function are specified
/// in IEC 61966-2-1.
sRGB,
/// This color space has the same colorimetry as `sRGB`, but uses a
/// linear transfer function.
linearSRGB,
/// This color space has the same colorimetry as `sRGB`, but you can
/// encode component values below `0.0` and above `1.0`. Negative values
/// are encoded as the signed reflection of the original encoding
/// function, as shown in the formula below:
/// ```
/// extendedTransferFunction(x) = sign(x) * sRGBTransferFunction(abs(x))
/// ```
extendedSRGB,
/// This color space has the same colorimetry as `sRGB`; in addition,
/// you may encode component values below `0.0` and above `1.0`.
extendedLinearSRGB,
/// ...
genericGrayGamma2_2,
/// ...
linearGray,
/// This color space has the same colorimetry as `genericGrayGamma2_2`,
/// but you can encode component values below `0.0` and above `1.0`.
/// Negative values are encoded as the signed reflection of the
/// original encoding function, as shown in the formula below:
/// ```
/// extendedGrayTransferFunction(x) = sign(x) * gamma22Function(abs(x))
/// ```
extendedGray,
/// This color space has the same colorimetry as `linearGray`; in
/// addition, you may encode component values below `0.0` and above `1.0`.
extendedLinearGray,
fn cfstring(self: Name) c.CFStringRef {
return switch (self) {
.displayP3 => c.kCGColorSpaceDisplayP3,
.extendedLinearDisplayP3 => c.kCGColorSpaceExtendedLinearDisplayP3,
.sRGB => c.kCGColorSpaceSRGB,
.extendedSRGB => c.kCGColorSpaceExtendedSRGB,
.linearSRGB => c.kCGColorSpaceLinearSRGB,
.extendedLinearSRGB => c.kCGColorSpaceExtendedLinearSRGB,
.genericGrayGamma2_2 => c.kCGColorSpaceGenericGrayGamma2_2,
.extendedGray => c.kCGColorSpaceExtendedGray,
.linearGray => c.kCGColorSpaceLinearGray,
.extendedLinearGray => c.kCGColorSpaceExtendedLinearGray,
};
}
};
};
test {
//const testing = std.testing;
const space = try ColorSpace.createDeviceGray();
defer space.release();
}
test {
const space = try ColorSpace.createDeviceRGB();
defer space.release();
}
+172
View File
@@ -0,0 +1,172 @@
const std = @import("std");
const assert = std.debug.assert;
const Allocator = std.mem.Allocator;
const graphics = @import("../graphics.zig");
const c = @import("c.zig").c;
/// Returns a struct that has all the shared context functions for the
/// given type.
pub fn Context(comptime T: type) type {
return struct {
value: *T,
pub fn release(self: *T) void {
c.CGContextRelease(@ptrCast(self));
}
pub fn setLineWidth(self: *T, width: f64) void {
c.CGContextSetLineWidth(
@ptrCast(self),
width,
);
}
pub fn setAllowsAntialiasing(self: *T, v: bool) void {
c.CGContextSetAllowsAntialiasing(
@ptrCast(self),
v,
);
}
pub fn setAllowsFontSmoothing(self: *T, v: bool) void {
c.CGContextSetAllowsFontSmoothing(
@ptrCast(self),
v,
);
}
pub fn setAllowsFontSubpixelPositioning(self: *T, v: bool) void {
c.CGContextSetAllowsFontSubpixelPositioning(
@ptrCast(self),
v,
);
}
pub fn setAllowsFontSubpixelQuantization(self: *T, v: bool) void {
c.CGContextSetAllowsFontSubpixelQuantization(
@ptrCast(self),
v,
);
}
pub fn setShouldAntialias(self: *T, v: bool) void {
c.CGContextSetShouldAntialias(
@ptrCast(self),
v,
);
}
pub fn setShouldSmoothFonts(self: *T, v: bool) void {
c.CGContextSetShouldSmoothFonts(
@ptrCast(self),
v,
);
}
pub fn setShouldSubpixelPositionFonts(self: *T, v: bool) void {
c.CGContextSetShouldSubpixelPositionFonts(
@ptrCast(self),
v,
);
}
pub fn setShouldSubpixelQuantizeFonts(self: *T, v: bool) void {
c.CGContextSetShouldSubpixelQuantizeFonts(
@ptrCast(self),
v,
);
}
pub fn setGrayFillColor(self: *T, gray: f64, alpha: f64) void {
c.CGContextSetGrayFillColor(
@ptrCast(self),
gray,
alpha,
);
}
pub fn setGrayStrokeColor(self: *T, gray: f64, alpha: f64) void {
c.CGContextSetGrayStrokeColor(
@ptrCast(self),
gray,
alpha,
);
}
pub fn setRGBFillColor(self: *T, r: f64, g: f64, b: f64, alpha: f64) void {
c.CGContextSetRGBFillColor(
@ptrCast(self),
r,
g,
b,
alpha,
);
}
pub fn setRGBStrokeColor(self: *T, r: f64, g: f64, b: f64, alpha: f64) void {
c.CGContextSetRGBStrokeColor(
@ptrCast(self),
r,
g,
b,
alpha,
);
}
pub fn setTextDrawingMode(self: *T, mode: TextDrawingMode) void {
c.CGContextSetTextDrawingMode(
@ptrCast(self),
@intFromEnum(mode),
);
}
pub fn setTextMatrix(self: *T, matrix: graphics.AffineTransform) void {
c.CGContextSetTextMatrix(
@ptrCast(self),
@bitCast(matrix),
);
}
pub fn setTextPosition(self: *T, x: f64, y: f64) void {
c.CGContextSetTextPosition(
@ptrCast(self),
x,
y,
);
}
pub fn fillRect(self: *T, rect: graphics.Rect) void {
c.CGContextFillRect(
@ptrCast(self),
@bitCast(rect),
);
}
pub fn scaleCTM(self: *T, sx: c.CGFloat, sy: c.CGFloat) void {
c.CGContextScaleCTM(
@ptrCast(self),
sx,
sy,
);
}
pub fn translateCTM(self: *T, tx: c.CGFloat, ty: c.CGFloat) void {
c.CGContextTranslateCTM(
@ptrCast(self),
tx,
ty,
);
}
};
}
pub const TextDrawingMode = enum(c_int) {
fill = c.kCGTextFill,
stroke = c.kCGTextStroke,
fill_stroke = c.kCGTextFillStroke,
invisible = c.kCGTextInvisible,
fill_clip = c.kCGTextFillClip,
stroke_clip = c.kCGTextStrokeClip,
fill_stroke_clip = c.kCGTextFillStrokeClip,
clip = c.kCGTextClip,
};
+3
View File
@@ -0,0 +1,3 @@
const c = @import("c.zig").c;
pub const Glyph = c.CGGlyph;
+34
View File
@@ -0,0 +1,34 @@
const std = @import("std");
const assert = std.debug.assert;
const c = @import("c.zig").c;
pub const Point = extern struct {
x: c.CGFloat,
y: c.CGFloat,
};
pub const Rect = extern struct {
origin: Point,
size: Size,
pub fn init(x: f64, y: f64, width: f64, height: f64) Rect {
return @bitCast(c.CGRectMake(x, y, width, height));
}
pub fn isNull(self: Rect) bool {
return c.CGRectIsNull(@bitCast(self));
}
pub fn getHeight(self: Rect) c.CGFloat {
return c.CGRectGetHeight(@bitCast(self));
}
pub fn getWidth(self: Rect) c.CGFloat {
return c.CGRectGetWidth(@bitCast(self));
}
};
pub const Size = extern struct {
width: c.CGFloat,
height: c.CGFloat,
};
+31
View File
@@ -0,0 +1,31 @@
const std = @import("std");
const assert = std.debug.assert;
const Allocator = std.mem.Allocator;
const graphics = @import("../graphics.zig");
const context = @import("context.zig");
const c = @import("c.zig").c;
pub const ImageAlphaInfo = enum(c_uint) {
none = c.kCGImageAlphaNone,
premultiplied_last = c.kCGImageAlphaPremultipliedLast,
premultiplied_first = c.kCGImageAlphaPremultipliedFirst,
last = c.kCGImageAlphaLast,
first = c.kCGImageAlphaFirst,
none_skip_last = c.kCGImageAlphaNoneSkipLast,
none_skip_first = c.kCGImageAlphaNoneSkipFirst,
only = c.kCGImageAlphaOnly,
};
pub const BitmapInfo = enum(c_uint) {
alpha_mask = c.kCGBitmapAlphaInfoMask,
float_mask = c.kCGBitmapFloatInfoMask,
float_components = c.kCGBitmapFloatComponents,
byte_order_mask = c.kCGBitmapByteOrderMask,
byte_order_default = c.kCGBitmapByteOrderDefault,
byte_order_16_little = c.kCGBitmapByteOrder16Little,
byte_order_32_little = c.kCGBitmapByteOrder32Little,
byte_order_16_big = c.kCGBitmapByteOrder16Big,
byte_order_32_big = c.kCGBitmapByteOrder32Big,
_,
};
+68
View File
@@ -0,0 +1,68 @@
const std = @import("std");
const assert = std.debug.assert;
const Allocator = std.mem.Allocator;
const foundation = @import("../foundation.zig");
const graphics = @import("../graphics.zig");
const c = @import("c.zig").c;
pub const Path = opaque {
pub fn createWithRect(
rect: graphics.Rect,
transform: ?*const graphics.AffineTransform,
) Allocator.Error!*Path {
return @as(
?*Path,
@ptrFromInt(@intFromPtr(c.CGPathCreateWithRect(
@bitCast(rect),
@ptrCast(transform),
))),
) orelse Allocator.Error.OutOfMemory;
}
pub fn release(self: *Path) void {
foundation.CFRelease(self);
}
};
pub const MutablePath = opaque {
pub fn create() Allocator.Error!*MutablePath {
return @as(
?*MutablePath,
@ptrFromInt(@intFromPtr(c.CGPathCreateMutable())),
) orelse Allocator.Error.OutOfMemory;
}
pub fn release(self: *MutablePath) void {
foundation.CFRelease(self);
}
pub fn addRect(
self: *MutablePath,
transform: ?*const graphics.AffineTransform,
rect: graphics.Rect,
) void {
c.CGPathAddRect(
@ptrCast(self),
@ptrCast(transform),
@bitCast(rect),
);
}
pub fn getBoundingBox(self: *MutablePath) graphics.Rect {
return @bitCast(c.CGPathGetBoundingBox(@ptrCast(self)));
}
};
test "mutable path" {
//const testing = std.testing;
const path = try MutablePath.create();
defer path.release();
path.addRect(null, graphics.Rect.init(0, 0, 100, 200));
}
test "path from rect" {
const path = try Path.createWithRect(graphics.Rect.init(0, 0, 100, 200), null);
defer path.release();
}
+8
View File
@@ -0,0 +1,8 @@
const iosurface = @import("iosurface/iosurface.zig");
pub const c = @import("iosurface/c.zig").c;
pub const IOSurface = iosurface.IOSurface;
test {
@import("std").testing.refAllDecls(@This());
}
+1
View File
@@ -0,0 +1 @@
pub const c = @import("../main.zig").c;
+136
View File
@@ -0,0 +1,136 @@
const std = @import("std");
const assert = std.debug.assert;
const Allocator = std.mem.Allocator;
const c = @import("c.zig").c;
const foundation = @import("../foundation.zig");
const graphics = @import("../graphics.zig");
const video = @import("../video.zig");
pub const IOSurface = opaque {
pub const Error = error{
InvalidOperation,
};
pub const Properties = struct {
width: c_int,
height: c_int,
pixel_format: video.PixelFormat,
bytes_per_element: c_int,
colorspace: ?*graphics.ColorSpace,
};
pub fn init(properties: Properties) Allocator.Error!*IOSurface {
var w = try foundation.Number.create(.int, &properties.width);
defer w.release();
var h = try foundation.Number.create(.int, &properties.height);
defer h.release();
var pf = try foundation.Number.create(.int, &@as(c_int, @intFromEnum(properties.pixel_format)));
defer pf.release();
var bpe = try foundation.Number.create(.int, &properties.bytes_per_element);
defer bpe.release();
var properties_dict = try foundation.Dictionary.create(
&[_]?*const anyopaque{
c.kIOSurfaceWidth,
c.kIOSurfaceHeight,
c.kIOSurfacePixelFormat,
c.kIOSurfaceBytesPerElement,
},
&[_]?*const anyopaque{ w, h, pf, bpe },
);
defer properties_dict.release();
var surface = @as(?*IOSurface, @ptrFromInt(@intFromPtr(
c.IOSurfaceCreate(@ptrCast(properties_dict)),
))) orelse return error.OutOfMemory;
if (properties.colorspace) |space| {
surface.setColorSpace(space);
}
return surface;
}
pub fn deinit(self: *IOSurface) void {
// We mark it purgeable so that it is immediately unloaded, so that we
// don't have to wait for CoreFoundation garbage collection to trigger.
_ = c.IOSurfaceSetPurgeable(
@ptrCast(self),
c.kIOSurfacePurgeableEmpty,
null,
);
foundation.CFRelease(self);
}
pub fn retain(self: *IOSurface) void {
foundation.CFRetain(self);
}
pub fn release(self: *IOSurface) void {
foundation.CFRelease(self);
}
pub fn setColorSpace(self: *IOSurface, colorspace: *graphics.ColorSpace) void {
const serialized_colorspace = graphics.c.CGColorSpaceCopyPropertyList(
@ptrCast(colorspace),
).?;
defer foundation.CFRelease(@constCast(serialized_colorspace));
c.IOSurfaceSetValue(
@ptrCast(self),
c.kIOSurfaceColorSpace,
@ptrCast(serialized_colorspace),
);
}
pub inline fn lock(self: *IOSurface) void {
c.IOSurfaceLock(
@ptrCast(self),
0,
null,
);
}
pub inline fn unlock(self: *IOSurface) void {
c.IOSurfaceUnlock(
@ptrCast(self),
0,
null,
);
}
pub inline fn getAllocSize(self: *IOSurface) usize {
return c.IOSurfaceGetAllocSize(@ptrCast(self));
}
pub inline fn getWidth(self: *IOSurface) usize {
return c.IOSurfaceGetWidth(@ptrCast(self));
}
pub inline fn getHeight(self: *IOSurface) usize {
return c.IOSurfaceGetHeight(@ptrCast(self));
}
pub inline fn getBytesPerElement(self: *IOSurface) usize {
return c.IOSurfaceGetBytesPerElement(@ptrCast(self));
}
pub inline fn getBytesPerRow(self: *IOSurface) usize {
return c.IOSurfaceGetBytesPerRow(@ptrCast(self));
}
pub inline fn getBaseAddress(self: *IOSurface) ?[*]u8 {
return @ptrCast(c.IOSurfaceGetBaseAddress(@ptrCast(self)));
}
pub inline fn getElementWidth(self: *IOSurface) usize {
return c.IOSurfaceGetElementWidth(@ptrCast(self));
}
pub inline fn getElementHeight(self: *IOSurface) usize {
return c.IOSurfaceGetElementHeight(@ptrCast(self));
}
pub inline fn getPixelFormat(self: *IOSurface) video.PixelFormat {
return @enumFromInt(c.IOSurfaceGetPixelFormat(@ptrCast(self)));
}
};
+35
View File
@@ -0,0 +1,35 @@
const builtin = @import("builtin");
pub const carbon = @import("carbon.zig");
pub const foundation = @import("foundation.zig");
pub const animation = @import("animation.zig");
pub const dispatch = @import("dispatch.zig");
pub const graphics = @import("graphics.zig");
pub const os = @import("os.zig");
pub const text = @import("text.zig");
pub const video = @import("video.zig");
pub const iosurface = @import("iosurface.zig");
// All of our C imports consolidated into one place. We used to
// import them one by one in each package but Zig 0.14 has some
// kind of issue with that I wasn't able to minimize.
pub const c = @cImport({
@cInclude("CoreFoundation/CoreFoundation.h");
@cInclude("CoreGraphics/CoreGraphics.h");
@cInclude("CoreText/CoreText.h");
@cInclude("CoreVideo/CoreVideo.h");
@cInclude("CoreVideo/CVPixelBuffer.h");
@cInclude("QuartzCore/CALayer.h");
@cInclude("IOSurface/IOSurfaceRef.h");
@cInclude("dispatch/dispatch.h");
@cInclude("os/log.h");
@cInclude("os/signpost.h");
if (builtin.os.tag == .macos) {
@cInclude("Carbon/Carbon.h");
}
});
test {
@import("std").testing.refAllDecls(@This());
}
+10
View File
@@ -0,0 +1,10 @@
const log = @import("os/log.zig");
pub const c = @import("os/c.zig");
pub const signpost = @import("os/signpost.zig");
pub const Log = log.Log;
pub const LogType = log.LogType;
test {
@import("std").testing.refAllDecls(@This());
}
+1
View File
@@ -0,0 +1 @@
pub const c = @import("../main.zig").c;
+65
View File
@@ -0,0 +1,65 @@
const std = @import("std");
const assert = std.debug.assert;
const Allocator = std.mem.Allocator;
const c = @import("c.zig").c;
pub const Log = opaque {
pub fn create(
subsystem: [:0]const u8,
category: [:0]const u8,
) *Log {
return @ptrCast(c.os_log_create(
subsystem.ptr,
category.ptr,
).?);
}
pub fn release(self: *Log) void {
c.os_release(self);
}
pub fn typeEnabled(self: *Log, typ: LogType) bool {
return c.os_log_type_enabled(
@ptrCast(self),
@intFromEnum(typ),
);
}
pub fn log(
self: *Log,
alloc: Allocator,
typ: LogType,
comptime format: []const u8,
args: anytype,
) void {
const str = nosuspend std.fmt.allocPrintSentinel(
alloc,
format,
args,
0,
) catch return;
defer alloc.free(str);
zig_os_log_with_type(self, typ, str.ptr);
}
extern "c" fn zig_os_log_with_type(*Log, LogType, [*c]const u8) void;
};
/// https://developer.apple.com/documentation/os/os_log_type_t?language=objc
pub const LogType = enum(c.os_log_type_t) {
default = c.OS_LOG_TYPE_DEFAULT,
debug = c.OS_LOG_TYPE_DEBUG,
info = c.OS_LOG_TYPE_INFO,
err = c.OS_LOG_TYPE_ERROR,
fault = c.OS_LOG_TYPE_FAULT,
};
test {
const testing = std.testing;
const log = Log.create("com.mitchellh.ghostty", "test");
defer log.release();
try testing.expect(log.typeEnabled(.fault));
log.log(testing.allocator, .default, "hello {d}", .{12});
}
+214
View File
@@ -0,0 +1,214 @@
const std = @import("std");
const assert = std.debug.assert;
const Allocator = std.mem.Allocator;
const c = @import("c.zig").c;
const logpkg = @import("log.zig");
const Log = logpkg.Log;
/// This should be called once at the start of the program to intialize
/// some required state for signpost logging.
///
/// This is all to workaround a Zig bug:
/// https://github.com/ziglang/zig/issues/24370
pub fn init() void {
if (__dso_handle != null) return;
const sym = comptime sym: {
const root = @import("root");
// If we have a main function, use that as the symbol.
if (@hasDecl(root, "main")) break :sym root.main;
// Otherwise, we're in a library, so we just use the first
// function in our root module. I actually don't know if this is
// all required or if we can just use the real `__dso_handle` symbol,
// but this seems to work for now.
for (@typeInfo(root).@"struct".decls) |decl_info| {
const decl = @field(root, decl_info.name);
if (@typeInfo(@TypeOf(decl)) == .@"fn") break :sym decl;
}
@compileError("no functions found in root module");
};
// Since __dso_handle is not automatically populated by the linker,
// we populate it by looking up the main function's module address
// which should be a mach-o header.
var info: DlInfo = undefined;
const result = dladdr(sym, &info);
assert(result != 0);
__dso_handle = @ptrCast(@alignCast(info.dli_fbase));
}
/// This should REALLY be an extern var that is populated by the linker,
/// but there is a Zig bug: https://github.com/ziglang/zig/issues/24370
var __dso_handle: ?*c.mach_header = null;
// Import the necessary C functions and types
extern "c" fn dladdr(addr: ?*const anyopaque, info: *DlInfo) c_int;
// Define the Dl_info structure
const DlInfo = extern struct {
dli_fname: [*:0]const u8, // Pathname of shared object
dli_fbase: ?*anyopaque, // Base address of shared object
dli_sname: [*:0]const u8, // Name of nearest symbol
dli_saddr: ?*anyopaque, // Address of nearest symbol
};
/// Checks whether signpost logging is enabled for the given log handle.
/// Returns true if signposts will be recorded for this log, false otherwise.
/// This can be used to avoid expensive operations when signpost logging is disabled.
///
/// https://developer.apple.com/documentation/os/os_signpost_enabled?language=objc
pub fn enabled(log: *Log) bool {
return c.os_signpost_enabled(@ptrCast(log));
}
/// Emits a signpost event - a single point in time marker.
/// Events are useful for marking when specific actions occur, such as
/// user interactions, state changes, or other discrete occurrences.
/// The event will appear as a vertical line in Instruments.
///
/// https://developer.apple.com/documentation/os/os_signpost_event_emit?language=objc
pub fn emitEvent(
log: *Log,
id: Id,
comptime name: [:0]const u8,
) void {
emitWithName(log, id, .event, name);
}
/// Marks the beginning of a time interval.
/// Use this with intervalEnd to measure the duration of operations.
/// The same ID must be used for both the begin and end calls.
/// Intervals appear as horizontal bars in Instruments timeline.
///
/// https://developer.apple.com/documentation/os/os_signpost_interval_begin?language=objc
pub fn intervalBegin(log: *Log, id: Id, comptime name: [:0]const u8) void {
emitWithName(log, id, .interval_begin, name);
}
/// Marks the end of a time interval.
/// Must be paired with a prior intervalBegin call using the same ID.
/// The name should match the name used in intervalBegin.
/// Instruments will calculate and display the duration between begin and end.
///
/// https://developer.apple.com/documentation/os/os_signpost_interval_end?language=objc
pub fn intervalEnd(log: *Log, id: Id, comptime name: [:0]const u8) void {
emitWithName(log, id, .interval_end, name);
}
/// The internal function to emit a signpost with a specific name.
fn emitWithName(
log: *Log,
id: Id,
typ: Type,
comptime name: [:0]const u8,
) void {
// Init must be called by this point.
assert(__dso_handle != null);
var buf: [2]u8 = @splat(0);
c._os_signpost_emit_with_name_impl(
__dso_handle,
@ptrCast(log),
@intFromEnum(typ),
@intFromEnum(id),
name.ptr,
"".ptr,
&buf,
buf.len,
);
}
/// https://developer.apple.com/documentation/os/os_signpost_id_t?language=objc
pub const Id = enum(u64) {
null = 0, // OS_SIGNPOST_ID_NULL
invalid = 0xFFFFFFFFFFFFFFFF, // OS_SIGNPOST_ID_INVALID
exclusive = 0xEEEEB0B5B2B2EEEE, // OS_SIGNPOST_ID_EXCLUSIVE
_,
/// Generates a new signpost ID for use with signpost operations.
/// The ID is unique for the given log handle and can be used to track
/// asynchronous operations or mark specific points of interest in the code.
/// Returns a unique signpost ID that can be used with os_signpost functions.
///
/// https://developer.apple.com/documentation/os/os_signpost_id_generate?language=objc
pub fn generate(log: *Log) Id {
return @enumFromInt(c.os_signpost_id_generate(@ptrCast(log)));
}
/// Creates a signpost ID based on a pointer value.
/// This is useful for tracking operations associated with a specific object
/// or memory location. The same pointer will always generate the same ID
/// for a given log handle, allowing correlation of signpost events.
/// Pass null to get the null signpost ID.
///
/// https://developer.apple.com/documentation/os/os_signpost_id_for_pointer?language=objc
pub fn forPointer(log: *Log, ptr: ?*anyopaque) Id {
return @enumFromInt(c.os_signpost_id_make_with_pointer(
@ptrCast(log),
@ptrCast(ptr),
));
}
test "generate ID" {
// We can't really test the return value because it may return null
// if signposts are disabled.
const id: Id = .generate(Log.create("com.mitchellh.ghostty", "test"));
try std.testing.expect(id != .invalid);
}
test "generate ID for pointer" {
var foo: usize = 0x1234;
const id: Id = .forPointer(Log.create("com.mitchellh.ghostty", "test"), &foo);
try std.testing.expect(id != .null);
}
};
/// https://developer.apple.com/documentation/os/ossignposttype?language=objc
pub const Type = enum(u8) {
event = 0, // OS_SIGNPOST_EVENT
interval_begin = 1, // OS_SIGNPOST_INTERVAL_BEGIN
interval_end = 2, // OS_SIGNPOST_INTERVAL_END
pub const mask: u8 = 0x03; // OS_SIGNPOST_TYPE_MASK
};
/// Special os_log category values that surface in Instruments and other
/// tooling.
pub const Category = struct {
/// Points of Interest appear as a dedicated track in Instruments.
/// Use this for high-level application events that help understand
/// the flow of your application.
pub const points_of_interest: [:0]const u8 = "PointsOfInterest";
/// Dynamic Tracing category enables runtime-configurable logging.
/// Signposts in this category can be enabled/disabled dynamically
/// without recompiling.
pub const dynamic_tracing: [:0]const u8 = "DynamicTracing";
/// Dynamic Stack Tracing category captures call stacks at signpost
/// events. This provides deeper debugging information but has higher
/// performance overhead.
pub const dynamic_stack_tracing: [:0]const u8 = "DynamicStackTracing";
};
test {
_ = Id;
}
test enabled {
_ = enabled(Log.create("com.mitchellh.ghostty", "test"));
}
test "intervals" {
init();
const log = Log.create("com.mitchellh.ghostty", "test");
defer log.release();
// Test that we can begin and end an interval
const id = Id.generate(log);
intervalBegin(log, id, "Test Interval");
}
+11
View File
@@ -0,0 +1,11 @@
#include <os/log.h>
#include <os/signpost.h>
// A wrapper so we can use the os_log_with_type macro.
void zig_os_log_with_type(
os_log_t log,
os_log_type_t type,
const char *message
) {
os_log_with_type(log, type, "%{public}s", message);
}
+38
View File
@@ -0,0 +1,38 @@
const font = @import("text/font.zig");
const font_collection = @import("text/font_collection.zig");
const font_descriptor = @import("text/font_descriptor.zig");
const font_manager = @import("text/font_manager.zig");
const frame = @import("text/frame.zig");
const framesetter = @import("text/framesetter.zig");
const typesetter = @import("text/typesetter.zig");
const line = @import("text/line.zig");
const paragraph_style = @import("text/paragraph_style.zig");
const run = @import("text/run.zig");
const stylized_strings = @import("text/stylized_strings.zig");
pub const c = @import("text/c.zig").c;
pub const Font = font.Font;
pub const FontTableTag = font.FontTableTag;
pub const FontCollection = font_collection.FontCollection;
pub const FontDescriptor = font_descriptor.FontDescriptor;
pub const FontAttribute = font_descriptor.FontAttribute;
pub const FontTraitKey = font_descriptor.FontTraitKey;
pub const FontVariationAxisKey = font_descriptor.FontVariationAxisKey;
pub const FontSymbolicTraits = font_descriptor.FontSymbolicTraits;
pub const createFontDescriptorsFromURL = font_manager.createFontDescriptorsFromURL;
pub const createFontDescriptorsFromData = font_manager.createFontDescriptorsFromData;
pub const createFontDescriptorFromData = font_manager.createFontDescriptorFromData;
pub const Frame = frame.Frame;
pub const Framesetter = framesetter.Framesetter;
pub const Typesetter = typesetter.Typesetter;
pub const Line = line.Line;
pub const ParagraphStyle = paragraph_style.ParagraphStyle;
pub const ParagraphStyleSetting = paragraph_style.ParagraphStyleSetting;
pub const ParagraphStyleSpecifier = paragraph_style.ParagraphStyleSpecifier;
pub const WritingDirection = paragraph_style.WritingDirection;
pub const Run = run.Run;
pub const StringAttribute = stylized_strings.StringAttribute;
test {
@import("std").testing.refAllDecls(@This());
}
+1
View File
@@ -0,0 +1 @@
pub const c = @import("../main.zig").c;
+10
View File
@@ -0,0 +1,10 @@
#include <CoreText/CoreText.h>
// A wrapper to fix a Zig C ABI issue.
void zig_cabi_CTLineGetBoundsWithOptions(
CTLineRef line,
CTLineBoundsOptions options,
CGRect *result
) {
*result = CTLineGetBoundsWithOptions(line, options);
}
+306
View File
@@ -0,0 +1,306 @@
const std = @import("std");
const assert = std.debug.assert;
const Allocator = std.mem.Allocator;
const foundation = @import("../foundation.zig");
const graphics = @import("../graphics.zig");
const text = @import("../text.zig");
const c = @import("c.zig").c;
pub const Font = opaque {
pub fn createWithFontDescriptor(desc: *const text.FontDescriptor, size: f32) Allocator.Error!*Font {
return @as(
?*Font,
@ptrFromInt(@intFromPtr(c.CTFontCreateWithFontDescriptor(
@ptrCast(desc),
size,
null,
))),
) orelse Allocator.Error.OutOfMemory;
}
pub fn createForString(
self: *Font,
str: *foundation.String,
range: foundation.Range,
) ?*Font {
return @ptrCast(@constCast(c.CTFontCreateForString(
@ptrCast(self),
@ptrCast(str),
@bitCast(range),
)));
}
pub fn copyWithAttributes(
self: *Font,
size: f32,
matrix: ?*const graphics.AffineTransform,
attrs: ?*text.FontDescriptor,
) Allocator.Error!*Font {
return @as(
?*Font,
@ptrFromInt(@intFromPtr(c.CTFontCreateCopyWithAttributes(
@ptrCast(self),
size,
@ptrCast(matrix),
@ptrCast(attrs),
))),
) orelse Allocator.Error.OutOfMemory;
}
pub fn release(self: *Font) void {
c.CFRelease(self);
}
pub fn retain(self: *Font) void {
_ = c.CFRetain(self);
}
pub fn copyDescriptor(self: *Font) *text.FontDescriptor {
return @ptrCast(@constCast(c.CTFontCopyFontDescriptor(@ptrCast(self))));
}
pub fn copyFeatures(self: *Font) *foundation.Array {
return @ptrCast(@constCast(c.CTFontCopyFeatures(@ptrCast(self))));
}
pub fn copyDefaultCascadeListForLanguages(self: *Font) *foundation.Array {
return @ptrCast(@constCast(c.CTFontCopyDefaultCascadeListForLanguages(@ptrCast(self), null)));
}
pub fn copyTable(self: *Font, tag: FontTableTag) ?*foundation.Data {
return @ptrCast(@constCast(c.CTFontCopyTable(
@ptrCast(self),
@intFromEnum(tag),
c.kCTFontTableOptionNoOptions,
)));
}
pub fn getGlyphCount(self: *Font) usize {
return @intCast(c.CTFontGetGlyphCount(@ptrCast(self)));
}
pub fn getGlyphsForCharacters(self: *Font, chars: []const u16, glyphs: []graphics.Glyph) bool {
assert(chars.len == glyphs.len);
return c.CTFontGetGlyphsForCharacters(
@ptrCast(self),
chars.ptr,
glyphs.ptr,
@intCast(chars.len),
);
}
pub fn createPathForGlyph(self: *Font, glyph: graphics.Glyph) ?*graphics.Path {
return @ptrCast(@constCast(c.CTFontCreatePathForGlyph(
@ptrCast(self),
glyph,
null,
)));
}
pub fn drawGlyphs(
self: *Font,
glyphs: []const graphics.Glyph,
positions: []const graphics.Point,
context: anytype, // Must be some context type from graphics
) void {
assert(positions.len == glyphs.len);
c.CTFontDrawGlyphs(
@ptrCast(self),
glyphs.ptr,
@ptrCast(positions.ptr),
glyphs.len,
@ptrCast(context),
);
}
pub fn getBoundingRectsForGlyphs(
self: *Font,
orientation: FontOrientation,
glyphs: []const graphics.Glyph,
rects: ?[]graphics.Rect,
) graphics.Rect {
if (rects) |s| assert(glyphs.len == s.len);
return @bitCast(c.CTFontGetBoundingRectsForGlyphs(
@ptrCast(self),
@intFromEnum(orientation),
glyphs.ptr,
@ptrCast(if (rects) |s| s.ptr else null),
@intCast(glyphs.len),
));
}
pub fn getAdvancesForGlyphs(
self: *Font,
orientation: FontOrientation,
glyphs: []const graphics.Glyph,
advances: ?[]graphics.Size,
) f64 {
if (advances) |s| assert(glyphs.len == s.len);
return c.CTFontGetAdvancesForGlyphs(
@ptrCast(self),
@intFromEnum(orientation),
glyphs.ptr,
@ptrCast(if (advances) |s| s.ptr else null),
@intCast(glyphs.len),
);
}
pub fn copyAttribute(self: *Font, comptime attr: text.FontAttribute) ?attr.Value() {
return @ptrFromInt(@intFromPtr(c.CTFontCopyAttribute(
@ptrCast(self),
@ptrCast(attr.key()),
)));
}
pub fn copyFamilyName(self: *Font) *foundation.String {
return @ptrFromInt(@intFromPtr(c.CTFontCopyFamilyName(@ptrCast(self))));
}
pub fn copyDisplayName(self: *Font) *foundation.String {
return @ptrFromInt(@intFromPtr(c.CTFontCopyDisplayName(@ptrCast(self))));
}
pub fn copyPostScriptName(self: *Font) *foundation.String {
return @ptrFromInt(@intFromPtr(c.CTFontCopyPostScriptName(@ptrCast(self))));
}
pub fn getSymbolicTraits(self: *Font) text.FontSymbolicTraits {
return @bitCast(c.CTFontGetSymbolicTraits(@ptrCast(self)));
}
pub fn getAscent(self: *Font) f64 {
return c.CTFontGetAscent(@ptrCast(self));
}
pub fn getDescent(self: *Font) f64 {
return c.CTFontGetDescent(@ptrCast(self));
}
pub fn getLeading(self: *Font) f64 {
return c.CTFontGetLeading(@ptrCast(self));
}
pub fn getBoundingBox(self: *Font) graphics.Rect {
return @bitCast(c.CTFontGetBoundingBox(@ptrCast(self)));
}
pub fn getUnderlinePosition(self: *Font) f64 {
return c.CTFontGetUnderlinePosition(@ptrCast(self));
}
pub fn getUnderlineThickness(self: *Font) f64 {
return c.CTFontGetUnderlineThickness(@ptrCast(self));
}
pub fn getCapHeight(self: *Font) f64 {
return c.CTFontGetCapHeight(@ptrCast(self));
}
pub fn getXHeight(self: *Font) f64 {
return c.CTFontGetXHeight(@ptrCast(self));
}
pub fn getUnitsPerEm(self: *Font) u32 {
return c.CTFontGetUnitsPerEm(@ptrCast(self));
}
pub fn getSize(self: *Font) f64 {
return c.CTFontGetSize(@ptrCast(self));
}
};
pub const FontOrientation = enum(c_uint) {
default = c.kCTFontOrientationDefault,
horizontal = c.kCTFontOrientationHorizontal,
vertical = c.kCTFontOrientationVertical,
};
pub const FontTableTag = enum(u32) {
svg = c.kCTFontTableSVG,
os2 = c.kCTFontTableOS2,
head = c.kCTFontTableHead,
hhea = c.kCTFontTableHhea,
post = c.kCTFontTablePost,
_,
pub fn init(v: *const [4]u8) FontTableTag {
const raw: u32 = @bitCast(foundation.FourCharCode.init(v));
return @enumFromInt(raw);
}
};
test {
const testing = std.testing;
const name = try foundation.String.createWithBytes("Monaco", .utf8, false);
defer name.release();
const desc = try text.FontDescriptor.createWithNameAndSize(name, 12);
defer desc.release();
const font = try Font.createWithFontDescriptor(desc, 12);
defer font.release();
// Traits
{
const traits = font.getSymbolicTraits();
try testing.expect(!traits.color_glyphs);
}
var glyphs = [1]graphics.Glyph{0};
try testing.expect(font.getGlyphsForCharacters(
&[_]u16{'A'},
&glyphs,
));
try testing.expect(glyphs[0] > 0);
// Bounding rect
{
var rect = font.getBoundingRectsForGlyphs(.horizontal, &glyphs, null);
try testing.expect(rect.size.width > 0);
var singles: [1]graphics.Rect = undefined;
rect = font.getBoundingRectsForGlyphs(.horizontal, &glyphs, &singles);
try testing.expect(rect.size.width > 0);
try testing.expect(singles[0].size.width > 0);
}
// Advances
{
var advance = font.getAdvancesForGlyphs(.horizontal, &glyphs, null);
try testing.expect(advance > 0);
var singles: [1]graphics.Size = undefined;
advance = font.getAdvancesForGlyphs(.horizontal, &glyphs, &singles);
try testing.expect(advance > 0);
try testing.expect(singles[0].width > 0);
}
// Draw
{
const cs = try graphics.ColorSpace.createDeviceGray();
defer cs.release();
const ctx = try graphics.BitmapContext.create(null, 80, 80, 8, 80, cs, 0);
const context = graphics.BitmapContext.context;
defer context.release(ctx);
var pos = [_]graphics.Point{.{ .x = 0, .y = 0 }};
font.drawGlyphs(
&glyphs,
&pos,
ctx,
);
}
}
test "copy" {
const name = try foundation.String.createWithBytes("Monaco", .utf8, false);
defer name.release();
const desc = try text.FontDescriptor.createWithNameAndSize(name, 12);
defer desc.release();
const font = try Font.createWithFontDescriptor(desc, 12);
defer font.release();
const f2 = try font.copyWithAttributes(14, null, null);
defer f2.release();
}
+139
View File
@@ -0,0 +1,139 @@
const std = @import("std");
const Allocator = std.mem.Allocator;
const foundation = @import("../foundation.zig");
const text = @import("../text.zig");
const c = @import("c.zig").c;
pub const FontCollection = opaque {
pub fn createFromAvailableFonts() Allocator.Error!*FontCollection {
return @as(
?*FontCollection,
@ptrFromInt(@intFromPtr(c.CTFontCollectionCreateFromAvailableFonts(null))),
) orelse Allocator.Error.OutOfMemory;
}
pub fn createWithFontDescriptors(descs: *foundation.Array) Allocator.Error!*FontCollection {
return @as(
?*FontCollection,
@ptrFromInt(@intFromPtr(c.CTFontCollectionCreateWithFontDescriptors(
@ptrCast(descs),
null,
))),
) orelse Allocator.Error.OutOfMemory;
}
pub fn release(self: *FontCollection) void {
c.CFRelease(self);
}
pub fn createMatchingFontDescriptors(self: *FontCollection) *foundation.Array {
const result = c.CTFontCollectionCreateMatchingFontDescriptors(@ptrCast(self));
if (result) |ptr| return @ptrFromInt(@intFromPtr(ptr));
// If we have no results, we create an empty array. This is not
// exactly matching the Mac API. We can fix this later if we want
// but I chose this to make it slightly more Zig-like at the cost
// of some memory in the rare case.
return foundation.Array.create(anyopaque, &[_]*const anyopaque{}) catch unreachable;
}
};
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 family_buf: [128]u8 = undefined;
const family = desc.copyAttribute(.family_name);
defer family.release();
const family_cstr = family.cstring(&family_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 "<no path>";
defer path.release();
const decoded = try foundation.URL.createStringByReplacingPercentEscapes(
path,
blank,
);
defer decoded.release();
break :path decoded.cstring(&buf2, .utf8) orelse
"<path cannot be converted to string>";
};
std.log.warn("i={d} name={s} family={s} path={s}", .{ i, cstr, family_cstr, path });
}
}
}
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);
}
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();
var desc_arr = [_]*const text.FontDescriptor{desc};
const arr = try foundation.Array.create(text.FontDescriptor, &desc_arr);
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);
}
test "from descriptors no match" {
const testing = std.testing;
const name = try foundation.String.createWithBytes("ThisShouldNeverExist", .utf8, false);
defer name.release();
const desc = try text.FontDescriptor.createWithNameAndSize(name, 12);
defer desc.release();
var desc_arr = [_]*const text.FontDescriptor{desc};
const arr = try foundation.Array.create(text.FontDescriptor, &desc_arr);
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);
}
+287
View File
@@ -0,0 +1,287 @@
const std = @import("std");
const Allocator = std.mem.Allocator;
const foundation = @import("../foundation.zig");
const c = @import("c.zig").c;
pub const FontDescriptor = opaque {
pub fn createWithNameAndSize(name: *foundation.String, size: f64) Allocator.Error!*FontDescriptor {
return @as(
?*FontDescriptor,
@ptrFromInt(@intFromPtr(c.CTFontDescriptorCreateWithNameAndSize(@ptrCast(name), size))),
) orelse Allocator.Error.OutOfMemory;
}
pub fn createWithAttributes(dict: *foundation.Dictionary) Allocator.Error!*FontDescriptor {
return @as(
?*FontDescriptor,
@ptrFromInt(@intFromPtr(c.CTFontDescriptorCreateWithAttributes(@ptrCast(dict)))),
) orelse Allocator.Error.OutOfMemory;
}
pub fn createCopyWithAttributes(
original: *FontDescriptor,
dict: *foundation.Dictionary,
) Allocator.Error!*FontDescriptor {
return @as(
?*FontDescriptor,
@ptrFromInt(@intFromPtr(c.CTFontDescriptorCreateCopyWithAttributes(
@ptrCast(original),
@ptrCast(dict),
))),
) orelse Allocator.Error.OutOfMemory;
}
pub fn createCopyWithVariation(
original: *FontDescriptor,
id: *foundation.Number,
value: f64,
) Allocator.Error!*FontDescriptor {
return @as(
?*FontDescriptor,
@ptrCast(@constCast(c.CTFontDescriptorCreateCopyWithVariation(
@ptrCast(original),
@ptrCast(id),
value,
))),
) orelse Allocator.Error.OutOfMemory;
}
pub fn retain(self: *FontDescriptor) void {
_ = c.CFRetain(self);
}
pub fn release(self: *FontDescriptor) void {
c.CFRelease(self);
}
pub fn copyAttribute(self: *const FontDescriptor, comptime attr: FontAttribute) ?attr.Value() {
return @ptrFromInt(@intFromPtr(c.CTFontDescriptorCopyAttribute(
@ptrCast(self),
@ptrCast(attr.key()),
)));
}
pub fn copyAttributes(self: *const FontDescriptor) *foundation.Dictionary {
return @ptrFromInt(@intFromPtr(c.CTFontDescriptorCopyAttributes(
@ptrCast(self),
)));
}
};
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,
// https://developer.apple.com/documentation/coretext/core_text_constants?language=objc
variation_axes,
pub fn key(self: FontAttribute) *foundation.String {
return @as(*foundation.String, @ptrFromInt(@intFromPtr(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,
.variation_axes => c.kCTFontVariationAxesAttribute,
})));
}
pub fn Value(comptime self: FontAttribute) type {
return switch (self) {
.url => *foundation.URL,
.name => *foundation.String,
.display_name => *foundation.String,
.family_name => *foundation.String,
.style_name => *foundation.String,
.traits => *foundation.Dictionary,
.variation => *foundation.Dictionary,
.size => *foundation.Number,
.matrix => *anyopaque, // CFDataRef
.cascade_list => *foundation.Array,
.character_set => *anyopaque, // CFCharacterSetRef
.languages => *foundation.Array,
.baseline_adjust => *foundation.Number,
.macintosh_encodings => *foundation.Number,
.features => *foundation.Array,
.feature_settings => *foundation.Array,
.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
.variation_axes => *foundation.Array,
};
}
};
pub const FontTraitKey = enum {
symbolic,
weight,
width,
slant,
pub fn key(self: FontTraitKey) *foundation.String {
return @as(*foundation.String, @ptrFromInt(@intFromPtr(switch (self) {
.symbolic => c.kCTFontSymbolicTrait,
.weight => c.kCTFontWeightTrait,
.width => c.kCTFontWidthTrait,
.slant => c.kCTFontSlantTrait,
})));
}
pub fn Value(self: FontTraitKey) type {
return switch (self) {
.symbolic => *foundation.Number,
.weight => *foundation.Number,
.width => *foundation.Number,
.slant => *foundation.Number,
};
}
};
// https://developer.apple.com/documentation/coretext/ctfont/font_variation_axis_dictionary_keys?language=objc
pub const FontVariationAxisKey = enum {
identifier,
minimum_value,
maximum_value,
default_value,
name,
hidden,
pub fn key(self: FontVariationAxisKey) *foundation.String {
return @as(*foundation.String, @ptrFromInt(@intFromPtr(switch (self) {
.identifier => c.kCTFontVariationAxisIdentifierKey,
.minimum_value => c.kCTFontVariationAxisMinimumValueKey,
.maximum_value => c.kCTFontVariationAxisMaximumValueKey,
.default_value => c.kCTFontVariationAxisDefaultValueKey,
.name => c.kCTFontVariationAxisNameKey,
.hidden => c.kCTFontVariationAxisHiddenKey,
})));
}
pub fn Value(comptime self: FontVariationAxisKey) type {
return switch (self) {
.identifier => foundation.Number,
.minimum_value => foundation.Number,
.maximum_value => foundation.Number,
.default_value => foundation.Number,
.name => foundation.String,
.hidden => foundation.Number,
};
}
};
pub const FontSymbolicTraits = packed struct(u32) {
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 @as(FontSymbolicTraits, @bitCast(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, @as(c.CTFontSymbolicTraits, @bitCast(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;
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);
}
}
+23
View File
@@ -0,0 +1,23 @@
const std = @import("std");
const Allocator = std.mem.Allocator;
const foundation = @import("../foundation.zig");
const FontDescriptor = @import("./font_descriptor.zig").FontDescriptor;
const c = @import("c.zig").c;
pub fn createFontDescriptorsFromURL(url: *foundation.URL) ?*foundation.Array {
return @ptrFromInt(@intFromPtr(c.CTFontManagerCreateFontDescriptorsFromURL(
@ptrCast(url),
)));
}
pub fn createFontDescriptorsFromData(data: *foundation.Data) ?*foundation.Array {
return @ptrFromInt(@intFromPtr(c.CTFontManagerCreateFontDescriptorsFromData(
@ptrCast(data),
)));
}
pub fn createFontDescriptorFromData(data: *foundation.Data) ?*FontDescriptor {
return @ptrFromInt(@intFromPtr(c.CTFontManagerCreateFontDescriptorFromData(
@ptrCast(data),
)));
}
+33
View File
@@ -0,0 +1,33 @@
const std = @import("std");
const assert = std.debug.assert;
const Allocator = std.mem.Allocator;
const foundation = @import("../foundation.zig");
const graphics = @import("../graphics.zig");
const text = @import("../text.zig");
const c = @import("c.zig").c;
pub const Frame = opaque {
pub fn release(self: *Frame) void {
foundation.CFRelease(self);
}
pub fn getLineOrigins(
self: *Frame,
range: foundation.Range,
points: []graphics.Point,
) void {
c.CTFrameGetLineOrigins(
@ptrCast(self),
@bitCast(range),
@ptrCast(points.ptr),
);
}
pub fn getLines(self: *Frame) *foundation.Array {
return @ptrFromInt(@intFromPtr(c.CTFrameGetLines(@ptrCast(self))));
}
};
test {
// See framesetter tests...
}
+66
View File
@@ -0,0 +1,66 @@
const std = @import("std");
const assert = std.debug.assert;
const Allocator = std.mem.Allocator;
const foundation = @import("../foundation.zig");
const graphics = @import("../graphics.zig");
const text = @import("../text.zig");
const c = @import("c.zig").c;
pub const Framesetter = opaque {
pub fn createWithAttributedString(str: *foundation.AttributedString) Allocator.Error!*Framesetter {
return @as(
?*Framesetter,
@ptrFromInt(@intFromPtr(c.CTFramesetterCreateWithAttributedString(
@ptrCast(str),
))),
) orelse Allocator.Error.OutOfMemory;
}
pub fn release(self: *Framesetter) void {
foundation.CFRelease(self);
}
pub fn createFrame(
self: *Framesetter,
range: foundation.Range,
path: *graphics.Path,
attrs: ?*foundation.Dictionary,
) !*text.Frame {
return @as(
?*text.Frame,
@ptrFromInt(@intFromPtr(c.CTFramesetterCreateFrame(
@ptrCast(self),
@bitCast(range),
@ptrCast(path),
@ptrCast(attrs),
))),
) orelse error.FrameCreateFailed;
}
};
test {
const str = try foundation.MutableAttributedString.create(0);
defer str.release();
{
const rep = try foundation.String.createWithBytes("hello", .utf8, false);
defer rep.release();
str.replaceString(foundation.Range.init(0, 0), rep);
}
const fs = try Framesetter.createWithAttributedString(@ptrCast(str));
defer fs.release();
const path = try graphics.Path.createWithRect(graphics.Rect.init(0, 0, 100, 200), null);
defer path.release();
const frame = try fs.createFrame(
foundation.Range.init(0, 0),
path,
null,
);
defer frame.release();
{
var points: [1]graphics.Point = undefined;
frame.getLineOrigins(foundation.Range.init(0, 1), &points);
}
}
+123
View File
@@ -0,0 +1,123 @@
const std = @import("std");
const assert = std.debug.assert;
const Allocator = std.mem.Allocator;
const foundation = @import("../foundation.zig");
const graphics = @import("../graphics.zig");
const text = @import("../text.zig");
const c = @import("c.zig").c;
pub const Line = opaque {
pub fn createWithAttributedString(str: *foundation.AttributedString) Allocator.Error!*Line {
return @as(
?*Line,
@ptrFromInt(@intFromPtr(c.CTLineCreateWithAttributedString(
@ptrCast(str),
))),
) orelse Allocator.Error.OutOfMemory;
}
pub fn release(self: *Line) void {
foundation.CFRelease(self);
}
pub fn getGlyphCount(self: *Line) usize {
return @intCast(c.CTLineGetGlyphCount(
@ptrCast(self),
));
}
pub fn getBoundsWithOptions(
self: *Line,
opts: LineBoundsOptions,
) graphics.Rect {
return @bitCast(c.CTLineGetBoundsWithOptions(
@ptrCast(self),
@bitCast(opts),
));
}
pub fn getTypographicBounds(
self: *Line,
ascent: ?*f64,
descent: ?*f64,
leading: ?*f64,
) f64 {
return c.CTLineGetTypographicBounds(
@ptrCast(self),
ascent,
descent,
leading,
);
}
pub fn getGlyphRuns(self: *Line) *foundation.Array {
return @ptrCast(@constCast(c.CTLineGetGlyphRuns(@ptrCast(self))));
}
};
pub const LineBoundsOptions = packed struct {
exclude_leading: bool = false,
exclude_shifts: bool = false,
hanging_punctuation: bool = false,
glyph_path_bounds: bool = false,
use_optical_bounds: bool = false,
language_extents: bool = false,
_padding: u58 = 0,
test {
try std.testing.expectEqual(
@bitSizeOf(c.CTLineBoundsOptions),
@bitSizeOf(LineBoundsOptions),
);
}
test "bitcast" {
const actual: c.CTLineBoundsOptions = c.kCTLineBoundsExcludeTypographicShifts |
c.kCTLineBoundsUseOpticalBounds;
const expected: LineBoundsOptions = .{
.exclude_shifts = true,
.use_optical_bounds = true,
};
try std.testing.expectEqual(actual, @as(c.CTLineBoundsOptions, @bitCast(expected)));
}
};
test {
@import("std").testing.refAllDecls(@This());
}
test "line" {
const testing = std.testing;
const font = font: {
const name = try foundation.String.createWithBytes("Monaco", .utf8, false);
defer name.release();
const desc = try text.FontDescriptor.createWithNameAndSize(name, 12);
defer desc.release();
break :font try text.Font.createWithFontDescriptor(desc, 12);
};
defer font.release();
const rep = try foundation.String.createWithBytes("hello", .utf8, false);
defer rep.release();
const str = try foundation.MutableAttributedString.create(rep.getLength());
defer str.release();
str.replaceString(foundation.Range.init(0, 0), rep);
str.setAttribute(
foundation.Range.init(0, rep.getLength()),
text.StringAttribute.font,
font,
);
const line = try Line.createWithAttributedString(@as(*foundation.AttributedString, @ptrCast(str)));
defer line.release();
try testing.expectEqual(@as(usize, 5), line.getGlyphCount());
// TODO: this is a garbage value but should work...
const bounds = line.getBoundsWithOptions(.{});
_ = bounds;
//std.log.warn("bounds={}", .{bounds});
}
+49
View File
@@ -0,0 +1,49 @@
const std = @import("std");
const assert = std.debug.assert;
const Allocator = std.mem.Allocator;
const foundation = @import("../foundation.zig");
const graphics = @import("../graphics.zig");
const text = @import("../text.zig");
const c = @import("c.zig").c;
// https://developer.apple.com/documentation/coretext/ctparagraphstyle?language=objc
pub const ParagraphStyle = opaque {
pub fn create(
settings: []const ParagraphStyleSetting,
) Allocator.Error!*ParagraphStyle {
return @ptrCast(@constCast(c.CTParagraphStyleCreate(
@ptrCast(settings.ptr),
settings.len,
)));
}
pub fn release(self: *ParagraphStyle) void {
foundation.CFRelease(self);
}
};
/// https://developer.apple.com/documentation/coretext/ctparagraphstylesetting?language=objc
pub const ParagraphStyleSetting = extern struct {
spec: ParagraphStyleSpecifier,
value_size: usize,
value: *const anyopaque,
};
/// https://developer.apple.com/documentation/coretext/ctparagraphstylespecifier?language=objc
pub const ParagraphStyleSpecifier = enum(c_uint) {
base_writing_direction = 13,
};
/// https://developer.apple.com/documentation/uikit/nswritingdirectionattributename?language=objc
pub const WritingDirection = enum(c_int) {
natural = -1,
ltr = 0,
rtl = 1,
lro = 2,
rlo = 3,
};
test ParagraphStyle {
const p = try ParagraphStyle.create(&.{});
defer p.release();
}
+117
View File
@@ -0,0 +1,117 @@
const std = @import("std");
const assert = std.debug.assert;
const Allocator = std.mem.Allocator;
const foundation = @import("../foundation.zig");
const graphics = @import("../graphics.zig");
const text = @import("../text.zig");
const c = @import("c.zig").c;
pub const Run = opaque {
pub fn release(self: *Run) void {
foundation.CFRelease(self);
}
pub fn getGlyphCount(self: *Run) usize {
return @intCast(c.CTRunGetGlyphCount(@ptrCast(self)));
}
pub fn getGlyphsPtr(self: *Run) ?[]const graphics.Glyph {
const len = self.getGlyphCount();
if (len == 0) return &.{};
const ptr: [*c]const graphics.Glyph = @ptrCast(
c.CTRunGetGlyphsPtr(@ptrCast(self)),
);
if (ptr == null) return null;
return ptr[0..len];
}
pub fn getGlyphs(self: *Run, alloc: Allocator) ![]const graphics.Glyph {
const len = self.getGlyphCount();
const ptr = try alloc.alloc(graphics.Glyph, len);
errdefer alloc.free(ptr);
c.CTRunGetGlyphs(
@ptrCast(self),
.{ .location = 0, .length = 0 },
@ptrCast(ptr.ptr),
);
return ptr;
}
pub fn getPositionsPtr(self: *Run) ?[]const graphics.Point {
const len = self.getGlyphCount();
if (len == 0) return &.{};
const ptr: [*c]const graphics.Point = @ptrCast(
c.CTRunGetPositionsPtr(@ptrCast(self)),
);
if (ptr == null) return null;
return ptr[0..len];
}
pub fn getPositions(self: *Run, alloc: Allocator) ![]const graphics.Point {
const len = self.getGlyphCount();
const ptr = try alloc.alloc(graphics.Point, len);
errdefer alloc.free(ptr);
c.CTRunGetPositions(
@ptrCast(self),
.{ .location = 0, .length = 0 },
@ptrCast(ptr.ptr),
);
return ptr;
}
pub fn getAdvancesPtr(self: *Run) ?[]const graphics.Size {
const len = self.getGlyphCount();
if (len == 0) return &.{};
const ptr: [*c]const graphics.Size = @ptrCast(
c.CTRunGetAdvancesPtr(@ptrCast(self)),
);
if (ptr == null) return null;
return ptr[0..len];
}
pub fn getAdvances(self: *Run, alloc: Allocator) ![]const graphics.Size {
const len = self.getGlyphCount();
const ptr = try alloc.alloc(graphics.Size, len);
errdefer alloc.free(ptr);
c.CTRunGetAdvances(
@ptrCast(self),
.{ .location = 0, .length = 0 },
@ptrCast(ptr.ptr),
);
return ptr;
}
pub fn getStringIndicesPtr(self: *Run) ?[]const usize {
const len = self.getGlyphCount();
if (len == 0) return &.{};
const ptr: [*c]const usize = @ptrCast(
c.CTRunGetStringIndicesPtr(@ptrCast(self)),
);
if (ptr == null) return null;
return ptr[0..len];
}
pub fn getStringIndices(self: *Run, alloc: Allocator) ![]const usize {
const len = self.getGlyphCount();
const ptr = try alloc.alloc(usize, len);
errdefer alloc.free(ptr);
c.CTRunGetStringIndices(
@ptrCast(self),
.{ .location = 0, .length = 0 },
@ptrCast(ptr.ptr),
);
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,
};
+16
View File
@@ -0,0 +1,16 @@
const foundation = @import("../foundation.zig");
const c = @import("c.zig").c;
pub const StringAttribute = enum {
font,
paragraph_style,
writing_direction,
pub fn key(self: StringAttribute) *foundation.String {
return @ptrFromInt(@intFromPtr(switch (self) {
.font => c.kCTFontAttributeName,
.paragraph_style => c.kCTParagraphStyleAttributeName,
.writing_direction => c.kCTWritingDirectionAttributeName,
}));
}
};
+36
View File
@@ -0,0 +1,36 @@
const std = @import("std");
const assert = std.debug.assert;
const Allocator = std.mem.Allocator;
const foundation = @import("../foundation.zig");
const graphics = @import("../graphics.zig");
const text = @import("../text.zig");
const c = @import("c.zig").c;
pub const Typesetter = opaque {
pub fn createWithAttributedStringAndOptions(
str: *foundation.AttributedString,
opts: *foundation.Dictionary,
) Allocator.Error!*Typesetter {
return @as(
?*Typesetter,
@ptrFromInt(@intFromPtr(c.CTTypesetterCreateWithAttributedStringAndOptions(
@ptrCast(str),
@ptrCast(opts),
))),
) orelse Allocator.Error.OutOfMemory;
}
pub fn release(self: *Typesetter) void {
foundation.CFRelease(self);
}
pub fn createLine(
self: *Typesetter,
range: foundation.c.CFRange,
) *text.Line {
return @ptrFromInt(@intFromPtr(c.CTTypesetterCreateLine(
@ptrCast(self),
range,
)));
}
};
+10
View File
@@ -0,0 +1,10 @@
const display_link = @import("video/display_link.zig");
const pixel_format = @import("video/pixel_format.zig");
pub const c = @import("video/c.zig").c;
pub const DisplayLink = display_link.DisplayLink;
pub const PixelFormat = pixel_format.PixelFormat;
test {
@import("std").testing.refAllDecls(@This());
}
+1
View File
@@ -0,0 +1 @@
pub const c = @import("../main.zig").c;
+86
View File
@@ -0,0 +1,86 @@
const std = @import("std");
const assert = std.debug.assert;
const Allocator = std.mem.Allocator;
const c = @import("c.zig").c;
pub const DisplayLink = opaque {
pub const Error = error{
InvalidOperation,
};
pub fn createWithActiveCGDisplays() Allocator.Error!*DisplayLink {
var result: ?*DisplayLink = null;
if (c.CVDisplayLinkCreateWithActiveCGDisplays(
@ptrCast(&result),
) != c.kCVReturnSuccess)
return error.OutOfMemory;
return result orelse error.OutOfMemory;
}
pub fn release(self: *DisplayLink) void {
c.CVDisplayLinkRelease(@ptrCast(self));
}
pub fn start(self: *DisplayLink) Error!void {
if (c.CVDisplayLinkStart(@ptrCast(self)) != c.kCVReturnSuccess)
return error.InvalidOperation;
}
pub fn stop(self: *DisplayLink) Error!void {
if (c.CVDisplayLinkStop(@ptrCast(self)) != c.kCVReturnSuccess)
return error.InvalidOperation;
}
pub fn isRunning(self: *DisplayLink) bool {
return c.CVDisplayLinkIsRunning(@ptrCast(self)) != 0;
}
pub fn setCurrentCGDisplay(
self: *DisplayLink,
display_id: c.CGDirectDisplayID,
) Error!void {
if (c.CVDisplayLinkSetCurrentCGDisplay(
@ptrCast(self),
display_id,
) != c.kCVReturnSuccess)
return error.InvalidOperation;
}
// Note: this purposely throws away a ton of arguments I didn't need.
// It would be trivial to refactor this into Zig types and properly
// pass this through.
pub fn setOutputCallback(
self: *DisplayLink,
comptime Userdata: type,
comptime callbackFn: *const fn (*DisplayLink, ?*Userdata) void,
userinfo: ?*Userdata,
) Error!void {
if (c.CVDisplayLinkSetOutputCallback(
@ptrCast(self),
@ptrCast(&(struct {
fn callback(
displayLink: *DisplayLink,
inNow: *const c.CVTimeStamp,
inOutputTime: *const c.CVTimeStamp,
flagsIn: c.CVOptionFlags,
flagsOut: *c.CVOptionFlags,
inner_userinfo: ?*anyopaque,
) callconv(.c) c.CVReturn {
_ = inNow;
_ = inOutputTime;
_ = flagsIn;
_ = flagsOut;
callbackFn(
displayLink,
@ptrCast(@alignCast(inner_userinfo)),
);
return c.kCVReturnSuccess;
}
}).callback),
userinfo,
) != c.kCVReturnSuccess)
return error.InvalidOperation;
}
};
+171
View File
@@ -0,0 +1,171 @@
const c = @import("c.zig").c;
pub const PixelFormat = enum(c_int) {
/// 1 bit indexed
@"1Monochrome" = c.kCVPixelFormatType_1Monochrome,
/// 2 bit indexed
@"2Indexed" = c.kCVPixelFormatType_2Indexed,
/// 4 bit indexed
@"4Indexed" = c.kCVPixelFormatType_4Indexed,
/// 8 bit indexed
@"8Indexed" = c.kCVPixelFormatType_8Indexed,
/// 1 bit indexed gray, white is zero
@"1IndexedGray_WhiteIsZero" = c.kCVPixelFormatType_1IndexedGray_WhiteIsZero,
/// 2 bit indexed gray, white is zero
@"2IndexedGray_WhiteIsZero" = c.kCVPixelFormatType_2IndexedGray_WhiteIsZero,
/// 4 bit indexed gray, white is zero
@"4IndexedGray_WhiteIsZero" = c.kCVPixelFormatType_4IndexedGray_WhiteIsZero,
/// 8 bit indexed gray, white is zero
@"8IndexedGray_WhiteIsZero" = c.kCVPixelFormatType_8IndexedGray_WhiteIsZero,
/// 16 bit BE RGB 555
@"16BE555" = c.kCVPixelFormatType_16BE555,
/// 16 bit LE RGB 555
@"16LE555" = c.kCVPixelFormatType_16LE555,
/// 16 bit LE RGB 5551
@"16LE5551" = c.kCVPixelFormatType_16LE5551,
/// 16 bit BE RGB 565
@"16BE565" = c.kCVPixelFormatType_16BE565,
/// 16 bit LE RGB 565
@"16LE565" = c.kCVPixelFormatType_16LE565,
/// 24 bit RGB
@"24RGB" = c.kCVPixelFormatType_24RGB,
/// 24 bit BGR
@"24BGR" = c.kCVPixelFormatType_24BGR,
/// 32 bit ARGB
@"32ARGB" = c.kCVPixelFormatType_32ARGB,
/// 32 bit BGRA
@"32BGRA" = c.kCVPixelFormatType_32BGRA,
/// 32 bit ABGR
@"32ABGR" = c.kCVPixelFormatType_32ABGR,
/// 32 bit RGBA
@"32RGBA" = c.kCVPixelFormatType_32RGBA,
/// 64 bit ARGB, 16-bit big-endian samples
@"64ARGB" = c.kCVPixelFormatType_64ARGB,
/// 64 bit RGBA, 16-bit little-endian full-range (0-65535) samples
@"64RGBALE" = c.kCVPixelFormatType_64RGBALE,
/// 48 bit RGB, 16-bit big-endian samples
@"48RGB" = c.kCVPixelFormatType_48RGB,
/// 32 bit AlphaGray, 16-bit big-endian samples, black is zero
@"32AlphaGray" = c.kCVPixelFormatType_32AlphaGray,
/// 16 bit Grayscale, 16-bit big-endian samples, black is zero
@"16Gray" = c.kCVPixelFormatType_16Gray,
/// 30 bit RGB, 10-bit big-endian samples, 2 unused padding bits (at least significant end).
@"30RGB" = c.kCVPixelFormatType_30RGB,
/// 30 bit RGB, 10-bit big-endian samples, 2 unused padding bits (at most significant end), video-range (64-940).
@"30RGB_r210" = c.kCVPixelFormatType_30RGB_r210,
/// Component Y'CbCr 8-bit 4:2:2, ordered Cb Y'0 Cr Y'1
@"422YpCbCr8" = c.kCVPixelFormatType_422YpCbCr8,
/// Component Y'CbCrA 8-bit 4:4:4:4, ordered Cb Y' Cr A
@"4444YpCbCrA8" = c.kCVPixelFormatType_4444YpCbCrA8,
/// Component Y'CbCrA 8-bit 4:4:4:4, rendering format. full range alpha, zero biased YUV, ordered A Y' Cb Cr
@"4444YpCbCrA8R" = c.kCVPixelFormatType_4444YpCbCrA8R,
/// Component Y'CbCrA 8-bit 4:4:4:4, ordered A Y' Cb Cr, full range alpha, video range Y'CbCr.
@"4444AYpCbCr8" = c.kCVPixelFormatType_4444AYpCbCr8,
/// Component Y'CbCrA 16-bit 4:4:4:4, ordered A Y' Cb Cr, full range alpha, video range Y'CbCr, 16-bit little-endian samples.
@"4444AYpCbCr16" = c.kCVPixelFormatType_4444AYpCbCr16,
/// Component AY'CbCr single precision floating-point 4:4:4:4
@"4444AYpCbCrFloat" = c.kCVPixelFormatType_4444AYpCbCrFloat,
/// Component Y'CbCr 8-bit 4:4:4, ordered Cr Y' Cb, video range Y'CbCr
@"444YpCbCr8" = c.kCVPixelFormatType_444YpCbCr8,
/// Component Y'CbCr 10,12,14,16-bit 4:2:2
@"422YpCbCr16" = c.kCVPixelFormatType_422YpCbCr16,
/// Component Y'CbCr 10-bit 4:2:2
@"422YpCbCr10" = c.kCVPixelFormatType_422YpCbCr10,
/// Component Y'CbCr 10-bit 4:4:4
@"444YpCbCr10" = c.kCVPixelFormatType_444YpCbCr10,
/// Planar Component Y'CbCr 8-bit 4:2:0. baseAddr points to a big-endian CVPlanarPixelBufferInfo_YCbCrPlanar struct
@"420YpCbCr8Planar" = c.kCVPixelFormatType_420YpCbCr8Planar,
/// Planar Component Y'CbCr 8-bit 4:2:0, full range. baseAddr points to a big-endian CVPlanarPixelBufferInfo_YCbCrPlanar struct
@"420YpCbCr8PlanarFullRange" = c.kCVPixelFormatType_420YpCbCr8PlanarFullRange,
/// First plane: Video-range Component Y'CbCr 8-bit 4:2:2, ordered Cb Y'0 Cr Y'1; second plane: alpha 8-bit 0-255
@"422YpCbCr_4A_8BiPlanar" = c.kCVPixelFormatType_422YpCbCr_4A_8BiPlanar,
/// Bi-Planar Component Y'CbCr 8-bit 4:2:0, video-range (luma=[16,235] chroma=[16,240]). baseAddr points to a big-endian CVPlanarPixelBufferInfo_YCbCrBiPlanar struct
@"420YpCbCr8BiPlanarVideoRange" = c.kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange,
/// Bi-Planar Component Y'CbCr 8-bit 4:2:0, full-range (luma=[0,255] chroma=[1,255]). baseAddr points to a big-endian CVPlanarPixelBufferInfo_YCbCrBiPlanar struct
@"420YpCbCr8BiPlanarFullRange" = c.kCVPixelFormatType_420YpCbCr8BiPlanarFullRange,
/// Bi-Planar Component Y'CbCr 8-bit 4:2:2, video-range (luma=[16,235] chroma=[16,240]). baseAddr points to a big-endian CVPlanarPixelBufferInfo_YCbCrBiPlanar struct
@"422YpCbCr8BiPlanarVideoRange" = c.kCVPixelFormatType_422YpCbCr8BiPlanarVideoRange,
/// Bi-Planar Component Y'CbCr 8-bit 4:2:2, full-range (luma=[0,255] chroma=[1,255]). baseAddr points to a big-endian CVPlanarPixelBufferInfo_YCbCrBiPlanar struct
@"422YpCbCr8BiPlanarFullRange" = c.kCVPixelFormatType_422YpCbCr8BiPlanarFullRange,
/// Bi-Planar Component Y'CbCr 8-bit 4:4:4, video-range (luma=[16,235] chroma=[16,240]). baseAddr points to a big-endian CVPlanarPixelBufferInfo_YCbCrBiPlanar struct
@"444YpCbCr8BiPlanarVideoRange" = c.kCVPixelFormatType_444YpCbCr8BiPlanarVideoRange,
/// Bi-Planar Component Y'CbCr 8-bit 4:4:4, full-range (luma=[0,255] chroma=[1,255]). baseAddr points to a big-endian CVPlanarPixelBufferInfo_YCbCrBiPlanar struct
@"444YpCbCr8BiPlanarFullRange" = c.kCVPixelFormatType_444YpCbCr8BiPlanarFullRange,
/// Component Y'CbCr 8-bit 4:2:2, ordered Y'0 Cb Y'1 Cr
@"422YpCbCr8_yuvs" = c.kCVPixelFormatType_422YpCbCr8_yuvs,
/// Component Y'CbCr 8-bit 4:2:2, full range, ordered Y'0 Cb Y'1 Cr
@"422YpCbCr8FullRange" = c.kCVPixelFormatType_422YpCbCr8FullRange,
/// 8 bit one component, black is zero
OneComponent8 = c.kCVPixelFormatType_OneComponent8,
/// 8 bit two component, black is zero
TwoComponent8 = c.kCVPixelFormatType_TwoComponent8,
/// little-endian RGB101010, 2 MSB are ignored, wide-gamut (384-895)
@"30RGBLEPackedWideGamut" = c.kCVPixelFormatType_30RGBLEPackedWideGamut,
/// little-endian ARGB2101010 full-range ARGB
ARGB2101010LEPacked = c.kCVPixelFormatType_ARGB2101010LEPacked,
/// little-endian ARGB10101010, each 10 bits in the MSBs of 16bits, wide-gamut (384-895, including alpha)
@"40ARGBLEWideGamut" = c.kCVPixelFormatType_40ARGBLEWideGamut,
/// little-endian ARGB10101010, each 10 bits in the MSBs of 16bits, wide-gamut (384-895, including alpha). Alpha premultiplied
@"40ARGBLEWideGamutPremultiplied" = c.kCVPixelFormatType_40ARGBLEWideGamutPremultiplied,
/// 10 bit little-endian one component, stored as 10 MSBs of 16 bits, black is zero
OneComponent10 = c.kCVPixelFormatType_OneComponent10,
/// 12 bit little-endian one component, stored as 12 MSBs of 16 bits, black is zero
OneComponent12 = c.kCVPixelFormatType_OneComponent12,
/// 16 bit little-endian one component, black is zero
OneComponent16 = c.kCVPixelFormatType_OneComponent16,
/// 16 bit little-endian two component, black is zero
TwoComponent16 = c.kCVPixelFormatType_TwoComponent16,
/// 16 bit one component IEEE half-precision float, 16-bit little-endian samples
OneComponent16Half = c.kCVPixelFormatType_OneComponent16Half,
/// 32 bit one component IEEE float, 32-bit little-endian samples
OneComponent32Float = c.kCVPixelFormatType_OneComponent32Float,
/// 16 bit two component IEEE half-precision float, 16-bit little-endian samples
TwoComponent16Half = c.kCVPixelFormatType_TwoComponent16Half,
/// 32 bit two component IEEE float, 32-bit little-endian samples
TwoComponent32Float = c.kCVPixelFormatType_TwoComponent32Float,
/// 64 bit RGBA IEEE half-precision float, 16-bit little-endian samples
@"64RGBAHalf" = c.kCVPixelFormatType_64RGBAHalf,
/// 128 bit RGBA IEEE float, 32-bit little-endian samples
@"128RGBAFloat" = c.kCVPixelFormatType_128RGBAFloat,
/// Bayer 14-bit Little-Endian, packed in 16-bits, ordered G R G R... alternating with B G B G...
@"14Bayer_GRBG" = c.kCVPixelFormatType_14Bayer_GRBG,
/// Bayer 14-bit Little-Endian, packed in 16-bits, ordered R G R G... alternating with G B G B...
@"14Bayer_RGGB" = c.kCVPixelFormatType_14Bayer_RGGB,
/// Bayer 14-bit Little-Endian, packed in 16-bits, ordered B G B G... alternating with G R G R...
@"14Bayer_BGGR" = c.kCVPixelFormatType_14Bayer_BGGR,
/// Bayer 14-bit Little-Endian, packed in 16-bits, ordered G B G B... alternating with R G R G...
@"14Bayer_GBRG" = c.kCVPixelFormatType_14Bayer_GBRG,
/// IEEE754-2008 binary16 (half float), describing the normalized shift when comparing two images. Units are 1/meters: ( pixelShift / (pixelFocalLength * baselineInMeters) )
DisparityFloat16 = c.kCVPixelFormatType_DisparityFloat16,
/// IEEE754-2008 binary32 float, describing the normalized shift when comparing two images. Units are 1/meters: ( pixelShift / (pixelFocalLength * baselineInMeters) )
DisparityFloat32 = c.kCVPixelFormatType_DisparityFloat32,
/// IEEE754-2008 binary16 (half float), describing the depth (distance to an object) in meters
DepthFloat16 = c.kCVPixelFormatType_DepthFloat16,
/// IEEE754-2008 binary32 float, describing the depth (distance to an object) in meters
DepthFloat32 = c.kCVPixelFormatType_DepthFloat32,
/// 2 plane YCbCr10 4:2:0, each 10 bits in the MSBs of 16bits, video-range (luma=[64,940] chroma=[64,960])
@"420YpCbCr10BiPlanarVideoRange" = c.kCVPixelFormatType_420YpCbCr10BiPlanarVideoRange,
/// 2 plane YCbCr10 4:2:2, each 10 bits in the MSBs of 16bits, video-range (luma=[64,940] chroma=[64,960])
@"422YpCbCr10BiPlanarVideoRange" = c.kCVPixelFormatType_422YpCbCr10BiPlanarVideoRange,
/// 2 plane YCbCr10 4:4:4, each 10 bits in the MSBs of 16bits, video-range (luma=[64,940] chroma=[64,960])
@"444YpCbCr10BiPlanarVideoRange" = c.kCVPixelFormatType_444YpCbCr10BiPlanarVideoRange,
/// 2 plane YCbCr10 4:2:0, each 10 bits in the MSBs of 16bits, full-range (Y range 0-1023)
@"420YpCbCr10BiPlanarFullRange" = c.kCVPixelFormatType_420YpCbCr10BiPlanarFullRange,
/// 2 plane YCbCr10 4:2:2, each 10 bits in the MSBs of 16bits, full-range (Y range 0-1023)
@"422YpCbCr10BiPlanarFullRange" = c.kCVPixelFormatType_422YpCbCr10BiPlanarFullRange,
/// 2 plane YCbCr10 4:4:4, each 10 bits in the MSBs of 16bits, full-range (Y range 0-1023)
@"444YpCbCr10BiPlanarFullRange" = c.kCVPixelFormatType_444YpCbCr10BiPlanarFullRange,
/// first and second planes as per 420YpCbCr8BiPlanarVideoRange (420v), alpha 8 bits in third plane full-range. No CVPlanarPixelBufferInfo struct.
@"420YpCbCr8VideoRange_8A_TriPlanar" = c.kCVPixelFormatType_420YpCbCr8VideoRange_8A_TriPlanar,
/// Single plane Bayer 16-bit little-endian sensor element ("sensel".*) samples from full-size decoding of ProRes RAW images; Bayer pattern (sensel ordering) and other raw conversion information is described via buffer attachments
@"16VersatileBayer" = c.kCVPixelFormatType_16VersatileBayer,
/// Single plane 64-bit RGBA (16-bit little-endian samples) from downscaled decoding of ProRes RAW images; components--which may not be co-sited with one another--are sensel values and require raw conversion, information for which is described via buffer attachments
@"64RGBA_DownscaledProResRAW" = c.kCVPixelFormatType_64RGBA_DownscaledProResRAW,
/// 2 plane YCbCr16 4:2:2, video-range (luma=[4096,60160] chroma=[4096,61440])
@"422YpCbCr16BiPlanarVideoRange" = c.kCVPixelFormatType_422YpCbCr16BiPlanarVideoRange,
/// 2 plane YCbCr16 4:4:4, video-range (luma=[4096,60160] chroma=[4096,61440])
@"444YpCbCr16BiPlanarVideoRange" = c.kCVPixelFormatType_444YpCbCr16BiPlanarVideoRange,
/// 3 plane video-range YCbCr16 4:4:4 with 16-bit full-range alpha (luma=[4096,60160] chroma=[4096,61440] alpha=[0,65535]). No CVPlanarPixelBufferInfo struct.
@"444YpCbCr16VideoRange_16A_TriPlanar" = c.kCVPixelFormatType_444YpCbCr16VideoRange_16A_TriPlanar,
_,
};