blob: 8d64da0778409a98b9251c5e8d7541d013067e1c [file] [log] [blame]
Anand S Katti09541352020-01-29 15:54:01 +05301package runewidth
2
3import (
4 "os"
5)
6
7//go:generate go run script/generate.go
8
9var (
10 // EastAsianWidth will be set true if the current locale is CJK
11 EastAsianWidth bool
12
13 // ZeroWidthJoiner is flag to set to use UTR#51 ZWJ
14 ZeroWidthJoiner bool
15
16 // DefaultCondition is a condition in current locale
17 DefaultCondition = &Condition{}
18)
19
20func init() {
21 handleEnv()
22}
23
24func handleEnv() {
25 env := os.Getenv("RUNEWIDTH_EASTASIAN")
26 if env == "" {
27 EastAsianWidth = IsEastAsian()
28 } else {
29 EastAsianWidth = env == "1"
30 }
31 // update DefaultCondition
32 DefaultCondition.EastAsianWidth = EastAsianWidth
33 DefaultCondition.ZeroWidthJoiner = ZeroWidthJoiner
34}
35
36type interval struct {
37 first rune
38 last rune
39}
40
41type table []interval
42
43func inTables(r rune, ts ...table) bool {
44 for _, t := range ts {
45 if inTable(r, t) {
46 return true
47 }
48 }
49 return false
50}
51
52func inTable(r rune, t table) bool {
53 // func (t table) IncludesRune(r rune) bool {
54 if r < t[0].first {
55 return false
56 }
57
58 bot := 0
59 top := len(t) - 1
60 for top >= bot {
61 mid := (bot + top) >> 1
62
63 switch {
64 case t[mid].last < r:
65 bot = mid + 1
66 case t[mid].first > r:
67 top = mid - 1
68 default:
69 return true
70 }
71 }
72
73 return false
74}
75
76var private = table{
77 {0x00E000, 0x00F8FF}, {0x0F0000, 0x0FFFFD}, {0x100000, 0x10FFFD},
78}
79
80var nonprint = table{
81 {0x0000, 0x001F}, {0x007F, 0x009F}, {0x00AD, 0x00AD},
82 {0x070F, 0x070F}, {0x180B, 0x180E}, {0x200B, 0x200F},
83 {0x2028, 0x202E}, {0x206A, 0x206F}, {0xD800, 0xDFFF},
84 {0xFEFF, 0xFEFF}, {0xFFF9, 0xFFFB}, {0xFFFE, 0xFFFF},
85}
86
87// Condition have flag EastAsianWidth whether the current locale is CJK or not.
88type Condition struct {
89 EastAsianWidth bool
90 ZeroWidthJoiner bool
91}
92
93// NewCondition return new instance of Condition which is current locale.
94func NewCondition() *Condition {
95 return &Condition{
96 EastAsianWidth: EastAsianWidth,
97 ZeroWidthJoiner: ZeroWidthJoiner,
98 }
99}
100
101// RuneWidth returns the number of cells in r.
102// See http://www.unicode.org/reports/tr11/
103func (c *Condition) RuneWidth(r rune) int {
104 switch {
105 case r < 0 || r > 0x10FFFF || inTables(r, nonprint, combining, notassigned):
106 return 0
107 case (c.EastAsianWidth && IsAmbiguousWidth(r)) || inTables(r, doublewidth):
108 return 2
109 default:
110 return 1
111 }
112}
113
114func (c *Condition) stringWidth(s string) (width int) {
115 for _, r := range []rune(s) {
116 width += c.RuneWidth(r)
117 }
118 return width
119}
120
121func (c *Condition) stringWidthZeroJoiner(s string) (width int) {
122 r1, r2 := rune(0), rune(0)
123 for _, r := range []rune(s) {
124 if r == 0xFE0E || r == 0xFE0F {
125 continue
126 }
127 w := c.RuneWidth(r)
128 if r2 == 0x200D && inTables(r, emoji) && inTables(r1, emoji) {
129 if width < w {
130 width = w
131 }
132 } else {
133 width += w
134 }
135 r1, r2 = r2, r
136 }
137 return width
138}
139
140// StringWidth return width as you can see
141func (c *Condition) StringWidth(s string) (width int) {
142 if c.ZeroWidthJoiner {
143 return c.stringWidthZeroJoiner(s)
144 }
145 return c.stringWidth(s)
146}
147
148// Truncate return string truncated with w cells
149func (c *Condition) Truncate(s string, w int, tail string) string {
150 if c.StringWidth(s) <= w {
151 return s
152 }
153 r := []rune(s)
154 tw := c.StringWidth(tail)
155 w -= tw
156 width := 0
157 i := 0
158 for ; i < len(r); i++ {
159 cw := c.RuneWidth(r[i])
160 if width+cw > w {
161 break
162 }
163 width += cw
164 }
165 return string(r[0:i]) + tail
166}
167
168// Wrap return string wrapped with w cells
169func (c *Condition) Wrap(s string, w int) string {
170 width := 0
171 out := ""
172 for _, r := range []rune(s) {
173 cw := RuneWidth(r)
174 if r == '\n' {
175 out += string(r)
176 width = 0
177 continue
178 } else if width+cw > w {
179 out += "\n"
180 width = 0
181 out += string(r)
182 width += cw
183 continue
184 }
185 out += string(r)
186 width += cw
187 }
188 return out
189}
190
191// FillLeft return string filled in left by spaces in w cells
192func (c *Condition) FillLeft(s string, w int) string {
193 width := c.StringWidth(s)
194 count := w - width
195 if count > 0 {
196 b := make([]byte, count)
197 for i := range b {
198 b[i] = ' '
199 }
200 return string(b) + s
201 }
202 return s
203}
204
205// FillRight return string filled in left by spaces in w cells
206func (c *Condition) FillRight(s string, w int) string {
207 width := c.StringWidth(s)
208 count := w - width
209 if count > 0 {
210 b := make([]byte, count)
211 for i := range b {
212 b[i] = ' '
213 }
214 return s + string(b)
215 }
216 return s
217}
218
219// RuneWidth returns the number of cells in r.
220// See http://www.unicode.org/reports/tr11/
221func RuneWidth(r rune) int {
222 return DefaultCondition.RuneWidth(r)
223}
224
225// IsAmbiguousWidth returns whether is ambiguous width or not.
226func IsAmbiguousWidth(r rune) bool {
227 return inTables(r, private, ambiguous)
228}
229
230// IsNeutralWidth returns whether is neutral width or not.
231func IsNeutralWidth(r rune) bool {
232 return inTable(r, neutral)
233}
234
235// StringWidth return width as you can see
236func StringWidth(s string) (width int) {
237 return DefaultCondition.StringWidth(s)
238}
239
240// Truncate return string truncated with w cells
241func Truncate(s string, w int, tail string) string {
242 return DefaultCondition.Truncate(s, w, tail)
243}
244
245// Wrap return string wrapped with w cells
246func Wrap(s string, w int) string {
247 return DefaultCondition.Wrap(s, w)
248}
249
250// FillLeft return string filled in left by spaces in w cells
251func FillLeft(s string, w int) string {
252 return DefaultCondition.FillLeft(s, w)
253}
254
255// FillRight return string filled in left by spaces in w cells
256func FillRight(s string, w int) string {
257 return DefaultCondition.FillRight(s, w)
258}