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}
258