diff options
| author | Can Celik <ogulcancelik@gmail.com> | 2026-04-04 19:22:04 +0300 |
|---|---|---|
| committer | Can Celik <ogulcancelik@gmail.com> | 2026-04-05 01:23:06 +0300 |
| commit | 41dd733ca7123c9ac4bfc6c781697a660e95b052 (patch) | |
| tree | 3fc91815ca139ef2e0b281bba0abda4ef15175f4 /vendor/libghostty-vt/pkg/macos/text | |
| parent | 55c138263e190a1e79d4b997bfb9397306c0ec1f (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.zig | 1 | ||||
| -rw-r--r-- | vendor/libghostty-vt/pkg/macos/text/ext.c | 10 | ||||
| -rw-r--r-- | vendor/libghostty-vt/pkg/macos/text/font.zig | 306 | ||||
| -rw-r--r-- | vendor/libghostty-vt/pkg/macos/text/font_collection.zig | 139 | ||||
| -rw-r--r-- | vendor/libghostty-vt/pkg/macos/text/font_descriptor.zig | 287 | ||||
| -rw-r--r-- | vendor/libghostty-vt/pkg/macos/text/font_manager.zig | 23 | ||||
| -rw-r--r-- | vendor/libghostty-vt/pkg/macos/text/frame.zig | 33 | ||||
| -rw-r--r-- | vendor/libghostty-vt/pkg/macos/text/framesetter.zig | 66 | ||||
| -rw-r--r-- | vendor/libghostty-vt/pkg/macos/text/line.zig | 123 | ||||
| -rw-r--r-- | vendor/libghostty-vt/pkg/macos/text/paragraph_style.zig | 49 | ||||
| -rw-r--r-- | vendor/libghostty-vt/pkg/macos/text/run.zig | 117 | ||||
| -rw-r--r-- | vendor/libghostty-vt/pkg/macos/text/stylized_strings.zig | 16 | ||||
| -rw-r--r-- | vendor/libghostty-vt/pkg/macos/text/typesetter.zig | 36 |
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, + ))); + } +}; |
