runewidth.go 7.6 KB

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