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.
 
 
 
 
 
 

258 lines
5.2 KiB

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