a modern tui library written in zig
1const std = @import("std");
2const unicode = std.unicode;
3const testing = std.testing;
4const DisplayWidth = @import("DisplayWidth");
5const code_point = @import("code_point");
6
7/// the method to use when calculating the width of a grapheme
8pub const Method = enum {
9 unicode,
10 wcwidth,
11 no_zwj,
12};
13
14/// returns the width of the provided string, as measured by the method chosen
15pub fn gwidth(str: []const u8, method: Method, data: *const DisplayWidth.DisplayWidthData) !usize {
16 switch (method) {
17 .unicode => {
18 const dw: DisplayWidth = .{ .data = data };
19 return dw.strWidth(str);
20 },
21 .wcwidth => {
22 var total: usize = 0;
23 var iter: code_point.Iterator = .{ .bytes = str };
24 while (iter.next()) |cp| {
25 const w = switch (cp.code) {
26 // undo an override in zg for emoji skintone selectors
27 0x1f3fb...0x1f3ff,
28 => 2,
29 else => data.codePointWidth(cp.code),
30 };
31 if (w < 0) continue;
32 total += @intCast(w);
33 }
34 return total;
35 },
36 .no_zwj => {
37 var out: [256]u8 = undefined;
38 if (str.len > out.len) return error.OutOfMemory;
39 const n = std.mem.replacementSize(u8, str, "\u{200D}", "");
40 _ = std.mem.replace(u8, str, "\u{200D}", "", &out);
41 return gwidth(out[0..n], .unicode, data);
42 },
43 }
44}
45
46test "gwidth: a" {
47 const alloc = testing.allocator_instance.allocator();
48 const data = try DisplayWidth.DisplayWidthData.init(alloc);
49 defer data.deinit();
50 try testing.expectEqual(1, try gwidth("a", .unicode, &data));
51 try testing.expectEqual(1, try gwidth("a", .wcwidth, &data));
52 try testing.expectEqual(1, try gwidth("a", .no_zwj, &data));
53}
54
55test "gwidth: emoji with ZWJ" {
56 const alloc = testing.allocator_instance.allocator();
57 const data = try DisplayWidth.DisplayWidthData.init(alloc);
58 defer data.deinit();
59 try testing.expectEqual(2, try gwidth("👩🚀", .unicode, &data));
60 try testing.expectEqual(4, try gwidth("👩🚀", .wcwidth, &data));
61 try testing.expectEqual(4, try gwidth("👩🚀", .no_zwj, &data));
62}
63
64test "gwidth: emoji with VS16 selector" {
65 const alloc = testing.allocator_instance.allocator();
66 const data = try DisplayWidth.DisplayWidthData.init(alloc);
67 defer data.deinit();
68 try testing.expectEqual(2, try gwidth("\xE2\x9D\xA4\xEF\xB8\x8F", .unicode, &data));
69 try testing.expectEqual(1, try gwidth("\xE2\x9D\xA4\xEF\xB8\x8F", .wcwidth, &data));
70 try testing.expectEqual(2, try gwidth("\xE2\x9D\xA4\xEF\xB8\x8F", .no_zwj, &data));
71}
72
73test "gwidth: emoji with skin tone selector" {
74 const alloc = testing.allocator_instance.allocator();
75 const data = try DisplayWidth.DisplayWidthData.init(alloc);
76 defer data.deinit();
77 try testing.expectEqual(2, try gwidth("👋🏿", .unicode, &data));
78 try testing.expectEqual(4, try gwidth("👋🏿", .wcwidth, &data));
79 try testing.expectEqual(2, try gwidth("👋🏿", .no_zwj, &data));
80}