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

This commit is contained in:
wehub-resource-sync
2026-07-13 12:02:48 +08:00
commit bd44b5aa08
5770 changed files with 506778 additions and 0 deletions
+126
View File
@@ -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);
}
+373
View File
@@ -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"));
}
+194
View File
@@ -0,0 +1,194 @@
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",
});
// Disable ubsan for MSVC: Zig's ubsan runtime cannot be bundled
// on Windows (LNK4229), leaving __ubsan_handle_* unresolved when
// the static archive is consumed by an external linker.
if (target.result.abi == .msvc) {
try flags.appendSlice(b.allocator, &.{
"-fno-sanitize=undefined",
"-fno-sanitize-trap=undefined",
});
}
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;
}
+18
View File
@@ -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" },
},
}
+8
View File
@@ -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");
});
+247
View File
@@ -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);
}
+30
View File
@@ -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();
}
+5
View File
@@ -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,
};
+17
View File
@@ -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);
}
};
+28
View File
@@ -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),
);
}
};
+78
View File
@@ -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);
}
+31
View File
@@ -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());
}
+27
View File
@@ -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,
);
}
+47
View File
@@ -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,
}));
}