a modern tui library written in zig
1const std = @import("std");
2const unicode = std.unicode;
3const testing = std.testing;
4const ziglyph = @import("ziglyph");
5
6/// the method to use when calculating the width of a grapheme
7pub const Method = enum {
8 unicode,
9 wcwidth,
10};
11
12/// returns the width of the provided string, as measured by the method chosen
13pub fn gwidth(str: []const u8, method: Method) !usize {
14 switch (method) {
15 .unicode => {
16 return try ziglyph.display_width.strWidth(str, .half);
17 },
18 .wcwidth => {
19 var total: usize = 0;
20 const utf8 = try unicode.Utf8View.init(str);
21 var iter = utf8.iterator();
22
23 while (iter.nextCodepoint()) |cp| {
24 const w = ziglyph.display_width.codePointWidth(cp, .half);
25 if (w < 0) continue;
26 total += @intCast(w);
27 }
28 return total;
29 },
30 }
31}
32
33test "gwidth: a" {
34 try testing.expectEqual(1, try gwidth("a", .unicode));
35 try testing.expectEqual(1, try gwidth("a", .wcwidth));
36}
37
38test "gwidth: emoji with ZWJ" {
39 try testing.expectEqual(2, try gwidth("👩🚀", .unicode));
40 try testing.expectEqual(4, try gwidth("👩🚀", .wcwidth));
41}
42
43test "gwidth: emoji with VS16 selector" {
44 try testing.expectEqual(2, try gwidth("\xE2\x9D\xA4\xEF\xB8\x8F", .unicode));
45 try testing.expectEqual(1, try gwidth("\xE2\x9D\xA4\xEF\xB8\x8F", .wcwidth));
46}
47
48test "gwidth: emoji with skin tone selector" {
49 try testing.expectEqual(2, try gwidth("👋🏿", .unicode));
50 try testing.expectEqual(4, try gwidth("👋🏿", .wcwidth));
51}
52
53test "gwidth: invalid string" {
54 try testing.expectError(error.InvalidUtf8, gwidth("\xc3\x28", .unicode));
55 try testing.expectError(error.InvalidUtf8, gwidth("\xc3\x28", .wcwidth));
56}