Repository navigation
Expand file tree
/
Copy pathwcwidth.zig
More file actions
157 lines (142 loc) · 4.68 KB
/
Copy pathwcwidth.zig
File metadata and controls
157 lines (142 loc) · 4.68 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
const std = @import("std");
const testing = std.testing;
const zero_width_cf = @import("manual.zig").zero_width_cf;
const wide_eastasian = @import("table_wide.zig").wide_eastasian;
const zero_width = @import("table_zero.zig").zero_width;
/// A simple binary search in a list containing lower and upper bounds
fn tableBisearch(ucs: u21, table: []const [2]u21) bool {
var lbound: usize = 0;
var ubound: usize = table.len - 1;
// Out of entire table bounds
if (ucs < table[lbound][0] or ucs > table[ubound][1]) {
return false;
}
// Search table
while (ubound >= lbound) {
const mid = (lbound + ubound) / 2;
if (ucs > table[mid][1]) {
lbound = mid + 1;
} else if (ucs < table[mid][0]) {
ubound = mid - 1;
} else {
return true;
}
}
return false;
}
/// A simple binary search for a simple ordered list
fn listBisearch(ucs: u21, list: []const u21) bool {
var lbound: usize = 0;
var ubound: usize = list.len - 1;
// Out of entire table bounds
if (ucs < list[lbound] or ucs > list[ubound]) {
return false;
}
// Search table
while (ubound >= lbound) {
const mid = (lbound + ubound) / 2;
if (ucs > list[mid]) {
lbound = mid + 1;
} else if (ucs < list[mid]) {
ubound = mid - 1;
} else {
return true;
}
}
return false;
}
/// Given one unicode character, return its printable length on a terminal.
///
/// The wcwidth() function returns 0 if the wc argument has no printable effect
/// on a terminal (such as NUL '\0'), -1 if wc is not printable, or has an
/// indeterminate effect on the terminal, such as a control character.
/// Otherwise, the number of column positions the character occupies on a
/// graphic terminal (1 or 2) is returned.
///
/// The following have a column width of -1:
///
/// - C0 control characters (U+001 through U+01F).
///
/// - C1 control characters and DEL (U+07F through U+0A0).
///
/// The following have a column width of 0:
///
/// - Non-spacing and enclosing combining characters (general
/// category code Mn or Me in the Unicode database).
///
/// - NULL (U+0000, 0).
///
/// - COMBINING GRAPHEME JOINER (U+034F).
///
/// - ZERO WIDTH SPACE (U+200B) through
/// RIGHT-TO-LEFT MARK (U+200F).
///
/// - LINE SEPERATOR (U+2028) and
/// PARAGRAPH SEPERATOR (U+2029).
///
/// - LEFT-TO-RIGHT EMBEDDING (U+202A) through
/// RIGHT-TO-LEFT OVERRIDE (U+202E).
///
/// - WORD JOINER (U+2060) through
/// INVISIBLE SEPARATOR (U+2063).
///
/// The following have a column width of 1:
///
/// - SOFT HYPHEN (U+00AD) has a column width of 1.
///
/// - All remaining characters (including all printable
/// ISO 8859-1 and WGL4 characters, Unicode control characters,
/// etc.) have a column width of 1.
///
/// The following have a column width of 2:
///
/// - Spacing characters in the East Asian Wide (W) or East Asian
/// Full-width (F) category as defined in Unicode Technical
/// Report #11 have a column width of 2.
pub fn wcwidth(wc: u21) isize {
// Manual list
if (listBisearch(wc, &zero_width_cf)) {
return 0;
}
// C0/C1 control characters
if (wc < 32 or 0x07F <= wc and wc < 0x0A0) {
return -1;
}
// combining characters with zero width
if (tableBisearch(wc, &zero_width)) {
return 0;
}
// double width
if (tableBisearch(wc, &wide_eastasian)) {
return 2;
} else {
return 1;
}
}
test "terminal widths cover controls combining marks CJK and emoji" {
const cases = .{
.{ @as(u21, 0), @as(isize, 0) },
.{ @as(u21, '\n'), @as(isize, -1) },
.{ @as(u21, 0x7f), @as(isize, -1) },
.{ @as(u21, 'A'), @as(isize, 1) },
.{ @as(u21, 0x301), @as(isize, 0) },
.{ @as(u21, 0x200d), @as(isize, 0) },
.{ @as(u21, '中'), @as(isize, 2) },
.{ @as(u21, 0x1f600), @as(isize, 2) },
};
inline for (cases) |case| try testing.expectEqual(case[1], wcwidth(case[0]));
}
test "width tables have ordered nonoverlapping valid ranges" {
inline for (.{ &zero_width, &wide_eastasian }) |table| {
for (table, 0..) |range, i| {
try testing.expect(range[0] <= range[1]);
if (i > 0) try testing.expect(table[i - 1][1] < range[0]);
try testing.expect(tableBisearch(range[0], table));
try testing.expect(tableBisearch(range[1], table));
}
}
for (zero_width_cf, 0..) |cp, i| {
if (i > 0) try testing.expect(zero_width_cf[i - 1] < cp);
try testing.expect(listBisearch(cp, &zero_width_cf));
}
}