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	// 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}
259