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