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