aboutsummaryrefslogtreecommitdiff
path: root/vendor/libghostty-vt/pkg/harfbuzz
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/harfbuzz
parent55c138263e190a1e79d4b997bfb9397306c0ec1f (diff)
feat: finish ghostty pane backend integration
Diffstat (limited to 'vendor/libghostty-vt/pkg/harfbuzz')
-rw-r--r--vendor/libghostty-vt/pkg/harfbuzz/blob.zig126
-rw-r--r--vendor/libghostty-vt/pkg/harfbuzz/buffer.zig373
-rw-r--r--vendor/libghostty-vt/pkg/harfbuzz/build.zig185
-rw-r--r--vendor/libghostty-vt/pkg/harfbuzz/build.zig.zon18
-rw-r--r--vendor/libghostty-vt/pkg/harfbuzz/c.zig8
-rw-r--r--vendor/libghostty-vt/pkg/harfbuzz/common.zig247
-rw-r--r--vendor/libghostty-vt/pkg/harfbuzz/coretext.zig30
-rw-r--r--vendor/libghostty-vt/pkg/harfbuzz/errors.zig5
-rw-r--r--vendor/libghostty-vt/pkg/harfbuzz/face.zig17
-rw-r--r--vendor/libghostty-vt/pkg/harfbuzz/font.zig28
-rw-r--r--vendor/libghostty-vt/pkg/harfbuzz/freetype.zig78
-rw-r--r--vendor/libghostty-vt/pkg/harfbuzz/main.zig31
-rw-r--r--vendor/libghostty-vt/pkg/harfbuzz/shape.zig27
-rw-r--r--vendor/libghostty-vt/pkg/harfbuzz/version.zig47
14 files changed, 1220 insertions, 0 deletions
diff --git a/vendor/libghostty-vt/pkg/harfbuzz/blob.zig b/vendor/libghostty-vt/pkg/harfbuzz/blob.zig
new file mode 100644
index 00000000..9472e4c7
--- /dev/null
+++ b/vendor/libghostty-vt/pkg/harfbuzz/blob.zig
@@ -0,0 +1,126 @@
+const std = @import("std");
+const c = @import("c.zig").c;
+const Error = @import("errors.zig").Error;
+
+/// Data type holding the memory modes available to client programs.
+///
+/// Regarding these various memory-modes:
+///
+/// - In no case shall the HarfBuzz client modify memory that is passed to
+/// HarfBuzz in a blob. If there is any such possibility,
+/// HB_MEMORY_MODE_DUPLICATE should be used such that HarfBuzz makes a
+/// copy immediately,
+///
+/// - Use HB_MEMORY_MODE_READONLY otherwise, unless you really really really
+/// know what you are doing,
+///
+/// - HB_MEMORY_MODE_WRITABLE is appropriate if you really made a copy of
+/// data solely for the purpose of passing to HarfBuzz and doing that
+/// just once (no reuse!),
+///
+/// - If the font is mmap()ed, it's okay to use
+/// HB_MEMORY_READONLY_MAY_MAKE_WRITABLE , however, using that mode
+/// correctly is very tricky. Use HB_MEMORY_MODE_READONLY instead.
+pub const MemoryMode = enum(u2) {
+ /// HarfBuzz immediately makes a copy of the data.
+ duplicate = c.HB_MEMORY_MODE_DUPLICATE,
+
+ /// HarfBuzz client will never modify the data, and HarfBuzz will never
+ /// modify the data.
+ readonly = c.HB_MEMORY_MODE_READONLY,
+
+ /// HarfBuzz client made a copy of the data solely for HarfBuzz, so
+ /// HarfBuzz may modify the data.
+ writable = c.HB_MEMORY_MODE_WRITABLE,
+
+ /// See above
+ readonly_may_make_writable = c.HB_MEMORY_MODE_READONLY_MAY_MAKE_WRITABLE,
+};
+
+/// Blobs wrap a chunk of binary data to handle lifecycle management of data
+/// while it is passed between client and HarfBuzz. Blobs are primarily
+/// used to create font faces, but also to access font face tables, as well as
+/// pass around other binary data.
+pub const Blob = struct {
+ handle: *c.hb_blob_t,
+
+ /// Creates a new "blob" object wrapping data . The mode parameter is used
+ /// to negotiate ownership and lifecycle of data .
+ ///
+ /// Note that this function returns a freshly-allocated empty blob even
+ /// if length is zero. This is in contrast to hb_blob_create(), which
+ /// returns the singleton empty blob (as returned by hb_blob_get_empty())
+ /// if length is zero.
+ pub fn create(data: []const u8, mode: MemoryMode) Error!Blob {
+ const handle = c.hb_blob_create_or_fail(
+ data.ptr,
+ @intCast(data.len),
+ @intFromEnum(mode),
+ null,
+ null,
+ ) orelse return Error.HarfbuzzFailed;
+
+ return Blob{ .handle = handle };
+ }
+
+ /// Decreases the reference count on blob , and if it reaches zero,
+ /// destroys blob , freeing all memory, possibly calling the
+ /// destroy-callback the blob was created for if it has not been
+ /// called already.
+ pub fn destroy(self: *Blob) void {
+ c.hb_blob_destroy(self.handle);
+ }
+
+ /// Attaches a user-data key/data pair to the specified blob.
+ pub fn setUserData(
+ self: Blob,
+ comptime T: type,
+ key: ?*anyopaque,
+ ptr: ?*T,
+ comptime destroycb: ?*const fn (?*T) callconv(.c) void,
+ replace: bool,
+ ) bool {
+ const Callback = struct {
+ pub fn callback(data: ?*anyopaque) callconv(.c) void {
+ @call(.{ .modifier = .always_inline }, destroycb, .{
+ @as(?*T, @ptrCast(@alignCast(data))),
+ });
+ }
+ };
+
+ return c.hb_blob_set_user_data(
+ self.handle,
+ @ptrCast(key),
+ ptr,
+ if (destroycb != null) Callback.callback else null,
+ if (replace) 1 else 0,
+ ) > 0;
+ }
+
+ /// Fetches the user data associated with the specified key, attached to
+ /// the specified font-functions structure.
+ pub fn getUserData(
+ self: Blob,
+ comptime T: type,
+ key: ?*anyopaque,
+ ) ?*T {
+ const opt = c.hb_blob_get_user_data(self.handle, @ptrCast(key));
+ if (opt) |ptr|
+ return @ptrCast(@alignCast(ptr))
+ else
+ return null;
+ }
+};
+
+test {
+ const testing = std.testing;
+
+ const data = "hello";
+ var blob = try Blob.create(data, .readonly);
+ defer blob.destroy();
+
+ var userdata: u8 = 127;
+ var key: u8 = 0;
+ try testing.expect(blob.setUserData(u8, &key, &userdata, null, false));
+ try testing.expect(blob.getUserData(u8, &key).?.* == 127);
+}
diff --git a/vendor/libghostty-vt/pkg/harfbuzz/buffer.zig b/vendor/libghostty-vt/pkg/harfbuzz/buffer.zig
new file mode 100644
index 00000000..b97c1bef
--- /dev/null
+++ b/vendor/libghostty-vt/pkg/harfbuzz/buffer.zig
@@ -0,0 +1,373 @@
+const std = @import("std");
+const c = @import("c.zig").c;
+const common = @import("common.zig");
+const Error = @import("errors.zig").Error;
+const Direction = common.Direction;
+const Script = common.Script;
+const Language = common.Language;
+
+/// Buffers serve a dual role in HarfBuzz; before shaping, they hold the
+/// input characters that are passed to hb_shape(), and after shaping they
+/// hold the output glyphs.
+pub const Buffer = struct {
+ handle: *c.hb_buffer_t,
+
+ /// Creates a new hb_buffer_t with all properties to defaults.
+ pub fn create() Error!Buffer {
+ const handle = c.hb_buffer_create() orelse return Error.HarfbuzzFailed;
+ return Buffer{ .handle = handle };
+ }
+
+ /// Deallocate the buffer . Decreases the reference count on buffer by one.
+ /// If the result is zero, then buffer and all associated resources are
+ /// freed. See hb_buffer_reference().
+ pub fn destroy(self: *Buffer) void {
+ c.hb_buffer_destroy(self.handle);
+ }
+
+ /// Resets the buffer to its initial status, as if it was just newly
+ /// created with hb_buffer_create().
+ pub fn reset(self: Buffer) void {
+ c.hb_buffer_reset(self.handle);
+ }
+
+ /// Returns the number of items in the buffer.
+ pub fn getLength(self: Buffer) u32 {
+ return c.hb_buffer_get_length(self.handle);
+ }
+
+ /// Sets the type of buffer contents. Buffers are either empty, contain
+ /// characters (before shaping), or contain glyphs (the result of shaping).
+ pub fn setContentType(self: Buffer, ct: ContentType) void {
+ c.hb_buffer_set_content_type(self.handle, @intFromEnum(ct));
+ }
+
+ /// Fetches the type of buffer contents. Buffers are either empty, contain
+ /// characters (before shaping), or contain glyphs (the result of shaping).
+ pub fn getContentType(self: Buffer) ContentType {
+ return @enumFromInt(c.hb_buffer_get_content_type(self.handle));
+ }
+
+ /// Appends a character with the Unicode value of codepoint to buffer,
+ /// and gives it the initial cluster value of cluster . Clusters can be
+ /// any thing the client wants, they are usually used to refer to the
+ /// index of the character in the input text stream and are output in
+ /// hb_glyph_info_t.cluster field.
+ ///
+ /// This function does not check the validity of codepoint, it is up to
+ /// the caller to ensure it is a valid Unicode code point.
+ pub fn add(self: Buffer, cp: u32, cluster: u32) void {
+ c.hb_buffer_add(self.handle, cp, cluster);
+ }
+
+ /// Appends characters from text array to buffer . The item_offset is the
+ /// position of the first character from text that will be appended, and
+ /// item_length is the number of character. When shaping part of a larger
+ /// text (e.g. a run of text from a paragraph), instead of passing just
+ /// the substring corresponding to the run, it is preferable to pass the
+ /// whole paragraph and specify the run start and length as item_offset and
+ /// item_length , respectively, to give HarfBuzz the full context to be
+ /// able, for example, to do cross-run Arabic shaping or properly handle
+ /// combining marks at stat of run.
+ ///
+ /// This function does not check the validity of text , it is up to the
+ /// caller to ensure it contains a valid Unicode code points.
+ pub fn addCodepoints(self: Buffer, text: []const u32) void {
+ c.hb_buffer_add_codepoints(
+ self.handle,
+ text.ptr,
+ @intCast(text.len),
+ 0,
+ @intCast(text.len),
+ );
+ }
+
+ /// See hb_buffer_add_codepoints().
+ ///
+ /// Replaces invalid UTF-32 characters with the buffer replacement code
+ /// point, see hb_buffer_set_replacement_codepoint().
+ pub fn addUTF32(self: Buffer, text: []const u32) void {
+ c.hb_buffer_add_utf32(
+ self.handle,
+ text.ptr,
+ @intCast(text.len),
+ 0,
+ @intCast(text.len),
+ );
+ }
+
+ /// See hb_buffer_add_codepoints().
+ ///
+ /// Replaces invalid UTF-16 characters with the buffer replacement code
+ /// point, see hb_buffer_set_replacement_codepoint().
+ pub fn addUTF16(self: Buffer, text: []const u16) void {
+ c.hb_buffer_add_utf16(
+ self.handle,
+ text.ptr,
+ @intCast(text.len),
+ 0,
+ @intCast(text.len),
+ );
+ }
+
+ /// See hb_buffer_add_codepoints().
+ ///
+ /// Replaces invalid UTF-8 characters with the buffer replacement code
+ /// point, see hb_buffer_set_replacement_codepoint().
+ pub fn addUTF8(self: Buffer, text: []const u8) void {
+ c.hb_buffer_add_utf8(
+ self.handle,
+ text.ptr,
+ @intCast(text.len),
+ 0,
+ @intCast(text.len),
+ );
+ }
+
+ /// Similar to hb_buffer_add_codepoints(), but allows only access to first
+ /// 256 Unicode code points that can fit in 8-bit strings.
+ pub fn addLatin1(self: Buffer, text: []const u8) void {
+ c.hb_buffer_add_latin1(
+ self.handle,
+ text.ptr,
+ @intCast(text.len),
+ 0,
+ @intCast(text.len),
+ );
+ }
+
+ /// Set the text flow direction of the buffer. No shaping can happen
+ /// without setting buffer direction, and it controls the visual direction
+ /// for the output glyphs; for RTL direction the glyphs will be reversed.
+ /// Many layout features depend on the proper setting of the direction,
+ /// for example, reversing RTL text before shaping, then shaping with LTR
+ /// direction is not the same as keeping the text in logical order and
+ /// shaping with RTL direction.
+ pub fn setDirection(self: Buffer, dir: Direction) void {
+ c.hb_buffer_set_direction(self.handle, @intFromEnum(dir));
+ }
+
+ /// See hb_buffer_set_direction()
+ pub fn getDirection(self: Buffer) Direction {
+ return @enumFromInt(c.hb_buffer_get_direction(self.handle));
+ }
+
+ /// Sets the script of buffer to script.
+ ///
+ /// Script is crucial for choosing the proper shaping behaviour for
+ /// scripts that require it (e.g. Arabic) and the which OpenType features
+ /// defined in the font to be applied.
+ ///
+ /// You can pass one of the predefined hb_script_t values, or use
+ /// hb_script_from_string() or hb_script_from_iso15924_tag() to get the
+ /// corresponding script from an ISO 15924 script tag.
+ pub fn setScript(self: Buffer, script: Script) void {
+ c.hb_buffer_set_script(self.handle, @intFromEnum(script));
+ }
+
+ /// See hb_buffer_set_script()
+ pub fn getScript(self: Buffer) Script {
+ return @enumFromInt(c.hb_buffer_get_script(self.handle));
+ }
+
+ /// Sets the language of buffer to language .
+ ///
+ /// Languages are crucial for selecting which OpenType feature to apply to
+ /// the buffer which can result in applying language-specific behaviour.
+ /// Languages are orthogonal to the scripts, and though they are related,
+ /// they are different concepts and should not be confused with each other.
+ ///
+ /// Use hb_language_from_string() to convert from BCP 47 language tags to
+ /// hb_language_t.
+ pub fn setLanguage(self: Buffer, language: Language) void {
+ c.hb_buffer_set_language(self.handle, language.handle);
+ }
+
+ /// See hb_buffer_set_language()
+ pub fn getLanguage(self: Buffer) Language {
+ return Language{ .handle = c.hb_buffer_get_language(self.handle) };
+ }
+
+ /// Returns buffer glyph information array. Returned pointer is valid as
+ /// long as buffer contents are not modified.
+ pub fn getGlyphInfos(self: Buffer) []GlyphInfo {
+ var length: u32 = 0;
+ const ptr: [*c]GlyphInfo = @ptrCast(c.hb_buffer_get_glyph_infos(self.handle, &length));
+ return ptr[0..length];
+ }
+
+ /// Returns buffer glyph position array. Returned pointer is valid as
+ /// long as buffer contents are not modified.
+ ///
+ /// If buffer did not have positions before, the positions will be
+ /// initialized to zeros, unless this function is called from within a
+ /// buffer message callback (see hb_buffer_set_message_func()), in which
+ /// case NULL is returned.
+ pub fn getGlyphPositions(self: Buffer) ?[]GlyphPosition {
+ var length: u32 = 0;
+
+ if (c.hb_buffer_get_glyph_positions(self.handle, &length)) |positions| {
+ const ptr: [*]GlyphPosition = @ptrCast(positions);
+ return ptr[0..length];
+ }
+
+ return null;
+ }
+
+ /// Sets unset buffer segment properties based on buffer Unicode contents.
+ /// If buffer is not empty, it must have content type
+ /// HB_BUFFER_CONTENT_TYPE_UNICODE.
+ ///
+ /// If buffer script is not set (ie. is HB_SCRIPT_INVALID), it will be set
+ /// to the Unicode script of the first character in the buffer that has a
+ /// script other than HB_SCRIPT_COMMON, HB_SCRIPT_INHERITED, and
+ /// HB_SCRIPT_UNKNOWN.
+ ///
+ /// Next, if buffer direction is not set (ie. is HB_DIRECTION_INVALID), it
+ /// will be set to the natural horizontal direction of the buffer script as
+ /// returned by hb_script_get_horizontal_direction(). If
+ /// hb_script_get_horizontal_direction() returns HB_DIRECTION_INVALID,
+ /// then HB_DIRECTION_LTR is used.
+ ///
+ /// Finally, if buffer language is not set (ie. is HB_LANGUAGE_INVALID), it
+ /// will be set to the process's default language as returned by
+ /// hb_language_get_default(). This may change in the future by taking
+ /// buffer script into consideration when choosing a language. Note that
+ /// hb_language_get_default() is NOT threadsafe the first time it is
+ /// called. See documentation for that function for details.
+ pub fn guessSegmentProperties(self: Buffer) void {
+ c.hb_buffer_guess_segment_properties(self.handle);
+ }
+
+ /// Sets the cluster level of a buffer. The `ClusterLevel` dictates one
+ /// aspect of how HarfBuzz will treat non-base characters during shaping.
+ pub fn setClusterLevel(self: Buffer, level: ClusterLevel) void {
+ c.hb_buffer_set_cluster_level(self.handle, @intFromEnum(level));
+ }
+};
+
+/// The type of hb_buffer_t contents.
+pub const ContentType = enum(u2) {
+ /// Initial value for new buffer.
+ invalid = c.HB_BUFFER_CONTENT_TYPE_INVALID,
+
+ /// The buffer contains input characters (before shaping).
+ unicode = c.HB_BUFFER_CONTENT_TYPE_UNICODE,
+
+ /// The buffer contains output glyphs (after shaping).
+ glyphs = c.HB_BUFFER_CONTENT_TYPE_GLYPHS,
+};
+
+/// Data type for holding HarfBuzz's clustering behavior options. The cluster
+/// level dictates one aspect of how HarfBuzz will treat non-base characters
+/// during shaping.
+pub const ClusterLevel = enum(u2) {
+ /// In `monotone_graphemes`, non-base characters are merged into the
+ /// cluster of the base character that precedes them. There is also cluster
+ /// merging every time the clusters will otherwise become non-monotone.
+ /// This is the default cluster level.
+ monotone_graphemes = c.HB_BUFFER_CLUSTER_LEVEL_MONOTONE_GRAPHEMES,
+
+ /// In `monotone_characters`, non-base characters are initially assigned
+ /// their own cluster values, which are not merged into preceding base
+ /// clusters. This allows HarfBuzz to perform additional operations like
+ /// reorder sequences of adjacent marks. The output is still monotone, but
+ /// the cluster values are more granular.
+ monotone_characters = c.HB_BUFFER_CLUSTER_LEVEL_MONOTONE_CHARACTERS,
+
+ /// In `characters`, non-base characters are assigned their own cluster
+ /// values, which are not merged into preceding base clusters. Moreover,
+ /// the cluster values are not merged into monotone order. This is the most
+ /// granular cluster level, and it is useful for clients that need to know
+ /// the exact cluster values of each character, but is harder to use for
+ /// clients, since clusters might appear in any order.
+ characters = c.HB_BUFFER_CLUSTER_LEVEL_CHARACTERS,
+
+ /// In `graphemes`, non-base characters are merged into the cluster of the
+ /// base character that precedes them. This is similar to the Unicode
+ /// Grapheme Cluster algorithm, but it is not exactly the same. The output
+ /// is not forced to be monotone. This is useful for clients that want to
+ /// use HarfBuzz as a cheap implementation of the Unicode Grapheme Cluster
+ /// algorithm.
+ graphemes = c.HB_BUFFER_CLUSTER_LEVEL_GRAPHEMES,
+};
+
+/// The hb_glyph_info_t is the structure that holds information about the
+/// glyphs and their relation to input text.
+pub const GlyphInfo = extern struct {
+ /// either a Unicode code point (before shaping) or a glyph index (after shaping).
+ codepoint: u32,
+ _mask: u32,
+
+ /// the index of the character in the original text that corresponds to
+ /// this hb_glyph_info_t, or whatever the client passes to hb_buffer_add().
+ /// More than one hb_glyph_info_t can have the same cluster value, if they
+ /// resulted from the same character (e.g. one to many glyph substitution),
+ /// and when more than one character gets merged in the same glyph (e.g.
+ /// many to one glyph substitution) the hb_glyph_info_t will have the
+ /// smallest cluster value of them. By default some characters are merged
+ /// into the same cluster (e.g. combining marks have the same cluster as
+ /// their bases) even if they are separate glyphs, hb_buffer_set_cluster_level()
+ /// allow selecting more fine-grained cluster handling.
+ cluster: u32,
+ _var1: u32,
+ _var2: u32,
+};
+
+/// The hb_glyph_position_t is the structure that holds the positions of the
+/// glyph in both horizontal and vertical directions. All positions in
+/// hb_glyph_position_t are relative to the current point.
+pub const GlyphPosition = extern struct {
+ /// how much the line advances after drawing this glyph when setting text
+ /// in horizontal direction.
+ x_advance: i32,
+
+ /// how much the line advances after drawing this glyph when setting text
+ /// in vertical direction.
+ y_advance: i32,
+
+ /// how much the glyph moves on the X-axis before drawing it, this should
+ /// not affect how much the line advances.
+ x_offset: i32,
+
+ /// how much the glyph moves on the Y-axis before drawing it, this should
+ /// not affect how much the line advances.
+ y_offset: i32,
+
+ _var: u32,
+};
+
+test "create" {
+ const testing = std.testing;
+
+ var buffer = try Buffer.create();
+ defer buffer.destroy();
+ buffer.reset();
+
+ // Content type
+ buffer.setContentType(.unicode);
+ try testing.expectEqual(ContentType.unicode, buffer.getContentType());
+
+ // Try add functions
+ buffer.add('🥹', 27);
+ var utf32 = [_]u32{ 'A', 'B', 'C' };
+ var utf16 = [_]u16{ 'A', 'B', 'C' };
+ var utf8 = [_]u8{ 'A', 'B', 'C' };
+ buffer.addCodepoints(&utf32);
+ buffer.addUTF32(&utf32);
+ buffer.addUTF16(&utf16);
+ buffer.addUTF8(&utf8);
+ buffer.addLatin1(&utf8);
+
+ // Guess properties first
+ buffer.guessSegmentProperties();
+
+ // Try to set properties
+ buffer.setDirection(.ltr);
+ try testing.expectEqual(Direction.ltr, buffer.getDirection());
+
+ buffer.setScript(.arabic);
+ try testing.expectEqual(Script.arabic, buffer.getScript());
+
+ buffer.setLanguage(Language.fromString("en"));
+}
diff --git a/vendor/libghostty-vt/pkg/harfbuzz/build.zig b/vendor/libghostty-vt/pkg/harfbuzz/build.zig
new file mode 100644
index 00000000..6d8f3be7
--- /dev/null
+++ b/vendor/libghostty-vt/pkg/harfbuzz/build.zig
@@ -0,0 +1,185 @@
+const std = @import("std");
+const apple_sdk = @import("apple_sdk");
+
+pub fn build(b: *std.Build) !void {
+ const target = b.standardTargetOptions(.{});
+ const optimize = b.standardOptimizeOption(.{});
+
+ const coretext_enabled = b.option(bool, "enable-coretext", "Build coretext") orelse false;
+ const freetype_enabled = b.option(bool, "enable-freetype", "Build freetype") orelse true;
+
+ const freetype = b.dependency("freetype", .{
+ .target = target,
+ .optimize = optimize,
+ .@"enable-libpng" = true,
+ });
+ const macos = b.dependency("macos", .{ .target = target, .optimize = optimize });
+
+ const module = harfbuzz: {
+ const module = b.addModule("harfbuzz", .{
+ .root_source_file = b.path("main.zig"),
+ .target = target,
+ .optimize = optimize,
+ .imports = &.{
+ .{ .name = "freetype", .module = freetype.module("freetype") },
+ .{ .name = "macos", .module = macos.module("macos") },
+ },
+ });
+
+ const options = b.addOptions();
+ options.addOption(bool, "coretext", coretext_enabled);
+ options.addOption(bool, "freetype", freetype_enabled);
+ module.addOptions("build_options", options);
+ break :harfbuzz module;
+ };
+
+ // For dynamic linking, we prefer dynamic linking and to search by
+ // mode first. Mode first will search all paths for a dynamic library
+ // before falling back to static.
+ const dynamic_link_opts: std.Build.Module.LinkSystemLibraryOptions = .{
+ .preferred_link_mode = .dynamic,
+ .search_strategy = .mode_first,
+ };
+
+ const test_exe = b.addTest(.{
+ .name = "test",
+ .root_module = b.createModule(.{
+ .root_source_file = b.path("main.zig"),
+ .target = target,
+ .optimize = optimize,
+ }),
+ });
+
+ {
+ var it = module.import_table.iterator();
+ while (it.next()) |entry| test_exe.root_module.addImport(entry.key_ptr.*, entry.value_ptr.*);
+ if (b.systemIntegrationOption("freetype", .{})) {
+ test_exe.linkSystemLibrary2("freetype2", dynamic_link_opts);
+ } else {
+ test_exe.linkLibrary(freetype.artifact("freetype"));
+ }
+ const tests_run = b.addRunArtifact(test_exe);
+ const test_step = b.step("test", "Run tests");
+ test_step.dependOn(&tests_run.step);
+ }
+
+ if (b.systemIntegrationOption("harfbuzz", .{})) {
+ module.linkSystemLibrary("harfbuzz", dynamic_link_opts);
+ test_exe.linkSystemLibrary2("harfbuzz", dynamic_link_opts);
+ } else {
+ const lib = try buildLib(b, module, .{
+ .target = target,
+ .optimize = optimize,
+
+ .coretext_enabled = coretext_enabled,
+ .freetype_enabled = freetype_enabled,
+
+ .dynamic_link_opts = dynamic_link_opts,
+ });
+
+ test_exe.linkLibrary(lib);
+ }
+}
+
+fn buildLib(b: *std.Build, module: *std.Build.Module, options: anytype) !*std.Build.Step.Compile {
+ const target = options.target;
+ const optimize = options.optimize;
+
+ const coretext_enabled = options.coretext_enabled;
+ const freetype_enabled = options.freetype_enabled;
+
+ const freetype = b.dependency("freetype", .{
+ .target = target,
+ .optimize = optimize,
+ .@"enable-libpng" = true,
+ });
+
+ const lib = b.addLibrary(.{
+ .name = "harfbuzz",
+ .root_module = b.createModule(.{
+ .target = target,
+ .optimize = optimize,
+ }),
+ .linkage = .static,
+ });
+ lib.linkLibC();
+ // On MSVC, we must not use linkLibCpp because Zig unconditionally
+ // passes -nostdinc++ and then adds its bundled libc++/libc++abi
+ // include paths, which conflict with MSVC's own C++ runtime headers.
+ // The MSVC SDK include directories (added via linkLibC) contain
+ // both C and C++ headers, so linkLibCpp is not needed.
+ if (target.result.abi != .msvc) {
+ lib.linkLibCpp();
+ }
+
+ if (target.result.os.tag.isDarwin()) {
+ try apple_sdk.addPaths(b, lib);
+ }
+
+ const dynamic_link_opts = options.dynamic_link_opts;
+
+ var flags: std.ArrayList([]const u8) = .empty;
+ defer flags.deinit(b.allocator);
+ try flags.appendSlice(b.allocator, &.{
+ "-DHAVE_STDBOOL_H",
+ });
+ if (target.result.os.tag != .windows) {
+ try flags.appendSlice(b.allocator, &.{
+ "-DHAVE_UNISTD_H",
+ "-DHAVE_SYS_MMAN_H",
+ "-DHAVE_PTHREAD=1",
+ });
+ }
+
+ // Freetype
+ _ = b.systemIntegrationOption("freetype", .{}); // So it shows up in help
+ if (freetype_enabled) {
+ try flags.appendSlice(b.allocator, &.{
+ "-DHAVE_FREETYPE=1",
+
+ // Let's just assume a new freetype
+ "-DHAVE_FT_GET_VAR_BLEND_COORDINATES=1",
+ "-DHAVE_FT_SET_VAR_BLEND_COORDINATES=1",
+ "-DHAVE_FT_DONE_MM_VAR=1",
+ "-DHAVE_FT_GET_TRANSFORM=1",
+ });
+
+ if (b.systemIntegrationOption("freetype", .{})) {
+ lib.linkSystemLibrary2("freetype2", dynamic_link_opts);
+ module.linkSystemLibrary("freetype2", dynamic_link_opts);
+ } else {
+ lib.linkLibrary(freetype.artifact("freetype"));
+
+ if (freetype.builder.lazyDependency(
+ "freetype",
+ .{},
+ )) |freetype_dep| {
+ module.addIncludePath(freetype_dep.path("include"));
+ }
+ }
+ }
+
+ if (coretext_enabled) {
+ try flags.appendSlice(b.allocator, &.{"-DHAVE_CORETEXT=1"});
+ lib.linkFramework("CoreText");
+ module.linkFramework("CoreText", .{});
+ }
+
+ if (b.lazyDependency("harfbuzz", .{})) |upstream| {
+ lib.addIncludePath(upstream.path("src"));
+ module.addIncludePath(upstream.path("src"));
+ lib.addCSourceFile(.{
+ .file = upstream.path("src/harfbuzz.cc"),
+ .flags = flags.items,
+ });
+ lib.installHeadersDirectory(
+ upstream.path("src"),
+ "",
+ .{ .include_extensions = &.{".h"} },
+ );
+ }
+
+ b.installArtifact(lib);
+
+ return lib;
+}
diff --git a/vendor/libghostty-vt/pkg/harfbuzz/build.zig.zon b/vendor/libghostty-vt/pkg/harfbuzz/build.zig.zon
new file mode 100644
index 00000000..b7d155e9
--- /dev/null
+++ b/vendor/libghostty-vt/pkg/harfbuzz/build.zig.zon
@@ -0,0 +1,18 @@
+.{
+ .name = .harfbuzz,
+ .version = "11.0.0",
+ .fingerprint = 0xbd60917cd18865d8,
+ .paths = .{""},
+ .dependencies = .{
+ // harfbuzz/harfbuzz
+ .harfbuzz = .{
+ .url = "https://deps.files.ghostty.org/harfbuzz-11.0.0.tar.xz",
+ .hash = "N-V-__8AAG02ugUcWec-Ndp-i7JTsJ0dgF8nnJRUInkGLG7G",
+ .lazy = true,
+ },
+
+ .freetype = .{ .path = "../freetype" },
+ .macos = .{ .path = "../macos" },
+ .apple_sdk = .{ .path = "../apple-sdk" },
+ },
+}
diff --git a/vendor/libghostty-vt/pkg/harfbuzz/c.zig b/vendor/libghostty-vt/pkg/harfbuzz/c.zig
new file mode 100644
index 00000000..49e87dce
--- /dev/null
+++ b/vendor/libghostty-vt/pkg/harfbuzz/c.zig
@@ -0,0 +1,8 @@
+const builtin = @import("builtin");
+const build_options = @import("build_options");
+
+pub const c = @cImport({
+ @cInclude("hb.h");
+ if (build_options.freetype) @cInclude("hb-ft.h");
+ if (build_options.coretext) @cInclude("hb-coretext.h");
+});
diff --git a/vendor/libghostty-vt/pkg/harfbuzz/common.zig b/vendor/libghostty-vt/pkg/harfbuzz/common.zig
new file mode 100644
index 00000000..840ff586
--- /dev/null
+++ b/vendor/libghostty-vt/pkg/harfbuzz/common.zig
@@ -0,0 +1,247 @@
+const std = @import("std");
+const c = @import("c.zig").c;
+
+/// The direction of a text segment or buffer.
+///
+/// A segment can also be tested for horizontal or vertical orientation
+/// (irrespective of specific direction) with HB_DIRECTION_IS_HORIZONTAL()
+/// or HB_DIRECTION_IS_VERTICAL().
+pub const Direction = enum(u3) {
+ invalid = c.HB_DIRECTION_INVALID,
+ ltr = c.HB_DIRECTION_LTR,
+ rtl = c.HB_DIRECTION_RTL,
+ ttb = c.HB_DIRECTION_TTB,
+ bit = c.HB_DIRECTION_BTT,
+};
+
+/// Data type for scripts. Each hb_script_t's value is an hb_tag_t
+/// corresponding to the four-letter values defined by ISO 15924.
+///
+/// See also the Script (sc) property of the Unicode Character Database.
+pub const Script = enum(u31) {
+ common = c.HB_SCRIPT_COMMON,
+ inherited = c.HB_SCRIPT_INHERITED,
+ unknown = c.HB_SCRIPT_UNKNOWN,
+ arabic = c.HB_SCRIPT_ARABIC,
+ armenian = c.HB_SCRIPT_ARMENIAN,
+ bengali = c.HB_SCRIPT_BENGALI,
+ cyrillic = c.HB_SCRIPT_CYRILLIC,
+ devanagari = c.HB_SCRIPT_DEVANAGARI,
+ georgian = c.HB_SCRIPT_GEORGIAN,
+ greek = c.HB_SCRIPT_GREEK,
+ gujarati = c.HB_SCRIPT_GUJARATI,
+ gurmukhi = c.HB_SCRIPT_GURMUKHI,
+ hangul = c.HB_SCRIPT_HANGUL,
+ han = c.HB_SCRIPT_HAN,
+ hebrew = c.HB_SCRIPT_HEBREW,
+ hiragana = c.HB_SCRIPT_HIRAGANA,
+ kannada = c.HB_SCRIPT_KANNADA,
+ katakana = c.HB_SCRIPT_KATAKANA,
+ lao = c.HB_SCRIPT_LAO,
+ latin = c.HB_SCRIPT_LATIN,
+ malayalam = c.HB_SCRIPT_MALAYALAM,
+ oriya = c.HB_SCRIPT_ORIYA,
+ tamil = c.HB_SCRIPT_TAMIL,
+ telugu = c.HB_SCRIPT_TELUGU,
+ thai = c.HB_SCRIPT_THAI,
+ tibetan = c.HB_SCRIPT_TIBETAN,
+ bopomofo = c.HB_SCRIPT_BOPOMOFO,
+ braille = c.HB_SCRIPT_BRAILLE,
+ canadian_syllabics = c.HB_SCRIPT_CANADIAN_SYLLABICS,
+ cherokee = c.HB_SCRIPT_CHEROKEE,
+ ethiopic = c.HB_SCRIPT_ETHIOPIC,
+ khmer = c.HB_SCRIPT_KHMER,
+ mongolian = c.HB_SCRIPT_MONGOLIAN,
+ myanmar = c.HB_SCRIPT_MYANMAR,
+ ogham = c.HB_SCRIPT_OGHAM,
+ runic = c.HB_SCRIPT_RUNIC,
+ sinhala = c.HB_SCRIPT_SINHALA,
+ syriac = c.HB_SCRIPT_SYRIAC,
+ thaana = c.HB_SCRIPT_THAANA,
+ yi = c.HB_SCRIPT_YI,
+ deseret = c.HB_SCRIPT_DESERET,
+ gothic = c.HB_SCRIPT_GOTHIC,
+ old_italic = c.HB_SCRIPT_OLD_ITALIC,
+ buhid = c.HB_SCRIPT_BUHID,
+ hanunoo = c.HB_SCRIPT_HANUNOO,
+ tagalog = c.HB_SCRIPT_TAGALOG,
+ tagbanwa = c.HB_SCRIPT_TAGBANWA,
+ cypriot = c.HB_SCRIPT_CYPRIOT,
+ limbu = c.HB_SCRIPT_LIMBU,
+ linear_b = c.HB_SCRIPT_LINEAR_B,
+ osmanya = c.HB_SCRIPT_OSMANYA,
+ shavian = c.HB_SCRIPT_SHAVIAN,
+ tai_le = c.HB_SCRIPT_TAI_LE,
+ ugaritic = c.HB_SCRIPT_UGARITIC,
+ buginese = c.HB_SCRIPT_BUGINESE,
+ coptic = c.HB_SCRIPT_COPTIC,
+ glagolitic = c.HB_SCRIPT_GLAGOLITIC,
+ kharoshthi = c.HB_SCRIPT_KHAROSHTHI,
+ new_tai_lue = c.HB_SCRIPT_NEW_TAI_LUE,
+ old_persian = c.HB_SCRIPT_OLD_PERSIAN,
+ syloti_nagri = c.HB_SCRIPT_SYLOTI_NAGRI,
+ tifinagh = c.HB_SCRIPT_TIFINAGH,
+ balinese = c.HB_SCRIPT_BALINESE,
+ cuneiform = c.HB_SCRIPT_CUNEIFORM,
+ nko = c.HB_SCRIPT_NKO,
+ phags_pa = c.HB_SCRIPT_PHAGS_PA,
+ phoenician = c.HB_SCRIPT_PHOENICIAN,
+ carian = c.HB_SCRIPT_CARIAN,
+ cham = c.HB_SCRIPT_CHAM,
+ kayah_li = c.HB_SCRIPT_KAYAH_LI,
+ lepcha = c.HB_SCRIPT_LEPCHA,
+ lycian = c.HB_SCRIPT_LYCIAN,
+ lydian = c.HB_SCRIPT_LYDIAN,
+ ol_chiki = c.HB_SCRIPT_OL_CHIKI,
+ rejang = c.HB_SCRIPT_REJANG,
+ saurashtra = c.HB_SCRIPT_SAURASHTRA,
+ sundanese = c.HB_SCRIPT_SUNDANESE,
+ vai = c.HB_SCRIPT_VAI,
+ avestan = c.HB_SCRIPT_AVESTAN,
+ bamum = c.HB_SCRIPT_BAMUM,
+ egyptian_hieroglyphs = c.HB_SCRIPT_EGYPTIAN_HIEROGLYPHS,
+ imperial_aramaic = c.HB_SCRIPT_IMPERIAL_ARAMAIC,
+ inscriptional_pahlavi = c.HB_SCRIPT_INSCRIPTIONAL_PAHLAVI,
+ inscriptional_parthian = c.HB_SCRIPT_INSCRIPTIONAL_PARTHIAN,
+ javanese = c.HB_SCRIPT_JAVANESE,
+ kaithi = c.HB_SCRIPT_KAITHI,
+ lisu = c.HB_SCRIPT_LISU,
+ meetei_mayek = c.HB_SCRIPT_MEETEI_MAYEK,
+ old_south_arabian = c.HB_SCRIPT_OLD_SOUTH_ARABIAN,
+ old_turkic = c.HB_SCRIPT_OLD_TURKIC,
+ samaritan = c.HB_SCRIPT_SAMARITAN,
+ tai_tham = c.HB_SCRIPT_TAI_THAM,
+ tai_viet = c.HB_SCRIPT_TAI_VIET,
+ batak = c.HB_SCRIPT_BATAK,
+ brahmi = c.HB_SCRIPT_BRAHMI,
+ mandaic = c.HB_SCRIPT_MANDAIC,
+ chakma = c.HB_SCRIPT_CHAKMA,
+ meroitic_cursive = c.HB_SCRIPT_MEROITIC_CURSIVE,
+ meroitic_hieroglyphs = c.HB_SCRIPT_MEROITIC_HIEROGLYPHS,
+ miao = c.HB_SCRIPT_MIAO,
+ sharada = c.HB_SCRIPT_SHARADA,
+ sora_sompeng = c.HB_SCRIPT_SORA_SOMPENG,
+ takri = c.HB_SCRIPT_TAKRI,
+ bassa_vah = c.HB_SCRIPT_BASSA_VAH,
+ caucasian_albanian = c.HB_SCRIPT_CAUCASIAN_ALBANIAN,
+ duployan = c.HB_SCRIPT_DUPLOYAN,
+ elbasan = c.HB_SCRIPT_ELBASAN,
+ grantha = c.HB_SCRIPT_GRANTHA,
+ khojki = c.HB_SCRIPT_KHOJKI,
+ khudawadi = c.HB_SCRIPT_KHUDAWADI,
+ linear_a = c.HB_SCRIPT_LINEAR_A,
+ mahajani = c.HB_SCRIPT_MAHAJANI,
+ manichaean = c.HB_SCRIPT_MANICHAEAN,
+ mende_kikakui = c.HB_SCRIPT_MENDE_KIKAKUI,
+ modi = c.HB_SCRIPT_MODI,
+ mro = c.HB_SCRIPT_MRO,
+ nabataean = c.HB_SCRIPT_NABATAEAN,
+ old_north_arabian = c.HB_SCRIPT_OLD_NORTH_ARABIAN,
+ old_permic = c.HB_SCRIPT_OLD_PERMIC,
+ pahawh_hmong = c.HB_SCRIPT_PAHAWH_HMONG,
+ palmyrene = c.HB_SCRIPT_PALMYRENE,
+ pau_cin_hau = c.HB_SCRIPT_PAU_CIN_HAU,
+ psalter_pahlavi = c.HB_SCRIPT_PSALTER_PAHLAVI,
+ siddham = c.HB_SCRIPT_SIDDHAM,
+ tirhuta = c.HB_SCRIPT_TIRHUTA,
+ warang_citi = c.HB_SCRIPT_WARANG_CITI,
+ ahom = c.HB_SCRIPT_AHOM,
+ anatolian_hieroglyphs = c.HB_SCRIPT_ANATOLIAN_HIEROGLYPHS,
+ hatran = c.HB_SCRIPT_HATRAN,
+ multani = c.HB_SCRIPT_MULTANI,
+ old_hungarian = c.HB_SCRIPT_OLD_HUNGARIAN,
+ signwriting = c.HB_SCRIPT_SIGNWRITING,
+ adlam = c.HB_SCRIPT_ADLAM,
+ bhaiksuki = c.HB_SCRIPT_BHAIKSUKI,
+ marchen = c.HB_SCRIPT_MARCHEN,
+ osage = c.HB_SCRIPT_OSAGE,
+ tangut = c.HB_SCRIPT_TANGUT,
+ newa = c.HB_SCRIPT_NEWA,
+ masaram_gondi = c.HB_SCRIPT_MASARAM_GONDI,
+ nushu = c.HB_SCRIPT_NUSHU,
+ soyombo = c.HB_SCRIPT_SOYOMBO,
+ zanabazar_square = c.HB_SCRIPT_ZANABAZAR_SQUARE,
+ dogra = c.HB_SCRIPT_DOGRA,
+ gunjala_gondi = c.HB_SCRIPT_GUNJALA_GONDI,
+ hanifi_rohingya = c.HB_SCRIPT_HANIFI_ROHINGYA,
+ makasar = c.HB_SCRIPT_MAKASAR,
+ medefaidrin = c.HB_SCRIPT_MEDEFAIDRIN,
+ old_sogdian = c.HB_SCRIPT_OLD_SOGDIAN,
+ sogdian = c.HB_SCRIPT_SOGDIAN,
+ elymaic = c.HB_SCRIPT_ELYMAIC,
+ nandinagari = c.HB_SCRIPT_NANDINAGARI,
+ nyiakeng_puachue_hmong = c.HB_SCRIPT_NYIAKENG_PUACHUE_HMONG,
+ wancho = c.HB_SCRIPT_WANCHO,
+ chorasmian = c.HB_SCRIPT_CHORASMIAN,
+ dives_akuru = c.HB_SCRIPT_DIVES_AKURU,
+ khitan_small_script = c.HB_SCRIPT_KHITAN_SMALL_SCRIPT,
+ yezidi = c.HB_SCRIPT_YEZIDI,
+ cypro_minoan = c.HB_SCRIPT_CYPRO_MINOAN,
+ old_uyghur = c.HB_SCRIPT_OLD_UYGHUR,
+ tangsa = c.HB_SCRIPT_TANGSA,
+ toto = c.HB_SCRIPT_TOTO,
+ vithkuqi = c.HB_SCRIPT_VITHKUQI,
+ math = c.HB_SCRIPT_MATH,
+ invalid = c.HB_SCRIPT_INVALID,
+};
+
+/// Data type for languages. Each hb_language_t corresponds to a BCP 47
+/// language tag.
+pub const Language = struct {
+ handle: c.hb_language_t,
+
+ /// Converts str representing a BCP 47 language tag to the corresponding
+ /// hb_language_t.
+ pub fn fromString(str: []const u8) Language {
+ return .{
+ .handle = c.hb_language_from_string(str.ptr, @intCast(str.len)),
+ };
+ }
+
+ /// Converts an hb_language_t to a string.
+ pub fn toString(self: Language) [:0]const u8 {
+ return std.mem.span(@as(
+ [*:0]const u8,
+ @ptrCast(c.hb_language_to_string(self.handle)),
+ ));
+ }
+
+ /// Fetch the default language from current locale.
+ pub fn getDefault() Language {
+ return .{ .handle = c.hb_language_get_default() };
+ }
+};
+
+/// The hb_feature_t is the structure that holds information about requested
+/// feature application. The feature will be applied with the given value to
+/// all glyphs which are in clusters between start (inclusive) and end
+/// (exclusive). Setting start to HB_FEATURE_GLOBAL_START and end to
+/// HB_FEATURE_GLOBAL_END specifies that the feature always applies to the
+/// entire buffer.
+pub const Feature = extern struct {
+ tag: c.hb_tag_t,
+ value: u32,
+ start: c_uint,
+ end: c_uint,
+
+ pub fn fromString(str: []const u8) ?Feature {
+ var f: c.hb_feature_t = undefined;
+ return if (c.hb_feature_from_string(
+ str.ptr,
+ @intCast(str.len),
+ &f,
+ ) > 0)
+ @bitCast(f)
+ else
+ null;
+ }
+
+ pub fn toString(self: *Feature, buf: []u8) void {
+ c.hb_feature_to_string(self, buf.ptr, @intCast(buf.len));
+ }
+};
+
+test "feature from string" {
+ const testing = std.testing;
+ try testing.expect(Feature.fromString("dlig") != null);
+}
diff --git a/vendor/libghostty-vt/pkg/harfbuzz/coretext.zig b/vendor/libghostty-vt/pkg/harfbuzz/coretext.zig
new file mode 100644
index 00000000..73042624
--- /dev/null
+++ b/vendor/libghostty-vt/pkg/harfbuzz/coretext.zig
@@ -0,0 +1,30 @@
+const macos = @import("macos");
+const std = @import("std");
+const c = @import("c.zig").c;
+const Face = @import("face.zig").Face;
+const Font = @import("font.zig").Font;
+const Error = @import("errors.zig").Error;
+
+// Use custom extern functions so that the proper CoreText structs are used
+// without a ptrcast.
+extern fn hb_coretext_font_create(ct_face: *macos.text.Font) ?*c.hb_font_t;
+
+/// Creates an hb_font_t font object from the specified CTFontRef.
+pub fn createFont(face: *macos.text.Font) Error!Font {
+ const handle = hb_coretext_font_create(face) orelse return Error.HarfbuzzFailed;
+ return Font{ .handle = handle };
+}
+
+test {
+ if (!@hasDecl(c, "hb_coretext_font_create")) return error.SkipZigTest;
+
+ const name = try macos.foundation.String.createWithBytes("Monaco", .utf8, false);
+ defer name.release();
+ const desc = try macos.text.FontDescriptor.createWithNameAndSize(name, 12);
+ defer desc.release();
+ const font = try macos.text.Font.createWithFontDescriptor(desc, 12);
+ defer font.release();
+
+ var hb_font = try createFont(font);
+ defer hb_font.destroy();
+}
diff --git a/vendor/libghostty-vt/pkg/harfbuzz/errors.zig b/vendor/libghostty-vt/pkg/harfbuzz/errors.zig
new file mode 100644
index 00000000..44a4d055
--- /dev/null
+++ b/vendor/libghostty-vt/pkg/harfbuzz/errors.zig
@@ -0,0 +1,5 @@
+pub const Error = error{
+ /// Not very descriptive but harfbuzz doesn't actually have error
+ /// codes so the best we can do!
+ HarfbuzzFailed,
+};
diff --git a/vendor/libghostty-vt/pkg/harfbuzz/face.zig b/vendor/libghostty-vt/pkg/harfbuzz/face.zig
new file mode 100644
index 00000000..5ffad5d3
--- /dev/null
+++ b/vendor/libghostty-vt/pkg/harfbuzz/face.zig
@@ -0,0 +1,17 @@
+const std = @import("std");
+const c = @import("c.zig").c;
+
+/// A font face is an object that represents a single face from within a font family.
+///
+/// More precisely, a font face represents a single face in a binary font file.
+/// Font faces are typically built from a binary blob and a face index.
+/// Font faces are used to create fonts.
+pub const Face = struct {
+ handle: *c.hb_face_t,
+
+ /// Decreases the reference count on a face object. When the reference
+ /// count reaches zero, the face is destroyed, freeing all memory.
+ pub fn destroy(self: *Face) void {
+ c.hb_face_destroy(self.handle);
+ }
+};
diff --git a/vendor/libghostty-vt/pkg/harfbuzz/font.zig b/vendor/libghostty-vt/pkg/harfbuzz/font.zig
new file mode 100644
index 00000000..13544fb9
--- /dev/null
+++ b/vendor/libghostty-vt/pkg/harfbuzz/font.zig
@@ -0,0 +1,28 @@
+const std = @import("std");
+const c = @import("c.zig").c;
+const Face = @import("face.zig").Face;
+const Error = @import("errors.zig").Error;
+
+pub const Font = struct {
+ handle: *c.hb_font_t,
+
+ /// Constructs a new font object from the specified face.
+ pub fn create(face: Face) Error!Font {
+ const handle = c.hb_font_create(face.handle) orelse return Error.HarfbuzzFailed;
+ return Font{ .handle = handle };
+ }
+
+ /// Decreases the reference count on the given font object. When the
+ /// reference count reaches zero, the font is destroyed, freeing all memory.
+ pub fn destroy(self: *Font) void {
+ c.hb_font_destroy(self.handle);
+ }
+
+ pub fn setScale(self: *Font, x: u32, y: u32) void {
+ c.hb_font_set_scale(
+ self.handle,
+ @intCast(x),
+ @intCast(y),
+ );
+ }
+};
diff --git a/vendor/libghostty-vt/pkg/harfbuzz/freetype.zig b/vendor/libghostty-vt/pkg/harfbuzz/freetype.zig
new file mode 100644
index 00000000..d90d3f83
--- /dev/null
+++ b/vendor/libghostty-vt/pkg/harfbuzz/freetype.zig
@@ -0,0 +1,78 @@
+const freetype = @import("freetype");
+const std = @import("std");
+const c = @import("c.zig").c;
+const Face = @import("face.zig").Face;
+const Font = @import("font.zig").Font;
+const Error = @import("errors.zig").Error;
+
+// Use custom extern functions so that the proper freetype structs are used
+// without a ptrcast. These are only needed when interacting with freetype
+// C structs.
+extern fn hb_ft_face_create_referenced(ft_face: freetype.c.FT_Face) ?*c.hb_face_t;
+extern fn hb_ft_font_create_referenced(ft_face: freetype.c.FT_Face) ?*c.hb_font_t;
+extern fn hb_ft_font_get_face(font: ?*c.hb_font_t) freetype.c.FT_Face;
+
+/// Creates an hb_face_t face object from the specified FT_Face.
+///
+/// This is the preferred variant of the hb_ft_face_create* function
+/// family, because it calls FT_Reference_Face() on ft_face , ensuring
+/// that ft_face remains alive as long as the resulting hb_face_t face
+/// object remains alive. Also calls FT_Done_Face() when the hb_face_t
+/// face object is destroyed.
+///
+/// Use this version unless you know you have good reasons not to.
+pub fn createFace(face: freetype.c.FT_Face) Error!Face {
+ const handle = hb_ft_face_create_referenced(face) orelse return Error.HarfbuzzFailed;
+ return Face{ .handle = handle };
+}
+
+/// Creates an hb_font_t font object from the specified FT_Face.
+pub fn createFont(face: freetype.c.FT_Face) Error!Font {
+ const handle = hb_ft_font_create_referenced(face) orelse return Error.HarfbuzzFailed;
+ return Font{ .handle = handle };
+}
+
+/// Configures the font-functions structure of the specified hb_font_t font
+/// object to use FreeType font functions.
+///
+/// In particular, you can use this function to configure an existing
+/// hb_face_t face object for use with FreeType font functions even if that
+/// hb_face_t face object was initially created with hb_face_create(), and
+/// therefore was not initially configured to use FreeType font functions.
+///
+/// An hb_face_t face object created with hb_ft_face_create() is preconfigured
+/// for FreeType font functions and does not require this function to be used.
+pub fn setFontFuncs(font: Font) void {
+ c.hb_ft_font_set_funcs(font.handle);
+}
+
+test {
+ if (!@hasDecl(c, "hb_ft_font_create_referenced")) return error.SkipZigTest;
+
+ const testing = std.testing;
+ const testFont = freetype.testing.font_regular;
+ const ftc = freetype.c;
+ const ftok = ftc.FT_Err_Ok;
+
+ var ft_lib: ftc.FT_Library = undefined;
+ if (ftc.FT_Init_FreeType(&ft_lib) != ftok)
+ return error.FreeTypeInitFailed;
+ defer _ = ftc.FT_Done_FreeType(ft_lib);
+
+ var ft_face: ftc.FT_Face = undefined;
+ try testing.expect(ftc.FT_New_Memory_Face(
+ ft_lib,
+ testFont,
+ @intCast(testFont.len),
+ 0,
+ &ft_face,
+ ) == ftok);
+ defer _ = ftc.FT_Done_Face(ft_face);
+
+ var face = try createFace(ft_face);
+ defer face.destroy();
+
+ var font = try createFont(ft_face);
+ defer font.destroy();
+ setFontFuncs(font);
+}
diff --git a/vendor/libghostty-vt/pkg/harfbuzz/main.zig b/vendor/libghostty-vt/pkg/harfbuzz/main.zig
new file mode 100644
index 00000000..08a4f9c2
--- /dev/null
+++ b/vendor/libghostty-vt/pkg/harfbuzz/main.zig
@@ -0,0 +1,31 @@
+const blob = @import("blob.zig");
+const buffer = @import("buffer.zig");
+const common = @import("common.zig");
+const errors = @import("errors.zig");
+const face = @import("face.zig");
+const font = @import("font.zig");
+const shapepkg = @import("shape.zig");
+const versionpkg = @import("version.zig");
+
+pub const c = @import("c.zig").c;
+pub const freetype = @import("freetype.zig");
+pub const coretext = @import("coretext.zig");
+pub const MemoryMode = blob.MemoryMode;
+pub const Blob = blob.Blob;
+pub const Buffer = buffer.Buffer;
+pub const GlyphPosition = buffer.GlyphPosition;
+pub const Direction = common.Direction;
+pub const Script = common.Script;
+pub const Language = common.Language;
+pub const Feature = common.Feature;
+pub const Face = face.Face;
+pub const Font = font.Font;
+pub const shape = shapepkg.shape;
+pub const Version = versionpkg.Version;
+pub const version = versionpkg.version;
+pub const versionAtLeast = versionpkg.versionAtLeast;
+pub const versionString = versionpkg.versionString;
+
+test {
+ @import("std").testing.refAllDecls(@This());
+}
diff --git a/vendor/libghostty-vt/pkg/harfbuzz/shape.zig b/vendor/libghostty-vt/pkg/harfbuzz/shape.zig
new file mode 100644
index 00000000..b49020c3
--- /dev/null
+++ b/vendor/libghostty-vt/pkg/harfbuzz/shape.zig
@@ -0,0 +1,27 @@
+const std = @import("std");
+const c = @import("c.zig").c;
+const Font = @import("font.zig").Font;
+const Buffer = @import("buffer.zig").Buffer;
+const Feature = @import("common.zig").Feature;
+
+/// Shapes buffer using font turning its Unicode characters content to
+/// positioned glyphs. If features is not NULL, it will be used to control
+/// the features applied during shaping. If two features have the same tag
+/// but overlapping ranges the value of the feature with the higher index
+/// takes precedence.
+pub fn shape(font: Font, buf: Buffer, features: ?[]const Feature) void {
+ const hb_feats: [*c]const c.hb_feature_t = feats: {
+ if (features) |fs| {
+ if (fs.len > 0) break :feats @ptrCast(fs.ptr);
+ }
+
+ break :feats null;
+ };
+
+ c.hb_shape(
+ font.handle,
+ buf.handle,
+ hb_feats,
+ if (features) |f| @intCast(f.len) else 0,
+ );
+}
diff --git a/vendor/libghostty-vt/pkg/harfbuzz/version.zig b/vendor/libghostty-vt/pkg/harfbuzz/version.zig
new file mode 100644
index 00000000..dbe25b52
--- /dev/null
+++ b/vendor/libghostty-vt/pkg/harfbuzz/version.zig
@@ -0,0 +1,47 @@
+const std = @import("std");
+const c = @import("c.zig").c;
+
+pub const Version = struct {
+ major: u32,
+ minor: u32,
+ micro: u32,
+};
+
+/// Returns library version as three integer components.
+pub fn version() Version {
+ var major: c_uint = 0;
+ var minor: c_uint = 0;
+ var micro: c_uint = 0;
+ c.hb_version(&major, &minor, &micro);
+ return .{ .major = major, .minor = minor, .micro = micro };
+}
+
+/// Tests the library version against a minimum value, as three integer components.
+pub fn versionAtLeast(vsn: Version) bool {
+ return c.hb_version_atleast(
+ vsn.major,
+ vsn.minor,
+ vsn.micro,
+ ) > 0;
+}
+
+/// Returns library version as a string with three components.
+pub fn versionString() [:0]const u8 {
+ const res = c.hb_version_string();
+ return std.mem.sliceTo(res, 0);
+}
+
+test {
+ const testing = std.testing;
+
+ // Should be able to get the version
+ const vsn = version();
+ try testing.expect(vsn.major > 0);
+
+ // Should be at least version 1
+ try testing.expect(versionAtLeast(.{
+ .major = 1,
+ .minor = 0,
+ .micro = 0,
+ }));
+}