runewidth.go 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358
  1. package runewidth
  2. import (
  3. "os"
  4. "strings"
  5. "github.com/rivo/uniseg"
  6. )
  7. //go:generate go run script/generate.go
  8. var (
  9. // EastAsianWidth will be set true if the current locale is CJK
  10. EastAsianWidth bool
  11. // StrictEmojiNeutral should be set false if handle broken fonts
  12. StrictEmojiNeutral bool = true
  13. // DefaultCondition is a condition in current locale
  14. DefaultCondition = &Condition{
  15. EastAsianWidth: false,
  16. StrictEmojiNeutral: true,
  17. }
  18. )
  19. func init() {
  20. handleEnv()
  21. }
  22. func handleEnv() {
  23. env := os.Getenv("RUNEWIDTH_EASTASIAN")
  24. if env == "" {
  25. EastAsianWidth = IsEastAsian()
  26. } else {
  27. EastAsianWidth = env == "1"
  28. }
  29. // update DefaultCondition
  30. if DefaultCondition.EastAsianWidth != EastAsianWidth {
  31. DefaultCondition.EastAsianWidth = EastAsianWidth
  32. if len(DefaultCondition.combinedLut) > 0 {
  33. DefaultCondition.combinedLut = DefaultCondition.combinedLut[:0]
  34. CreateLUT()
  35. }
  36. }
  37. }
  38. type interval struct {
  39. first rune
  40. last rune
  41. }
  42. type table []interval
  43. func 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. func inTable(r rune, t table) bool {
  52. if r < t[0].first {
  53. return false
  54. }
  55. bot := 0
  56. top := len(t) - 1
  57. for top >= bot {
  58. mid := (bot + top) >> 1
  59. switch {
  60. case t[mid].last < r:
  61. bot = mid + 1
  62. case t[mid].first > r:
  63. top = mid - 1
  64. default:
  65. return true
  66. }
  67. }
  68. return false
  69. }
  70. var private = table{
  71. {0x00E000, 0x00F8FF}, {0x0F0000, 0x0FFFFD}, {0x100000, 0x10FFFD},
  72. }
  73. var nonprint = table{
  74. {0x0000, 0x001F}, {0x007F, 0x009F}, {0x00AD, 0x00AD},
  75. {0x070F, 0x070F}, {0x180B, 0x180E}, {0x200B, 0x200F},
  76. {0x2028, 0x202E}, {0x206A, 0x206F}, {0xD800, 0xDFFF},
  77. {0xFEFF, 0xFEFF}, {0xFFF9, 0xFFFB}, {0xFFFE, 0xFFFF},
  78. }
  79. // Condition have flag EastAsianWidth whether the current locale is CJK or not.
  80. type Condition struct {
  81. combinedLut []byte
  82. EastAsianWidth bool
  83. StrictEmojiNeutral bool
  84. }
  85. // NewCondition return new instance of Condition which is current locale.
  86. func NewCondition() *Condition {
  87. return &Condition{
  88. EastAsianWidth: EastAsianWidth,
  89. StrictEmojiNeutral: StrictEmojiNeutral,
  90. }
  91. }
  92. // RuneWidth returns the number of cells in r.
  93. // See http://www.unicode.org/reports/tr11/
  94. func (c *Condition) RuneWidth(r rune) int {
  95. if r < 0 || r > 0x10FFFF {
  96. return 0
  97. }
  98. if len(c.combinedLut) > 0 {
  99. return int(c.combinedLut[r>>1]>>(uint(r&1)*4)) & 3
  100. }
  101. // optimized version, verified by TestRuneWidthChecksums()
  102. if !c.EastAsianWidth {
  103. switch {
  104. case r < 0x20:
  105. return 0
  106. case (r >= 0x7F && r <= 0x9F) || r == 0xAD: // nonprint
  107. return 0
  108. case r < 0x300:
  109. return 1
  110. case inTable(r, narrow):
  111. return 1
  112. case inTables(r, nonprint, combining):
  113. return 0
  114. case inTable(r, doublewidth):
  115. return 2
  116. default:
  117. return 1
  118. }
  119. } else {
  120. switch {
  121. case inTables(r, nonprint, combining):
  122. return 0
  123. case inTable(r, narrow):
  124. return 1
  125. case inTables(r, ambiguous, doublewidth):
  126. return 2
  127. case !c.StrictEmojiNeutral && inTables(r, ambiguous, emoji, narrow):
  128. return 2
  129. default:
  130. return 1
  131. }
  132. }
  133. }
  134. // CreateLUT will create an in-memory lookup table of 557056 bytes for faster operation.
  135. // This should not be called concurrently with other operations on c.
  136. // If options in c is changed, CreateLUT should be called again.
  137. func (c *Condition) CreateLUT() {
  138. const max = 0x110000
  139. lut := c.combinedLut
  140. if len(c.combinedLut) != 0 {
  141. // Remove so we don't use it.
  142. c.combinedLut = nil
  143. } else {
  144. lut = make([]byte, max/2)
  145. }
  146. for i := range lut {
  147. i32 := int32(i * 2)
  148. x0 := c.RuneWidth(i32)
  149. x1 := c.RuneWidth(i32 + 1)
  150. lut[i] = uint8(x0) | uint8(x1)<<4
  151. }
  152. c.combinedLut = lut
  153. }
  154. // StringWidth return width as you can see
  155. func (c *Condition) StringWidth(s string) (width int) {
  156. g := uniseg.NewGraphemes(s)
  157. for g.Next() {
  158. var chWidth int
  159. for _, r := range g.Runes() {
  160. chWidth = c.RuneWidth(r)
  161. if chWidth > 0 {
  162. break // Our best guess at this point is to use the width of the first non-zero-width rune.
  163. }
  164. }
  165. width += chWidth
  166. }
  167. return
  168. }
  169. // Truncate return string truncated with w cells
  170. func (c *Condition) Truncate(s string, w int, tail string) string {
  171. if c.StringWidth(s) <= w {
  172. return s
  173. }
  174. w -= c.StringWidth(tail)
  175. var width int
  176. pos := len(s)
  177. g := uniseg.NewGraphemes(s)
  178. for g.Next() {
  179. var chWidth int
  180. for _, r := range g.Runes() {
  181. chWidth = c.RuneWidth(r)
  182. if chWidth > 0 {
  183. break // See StringWidth() for details.
  184. }
  185. }
  186. if width+chWidth > w {
  187. pos, _ = g.Positions()
  188. break
  189. }
  190. width += chWidth
  191. }
  192. return s[:pos] + tail
  193. }
  194. // TruncateLeft cuts w cells from the beginning of the `s`.
  195. func (c *Condition) TruncateLeft(s string, w int, prefix string) string {
  196. if c.StringWidth(s) <= w {
  197. return prefix
  198. }
  199. var width int
  200. pos := len(s)
  201. g := uniseg.NewGraphemes(s)
  202. for g.Next() {
  203. var chWidth int
  204. for _, r := range g.Runes() {
  205. chWidth = c.RuneWidth(r)
  206. if chWidth > 0 {
  207. break // See StringWidth() for details.
  208. }
  209. }
  210. if width+chWidth > w {
  211. if width < w {
  212. _, pos = g.Positions()
  213. prefix += strings.Repeat(" ", width+chWidth-w)
  214. } else {
  215. pos, _ = g.Positions()
  216. }
  217. break
  218. }
  219. width += chWidth
  220. }
  221. return prefix + s[pos:]
  222. }
  223. // Wrap return string wrapped with w cells
  224. func (c *Condition) Wrap(s string, w int) string {
  225. width := 0
  226. out := ""
  227. for _, r := range s {
  228. cw := c.RuneWidth(r)
  229. if r == '\n' {
  230. out += string(r)
  231. width = 0
  232. continue
  233. } else if width+cw > w {
  234. out += "\n"
  235. width = 0
  236. out += string(r)
  237. width += cw
  238. continue
  239. }
  240. out += string(r)
  241. width += cw
  242. }
  243. return out
  244. }
  245. // FillLeft return string filled in left by spaces in w cells
  246. func (c *Condition) FillLeft(s string, w int) string {
  247. width := c.StringWidth(s)
  248. count := w - width
  249. if count > 0 {
  250. b := make([]byte, count)
  251. for i := range b {
  252. b[i] = ' '
  253. }
  254. return string(b) + s
  255. }
  256. return s
  257. }
  258. // FillRight return string filled in left by spaces in w cells
  259. func (c *Condition) FillRight(s string, w int) string {
  260. width := c.StringWidth(s)
  261. count := w - width
  262. if count > 0 {
  263. b := make([]byte, count)
  264. for i := range b {
  265. b[i] = ' '
  266. }
  267. return s + string(b)
  268. }
  269. return s
  270. }
  271. // RuneWidth returns the number of cells in r.
  272. // See http://www.unicode.org/reports/tr11/
  273. func RuneWidth(r rune) int {
  274. return DefaultCondition.RuneWidth(r)
  275. }
  276. // IsAmbiguousWidth returns whether is ambiguous width or not.
  277. func IsAmbiguousWidth(r rune) bool {
  278. return inTables(r, private, ambiguous)
  279. }
  280. // IsNeutralWidth returns whether is neutral width or not.
  281. func IsNeutralWidth(r rune) bool {
  282. return inTable(r, neutral)
  283. }
  284. // StringWidth return width as you can see
  285. func StringWidth(s string) (width int) {
  286. return DefaultCondition.StringWidth(s)
  287. }
  288. // Truncate return string truncated with w cells
  289. func Truncate(s string, w int, tail string) string {
  290. return DefaultCondition.Truncate(s, w, tail)
  291. }
  292. // TruncateLeft cuts w cells from the beginning of the `s`.
  293. func TruncateLeft(s string, w int, prefix string) string {
  294. return DefaultCondition.TruncateLeft(s, w, prefix)
  295. }
  296. // Wrap return string wrapped with w cells
  297. func Wrap(s string, w int) string {
  298. return DefaultCondition.Wrap(s, w)
  299. }
  300. // FillLeft return string filled in left by spaces in w cells
  301. func FillLeft(s string, w int) string {
  302. return DefaultCondition.FillLeft(s, w)
  303. }
  304. // FillRight return string filled in left by spaces in w cells
  305. func FillRight(s string, w int) string {
  306. return DefaultCondition.FillRight(s, w)
  307. }
  308. // CreateLUT will create an in-memory lookup table of 557055 bytes for faster operation.
  309. // This should not be called concurrently with other operations.
  310. func CreateLUT() {
  311. if len(DefaultCondition.combinedLut) > 0 {
  312. return
  313. }
  314. DefaultCondition.CreateLUT()
  315. }