runewidth.go 5.6 KB

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