You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

runewidth.go 5.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258
  1. package runewidth
  2. import (
  3. "os"
  4. )
  5. //go:generate go run script/generate.go
  6. var (
  7. // EastAsianWidth will be set true if the current locale is CJK
  8. EastAsianWidth bool
  9. // ZeroWidthJoiner is flag to set to use UTR#51 ZWJ
  10. ZeroWidthJoiner bool
  11. // DefaultCondition is a condition in current locale
  12. DefaultCondition = &Condition{}
  13. )
  14. func init() {
  15. handleEnv()
  16. }
  17. func handleEnv() {
  18. env := os.Getenv("RUNEWIDTH_EASTASIAN")
  19. if env == "" {
  20. EastAsianWidth = IsEastAsian()
  21. } else {
  22. EastAsianWidth = env == "1"
  23. }
  24. // update DefaultCondition
  25. DefaultCondition.EastAsianWidth = EastAsianWidth
  26. DefaultCondition.ZeroWidthJoiner = ZeroWidthJoiner
  27. }
  28. type interval struct {
  29. first rune
  30. last rune
  31. }
  32. type table []interval
  33. func inTables(r rune, ts ...table) bool {
  34. for _, t := range ts {
  35. if inTable(r, t) {
  36. return true
  37. }
  38. }
  39. return false
  40. }
  41. func inTable(r rune, t table) bool {
  42. // func (t table) IncludesRune(r rune) bool {
  43. if r < t[0].first {
  44. return false
  45. }
  46. bot := 0
  47. top := len(t) - 1
  48. for top >= bot {
  49. mid := (bot + top) >> 1
  50. switch {
  51. case t[mid].last < r:
  52. bot = mid + 1
  53. case t[mid].first > r:
  54. top = mid - 1
  55. default:
  56. return true
  57. }
  58. }
  59. return false
  60. }
  61. var private = table{
  62. {0x00E000, 0x00F8FF}, {0x0F0000, 0x0FFFFD}, {0x100000, 0x10FFFD},
  63. }
  64. var nonprint = table{
  65. {0x0000, 0x001F}, {0x007F, 0x009F}, {0x00AD, 0x00AD},
  66. {0x070F, 0x070F}, {0x180B, 0x180E}, {0x200B, 0x200F},
  67. {0x2028, 0x202E}, {0x206A, 0x206F}, {0xD800, 0xDFFF},
  68. {0xFEFF, 0xFEFF}, {0xFFF9, 0xFFFB}, {0xFFFE, 0xFFFF},
  69. }
  70. // Condition have flag EastAsianWidth whether the current locale is CJK or not.
  71. type Condition struct {
  72. EastAsianWidth bool
  73. ZeroWidthJoiner bool
  74. }
  75. // NewCondition return new instance of Condition which is current locale.
  76. func NewCondition() *Condition {
  77. return &Condition{
  78. EastAsianWidth: EastAsianWidth,
  79. ZeroWidthJoiner: ZeroWidthJoiner,
  80. }
  81. }
  82. // RuneWidth returns the number of cells in r.
  83. // See http://www.unicode.org/reports/tr11/
  84. func (c *Condition) RuneWidth(r rune) int {
  85. switch {
  86. case r < 0 || r > 0x10FFFF || inTables(r, nonprint, combining, notassigned):
  87. return 0
  88. case (c.EastAsianWidth && IsAmbiguousWidth(r)) || inTables(r, doublewidth):
  89. return 2
  90. default:
  91. return 1
  92. }
  93. }
  94. func (c *Condition) stringWidth(s string) (width int) {
  95. for _, r := range []rune(s) {
  96. width += c.RuneWidth(r)
  97. }
  98. return width
  99. }
  100. func (c *Condition) stringWidthZeroJoiner(s string) (width int) {
  101. r1, r2 := rune(0), rune(0)
  102. for _, r := range []rune(s) {
  103. if r == 0xFE0E || r == 0xFE0F {
  104. continue
  105. }
  106. w := c.RuneWidth(r)
  107. if r2 == 0x200D && inTables(r, emoji) && inTables(r1, emoji) {
  108. if width < w {
  109. width = w
  110. }
  111. } else {
  112. width += w
  113. }
  114. r1, r2 = r2, r
  115. }
  116. return width
  117. }
  118. // StringWidth return width as you can see
  119. func (c *Condition) StringWidth(s string) (width int) {
  120. if c.ZeroWidthJoiner {
  121. return c.stringWidthZeroJoiner(s)
  122. }
  123. return c.stringWidth(s)
  124. }
  125. // Truncate return string truncated with w cells
  126. func (c *Condition) Truncate(s string, w int, tail string) string {
  127. if c.StringWidth(s) <= w {
  128. return s
  129. }
  130. r := []rune(s)
  131. tw := c.StringWidth(tail)
  132. w -= tw
  133. width := 0
  134. i := 0
  135. for ; i < len(r); i++ {
  136. cw := c.RuneWidth(r[i])
  137. if width+cw > w {
  138. break
  139. }
  140. width += cw
  141. }
  142. return string(r[0:i]) + tail
  143. }
  144. // Wrap return string wrapped with w cells
  145. func (c *Condition) Wrap(s string, w int) string {
  146. width := 0
  147. out := ""
  148. for _, r := range []rune(s) {
  149. cw := RuneWidth(r)
  150. if r == '\n' {
  151. out += string(r)
  152. width = 0
  153. continue
  154. } else if width+cw > w {
  155. out += "\n"
  156. width = 0
  157. out += string(r)
  158. width += cw
  159. continue
  160. }
  161. out += string(r)
  162. width += cw
  163. }
  164. return out
  165. }
  166. // FillLeft return string filled in left by spaces in w cells
  167. func (c *Condition) FillLeft(s string, w int) string {
  168. width := c.StringWidth(s)
  169. count := w - width
  170. if count > 0 {
  171. b := make([]byte, count)
  172. for i := range b {
  173. b[i] = ' '
  174. }
  175. return string(b) + s
  176. }
  177. return s
  178. }
  179. // FillRight return string filled in left by spaces in w cells
  180. func (c *Condition) FillRight(s string, w int) string {
  181. width := c.StringWidth(s)
  182. count := w - width
  183. if count > 0 {
  184. b := make([]byte, count)
  185. for i := range b {
  186. b[i] = ' '
  187. }
  188. return s + string(b)
  189. }
  190. return s
  191. }
  192. // RuneWidth returns the number of cells in r.
  193. // See http://www.unicode.org/reports/tr11/
  194. func RuneWidth(r rune) int {
  195. return DefaultCondition.RuneWidth(r)
  196. }
  197. // IsAmbiguousWidth returns whether is ambiguous width or not.
  198. func IsAmbiguousWidth(r rune) bool {
  199. return inTables(r, private, ambiguous)
  200. }
  201. // IsNeutralWidth returns whether is neutral width or not.
  202. func IsNeutralWidth(r rune) bool {
  203. return inTable(r, neutral)
  204. }
  205. // StringWidth return width as you can see
  206. func StringWidth(s string) (width int) {
  207. return DefaultCondition.StringWidth(s)
  208. }
  209. // Truncate return string truncated with w cells
  210. func Truncate(s string, w int, tail string) string {
  211. return DefaultCondition.Truncate(s, w, tail)
  212. }
  213. // Wrap return string wrapped with w cells
  214. func Wrap(s string, w int) string {
  215. return DefaultCondition.Wrap(s, w)
  216. }
  217. // FillLeft return string filled in left by spaces in w cells
  218. func FillLeft(s string, w int) string {
  219. return DefaultCondition.FillLeft(s, w)
  220. }
  221. // FillRight return string filled in left by spaces in w cells
  222. func FillRight(s string, w int) string {
  223. return DefaultCondition.FillRight(s, w)
  224. }