runewidth.go 4.9 KB

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