2024-01-24 04:06:02 +01:00
|
|
|
const std = @import("std");
|
|
|
|
const unicode = std.unicode;
|
|
|
|
const testing = std.testing;
|
|
|
|
const ziglyph = @import("ziglyph");
|
|
|
|
|
|
|
|
/// the method to use when calculating the width of a grapheme
|
|
|
|
pub const Method = enum {
|
|
|
|
unicode,
|
|
|
|
wcwidth,
|
2024-02-18 19:26:23 +01:00
|
|
|
no_zwj,
|
2024-01-24 04:06:02 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
/// returns the width of the provided string, as measured by the method chosen
|
|
|
|
pub fn gwidth(str: []const u8, method: Method) !usize {
|
|
|
|
switch (method) {
|
|
|
|
.unicode => {
|
|
|
|
return try ziglyph.display_width.strWidth(str, .half);
|
|
|
|
},
|
|
|
|
.wcwidth => {
|
|
|
|
var total: usize = 0;
|
|
|
|
const utf8 = try unicode.Utf8View.init(str);
|
|
|
|
var iter = utf8.iterator();
|
|
|
|
|
|
|
|
while (iter.nextCodepoint()) |cp| {
|
|
|
|
const w = ziglyph.display_width.codePointWidth(cp, .half);
|
|
|
|
if (w < 0) continue;
|
|
|
|
total += @intCast(w);
|
|
|
|
}
|
|
|
|
return total;
|
|
|
|
},
|
2024-02-18 19:26:23 +01:00
|
|
|
.no_zwj => {
|
|
|
|
var out: [256]u8 = undefined;
|
2024-02-19 04:26:08 +01:00
|
|
|
if (str.len > out.len) return error.OutOfMemory;
|
2024-02-18 19:26:23 +01:00
|
|
|
const n = std.mem.replace(u8, str, "\u{200D}", "", &out);
|
|
|
|
return gwidth(out[0..n], .unicode);
|
|
|
|
},
|
2024-01-24 04:06:02 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
test "gwidth: a" {
|
|
|
|
try testing.expectEqual(1, try gwidth("a", .unicode));
|
|
|
|
try testing.expectEqual(1, try gwidth("a", .wcwidth));
|
2024-02-18 19:26:23 +01:00
|
|
|
try testing.expectEqual(1, try gwidth("a", .no_zwj));
|
2024-01-24 04:06:02 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
test "gwidth: emoji with ZWJ" {
|
|
|
|
try testing.expectEqual(2, try gwidth("👩🚀", .unicode));
|
|
|
|
try testing.expectEqual(4, try gwidth("👩🚀", .wcwidth));
|
2024-02-18 19:26:23 +01:00
|
|
|
try testing.expectEqual(4, try gwidth("👩🚀", .no_zwj));
|
2024-01-24 04:06:02 +01:00
|
|
|
}
|
|
|
|
|
2024-01-29 04:35:31 +01:00
|
|
|
test "gwidth: emoji with VS16 selector" {
|
|
|
|
try testing.expectEqual(2, try gwidth("\xE2\x9D\xA4\xEF\xB8\x8F", .unicode));
|
|
|
|
try testing.expectEqual(1, try gwidth("\xE2\x9D\xA4\xEF\xB8\x8F", .wcwidth));
|
2024-02-18 19:26:23 +01:00
|
|
|
try testing.expectEqual(2, try gwidth("\xE2\x9D\xA4\xEF\xB8\x8F", .no_zwj));
|
2024-01-29 04:35:31 +01:00
|
|
|
}
|
|
|
|
|
2024-01-24 04:06:02 +01:00
|
|
|
test "gwidth: emoji with skin tone selector" {
|
|
|
|
try testing.expectEqual(2, try gwidth("👋🏿", .unicode));
|
|
|
|
try testing.expectEqual(4, try gwidth("👋🏿", .wcwidth));
|
2024-02-18 19:26:23 +01:00
|
|
|
try testing.expectEqual(2, try gwidth("👋🏿", .no_zwj));
|
2024-01-24 04:06:02 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
test "gwidth: invalid string" {
|
|
|
|
try testing.expectError(error.InvalidUtf8, gwidth("\xc3\x28", .unicode));
|
|
|
|
try testing.expectError(error.InvalidUtf8, gwidth("\xc3\x28", .wcwidth));
|
2024-02-18 19:26:23 +01:00
|
|
|
try testing.expectError(error.InvalidUtf8, gwidth("\xc3\x28", .no_zwj));
|
2024-01-24 04:06:02 +01:00
|
|
|
}
|