aboutsummaryrefslogtreecommitdiff
path: root/vendor/libghostty-vt/pkg/macos/text
diff options
context:
space:
mode:
authorCan Celik <ogulcancelik@gmail.com>2026-04-04 19:22:04 +0300
committerCan Celik <ogulcancelik@gmail.com>2026-04-05 01:23:06 +0300
commit41dd733ca7123c9ac4bfc6c781697a660e95b052 (patch)
tree3fc91815ca139ef2e0b281bba0abda4ef15175f4 /vendor/libghostty-vt/pkg/macos/text
parent55c138263e190a1e79d4b997bfb9397306c0ec1f (diff)
feat: finish ghostty pane backend integration
Diffstat (limited to 'vendor/libghostty-vt/pkg/macos/text')
-rw-r--r--vendor/libghostty-vt/pkg/macos/text/c.zig1
-rw-r--r--vendor/libghostty-vt/pkg/macos/text/ext.c10
-rw-r--r--vendor/libghostty-vt/pkg/macos/text/font.zig306
-rw-r--r--vendor/libghostty-vt/pkg/macos/text/font_collection.zig139
-rw-r--r--vendor/libghostty-vt/pkg/macos/text/font_descriptor.zig287
-rw-r--r--vendor/libghostty-vt/pkg/macos/text/font_manager.zig23
-rw-r--r--vendor/libghostty-vt/pkg/macos/text/frame.zig33
-rw-r--r--vendor/libghostty-vt/pkg/macos/text/framesetter.zig66
-rw-r--r--vendor/libghostty-vt/pkg/macos/text/line.zig123
-rw-r--r--vendor/libghostty-vt/pkg/macos/text/paragraph_style.zig49
-rw-r--r--vendor/libghostty-vt/pkg/macos/text/run.zig117
-rw-r--r--vendor/libghostty-vt/pkg/macos/text/stylized_strings.zig16
-rw-r--r--vendor/libghostty-vt/pkg/macos/text/typesetter.zig36
13 files changed, 1206 insertions, 0 deletions
diff --git a/vendor/libghostty-vt/pkg/macos/text/c.zig b/vendor/libghostty-vt/pkg/macos/text/c.zig
new file mode 100644
index 00000000..1a7d1627
--- /dev/null
+++ b/vendor/libghostty-vt/pkg/macos/text/c.zig
@@ -0,0 +1 @@
+pub const c = @import("../main.zig").c;
diff --git a/vendor/libghostty-vt/pkg/macos/text/ext.c b/vendor/libghostty-vt/pkg/macos/text/ext.c
new file mode 100644
index 00000000..55bff8a6
--- /dev/null
+++ b/vendor/libghostty-vt/pkg/macos/text/ext.c
@@ -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);
+}
diff --git a/vendor/libghostty-vt/pkg/macos/text/font.zig b/vendor/libghostty-vt/pkg/macos/text/font.zig
new file mode 100644
index 00000000..ea37891f
--- /dev/null
+++ b/vendor/libghostty-vt/pkg/macos/text/font.zig
@@ -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();
+}
diff --git a/vendor/libghostty-vt/pkg/macos/text/font_collection.zig b/vendor/libghostty-vt/pkg/macos/text/font_collection.zig
new file mode 100644
index 00000000..8b4a86a6
--- /dev/null
+++ b/vendor/libghostty-vt/pkg/macos/text/font_collection.zig
@@ -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);
+}
diff --git a/vendor/libghostty-vt/pkg/macos/text/font_descriptor.zig b/vendor/libghostty-vt/pkg/macos/text/font_descriptor.zig
new file mode 100644
index 00000000..563165bc
--- /dev/null
+++ b/vendor/libghostty-vt/pkg/macos/text/font_descriptor.zig
@@ -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);
+ }
+}
diff --git a/vendor/libghostty-vt/pkg/macos/text/font_manager.zig b/vendor/libghostty-vt/pkg/macos/text/font_manager.zig
new file mode 100644
index 00000000..988da122
--- /dev/null
+++ b/vendor/libghostty-vt/pkg/macos/text/font_manager.zig
@@ -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),
+ )));
+}
diff --git a/vendor/libghostty-vt/pkg/macos/text/frame.zig b/vendor/libghostty-vt/pkg/macos/text/frame.zig
new file mode 100644
index 00000000..979134a1
--- /dev/null
+++ b/vendor/libghostty-vt/pkg/macos/text/frame.zig
@@ -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...
+}
diff --git a/vendor/libghostty-vt/pkg/macos/text/framesetter.zig b/vendor/libghostty-vt/pkg/macos/text/framesetter.zig
new file mode 100644
index 00000000..dbd82762
--- /dev/null
+++ b/vendor/libghostty-vt/pkg/macos/text/framesetter.zig
@@ -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);
+ }
+}
diff --git a/vendor/libghostty-vt/pkg/macos/text/line.zig b/vendor/libghostty-vt/pkg/macos/text/line.zig
new file mode 100644
index 00000000..248f8e64
--- /dev/null
+++ b/vendor/libghostty-vt/pkg/macos/text/line.zig
@@ -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});
+}
diff --git a/vendor/libghostty-vt/pkg/macos/text/paragraph_style.zig b/vendor/libghostty-vt/pkg/macos/text/paragraph_style.zig
new file mode 100644
index 00000000..f1d7fe95
--- /dev/null
+++ b/vendor/libghostty-vt/pkg/macos/text/paragraph_style.zig
@@ -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();
+}
diff --git a/vendor/libghostty-vt/pkg/macos/text/run.zig b/vendor/libghostty-vt/pkg/macos/text/run.zig
new file mode 100644
index 00000000..2895bfe3
--- /dev/null
+++ b/vendor/libghostty-vt/pkg/macos/text/run.zig
@@ -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,
+};
diff --git a/vendor/libghostty-vt/pkg/macos/text/stylized_strings.zig b/vendor/libghostty-vt/pkg/macos/text/stylized_strings.zig
new file mode 100644
index 00000000..8e262f9a
--- /dev/null
+++ b/vendor/libghostty-vt/pkg/macos/text/stylized_strings.zig
@@ -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,
+ }));
+ }
+};
diff --git a/vendor/libghostty-vt/pkg/macos/text/typesetter.zig b/vendor/libghostty-vt/pkg/macos/text/typesetter.zig
new file mode 100644
index 00000000..dc07df98
--- /dev/null
+++ b/vendor/libghostty-vt/pkg/macos/text/typesetter.zig
@@ -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,
+ )));
+ }
+};