├── .gitignore ├── LICENSE ├── README.md ├── blob.zig ├── buffer.zig ├── build.zig ├── build.zig.zon ├── c.zig ├── common.zig ├── errors.zig ├── face.zig ├── font.zig ├── freetype.zig ├── main.zig ├── shape.zig └── version.zig /.gitignore: -------------------------------------------------------------------------------- 1 | /.zig-cache/ 2 | /zig-out 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright © Mitchell Hashimoto 2 | Copyright © Andrew Kelley 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the “Software”), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 8 | of the Software, and to permit persons to whom the Software is furnished to do 9 | so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # harfbuzz 2 | 3 | This is [harfbuzz](https://harfbuzz.github.io/) packaged for [Zig](https://ziglang.org/). 4 | 5 | -------------------------------------------------------------------------------- /blob.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const c = @import("c.zig").c; 3 | const Error = @import("errors.zig").Error; 4 | 5 | /// Data type holding the memory modes available to client programs. 6 | /// 7 | /// Regarding these various memory-modes: 8 | /// 9 | /// - In no case shall the HarfBuzz client modify memory that is passed to 10 | /// HarfBuzz in a blob. If there is any such possibility, 11 | /// HB_MEMORY_MODE_DUPLICATE should be used such that HarfBuzz makes a 12 | /// copy immediately, 13 | /// 14 | /// - Use HB_MEMORY_MODE_READONLY otherwise, unless you really really really 15 | /// know what you are doing, 16 | /// 17 | /// - HB_MEMORY_MODE_WRITABLE is appropriate if you really made a copy of 18 | /// data solely for the purpose of passing to HarfBuzz and doing that 19 | /// just once (no reuse!), 20 | /// 21 | /// - If the font is mmap()ed, it's okay to use 22 | /// HB_MEMORY_READONLY_MAY_MAKE_WRITABLE , however, using that mode 23 | /// correctly is very tricky. Use HB_MEMORY_MODE_READONLY instead. 24 | pub const MemoryMode = enum(u2) { 25 | /// HarfBuzz immediately makes a copy of the data. 26 | duplicate = c.HB_MEMORY_MODE_DUPLICATE, 27 | 28 | /// HarfBuzz client will never modify the data, and HarfBuzz will never 29 | /// modify the data. 30 | readonly = c.HB_MEMORY_MODE_READONLY, 31 | 32 | /// HarfBuzz client made a copy of the data solely for HarfBuzz, so 33 | /// HarfBuzz may modify the data. 34 | writable = c.HB_MEMORY_MODE_WRITABLE, 35 | 36 | /// See above 37 | readonly_may_make_writable = c.HB_MEMORY_MODE_READONLY_MAY_MAKE_WRITABLE, 38 | }; 39 | 40 | /// Blobs wrap a chunk of binary data to handle lifecycle management of data 41 | /// while it is passed between client and HarfBuzz. Blobs are primarily 42 | /// used to create font faces, but also to access font face tables, as well as 43 | /// pass around other binary data. 44 | pub const Blob = struct { 45 | handle: *c.hb_blob_t, 46 | 47 | /// Creates a new "blob" object wrapping data . The mode parameter is used 48 | /// to negotiate ownership and lifecycle of data . 49 | /// 50 | /// Note that this function returns a freshly-allocated empty blob even 51 | /// if length is zero. This is in contrast to hb_blob_create(), which 52 | /// returns the singleton empty blob (as returned by hb_blob_get_empty()) 53 | /// if length is zero. 54 | pub fn create(data: []const u8, mode: MemoryMode) Error!Blob { 55 | const handle = c.hb_blob_create_or_fail( 56 | data.ptr, 57 | @intCast(data.len), 58 | @intFromEnum(mode), 59 | null, 60 | null, 61 | ) orelse return Error.HarfbuzzFailed; 62 | 63 | return Blob{ .handle = handle }; 64 | } 65 | 66 | /// Decreases the reference count on blob , and if it reaches zero, 67 | /// destroys blob , freeing all memory, possibly calling the 68 | /// destroy-callback the blob was created for if it has not been 69 | /// called already. 70 | pub fn destroy(self: *Blob) void { 71 | c.hb_blob_destroy(self.handle); 72 | } 73 | 74 | /// Attaches a user-data key/data pair to the specified blob. 75 | pub fn setUserData( 76 | self: Blob, 77 | comptime T: type, 78 | key: ?*anyopaque, 79 | ptr: ?*T, 80 | comptime destroycb: ?*const fn (?*T) callconv(.C) void, 81 | replace: bool, 82 | ) bool { 83 | const Callback = struct { 84 | pub fn callback(data: ?*anyopaque) callconv(.C) void { 85 | @call(.{ .modifier = .always_inline }, destroycb, .{ 86 | @as(?*T, @ptrCast(@alignCast(data))), 87 | }); 88 | } 89 | }; 90 | 91 | return c.hb_blob_set_user_data( 92 | self.handle, 93 | @ptrCast(key), 94 | ptr, 95 | if (destroycb != null) Callback.callback else null, 96 | if (replace) 1 else 0, 97 | ) > 0; 98 | } 99 | 100 | /// Fetches the user data associated with the specified key, attached to 101 | /// the specified font-functions structure. 102 | pub fn getUserData( 103 | self: Blob, 104 | comptime T: type, 105 | key: ?*anyopaque, 106 | ) ?*T { 107 | const opt = c.hb_blob_get_user_data(self.handle, @ptrCast(key)); 108 | if (opt) |ptr| 109 | return @ptrCast(@alignCast(ptr)) 110 | else 111 | return null; 112 | } 113 | }; 114 | 115 | test { 116 | const testing = std.testing; 117 | 118 | const data = "hello"; 119 | var blob = try Blob.create(data, .readonly); 120 | defer blob.destroy(); 121 | 122 | var userdata: u8 = 127; 123 | var key: u8 = 0; 124 | try testing.expect(blob.setUserData(u8, &key, &userdata, null, false)); 125 | try testing.expect(blob.getUserData(u8, &key).?.* == 127); 126 | } 127 | -------------------------------------------------------------------------------- /buffer.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const c = @import("c.zig").c; 3 | const common = @import("common.zig"); 4 | const Error = @import("errors.zig").Error; 5 | const Direction = common.Direction; 6 | const Script = common.Script; 7 | const Language = common.Language; 8 | 9 | /// Buffers serve a dual role in HarfBuzz; before shaping, they hold the 10 | /// input characters that are passed to hb_shape(), and after shaping they 11 | /// hold the output glyphs. 12 | pub const Buffer = struct { 13 | handle: *c.hb_buffer_t, 14 | 15 | /// Creates a new hb_buffer_t with all properties to defaults. 16 | pub fn create() Error!Buffer { 17 | const handle = c.hb_buffer_create() orelse return Error.HarfbuzzFailed; 18 | return Buffer{ .handle = handle }; 19 | } 20 | 21 | /// Deallocate the buffer . Decreases the reference count on buffer by one. 22 | /// If the result is zero, then buffer and all associated resources are 23 | /// freed. See hb_buffer_reference(). 24 | pub fn destroy(self: *Buffer) void { 25 | c.hb_buffer_destroy(self.handle); 26 | } 27 | 28 | /// Resets the buffer to its initial status, as if it was just newly 29 | /// created with hb_buffer_create(). 30 | pub fn reset(self: Buffer) void { 31 | c.hb_buffer_reset(self.handle); 32 | } 33 | 34 | /// Returns the number of items in the buffer. 35 | pub fn getLength(self: Buffer) u32 { 36 | return c.hb_buffer_get_length(self.handle); 37 | } 38 | 39 | /// Sets the type of buffer contents. Buffers are either empty, contain 40 | /// characters (before shaping), or contain glyphs (the result of shaping). 41 | pub fn setContentType(self: Buffer, ct: ContentType) void { 42 | c.hb_buffer_set_content_type(self.handle, @intFromEnum(ct)); 43 | } 44 | 45 | /// Fetches the type of buffer contents. Buffers are either empty, contain 46 | /// characters (before shaping), or contain glyphs (the result of shaping). 47 | pub fn getContentType(self: Buffer) ContentType { 48 | return @enumFromInt(c.hb_buffer_get_content_type(self.handle)); 49 | } 50 | 51 | /// Appends a character with the Unicode value of codepoint to buffer, 52 | /// and gives it the initial cluster value of cluster . Clusters can be 53 | /// any thing the client wants, they are usually used to refer to the 54 | /// index of the character in the input text stream and are output in 55 | /// hb_glyph_info_t.cluster field. 56 | /// 57 | /// This function does not check the validity of codepoint, it is up to 58 | /// the caller to ensure it is a valid Unicode code point. 59 | pub fn add(self: Buffer, cp: u32, cluster: u32) void { 60 | c.hb_buffer_add(self.handle, cp, cluster); 61 | } 62 | 63 | /// Appends characters from text array to buffer . The item_offset is the 64 | /// position of the first character from text that will be appended, and 65 | /// item_length is the number of character. When shaping part of a larger 66 | /// text (e.g. a run of text from a paragraph), instead of passing just 67 | /// the substring corresponding to the run, it is preferable to pass the 68 | /// whole paragraph and specify the run start and length as item_offset and 69 | /// item_length , respectively, to give HarfBuzz the full context to be 70 | /// able, for example, to do cross-run Arabic shaping or properly handle 71 | /// combining marks at stat of run. 72 | /// 73 | /// This function does not check the validity of text , it is up to the 74 | /// caller to ensure it contains a valid Unicode code points. 75 | pub fn addCodepoints(self: Buffer, text: []const u32) void { 76 | c.hb_buffer_add_codepoints( 77 | self.handle, 78 | text.ptr, 79 | @intCast(text.len), 80 | 0, 81 | @intCast(text.len), 82 | ); 83 | } 84 | 85 | /// See hb_buffer_add_codepoints(). 86 | /// 87 | /// Replaces invalid UTF-32 characters with the buffer replacement code 88 | /// point, see hb_buffer_set_replacement_codepoint(). 89 | pub fn addUTF32(self: Buffer, text: []const u32) void { 90 | c.hb_buffer_add_utf32( 91 | self.handle, 92 | text.ptr, 93 | @intCast(text.len), 94 | 0, 95 | @intCast(text.len), 96 | ); 97 | } 98 | 99 | /// See hb_buffer_add_codepoints(). 100 | /// 101 | /// Replaces invalid UTF-16 characters with the buffer replacement code 102 | /// point, see hb_buffer_set_replacement_codepoint(). 103 | pub fn addUTF16(self: Buffer, text: []const u16) void { 104 | c.hb_buffer_add_utf16( 105 | self.handle, 106 | text.ptr, 107 | @intCast(text.len), 108 | 0, 109 | @intCast(text.len), 110 | ); 111 | } 112 | 113 | /// See hb_buffer_add_codepoints(). 114 | /// 115 | /// Replaces invalid UTF-8 characters with the buffer replacement code 116 | /// point, see hb_buffer_set_replacement_codepoint(). 117 | pub fn addUTF8(self: Buffer, text: []const u8) void { 118 | c.hb_buffer_add_utf8( 119 | self.handle, 120 | text.ptr, 121 | @intCast(text.len), 122 | 0, 123 | @intCast(text.len), 124 | ); 125 | } 126 | 127 | /// Similar to hb_buffer_add_codepoints(), but allows only access to first 128 | /// 256 Unicode code points that can fit in 8-bit strings. 129 | pub fn addLatin1(self: Buffer, text: []const u8) void { 130 | c.hb_buffer_add_latin1( 131 | self.handle, 132 | text.ptr, 133 | @intCast(text.len), 134 | 0, 135 | @intCast(text.len), 136 | ); 137 | } 138 | 139 | /// Set the text flow direction of the buffer. No shaping can happen 140 | /// without setting buffer direction, and it controls the visual direction 141 | /// for the output glyphs; for RTL direction the glyphs will be reversed. 142 | /// Many layout features depend on the proper setting of the direction, 143 | /// for example, reversing RTL text before shaping, then shaping with LTR 144 | /// direction is not the same as keeping the text in logical order and 145 | /// shaping with RTL direction. 146 | pub fn setDirection(self: Buffer, dir: Direction) void { 147 | c.hb_buffer_set_direction(self.handle, @intFromEnum(dir)); 148 | } 149 | 150 | /// See hb_buffer_set_direction() 151 | pub fn getDirection(self: Buffer) Direction { 152 | return @enumFromInt(c.hb_buffer_get_direction(self.handle)); 153 | } 154 | 155 | /// Sets the script of buffer to script. 156 | /// 157 | /// Script is crucial for choosing the proper shaping behaviour for 158 | /// scripts that require it (e.g. Arabic) and the which OpenType features 159 | /// defined in the font to be applied. 160 | /// 161 | /// You can pass one of the predefined hb_script_t values, or use 162 | /// hb_script_from_string() or hb_script_from_iso15924_tag() to get the 163 | /// corresponding script from an ISO 15924 script tag. 164 | pub fn setScript(self: Buffer, script: Script) void { 165 | c.hb_buffer_set_script(self.handle, @intFromEnum(script)); 166 | } 167 | 168 | /// See hb_buffer_set_script() 169 | pub fn getScript(self: Buffer) Script { 170 | return @enumFromInt(c.hb_buffer_get_script(self.handle)); 171 | } 172 | 173 | /// Sets the language of buffer to language . 174 | /// 175 | /// Languages are crucial for selecting which OpenType feature to apply to 176 | /// the buffer which can result in applying language-specific behaviour. 177 | /// Languages are orthogonal to the scripts, and though they are related, 178 | /// they are different concepts and should not be confused with each other. 179 | /// 180 | /// Use hb_language_from_string() to convert from BCP 47 language tags to 181 | /// hb_language_t. 182 | pub fn setLanguage(self: Buffer, language: Language) void { 183 | c.hb_buffer_set_language(self.handle, language.handle); 184 | } 185 | 186 | /// See hb_buffer_set_language() 187 | pub fn getLanguage(self: Buffer) Language { 188 | return Language{ .handle = c.hb_buffer_get_language(self.handle) }; 189 | } 190 | 191 | /// Returns buffer glyph information array. Returned pointer is valid as 192 | /// long as buffer contents are not modified. 193 | pub fn getGlyphInfos(self: Buffer) []GlyphInfo { 194 | var length: u32 = 0; 195 | const ptr: [*c]GlyphInfo = @ptrCast(c.hb_buffer_get_glyph_infos(self.handle, &length)); 196 | return ptr[0..length]; 197 | } 198 | 199 | /// Returns buffer glyph position array. Returned pointer is valid as 200 | /// long as buffer contents are not modified. 201 | /// 202 | /// If buffer did not have positions before, the positions will be 203 | /// initialized to zeros, unless this function is called from within a 204 | /// buffer message callback (see hb_buffer_set_message_func()), in which 205 | /// case NULL is returned. 206 | pub fn getGlyphPositions(self: Buffer) ?[]GlyphPosition { 207 | var length: u32 = 0; 208 | 209 | if (c.hb_buffer_get_glyph_positions(self.handle, &length)) |positions| { 210 | const ptr: [*]GlyphPosition = @ptrCast(positions); 211 | return ptr[0..length]; 212 | } 213 | 214 | return null; 215 | } 216 | 217 | /// Sets unset buffer segment properties based on buffer Unicode contents. 218 | /// If buffer is not empty, it must have content type 219 | /// HB_BUFFER_CONTENT_TYPE_UNICODE. 220 | /// 221 | /// If buffer script is not set (ie. is HB_SCRIPT_INVALID), it will be set 222 | /// to the Unicode script of the first character in the buffer that has a 223 | /// script other than HB_SCRIPT_COMMON, HB_SCRIPT_INHERITED, and 224 | /// HB_SCRIPT_UNKNOWN. 225 | /// 226 | /// Next, if buffer direction is not set (ie. is HB_DIRECTION_INVALID), it 227 | /// will be set to the natural horizontal direction of the buffer script as 228 | /// returned by hb_script_get_horizontal_direction(). If 229 | /// hb_script_get_horizontal_direction() returns HB_DIRECTION_INVALID, 230 | /// then HB_DIRECTION_LTR is used. 231 | /// 232 | /// Finally, if buffer language is not set (ie. is HB_LANGUAGE_INVALID), it 233 | /// will be set to the process's default language as returned by 234 | /// hb_language_get_default(). This may change in the future by taking 235 | /// buffer script into consideration when choosing a language. Note that 236 | /// hb_language_get_default() is NOT threadsafe the first time it is 237 | /// called. See documentation for that function for details. 238 | pub fn guessSegmentProperties(self: Buffer) void { 239 | c.hb_buffer_guess_segment_properties(self.handle); 240 | } 241 | }; 242 | 243 | /// The type of hb_buffer_t contents. 244 | pub const ContentType = enum(u2) { 245 | /// Initial value for new buffer. 246 | invalid = c.HB_BUFFER_CONTENT_TYPE_INVALID, 247 | 248 | /// The buffer contains input characters (before shaping). 249 | unicode = c.HB_BUFFER_CONTENT_TYPE_UNICODE, 250 | 251 | /// The buffer contains output glyphs (after shaping). 252 | glyphs = c.HB_BUFFER_CONTENT_TYPE_GLYPHS, 253 | }; 254 | 255 | /// The hb_glyph_info_t is the structure that holds information about the 256 | /// glyphs and their relation to input text. 257 | pub const GlyphInfo = extern struct { 258 | /// either a Unicode code point (before shaping) or a glyph index (after shaping). 259 | codepoint: u32, 260 | _mask: u32, 261 | 262 | /// the index of the character in the original text that corresponds to 263 | /// this hb_glyph_info_t, or whatever the client passes to hb_buffer_add(). 264 | /// More than one hb_glyph_info_t can have the same cluster value, if they 265 | /// resulted from the same character (e.g. one to many glyph substitution), 266 | /// and when more than one character gets merged in the same glyph (e.g. 267 | /// many to one glyph substitution) the hb_glyph_info_t will have the 268 | /// smallest cluster value of them. By default some characters are merged 269 | /// into the same cluster (e.g. combining marks have the same cluster as 270 | /// their bases) even if they are separate glyphs, hb_buffer_set_cluster_level() 271 | /// allow selecting more fine-grained cluster handling. 272 | cluster: u32, 273 | _var1: u32, 274 | _var2: u32, 275 | }; 276 | 277 | /// The hb_glyph_position_t is the structure that holds the positions of the 278 | /// glyph in both horizontal and vertical directions. All positions in 279 | /// hb_glyph_position_t are relative to the current point. 280 | pub const GlyphPosition = extern struct { 281 | /// how much the line advances after drawing this glyph when setting text 282 | /// in horizontal direction. 283 | x_advance: i32, 284 | 285 | /// how much the line advances after drawing this glyph when setting text 286 | /// in vertical direction. 287 | y_advance: i32, 288 | 289 | /// how much the glyph moves on the X-axis before drawing it, this should 290 | /// not affect how much the line advances. 291 | x_offset: i32, 292 | 293 | /// how much the glyph moves on the Y-axis before drawing it, this should 294 | /// not affect how much the line advances. 295 | y_offset: i32, 296 | 297 | _var: u32, 298 | }; 299 | 300 | test "create" { 301 | const testing = std.testing; 302 | 303 | var buffer = try Buffer.create(); 304 | defer buffer.destroy(); 305 | buffer.reset(); 306 | 307 | // Content type 308 | buffer.setContentType(.unicode); 309 | try testing.expectEqual(ContentType.unicode, buffer.getContentType()); 310 | 311 | // Try add functions 312 | buffer.add('🥹', 27); 313 | var utf32 = [_]u32{ 'A', 'B', 'C' }; 314 | var utf16 = [_]u16{ 'A', 'B', 'C' }; 315 | var utf8 = [_]u8{ 'A', 'B', 'C' }; 316 | buffer.addCodepoints(&utf32); 317 | buffer.addUTF32(&utf32); 318 | buffer.addUTF16(&utf16); 319 | buffer.addUTF8(&utf8); 320 | buffer.addLatin1(&utf8); 321 | 322 | // Guess properties first 323 | buffer.guessSegmentProperties(); 324 | 325 | // Try to set properties 326 | buffer.setDirection(.ltr); 327 | try testing.expectEqual(Direction.ltr, buffer.getDirection()); 328 | 329 | buffer.setScript(.arabic); 330 | try testing.expectEqual(Script.arabic, buffer.getScript()); 331 | 332 | buffer.setLanguage(Language.fromString("en")); 333 | } 334 | -------------------------------------------------------------------------------- /build.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | pub fn build(b: *std.Build) !void { 4 | const target = b.standardTargetOptions(.{}); 5 | const optimize = b.standardOptimizeOption(.{}); 6 | 7 | const coretext_enabled = b.option(bool, "enable-coretext", "Build coretext") orelse false; 8 | const freetype_enabled = b.option(bool, "enable-freetype", "Build freetype") orelse true; 9 | 10 | const freetype = b.dependency("freetype", .{ 11 | .target = target, 12 | .optimize = optimize, 13 | .@"enable-libpng" = true, 14 | }); 15 | const upstream = b.dependency("harfbuzz", .{}); 16 | 17 | const module = b.addModule("harfbuzz", .{ 18 | .root_source_file = b.path("main.zig"), 19 | .target = target, 20 | .optimize = optimize, 21 | .imports = &.{ 22 | .{ .name = "freetype", .module = freetype.module("freetype") }, 23 | }, 24 | }); 25 | 26 | const lib = b.addStaticLibrary(.{ 27 | .name = "harfbuzz", 28 | .target = target, 29 | .optimize = optimize, 30 | }); 31 | lib.linkLibC(); 32 | lib.linkLibCpp(); 33 | lib.addIncludePath(upstream.path("src")); 34 | module.addIncludePath(upstream.path("src")); 35 | 36 | const freetype_dep = b.dependency("freetype", .{ 37 | .target = target, 38 | .optimize = optimize, 39 | .@"enable-libpng" = true, 40 | }); 41 | lib.linkLibrary(freetype_dep.artifact("freetype")); 42 | module.addIncludePath(freetype_dep.builder.dependency("freetype", .{}).path("include")); 43 | 44 | var flags = std.ArrayList([]const u8).init(b.allocator); 45 | defer flags.deinit(); 46 | try flags.appendSlice(&.{ 47 | "-DHAVE_STDBOOL_H", 48 | }); 49 | if (target.result.os.tag != .windows) { 50 | try flags.appendSlice(&.{ 51 | "-DHAVE_UNISTD_H", 52 | "-DHAVE_SYS_MMAN_H", 53 | "-DHAVE_PTHREAD=1", 54 | }); 55 | } 56 | if (freetype_enabled) try flags.appendSlice(&.{ 57 | "-DHAVE_FREETYPE=1", 58 | 59 | // Let's just assume a new freetype 60 | "-DHAVE_FT_GET_VAR_BLEND_COORDINATES=1", 61 | "-DHAVE_FT_SET_VAR_BLEND_COORDINATES=1", 62 | "-DHAVE_FT_DONE_MM_VAR=1", 63 | "-DHAVE_FT_GET_TRANSFORM=1", 64 | }); 65 | if (coretext_enabled) { 66 | try flags.appendSlice(&.{"-DHAVE_CORETEXT=1"}); 67 | lib.linkFramework("CoreText"); 68 | module.linkFramework("CoreText", .{}); 69 | } 70 | 71 | lib.addCSourceFile(.{ 72 | .file = upstream.path("src/harfbuzz.cc"), 73 | .flags = flags.items, 74 | }); 75 | lib.installHeadersDirectory( 76 | upstream.path("src"), 77 | "", 78 | .{ .include_extensions = &.{".h"} }, 79 | ); 80 | 81 | b.installArtifact(lib); 82 | 83 | { 84 | const test_exe = b.addTest(.{ 85 | .name = "test", 86 | .root_source_file = b.path("main.zig"), 87 | .target = target, 88 | .optimize = optimize, 89 | }); 90 | test_exe.linkLibrary(lib); 91 | 92 | var it = module.import_table.iterator(); 93 | while (it.next()) |entry| test_exe.root_module.addImport(entry.key_ptr.*, entry.value_ptr.*); 94 | test_exe.linkLibrary(freetype_dep.artifact("freetype")); 95 | const tests_run = b.addRunArtifact(test_exe); 96 | const test_step = b.step("test", "Run tests"); 97 | test_step.dependOn(&tests_run.step); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /build.zig.zon: -------------------------------------------------------------------------------- 1 | .{ 2 | .name = .harfbuzz, 3 | .version = "8.4.0", 4 | .fingerprint = 0xbd60917c02559d22, 5 | .minimum_zig_version = "0.14.0", 6 | .paths = .{ 7 | "LICENSE", 8 | "blob.zig", 9 | "buffer.zig", 10 | "build.zig", 11 | "build.zig.zon", 12 | "c.zig", 13 | "common.zig", 14 | "errors.zig", 15 | "face.zig", 16 | "font.zig", 17 | "freetype.zig", 18 | "main.zig", 19 | "shape.zig", 20 | "version.zig", 21 | }, 22 | .dependencies = .{ 23 | .harfbuzz = .{ 24 | .url = "https://github.com/harfbuzz/harfbuzz/archive/refs/tags/8.4.0.tar.gz", 25 | .hash = "N-V-__8AAKa0rgW4WI8QbJlq8QJJv6CSxvsvNfussVBe9Heg", 26 | }, 27 | .freetype = .{ 28 | .url = "git+https://github.com/allyourcodebase/freetype#d0a748192f26bd20f9f339d0d7156b84e90eb4dc", 29 | .hash = "freetype-2.13.2-AAAAAByJAAD7KQxAcdDYidJ3YigvRztfnLmn4rQpvLms", 30 | }, 31 | }, 32 | } 33 | -------------------------------------------------------------------------------- /c.zig: -------------------------------------------------------------------------------- 1 | const builtin = @import("builtin"); 2 | 3 | pub const c = @cImport({ 4 | @cInclude("hb.h"); 5 | @cInclude("hb-ft.h"); 6 | if (builtin.os.tag == .macos) @cInclude("hb-coretext.h"); 7 | }); 8 | -------------------------------------------------------------------------------- /common.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const c = @import("c.zig").c; 3 | 4 | /// The direction of a text segment or buffer. 5 | /// 6 | /// A segment can also be tested for horizontal or vertical orientation 7 | /// (irrespective of specific direction) with HB_DIRECTION_IS_HORIZONTAL() 8 | /// or HB_DIRECTION_IS_VERTICAL(). 9 | pub const Direction = enum(u3) { 10 | invalid = c.HB_DIRECTION_INVALID, 11 | ltr = c.HB_DIRECTION_LTR, 12 | rtl = c.HB_DIRECTION_RTL, 13 | ttb = c.HB_DIRECTION_TTB, 14 | bit = c.HB_DIRECTION_BTT, 15 | }; 16 | 17 | /// Data type for scripts. Each hb_script_t's value is an hb_tag_t 18 | /// corresponding to the four-letter values defined by ISO 15924. 19 | /// 20 | /// See also the Script (sc) property of the Unicode Character Database. 21 | pub const Script = enum(u31) { 22 | common = c.HB_SCRIPT_COMMON, 23 | inherited = c.HB_SCRIPT_INHERITED, 24 | unknown = c.HB_SCRIPT_UNKNOWN, 25 | arabic = c.HB_SCRIPT_ARABIC, 26 | armenian = c.HB_SCRIPT_ARMENIAN, 27 | bengali = c.HB_SCRIPT_BENGALI, 28 | cyrillic = c.HB_SCRIPT_CYRILLIC, 29 | devanagari = c.HB_SCRIPT_DEVANAGARI, 30 | georgian = c.HB_SCRIPT_GEORGIAN, 31 | greek = c.HB_SCRIPT_GREEK, 32 | gujarati = c.HB_SCRIPT_GUJARATI, 33 | gurmukhi = c.HB_SCRIPT_GURMUKHI, 34 | hangul = c.HB_SCRIPT_HANGUL, 35 | han = c.HB_SCRIPT_HAN, 36 | hebrew = c.HB_SCRIPT_HEBREW, 37 | hiragana = c.HB_SCRIPT_HIRAGANA, 38 | kannada = c.HB_SCRIPT_KANNADA, 39 | katakana = c.HB_SCRIPT_KATAKANA, 40 | lao = c.HB_SCRIPT_LAO, 41 | latin = c.HB_SCRIPT_LATIN, 42 | malayalam = c.HB_SCRIPT_MALAYALAM, 43 | oriya = c.HB_SCRIPT_ORIYA, 44 | tamil = c.HB_SCRIPT_TAMIL, 45 | telugu = c.HB_SCRIPT_TELUGU, 46 | thai = c.HB_SCRIPT_THAI, 47 | tibetan = c.HB_SCRIPT_TIBETAN, 48 | bopomofo = c.HB_SCRIPT_BOPOMOFO, 49 | braille = c.HB_SCRIPT_BRAILLE, 50 | canadian_syllabics = c.HB_SCRIPT_CANADIAN_SYLLABICS, 51 | cherokee = c.HB_SCRIPT_CHEROKEE, 52 | ethiopic = c.HB_SCRIPT_ETHIOPIC, 53 | khmer = c.HB_SCRIPT_KHMER, 54 | mongolian = c.HB_SCRIPT_MONGOLIAN, 55 | myanmar = c.HB_SCRIPT_MYANMAR, 56 | ogham = c.HB_SCRIPT_OGHAM, 57 | runic = c.HB_SCRIPT_RUNIC, 58 | sinhala = c.HB_SCRIPT_SINHALA, 59 | syriac = c.HB_SCRIPT_SYRIAC, 60 | thaana = c.HB_SCRIPT_THAANA, 61 | yi = c.HB_SCRIPT_YI, 62 | deseret = c.HB_SCRIPT_DESERET, 63 | gothic = c.HB_SCRIPT_GOTHIC, 64 | old_italic = c.HB_SCRIPT_OLD_ITALIC, 65 | buhid = c.HB_SCRIPT_BUHID, 66 | hanunoo = c.HB_SCRIPT_HANUNOO, 67 | tagalog = c.HB_SCRIPT_TAGALOG, 68 | tagbanwa = c.HB_SCRIPT_TAGBANWA, 69 | cypriot = c.HB_SCRIPT_CYPRIOT, 70 | limbu = c.HB_SCRIPT_LIMBU, 71 | linear_b = c.HB_SCRIPT_LINEAR_B, 72 | osmanya = c.HB_SCRIPT_OSMANYA, 73 | shavian = c.HB_SCRIPT_SHAVIAN, 74 | tai_le = c.HB_SCRIPT_TAI_LE, 75 | ugaritic = c.HB_SCRIPT_UGARITIC, 76 | buginese = c.HB_SCRIPT_BUGINESE, 77 | coptic = c.HB_SCRIPT_COPTIC, 78 | glagolitic = c.HB_SCRIPT_GLAGOLITIC, 79 | kharoshthi = c.HB_SCRIPT_KHAROSHTHI, 80 | new_tai_lue = c.HB_SCRIPT_NEW_TAI_LUE, 81 | old_persian = c.HB_SCRIPT_OLD_PERSIAN, 82 | syloti_nagri = c.HB_SCRIPT_SYLOTI_NAGRI, 83 | tifinagh = c.HB_SCRIPT_TIFINAGH, 84 | balinese = c.HB_SCRIPT_BALINESE, 85 | cuneiform = c.HB_SCRIPT_CUNEIFORM, 86 | nko = c.HB_SCRIPT_NKO, 87 | phags_pa = c.HB_SCRIPT_PHAGS_PA, 88 | phoenician = c.HB_SCRIPT_PHOENICIAN, 89 | carian = c.HB_SCRIPT_CARIAN, 90 | cham = c.HB_SCRIPT_CHAM, 91 | kayah_li = c.HB_SCRIPT_KAYAH_LI, 92 | lepcha = c.HB_SCRIPT_LEPCHA, 93 | lycian = c.HB_SCRIPT_LYCIAN, 94 | lydian = c.HB_SCRIPT_LYDIAN, 95 | ol_chiki = c.HB_SCRIPT_OL_CHIKI, 96 | rejang = c.HB_SCRIPT_REJANG, 97 | saurashtra = c.HB_SCRIPT_SAURASHTRA, 98 | sundanese = c.HB_SCRIPT_SUNDANESE, 99 | vai = c.HB_SCRIPT_VAI, 100 | avestan = c.HB_SCRIPT_AVESTAN, 101 | bamum = c.HB_SCRIPT_BAMUM, 102 | egyptian_hieroglyphs = c.HB_SCRIPT_EGYPTIAN_HIEROGLYPHS, 103 | imperial_aramaic = c.HB_SCRIPT_IMPERIAL_ARAMAIC, 104 | inscriptional_pahlavi = c.HB_SCRIPT_INSCRIPTIONAL_PAHLAVI, 105 | inscriptional_parthian = c.HB_SCRIPT_INSCRIPTIONAL_PARTHIAN, 106 | javanese = c.HB_SCRIPT_JAVANESE, 107 | kaithi = c.HB_SCRIPT_KAITHI, 108 | lisu = c.HB_SCRIPT_LISU, 109 | meetei_mayek = c.HB_SCRIPT_MEETEI_MAYEK, 110 | old_south_arabian = c.HB_SCRIPT_OLD_SOUTH_ARABIAN, 111 | old_turkic = c.HB_SCRIPT_OLD_TURKIC, 112 | samaritan = c.HB_SCRIPT_SAMARITAN, 113 | tai_tham = c.HB_SCRIPT_TAI_THAM, 114 | tai_viet = c.HB_SCRIPT_TAI_VIET, 115 | batak = c.HB_SCRIPT_BATAK, 116 | brahmi = c.HB_SCRIPT_BRAHMI, 117 | mandaic = c.HB_SCRIPT_MANDAIC, 118 | chakma = c.HB_SCRIPT_CHAKMA, 119 | meroitic_cursive = c.HB_SCRIPT_MEROITIC_CURSIVE, 120 | meroitic_hieroglyphs = c.HB_SCRIPT_MEROITIC_HIEROGLYPHS, 121 | miao = c.HB_SCRIPT_MIAO, 122 | sharada = c.HB_SCRIPT_SHARADA, 123 | sora_sompeng = c.HB_SCRIPT_SORA_SOMPENG, 124 | takri = c.HB_SCRIPT_TAKRI, 125 | bassa_vah = c.HB_SCRIPT_BASSA_VAH, 126 | caucasian_albanian = c.HB_SCRIPT_CAUCASIAN_ALBANIAN, 127 | duployan = c.HB_SCRIPT_DUPLOYAN, 128 | elbasan = c.HB_SCRIPT_ELBASAN, 129 | grantha = c.HB_SCRIPT_GRANTHA, 130 | khojki = c.HB_SCRIPT_KHOJKI, 131 | khudawadi = c.HB_SCRIPT_KHUDAWADI, 132 | linear_a = c.HB_SCRIPT_LINEAR_A, 133 | mahajani = c.HB_SCRIPT_MAHAJANI, 134 | manichaean = c.HB_SCRIPT_MANICHAEAN, 135 | mende_kikakui = c.HB_SCRIPT_MENDE_KIKAKUI, 136 | modi = c.HB_SCRIPT_MODI, 137 | mro = c.HB_SCRIPT_MRO, 138 | nabataean = c.HB_SCRIPT_NABATAEAN, 139 | old_north_arabian = c.HB_SCRIPT_OLD_NORTH_ARABIAN, 140 | old_permic = c.HB_SCRIPT_OLD_PERMIC, 141 | pahawh_hmong = c.HB_SCRIPT_PAHAWH_HMONG, 142 | palmyrene = c.HB_SCRIPT_PALMYRENE, 143 | pau_cin_hau = c.HB_SCRIPT_PAU_CIN_HAU, 144 | psalter_pahlavi = c.HB_SCRIPT_PSALTER_PAHLAVI, 145 | siddham = c.HB_SCRIPT_SIDDHAM, 146 | tirhuta = c.HB_SCRIPT_TIRHUTA, 147 | warang_citi = c.HB_SCRIPT_WARANG_CITI, 148 | ahom = c.HB_SCRIPT_AHOM, 149 | anatolian_hieroglyphs = c.HB_SCRIPT_ANATOLIAN_HIEROGLYPHS, 150 | hatran = c.HB_SCRIPT_HATRAN, 151 | multani = c.HB_SCRIPT_MULTANI, 152 | old_hungarian = c.HB_SCRIPT_OLD_HUNGARIAN, 153 | signwriting = c.HB_SCRIPT_SIGNWRITING, 154 | adlam = c.HB_SCRIPT_ADLAM, 155 | bhaiksuki = c.HB_SCRIPT_BHAIKSUKI, 156 | marchen = c.HB_SCRIPT_MARCHEN, 157 | osage = c.HB_SCRIPT_OSAGE, 158 | tangut = c.HB_SCRIPT_TANGUT, 159 | newa = c.HB_SCRIPT_NEWA, 160 | masaram_gondi = c.HB_SCRIPT_MASARAM_GONDI, 161 | nushu = c.HB_SCRIPT_NUSHU, 162 | soyombo = c.HB_SCRIPT_SOYOMBO, 163 | zanabazar_square = c.HB_SCRIPT_ZANABAZAR_SQUARE, 164 | dogra = c.HB_SCRIPT_DOGRA, 165 | gunjala_gondi = c.HB_SCRIPT_GUNJALA_GONDI, 166 | hanifi_rohingya = c.HB_SCRIPT_HANIFI_ROHINGYA, 167 | makasar = c.HB_SCRIPT_MAKASAR, 168 | medefaidrin = c.HB_SCRIPT_MEDEFAIDRIN, 169 | old_sogdian = c.HB_SCRIPT_OLD_SOGDIAN, 170 | sogdian = c.HB_SCRIPT_SOGDIAN, 171 | elymaic = c.HB_SCRIPT_ELYMAIC, 172 | nandinagari = c.HB_SCRIPT_NANDINAGARI, 173 | nyiakeng_puachue_hmong = c.HB_SCRIPT_NYIAKENG_PUACHUE_HMONG, 174 | wancho = c.HB_SCRIPT_WANCHO, 175 | chorasmian = c.HB_SCRIPT_CHORASMIAN, 176 | dives_akuru = c.HB_SCRIPT_DIVES_AKURU, 177 | khitan_small_script = c.HB_SCRIPT_KHITAN_SMALL_SCRIPT, 178 | yezidi = c.HB_SCRIPT_YEZIDI, 179 | cypro_minoan = c.HB_SCRIPT_CYPRO_MINOAN, 180 | old_uyghur = c.HB_SCRIPT_OLD_UYGHUR, 181 | tangsa = c.HB_SCRIPT_TANGSA, 182 | toto = c.HB_SCRIPT_TOTO, 183 | vithkuqi = c.HB_SCRIPT_VITHKUQI, 184 | math = c.HB_SCRIPT_MATH, 185 | invalid = c.HB_SCRIPT_INVALID, 186 | }; 187 | 188 | /// Data type for languages. Each hb_language_t corresponds to a BCP 47 189 | /// language tag. 190 | pub const Language = struct { 191 | handle: c.hb_language_t, 192 | 193 | /// Converts str representing a BCP 47 language tag to the corresponding 194 | /// hb_language_t. 195 | pub fn fromString(str: []const u8) Language { 196 | return .{ 197 | .handle = c.hb_language_from_string(str.ptr, @intCast(str.len)), 198 | }; 199 | } 200 | 201 | /// Converts an hb_language_t to a string. 202 | pub fn toString(self: Language) [:0]const u8 { 203 | return std.mem.span(@as( 204 | [*:0]const u8, 205 | @ptrCast(c.hb_language_to_string(self.handle)), 206 | )); 207 | } 208 | 209 | /// Fetch the default language from current locale. 210 | pub fn getDefault() Language { 211 | return .{ .handle = c.hb_language_get_default() }; 212 | } 213 | }; 214 | 215 | /// The hb_feature_t is the structure that holds information about requested 216 | /// feature application. The feature will be applied with the given value to 217 | /// all glyphs which are in clusters between start (inclusive) and end 218 | /// (exclusive). Setting start to HB_FEATURE_GLOBAL_START and end to 219 | /// HB_FEATURE_GLOBAL_END specifies that the feature always applies to the 220 | /// entire buffer. 221 | pub const Feature = extern struct { 222 | tag: c.hb_tag_t, 223 | value: u32, 224 | start: c_uint, 225 | end: c_uint, 226 | 227 | pub fn fromString(str: []const u8) ?Feature { 228 | var f: c.hb_feature_t = undefined; 229 | return if (c.hb_feature_from_string( 230 | str.ptr, 231 | @intCast(str.len), 232 | &f, 233 | ) > 0) 234 | @bitCast(f) 235 | else 236 | null; 237 | } 238 | 239 | pub fn toString(self: *Feature, buf: []u8) void { 240 | c.hb_feature_to_string(self, buf.ptr, @intCast(buf.len)); 241 | } 242 | }; 243 | 244 | test "feature from string" { 245 | const testing = std.testing; 246 | try testing.expect(Feature.fromString("dlig") != null); 247 | } 248 | -------------------------------------------------------------------------------- /errors.zig: -------------------------------------------------------------------------------- 1 | pub const Error = error{ 2 | /// Not very descriptive but harfbuzz doesn't actually have error 3 | /// codes so the best we can do! 4 | HarfbuzzFailed, 5 | }; 6 | -------------------------------------------------------------------------------- /face.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const c = @import("c.zig").c; 3 | 4 | /// A font face is an object that represents a single face from within a font family. 5 | /// 6 | /// More precisely, a font face represents a single face in a binary font file. 7 | /// Font faces are typically built from a binary blob and a face index. 8 | /// Font faces are used to create fonts. 9 | pub const Face = struct { 10 | handle: *c.hb_face_t, 11 | 12 | /// Decreases the reference count on a face object. When the reference 13 | /// count reaches zero, the face is destroyed, freeing all memory. 14 | pub fn destroy(self: *Face) void { 15 | c.hb_face_destroy(self.handle); 16 | } 17 | }; 18 | -------------------------------------------------------------------------------- /font.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const c = @import("c.zig").c; 3 | const Face = @import("face.zig").Face; 4 | const Error = @import("errors.zig").Error; 5 | 6 | pub const Font = struct { 7 | handle: *c.hb_font_t, 8 | 9 | /// Constructs a new font object from the specified face. 10 | pub fn create(face: Face) Error!Font { 11 | const handle = c.hb_font_create(face.handle) orelse return Error.HarfbuzzFailed; 12 | return Font{ .handle = handle }; 13 | } 14 | 15 | /// Decreases the reference count on the given font object. When the 16 | /// reference count reaches zero, the font is destroyed, freeing all memory. 17 | pub fn destroy(self: *Font) void { 18 | c.hb_font_destroy(self.handle); 19 | } 20 | 21 | pub fn setScale(self: *Font, x: u32, y: u32) void { 22 | c.hb_font_set_scale( 23 | self.handle, 24 | @intCast(x), 25 | @intCast(y), 26 | ); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /freetype.zig: -------------------------------------------------------------------------------- 1 | const freetype = @import("freetype"); 2 | const std = @import("std"); 3 | const c = @import("c.zig").c; 4 | const Face = @import("face.zig").Face; 5 | const Font = @import("font.zig").Font; 6 | const Error = @import("errors.zig").Error; 7 | 8 | // Use custom extern functions so that the proper freetype structs are used 9 | // without a ptrcast. These are only needed when interacting with freetype 10 | // C structs. 11 | extern fn hb_ft_face_create_referenced(ft_face: freetype.c.FT_Face) ?*c.hb_face_t; 12 | extern fn hb_ft_font_create_referenced(ft_face: freetype.c.FT_Face) ?*c.hb_font_t; 13 | extern fn hb_ft_font_get_face(font: ?*c.hb_font_t) freetype.c.FT_Face; 14 | 15 | /// Creates an hb_face_t face object from the specified FT_Face. 16 | /// 17 | /// This is the preferred variant of the hb_ft_face_create* function 18 | /// family, because it calls FT_Reference_Face() on ft_face , ensuring 19 | /// that ft_face remains alive as long as the resulting hb_face_t face 20 | /// object remains alive. Also calls FT_Done_Face() when the hb_face_t 21 | /// face object is destroyed. 22 | /// 23 | /// Use this version unless you know you have good reasons not to. 24 | pub fn createFace(face: freetype.c.FT_Face) Error!Face { 25 | const handle = hb_ft_face_create_referenced(face) orelse return Error.HarfbuzzFailed; 26 | return Face{ .handle = handle }; 27 | } 28 | 29 | /// Creates an hb_font_t font object from the specified FT_Face. 30 | pub fn createFont(face: freetype.c.FT_Face) Error!Font { 31 | const handle = hb_ft_font_create_referenced(face) orelse return Error.HarfbuzzFailed; 32 | return Font{ .handle = handle }; 33 | } 34 | 35 | /// Configures the font-functions structure of the specified hb_font_t font 36 | /// object to use FreeType font functions. 37 | /// 38 | /// In particular, you can use this function to configure an existing 39 | /// hb_face_t face object for use with FreeType font functions even if that 40 | /// hb_face_t face object was initially created with hb_face_create(), and 41 | /// therefore was not initially configured to use FreeType font functions. 42 | /// 43 | /// An hb_face_t face object created with hb_ft_face_create() is preconfigured 44 | /// for FreeType font functions and does not require this function to be used. 45 | pub fn setFontFuncs(font: Font) void { 46 | c.hb_ft_font_set_funcs(font.handle); 47 | } 48 | 49 | test { 50 | if (!@hasDecl(c, "hb_ft_font_create_referenced")) return error.SkipZigTest; 51 | 52 | const testing = std.testing; 53 | const testFont = freetype.testing.font_regular; 54 | const ftc = freetype.c; 55 | const ftok = ftc.FT_Err_Ok; 56 | 57 | var ft_lib: ftc.FT_Library = undefined; 58 | if (ftc.FT_Init_FreeType(&ft_lib) != ftok) 59 | return error.FreeTypeInitFailed; 60 | defer _ = ftc.FT_Done_FreeType(ft_lib); 61 | 62 | var ft_face: ftc.FT_Face = undefined; 63 | try testing.expect(ftc.FT_New_Memory_Face( 64 | ft_lib, 65 | testFont, 66 | @intCast(testFont.len), 67 | 0, 68 | &ft_face, 69 | ) == ftok); 70 | defer _ = ftc.FT_Done_Face(ft_face); 71 | 72 | var face = try createFace(ft_face); 73 | defer face.destroy(); 74 | 75 | var font = try createFont(ft_face); 76 | defer font.destroy(); 77 | setFontFuncs(font); 78 | } 79 | -------------------------------------------------------------------------------- /main.zig: -------------------------------------------------------------------------------- 1 | const blob = @import("blob.zig"); 2 | const buffer = @import("buffer.zig"); 3 | const common = @import("common.zig"); 4 | const errors = @import("errors.zig"); 5 | const face = @import("face.zig"); 6 | const font = @import("font.zig"); 7 | const shapepkg = @import("shape.zig"); 8 | const versionpkg = @import("version.zig"); 9 | 10 | pub const c = @import("c.zig").c; 11 | pub const freetype = @import("freetype.zig"); 12 | pub const MemoryMode = blob.MemoryMode; 13 | pub const Blob = blob.Blob; 14 | pub const Buffer = buffer.Buffer; 15 | pub const Direction = common.Direction; 16 | pub const Script = common.Script; 17 | pub const Language = common.Language; 18 | pub const Feature = common.Feature; 19 | pub const Face = face.Face; 20 | pub const Font = font.Font; 21 | pub const shape = shapepkg.shape; 22 | pub const Version = versionpkg.Version; 23 | pub const version = versionpkg.version; 24 | pub const versionAtLeast = versionpkg.versionAtLeast; 25 | pub const versionString = versionpkg.versionString; 26 | 27 | test { 28 | @import("std").testing.refAllDecls(@This()); 29 | } 30 | -------------------------------------------------------------------------------- /shape.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const c = @import("c.zig").c; 3 | const Font = @import("font.zig").Font; 4 | const Buffer = @import("buffer.zig").Buffer; 5 | const Feature = @import("common.zig").Feature; 6 | 7 | /// Shapes buffer using font turning its Unicode characters content to 8 | /// positioned glyphs. If features is not NULL, it will be used to control 9 | /// the features applied during shaping. If two features have the same tag 10 | /// but overlapping ranges the value of the feature with the higher index 11 | /// takes precedence. 12 | pub fn shape(font: Font, buf: Buffer, features: ?[]const Feature) void { 13 | const hb_feats: [*c]const c.hb_feature_t = feats: { 14 | if (features) |fs| { 15 | if (fs.len > 0) break :feats @ptrCast(fs.ptr); 16 | } 17 | 18 | break :feats null; 19 | }; 20 | 21 | c.hb_shape( 22 | font.handle, 23 | buf.handle, 24 | hb_feats, 25 | if (features) |f| @intCast(f.len) else 0, 26 | ); 27 | } 28 | -------------------------------------------------------------------------------- /version.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const c = @import("c.zig").c; 3 | 4 | pub const Version = struct { 5 | major: u32, 6 | minor: u32, 7 | micro: u32, 8 | }; 9 | 10 | /// Returns library version as three integer components. 11 | pub fn version() Version { 12 | var major: c_uint = 0; 13 | var minor: c_uint = 0; 14 | var micro: c_uint = 0; 15 | c.hb_version(&major, &minor, µ); 16 | return .{ .major = major, .minor = minor, .micro = micro }; 17 | } 18 | 19 | /// Tests the library version against a minimum value, as three integer components. 20 | pub fn versionAtLeast(vsn: Version) bool { 21 | return c.hb_version_atleast( 22 | vsn.major, 23 | vsn.minor, 24 | vsn.micro, 25 | ) > 0; 26 | } 27 | 28 | /// Returns library version as a string with three components. 29 | pub fn versionString() [:0]const u8 { 30 | const res = c.hb_version_string(); 31 | return std.mem.sliceTo(res, 0); 32 | } 33 | 34 | test { 35 | const testing = std.testing; 36 | 37 | // Should be able to get the version 38 | const vsn = version(); 39 | try testing.expect(vsn.major > 0); 40 | 41 | // Should be at least version 1 42 | try testing.expect(versionAtLeast(.{ 43 | .major = 1, 44 | .minor = 0, 45 | .micro = 0, 46 | })); 47 | } 48 | --------------------------------------------------------------------------------