1package core
2
3import "unicode"
4
5// RuneWidth returns the terminal cell width of a rune.
6//
7// Returns 0 for combining marks, zero-width characters, and control codes.
8// Returns 2 for CJK, fullwidth, and wide emoji characters.
9// Returns 1 for all other printable characters.
10//
11// Wide runes (width 2) occupy two grid cells: a left cell holding the rune and
12// a right continuation cell. Combining marks (Mn/Me) and zero-width joiner
13// sequences are out of scope: combining marks report width 0 (current
14// behavior), ZWJ sequences are not coalesced (each rune is measured on its own).
15func RuneWidth(r rune) int {
16 switch {
17 case r < 0x20 || r == 0x7F || (r >= 0x80 && r <= 0x9F):
18 return 0
19 // Soft hyphen, combining grapheme joiner, Arabic sign, etc.
20 case r == 0x00AD || r == 0x034F || r == 0x061C:
21 return 0
22 // Hangul Jungseong O/E, Hangul Jungseong Yu
23 case r == 0x115F || r == 0x1160:
24 return 0
25 // Balinese musical symbols
26 case r == 0x17B4 || r == 0x17B5:
27 return 0
28 // Mongolian variation selectors, free variation selector
29 case r == 0x180B || r == 0x180E || r == 0x200B || r == 0x200C || r == 0x200D || r == 0x200E || r == 0x200F:
30 return 0
31 // Line/paragraph separator, bidirectional overrides
32 case r >= 0x2028 && r <= 0x202E:
33 return 0
34 // Word joiner, invisible operators, Arabic/Syriac number marks, etc.
35 case r >= 0x2060 && r <= 0x206F:
36 return 0
37 case r == 0xFEFF || r == 0xFFF9 || r == 0xFFFA || r == 0xFFFB:
38 return 0
39 // Tags
40 case r >= 0xE0001 && r <= 0xE007F:
41 return 0
42 // Combining diacritical marks (Unicode categories Mn, Me)
43 case unicode.Is(unicode.Mn, r) || unicode.Is(unicode.Me, r):
44 return 0
45 // --- Wide characters (width 2) ---
46 // Hangul Jamo
47 case r >= 0x1100 && r <= 0x115F:
48 return 2
49 // Left/right-pointing angle bracket (for compatibility)
50 case r == 0x2329 || r == 0x232A:
51 return 2
52 // CJK Radicals Supplement, Kangxi Radicals, Ideographic Description
53 // CJK Symbols, Hiragana, Katakana, Bopomofo, Hangul Compatibility Jamo
54 // Kanbun, CJK Strokes, Enclosed CJK Letters
55 case r >= 0x2E80 && r <= 0x303E:
56 return 2
57 // Hiragana, Katakana, Bopomofo, Hangul Compatibility Jamo, Kanbun
58 case r >= 0x3040 && r <= 0x33FF:
59 return 2
60 // CJK Unified Ideographs Extension A
61 case r >= 0x3400 && r <= 0x4DBF:
62 return 2
63 // CJK Unified Ideographs, Yi Syllables, Yi Radicals
64 case r >= 0x4E00 && r <= 0xA4CF:
65 return 2
66 // Hangul Jamo Extended-A
67 case r >= 0xA960 && r <= 0xA97C:
68 return 2
69 // Hangul Syllables
70 case r >= 0xAC00 && r <= 0xD7AF:
71 return 2
72 // Hangul Jamo Extended-B
73 case r >= 0xD7B0 && r <= 0xD7FF:
74 return 2
75 // CJK Compatibility Ideographs
76 case r >= 0xF900 && r <= 0xFAFF:
77 return 2
78 // Vertical Forms
79 case r >= 0xFE10 && r <= 0xFE19:
80 return 2
81 // CJK Compatibility Forms
82 case r >= 0xFE30 && r <= 0xFE6F:
83 return 2
84 // Fullwidth Forms (variation selectors, fullwidth punctuation, etc.)
85 case r >= 0xFF01 && r <= 0xFF60:
86 return 2
87 // Fullwidth Signs (cent, pound, yen, won, etc.)
88 case r >= 0xFFE0 && r <= 0xFFE6:
89 return 2
90 // Kana Supplement
91 case r >= 0x1B000 && r <= 0x1B0FF:
92 return 2
93 // Kana Extended-A
94 case r >= 0x1B100 && r <= 0x1B12F:
95 return 2
96 // Mahjong Tiles, Domino Tiles, Playing Cards
97 case r >= 0x1F000 && r <= 0x1F09F:
98 return 2
99 // Miscellaneous Symbols and Pictographs, Emoticons, Ornamental Dingbats
100 case r >= 0x1F0A0 && r <= 0x1F64F:
101 return 2
102 // Transport and Map Symbols
103 case r >= 0x1F680 && r <= 0x1F6FF:
104 return 2
105 // Supplemental Symbols and Pictographs
106 case r >= 0x1F900 && r <= 0x1F9FF:
107 return 2
108 // Symbols and Pictographs Extended-A (emoji added in recent Unicode)
109 case r >= 0x1FA70 && r <= 0x1FAFF:
110 return 2
111 // CJK Unified Ideographs Extension B and beyond
112 case r >= 0x20000 && r <= 0x2FFFF:
113 return 2
114 // CJK Unified Ideographs Extension G and beyond
115 case r >= 0x30000 && r <= 0x3FFFF:
116 return 2
117 default:
118 return 1
119 }
120}