diff --git a/pkg/harfbuzz/blob.zig b/pkg/harfbuzz/blob.zig new file mode 100644 index 000000000..0b9eadc56 --- /dev/null +++ b/pkg/harfbuzz/blob.zig @@ -0,0 +1,130 @@ +const std = @import("std"); +const c = @import("c.zig"); +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(c_uint, data.len), + @enumToInt(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: ?fn (?*T) callconv(.C) void, + replace: bool, + ) bool { + const Callback = struct { + pub fn callback(data: ?*anyopaque) callconv(.C) void { + @call(.{ .modifier = .always_inline }, destroycb, .{ + @ptrCast(?*T, @alignCast(@alignOf(T), data)), + }); + } + }; + + return c.hb_blob_set_user_data( + self.handle, + @ptrCast([*c]c.hb_user_data_key_t, 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([*c]c.hb_user_data_key_t, key), + ); + + if (opt) |ptr| + return @ptrCast(?*T, @alignCast(@alignOf(T), 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/pkg/harfbuzz/errors.zig b/pkg/harfbuzz/errors.zig new file mode 100644 index 000000000..44a4d055e --- /dev/null +++ b/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/pkg/harfbuzz/main.zig b/pkg/harfbuzz/main.zig index 777a4d571..6959edc2f 100644 --- a/pkg/harfbuzz/main.zig +++ b/pkg/harfbuzz/main.zig @@ -1,4 +1,6 @@ pub const c = @import("c.zig"); +pub usingnamespace @import("blob.zig"); +pub usingnamespace @import("errors.zig"); pub usingnamespace @import("version.zig"); test {