1use unicode_width::UnicodeWidthChar;
2
3const ZWJ: u32 = 0x200d;
4
5pub fn width(string: &str) -> usize {
7 let mut cw = 0;
8 let mut iter = string.chars();
9 while let Some(c) = iter.next() {
10 if u32::from(c) == ZWJ {
11 iter.next();
12 continue;
13 }
14 cw += c.width().unwrap_or(0);
15 }
16 cw
17}
18
19pub fn truncate(string: &str, width: usize) -> &str {
21 let mut cw = 0;
22 let mut iter = string.char_indices();
23 while let Some((i, c)) = iter.next() {
24 if u32::from(c) == ZWJ {
25 iter.next();
26 continue;
27 }
28 cw += c.width().unwrap_or(0);
29 if cw > width {
30 return &string[..i];
31 }
32 }
33 string
34}
35
36#[cfg(test)]
37mod tests {
38 use super::*;
39
40 #[test]
41 fn test_width() {
42 assert_eq!(width("teststring"), 10);
44 assert_eq!(width("잘라야"), 6);
46 assert_eq!(width("ę̵̡̛̮̹̼̝̲͓̳̣͉̞͔̳̥̝͍̩̣̹͙̘̼̥̗̼͈̯͎̮̥̤̪̻̮͕̩̮͓͔̟͈͇͎̣͉͇̦͔̝̣͎͎͔͇̭͈̌̂̈̄̈́̾͑̀̈̓̂͗̾̉͊͒̆̽͊̽͘̕͜͜͝͠ :width"), 8);
48 assert_eq!(width("👨👩👦:width"), 8);
50 }
51
52 #[test]
53 fn test_truncation() {
54 assert_eq!(truncate("teststring", 50), "teststring");
56 assert_eq!(truncate("teststring", 5), "tests");
57 assert_eq!(truncate("teststring", 0), "");
58 assert_eq!(truncate("잘라야", 4), "잘라");
60 assert_eq!(truncate("ę̵̡̛̮̹̼̝̲͓̳̣͉̞͔̳̥̝͍̩̣̹͙̘̼̥̗̼͈̯͎̮̥̤̪̻̮͕̩̮͓͔̟͈͇͎̣͉͇̦͔̝̣͎͎͔͇̭͈̌̂̈̄̈́̾͑̀̈̓̂͗̾̉͊͒̆̽͊̽͘̕͜͜͝͠ :trunc", 3), "ę̵̡̛̮̹̼̝̲͓̳̣͉̞͔̳̥̝͍̩̣̹͙̘̼̥̗̼͈̯͎̮̥̤̪̻̮͕̩̮͓͔̟͈͇͎̣͉͇̦͔̝̣͎͎͔͇̭͈̌̂̈̄̈́̾͑̀̈̓̂͗̾̉͊͒̆̽͊̽͘̕͜͜͝͠ :");
62 assert_eq!(truncate("👨👩👦:trunc", 3), "👨👩👦:");
64 }
65}