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