mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-15 00:06:09 +03:00
Merge pull request #16 from mitchellh/macos
Mac Core Foundation and Core Text Bindings
This commit is contained in:
@ -9,6 +9,7 @@ const harfbuzz = @import("pkg/harfbuzz/build.zig");
|
||||
const libxml2 = @import("vendor/zig-libxml2/libxml2.zig");
|
||||
const libuv = @import("pkg/libuv/build.zig");
|
||||
const libpng = @import("pkg/libpng/build.zig");
|
||||
const macos = @import("pkg/macos/build.zig");
|
||||
const utf8proc = @import("pkg/utf8proc/build.zig");
|
||||
const zlib = @import("pkg/zlib/build.zig");
|
||||
const tracylib = @import("pkg/tracy/build.zig");
|
||||
@ -184,6 +185,12 @@ fn addDeps(
|
||||
step.addPackage(libuv.pkg);
|
||||
step.addPackage(utf8proc.pkg);
|
||||
|
||||
// Mac Stuff
|
||||
if (step.target.isDarwin()) {
|
||||
step.addPackage(macos.pkg);
|
||||
_ = try macos.link(b, step, .{});
|
||||
}
|
||||
|
||||
// We always statically compile glad
|
||||
step.addIncludePath("vendor/glad/include/");
|
||||
step.addCSourceFile("vendor/glad/src/gl.c", &.{});
|
||||
|
25
pkg/macos/build.zig
Normal file
25
pkg/macos/build.zig
Normal file
@ -0,0 +1,25 @@
|
||||
const std = @import("std");
|
||||
const builtin = @import("builtin");
|
||||
|
||||
pub const pkg = std.build.Pkg{
|
||||
.name = "macos",
|
||||
.source = .{ .path = thisDir() ++ "/main.zig" },
|
||||
};
|
||||
|
||||
fn thisDir() []const u8 {
|
||||
return std.fs.path.dirname(@src().file) orelse ".";
|
||||
}
|
||||
|
||||
pub const Options = struct {};
|
||||
|
||||
pub fn link(
|
||||
b: *std.build.Builder,
|
||||
step: *std.build.LibExeObjStep,
|
||||
opt: Options,
|
||||
) !*std.build.LibExeObjStep {
|
||||
_ = opt;
|
||||
const lib = b.addStaticLibrary("macos", null);
|
||||
step.linkFramework("CoreFoundation");
|
||||
step.linkFramework("CoreText");
|
||||
return lib;
|
||||
}
|
12
pkg/macos/foundation.zig
Normal file
12
pkg/macos/foundation.zig
Normal file
@ -0,0 +1,12 @@
|
||||
pub const c = @import("foundation/c.zig");
|
||||
pub usingnamespace @import("foundation/array.zig");
|
||||
pub usingnamespace @import("foundation/base.zig");
|
||||
pub usingnamespace @import("foundation/dictionary.zig");
|
||||
pub usingnamespace @import("foundation/number.zig");
|
||||
pub usingnamespace @import("foundation/string.zig");
|
||||
pub usingnamespace @import("foundation/type.zig");
|
||||
pub usingnamespace @import("foundation/url.zig");
|
||||
|
||||
test {
|
||||
@import("std").testing.refAllDecls(@This());
|
||||
}
|
55
pkg/macos/foundation/array.zig
Normal file
55
pkg/macos/foundation/array.zig
Normal file
@ -0,0 +1,55 @@
|
||||
const std = @import("std");
|
||||
const Allocator = std.mem.Allocator;
|
||||
const cftype = @import("type.zig");
|
||||
|
||||
pub const Array = opaque {
|
||||
pub fn create(comptime T: type, values: []*const T) Allocator.Error!*Array {
|
||||
return CFArrayCreate(
|
||||
null,
|
||||
@ptrCast([*]*const anyopaque, values.ptr),
|
||||
@intCast(usize, values.len),
|
||||
null,
|
||||
) orelse error.OutOfMemory;
|
||||
}
|
||||
|
||||
pub fn release(self: *Array) void {
|
||||
cftype.CFRelease(self);
|
||||
}
|
||||
|
||||
pub fn getCount(self: *Array) usize {
|
||||
return CFArrayGetCount(self);
|
||||
}
|
||||
|
||||
/// Note the return type is actually a `*const T` but we strip the
|
||||
/// constness so that further API calls work correctly. The Foundation
|
||||
/// API doesn't properly mark things const/non-const.
|
||||
pub fn getValueAtIndex(self: *Array, comptime T: type, idx: usize) *T {
|
||||
return @ptrCast(*T, CFArrayGetValueAtIndex(self, idx));
|
||||
}
|
||||
|
||||
pub extern "c" fn CFArrayCreate(
|
||||
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;
|
||||
};
|
||||
|
||||
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.*);
|
||||
}
|
||||
}
|
5
pkg/macos/foundation/base.zig
Normal file
5
pkg/macos/foundation/base.zig
Normal file
@ -0,0 +1,5 @@
|
||||
pub const ComparisonResult = enum(c_int) {
|
||||
less = -1,
|
||||
equal = 0,
|
||||
greater = 1,
|
||||
};
|
3
pkg/macos/foundation/c.zig
Normal file
3
pkg/macos/foundation/c.zig
Normal file
@ -0,0 +1,3 @@
|
||||
pub usingnamespace @cImport({
|
||||
@cInclude("CoreFoundation/CoreFoundation.h");
|
||||
});
|
58
pkg/macos/foundation/dictionary.zig
Normal file
58
pkg/macos/foundation/dictionary.zig
Normal file
@ -0,0 +1,58 @@
|
||||
const std = @import("std");
|
||||
const assert = std.debug.assert;
|
||||
const Allocator = std.mem.Allocator;
|
||||
const foundation = @import("../foundation.zig");
|
||||
const c = @import("c.zig");
|
||||
|
||||
pub const Dictionary = opaque {
|
||||
pub fn create(
|
||||
keys: ?[]?*const anyopaque,
|
||||
values: ?[]?*const anyopaque,
|
||||
) Allocator.Error!*Dictionary {
|
||||
if (keys != null or values != null) {
|
||||
assert(keys != null);
|
||||
assert(values != null);
|
||||
assert(keys.?.len == values.?.len);
|
||||
}
|
||||
|
||||
return @intToPtr(?*Dictionary, @ptrToInt(c.CFDictionaryCreate(
|
||||
null,
|
||||
@ptrCast([*c]?*const anyopaque, if (keys) |slice| slice.ptr else null),
|
||||
@ptrCast([*c]?*const anyopaque, if (values) |slice| slice.ptr else null),
|
||||
@intCast(c.CFIndex, if (keys) |slice| slice.len else 0),
|
||||
&c.kCFTypeDictionaryKeyCallBacks,
|
||||
&c.kCFTypeDictionaryValueCallBacks,
|
||||
))) orelse Allocator.Error.OutOfMemory;
|
||||
}
|
||||
|
||||
pub fn release(self: *Dictionary) void {
|
||||
foundation.CFRelease(self);
|
||||
}
|
||||
|
||||
pub fn getCount(self: *Dictionary) usize {
|
||||
return @intCast(usize, c.CFDictionaryGetCount(@ptrCast(c.CFDictionaryRef, self)));
|
||||
}
|
||||
|
||||
pub fn getValue(self: *Dictionary, comptime V: type, key: ?*const anyopaque) ?*V {
|
||||
return @intToPtr(?*V, @ptrToInt(c.CFDictionaryGetValue(
|
||||
@ptrCast(c.CFDictionaryRef, self),
|
||||
key,
|
||||
)));
|
||||
}
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
79
pkg/macos/foundation/number.zig
Normal file
79
pkg/macos/foundation/number.zig
Normal file
@ -0,0 +1,79 @@
|
||||
const std = @import("std");
|
||||
const Allocator = std.mem.Allocator;
|
||||
const foundation = @import("../foundation.zig");
|
||||
const c = @import("c.zig");
|
||||
|
||||
pub const Number = opaque {
|
||||
pub fn create(
|
||||
comptime type_: NumberType,
|
||||
value: *const type_.ValueType(),
|
||||
) Allocator.Error!*Number {
|
||||
return @intToPtr(?*Number, @ptrToInt(c.CFNumberCreate(
|
||||
null,
|
||||
@enumToInt(type_),
|
||||
value,
|
||||
))) orelse Allocator.Error.OutOfMemory;
|
||||
}
|
||||
|
||||
pub fn getValue(self: *Number, comptime t: NumberType, ptr: *t.ValueType()) bool {
|
||||
return c.CFNumberGetValue(
|
||||
@ptrCast(c.CFNumberRef, self),
|
||||
@enumToInt(t),
|
||||
ptr,
|
||||
) == 1;
|
||||
}
|
||||
|
||||
pub fn release(self: *Number) void {
|
||||
c.CFRelease(self);
|
||||
}
|
||||
};
|
||||
|
||||
pub const NumberType = enum(c.CFNumberType) {
|
||||
sint8 = c.kCFNumberSInt8Type,
|
||||
sint16 = c.kCFNumberSInt16Type,
|
||||
sint32 = c.kCFNumberSInt32Type,
|
||||
sint64 = c.kCFNumberSInt64Type,
|
||||
float32 = c.kCFNumberFloat32Type,
|
||||
float64 = c.kCFNumberFloat64Type,
|
||||
char = c.kCFNumberCharType,
|
||||
short = c.kCFNumberShortType,
|
||||
int = c.kCFNumberIntType,
|
||||
long = c.kCFNumberLongType,
|
||||
long_long = c.kCFNumberLongLongType,
|
||||
float = c.kCFNumberFloatType,
|
||||
double = c.kCFNumberDoubleType,
|
||||
cf_index = c.kCFNumberCFIndexType,
|
||||
ns_integer = c.kCFNumberNSIntegerType,
|
||||
cg_float = c.kCFNumberCGFloatType,
|
||||
|
||||
pub fn ValueType(self: NumberType) type {
|
||||
return switch (self) {
|
||||
.sint8 => i8,
|
||||
.sint16 => i16,
|
||||
.sint32 => i32,
|
||||
.sint64 => i64,
|
||||
.float32 => f32,
|
||||
.float64 => f64,
|
||||
.char => u8,
|
||||
.short => c_short,
|
||||
.int => c_int,
|
||||
.long => c_long,
|
||||
.long_long => c_longlong,
|
||||
.float => f32,
|
||||
.double => f64,
|
||||
else => unreachable, // TODO
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
test {
|
||||
const testing = std.testing;
|
||||
|
||||
const inner: i8 = 42;
|
||||
const v = try Number.create(.sint8, &inner);
|
||||
defer v.release();
|
||||
|
||||
var result: i8 = undefined;
|
||||
try testing.expect(v.getValue(.sint8, &result));
|
||||
try testing.expectEqual(result, inner);
|
||||
}
|
121
pkg/macos/foundation/string.zig
Normal file
121
pkg/macos/foundation/string.zig
Normal file
@ -0,0 +1,121 @@
|
||||
const std = @import("std");
|
||||
const Allocator = std.mem.Allocator;
|
||||
const foundation = @import("../foundation.zig");
|
||||
const c = @import("c.zig");
|
||||
|
||||
pub const String = opaque {
|
||||
pub fn createWithBytes(
|
||||
bs: []const u8,
|
||||
encoding: StringEncoding,
|
||||
external: bool,
|
||||
) Allocator.Error!*String {
|
||||
return @intToPtr(?*String, @ptrToInt(c.CFStringCreateWithBytes(
|
||||
null,
|
||||
bs.ptr,
|
||||
@intCast(c_long, bs.len),
|
||||
@enumToInt(encoding),
|
||||
@boolToInt(external),
|
||||
))) orelse Allocator.Error.OutOfMemory;
|
||||
}
|
||||
|
||||
pub fn release(self: *String) void {
|
||||
c.CFRelease(self);
|
||||
}
|
||||
|
||||
pub fn hasPrefix(self: *String, prefix: *String) bool {
|
||||
return c.CFStringHasPrefix(
|
||||
@ptrCast(c.CFStringRef, self),
|
||||
@ptrCast(c.CFStringRef, prefix),
|
||||
) == 1;
|
||||
}
|
||||
|
||||
pub fn compare(
|
||||
self: *String,
|
||||
other: *String,
|
||||
options: StringComparison,
|
||||
) foundation.ComparisonResult {
|
||||
return @intToEnum(
|
||||
foundation.ComparisonResult,
|
||||
c.CFStringCompare(
|
||||
@ptrCast(c.CFStringRef, self),
|
||||
@ptrCast(c.CFStringRef, other),
|
||||
@intCast(c_ulong, @bitCast(c_int, options)),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn cstring(self: *String, buf: []u8, encoding: StringEncoding) ?[]const u8 {
|
||||
if (c.CFStringGetCString(
|
||||
@ptrCast(c.CFStringRef, self),
|
||||
buf.ptr,
|
||||
@intCast(c_long, buf.len),
|
||||
@enumToInt(encoding),
|
||||
) == 0) return null;
|
||||
return std.mem.sliceTo(buf, 0);
|
||||
}
|
||||
|
||||
pub fn cstringPtr(self: *String, encoding: StringEncoding) ?[:0]const u8 {
|
||||
const ptr = c.CFStringGetCStringPtr(
|
||||
@ptrCast(c.CFStringRef, self),
|
||||
@enumToInt(encoding),
|
||||
);
|
||||
if (ptr == null) return null;
|
||||
return std.mem.sliceTo(ptr, 0);
|
||||
}
|
||||
};
|
||||
|
||||
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,
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
1
pkg/macos/foundation/type.zig
Normal file
1
pkg/macos/foundation/type.zig
Normal file
@ -0,0 +1 @@
|
||||
pub extern "c" fn CFRelease(*anyopaque) void;
|
63
pkg/macos/foundation/url.zig
Normal file
63
pkg/macos/foundation/url.zig
Normal file
@ -0,0 +1,63 @@
|
||||
const std = @import("std");
|
||||
const Allocator = std.mem.Allocator;
|
||||
const foundation = @import("../foundation.zig");
|
||||
|
||||
pub const URL = opaque {
|
||||
pub fn createWithString(str: *foundation.String, base: ?*URL) Allocator.Error!*URL {
|
||||
return CFURLCreateWithString(
|
||||
null,
|
||||
str,
|
||||
base,
|
||||
) orelse error.OutOfMemory;
|
||||
}
|
||||
|
||||
pub fn 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;
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
6
pkg/macos/main.zig
Normal file
6
pkg/macos/main.zig
Normal file
@ -0,0 +1,6 @@
|
||||
pub const foundation = @import("foundation.zig");
|
||||
pub const text = @import("text.zig");
|
||||
|
||||
test {
|
||||
@import("std").testing.refAllDecls(@This());
|
||||
}
|
6
pkg/macos/text.zig
Normal file
6
pkg/macos/text.zig
Normal file
@ -0,0 +1,6 @@
|
||||
pub usingnamespace @import("text/font_collection.zig");
|
||||
pub usingnamespace @import("text/font_descriptor.zig");
|
||||
|
||||
test {
|
||||
@import("std").testing.refAllDecls(@This());
|
||||
}
|
110
pkg/macos/text/font_collection.zig
Normal file
110
pkg/macos/text/font_collection.zig
Normal file
@ -0,0 +1,110 @@
|
||||
const std = @import("std");
|
||||
const Allocator = std.mem.Allocator;
|
||||
const foundation = @import("../foundation.zig");
|
||||
const text = @import("../text.zig");
|
||||
const c = @import("c.zig");
|
||||
|
||||
pub const FontCollection = opaque {
|
||||
pub fn createFromAvailableFonts() Allocator.Error!*FontCollection {
|
||||
return @intToPtr(
|
||||
?*FontCollection,
|
||||
@ptrToInt(c.CTFontCollectionCreateFromAvailableFonts(null)),
|
||||
) orelse Allocator.Error.OutOfMemory;
|
||||
}
|
||||
|
||||
pub fn createWithFontDescriptors(descs: *foundation.Array) Allocator.Error!*FontCollection {
|
||||
return @intToPtr(
|
||||
?*FontCollection,
|
||||
@ptrToInt(c.CTFontCollectionCreateWithFontDescriptors(
|
||||
@ptrCast(c.CFArrayRef, descs),
|
||||
null,
|
||||
)),
|
||||
) orelse Allocator.Error.OutOfMemory;
|
||||
}
|
||||
|
||||
pub fn release(self: *FontCollection) void {
|
||||
c.CFRelease(self);
|
||||
}
|
||||
|
||||
pub fn createMatchingFontDescriptors(self: *FontCollection) *foundation.Array {
|
||||
return @intToPtr(
|
||||
*foundation.Array,
|
||||
@ptrToInt(c.CTFontCollectionCreateMatchingFontDescriptors(
|
||||
@ptrCast(c.CTFontCollectionRef, self),
|
||||
)),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
fn debugDumpList(list: *foundation.Array) !void {
|
||||
var i: usize = 0;
|
||||
while (i < list.getCount()) : (i += 1) {
|
||||
const desc = list.getValueAtIndex(text.FontDescriptor, i);
|
||||
{
|
||||
var buf: [128]u8 = undefined;
|
||||
const name = desc.copyAttribute(.name);
|
||||
defer name.release();
|
||||
const cstr = name.cstring(&buf, .utf8).?;
|
||||
|
||||
var buf2: [128]u8 = undefined;
|
||||
const url = desc.copyAttribute(.url);
|
||||
defer url.release();
|
||||
const path = path: {
|
||||
const blank = try foundation.String.createWithBytes("", .utf8, false);
|
||||
defer blank.release();
|
||||
|
||||
const path = url.copyPath() orelse break :path "<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} path={s}", .{ i, 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();
|
||||
|
||||
const arr = try foundation.Array.create(
|
||||
text.FontDescriptor,
|
||||
&[_]*const text.FontDescriptor{desc},
|
||||
);
|
||||
defer arr.release();
|
||||
|
||||
const v = try FontCollection.createWithFontDescriptors(arr);
|
||||
defer v.release();
|
||||
|
||||
const list = v.createMatchingFontDescriptors();
|
||||
defer list.release();
|
||||
|
||||
try testing.expect(list.getCount() > 0);
|
||||
|
||||
//try debugDumpList(list);
|
||||
}
|
205
pkg/macos/text/font_descriptor.zig
Normal file
205
pkg/macos/text/font_descriptor.zig
Normal file
@ -0,0 +1,205 @@
|
||||
const std = @import("std");
|
||||
const Allocator = std.mem.Allocator;
|
||||
const foundation = @import("../foundation.zig");
|
||||
const c = @import("c.zig");
|
||||
|
||||
pub const FontDescriptor = opaque {
|
||||
pub fn createWithNameAndSize(name: *foundation.String, size: f64) Allocator.Error!*FontDescriptor {
|
||||
return @intToPtr(
|
||||
?*FontDescriptor,
|
||||
@ptrToInt(c.CTFontDescriptorCreateWithNameAndSize(@ptrCast(c.CFStringRef, name), size)),
|
||||
) orelse Allocator.Error.OutOfMemory;
|
||||
}
|
||||
|
||||
pub fn release(self: *FontDescriptor) void {
|
||||
c.CFRelease(self);
|
||||
}
|
||||
|
||||
pub fn copyAttribute(self: *FontDescriptor, comptime attr: FontAttribute) attr.Value() {
|
||||
return @intToPtr(attr.Value(), @ptrToInt(c.CTFontDescriptorCopyAttribute(
|
||||
@ptrCast(c.CTFontDescriptorRef, self),
|
||||
@ptrCast(c.CFStringRef, attr.key()),
|
||||
)));
|
||||
}
|
||||
};
|
||||
|
||||
pub const FontAttribute = enum {
|
||||
url,
|
||||
name,
|
||||
display_name,
|
||||
family_name,
|
||||
style_name,
|
||||
traits,
|
||||
variation,
|
||||
size,
|
||||
matrix,
|
||||
cascade_list,
|
||||
character_set,
|
||||
languages,
|
||||
baseline_adjust,
|
||||
macintosh_encodings,
|
||||
features,
|
||||
feature_settings,
|
||||
fixed_advance,
|
||||
orientation,
|
||||
format,
|
||||
registration_scope,
|
||||
priority,
|
||||
enabled,
|
||||
downloadable,
|
||||
downloaded,
|
||||
|
||||
pub fn key(self: FontAttribute) *foundation.String {
|
||||
return @intToPtr(*foundation.String, @ptrToInt(switch (self) {
|
||||
.url => c.kCTFontURLAttribute,
|
||||
.name => c.kCTFontNameAttribute,
|
||||
.display_name => c.kCTFontDisplayNameAttribute,
|
||||
.family_name => c.kCTFontFamilyNameAttribute,
|
||||
.style_name => c.kCTFontStyleNameAttribute,
|
||||
.traits => c.kCTFontTraitsAttribute,
|
||||
.variation => c.kCTFontVariationAttribute,
|
||||
.size => c.kCTFontSizeAttribute,
|
||||
.matrix => c.kCTFontMatrixAttribute,
|
||||
.cascade_list => c.kCTFontCascadeListAttribute,
|
||||
.character_set => c.kCTFontCharacterSetAttribute,
|
||||
.languages => c.kCTFontLanguagesAttribute,
|
||||
.baseline_adjust => c.kCTFontBaselineAdjustAttribute,
|
||||
.macintosh_encodings => c.kCTFontMacintoshEncodingsAttribute,
|
||||
.features => c.kCTFontFeaturesAttribute,
|
||||
.feature_settings => c.kCTFontFeatureSettingsAttribute,
|
||||
.fixed_advance => c.kCTFontFixedAdvanceAttribute,
|
||||
.orientation => c.kCTFontOrientationAttribute,
|
||||
.format => c.kCTFontFormatAttribute,
|
||||
.registration_scope => c.kCTFontRegistrationScopeAttribute,
|
||||
.priority => c.kCTFontPriorityAttribute,
|
||||
.enabled => c.kCTFontEnabledAttribute,
|
||||
.downloadable => c.kCTFontDownloadableAttribute,
|
||||
.downloaded => c.kCTFontDownloadedAttribute,
|
||||
}));
|
||||
}
|
||||
|
||||
pub fn Value(self: FontAttribute) type {
|
||||
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
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
pub const FontTraitKey = enum {
|
||||
symbolic,
|
||||
weight,
|
||||
width,
|
||||
slant,
|
||||
|
||||
pub fn key(self: FontTraitKey) *foundation.String {
|
||||
return @intToPtr(*foundation.String, @ptrToInt(switch (self) {
|
||||
.symbolic => c.kCTFontSymbolicTrait,
|
||||
.weight => c.kCTFontWeightTrait,
|
||||
.width => c.kCTFontWidthTrait,
|
||||
.slant => c.kCTFontFontSlantTrait,
|
||||
}));
|
||||
}
|
||||
|
||||
pub fn Value(self: FontTraitKey) type {
|
||||
return switch (self) {
|
||||
.symbolic => *foundation.Number,
|
||||
.weight => *foundation.Number,
|
||||
.width => *foundation.Number,
|
||||
.slant => *foundation.Number,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
pub const FontSymbolicTraits = packed struct {
|
||||
italic: bool = false,
|
||||
bold: bool = false,
|
||||
_unused1: u3 = 0,
|
||||
expanded: bool = false,
|
||||
condensed: bool = false,
|
||||
_unused2: u3 = 0,
|
||||
monospace: bool = false,
|
||||
vertical: bool = false,
|
||||
ui_optimized: bool = false,
|
||||
color_glyphs: bool = false,
|
||||
composite: bool = false,
|
||||
_padding: u17 = 0,
|
||||
|
||||
pub fn init(num: *foundation.Number) FontSymbolicTraits {
|
||||
var raw: i32 = undefined;
|
||||
_ = num.getValue(.sint32, &raw);
|
||||
return @bitCast(FontSymbolicTraits, raw);
|
||||
}
|
||||
|
||||
test {
|
||||
try std.testing.expectEqual(
|
||||
@bitSizeOf(c.CTFontSymbolicTraits),
|
||||
@bitSizeOf(FontSymbolicTraits),
|
||||
);
|
||||
}
|
||||
|
||||
test "bitcast" {
|
||||
const actual: c.CTFontSymbolicTraits = c.kCTFontTraitMonoSpace | c.kCTFontTraitExpanded;
|
||||
const expected: FontSymbolicTraits = .{
|
||||
.monospace = true,
|
||||
.expanded = true,
|
||||
};
|
||||
|
||||
try std.testing.expectEqual(actual, @bitCast(c.CTFontSymbolicTraits, expected));
|
||||
}
|
||||
|
||||
test "number" {
|
||||
const raw: i32 = c.kCTFontTraitMonoSpace | c.kCTFontTraitExpanded;
|
||||
const num = try foundation.Number.create(.sint32, &raw);
|
||||
defer num.release();
|
||||
|
||||
const expected: FontSymbolicTraits = .{ .monospace = true, .expanded = true };
|
||||
const actual = FontSymbolicTraits.init(num);
|
||||
try std.testing.expect(std.meta.eql(expected, actual));
|
||||
}
|
||||
};
|
||||
|
||||
test {
|
||||
@import("std").testing.refAllDecls(@This());
|
||||
}
|
||||
|
||||
test "descriptor" {
|
||||
const testing = std.testing;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user