summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/DisplayWidth.zig55
1 files changed, 32 insertions, 23 deletions
diff --git a/src/DisplayWidth.zig b/src/DisplayWidth.zig
index d15dbae..dee7ebd 100644
--- a/src/DisplayWidth.zig
+++ b/src/DisplayWidth.zig
@@ -100,6 +100,34 @@ test "codePointWidth" {
100 try testing.expectEqual(@as(i4, 2), dw.codePointWidth('统')); 100 try testing.expectEqual(@as(i4, 2), dw.codePointWidth('统'));
101} 101}
102 102
103/// graphemeClusterWidth returns the total display width of `gc` as the number
104/// of cells required in a fixed-pitch font (i.e. a terminal screen).
105/// `gc` is a slice corresponding to one grapheme cluster.
106pub fn graphemeClusterWidth(dw: DisplayWidth, gc: []const u8) isize {
107 var cp_iter = CodePointIterator{ .bytes = gc };
108 var gc_total: isize = 0;
109
110 while (cp_iter.next()) |cp| {
111 var w = dw.codePointWidth(cp.code);
112
113 if (w != 0) {
114 // Handle text emoji sequence.
115 if (cp_iter.next()) |ncp| {
116 // emoji text sequence.
117 if (ncp.code == 0xFE0E) w = 1;
118 if (ncp.code == 0xFE0F) w = 2;
119 // Skin tones
120 if (0x1F3FB <= ncp.code and ncp.code <= 0x1F3FF) w = 2;
121 }
122
123 // Only adding width of first non-zero-width code point.
124 gc_total = w;
125 break;
126 }
127 }
128 return gc_total;
129}
130
103/// strWidth returns the total display width of `str` as the number of cells 131/// strWidth returns the total display width of `str` as the number of cells
104/// required in a fixed-pitch font (i.e. a terminal screen). 132/// required in a fixed-pitch font (i.e. a terminal screen).
105pub fn strWidth(dw: DisplayWidth, str: []const u8) usize { 133pub fn strWidth(dw: DisplayWidth, str: []const u8) usize {
@@ -114,29 +142,7 @@ pub fn strWidth(dw: DisplayWidth, str: []const u8) usize {
114 var giter = dw.graphemes.iterator(str); 142 var giter = dw.graphemes.iterator(str);
115 143
116 while (giter.next()) |gc| { 144 while (giter.next()) |gc| {
117 var cp_iter = CodePointIterator{ .bytes = gc.bytes(str) }; 145 total += dw.graphemeClusterWidth(gc.bytes(str));
118 var gc_total: isize = 0;
119
120 while (cp_iter.next()) |cp| {
121 var w = dw.codePointWidth(cp.code);
122
123 if (w != 0) {
124 // Handle text emoji sequence.
125 if (cp_iter.next()) |ncp| {
126 // emoji text sequence.
127 if (ncp.code == 0xFE0E) w = 1;
128 if (ncp.code == 0xFE0F) w = 2;
129 }
130
131 // Only adding width of first non-zero-width code point.
132 if (gc_total == 0) {
133 gc_total = w;
134 break;
135 }
136 }
137 }
138
139 total += gc_total;
140 } 146 }
141 147
142 return @intCast(@max(0, total)); 148 return @intCast(@max(0, total));
@@ -201,6 +207,9 @@ test "strWidth" {
201 try testing.expectEqual(@as(usize, 9), dw.strWidth("Ẓ̌á̲l͔̝̞̄̑͌g̖̘̘̔̔͢͞͝o̪̔T̢̙̫̈̍͞e̬͈͕͌̏͑x̺̍ṭ̓̓ͅ")); 207 try testing.expectEqual(@as(usize, 9), dw.strWidth("Ẓ̌á̲l͔̝̞̄̑͌g̖̘̘̔̔͢͞͝o̪̔T̢̙̫̈̍͞e̬͈͕͌̏͑x̺̍ṭ̓̓ͅ"));
202 try testing.expectEqual(@as(usize, 17), dw.strWidth("슬라바 우크라이나")); 208 try testing.expectEqual(@as(usize, 17), dw.strWidth("슬라바 우크라이나"));
203 try testing.expectEqual(@as(usize, 1), dw.strWidth("\u{378}")); 209 try testing.expectEqual(@as(usize, 1), dw.strWidth("\u{378}"));
210
211 // https://codeberg.org/atman/zg/issues/82
212 try testing.expectEqual(@as(usize, 12), dw.strWidth("✍️✍🏻✍🏼✍🏽✍🏾✍🏿"));
204} 213}
205 214
206/// centers `str` in a new string of width `total_width` (in display cells) using `pad` as padding. 215/// centers `str` in a new string of width `total_width` (in display cells) using `pad` as padding.