2026-03-25 08:39:31 +09:00
|
|
|
package util
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"unicode"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func isWide(r rune) bool {
|
2026-03-25 10:47:11 +09:00
|
|
|
return r >= 0x1100 && (r <= 0x115f || // Hangul Jamo
|
2026-03-25 08:39:31 +09:00
|
|
|
r == 0x2329 || r == 0x232a ||
|
|
|
|
|
(r >= 0x2e80 && r <= 0xa4cf) ||
|
|
|
|
|
(r >= 0xac00 && r <= 0xd7a3) ||
|
|
|
|
|
(r >= 0xf900 && r <= 0xfaff) ||
|
|
|
|
|
(r >= 0xfe10 && r <= 0xfe19) ||
|
|
|
|
|
(r >= 0xfe30 && r <= 0xfe6f) ||
|
|
|
|
|
(r >= 0xff00 && r <= 0xff60) ||
|
|
|
|
|
(r >= 0xffe0 && r <= 0xffe6))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func isEmoji(r rune) bool {
|
|
|
|
|
return r >= 0x1f300 && r <= 0x1faff
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func RuneWidth(r rune) int {
|
|
|
|
|
// control code
|
|
|
|
|
if r == 0 {
|
|
|
|
|
return 0
|
|
|
|
|
}
|
|
|
|
|
if r < 32 || (r >= 0x7f && r < 0xa0) {
|
|
|
|
|
return 0
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// combining mark
|
|
|
|
|
if unicode.Is(unicode.Mn, r) {
|
|
|
|
|
return 0
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// wide (loose CJK)
|
|
|
|
|
if isWide(r) {
|
|
|
|
|
return 2
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// emoji
|
|
|
|
|
if isEmoji(r) {
|
|
|
|
|
return 2
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 1
|
|
|
|
|
}
|
2026-03-25 10:47:11 +09:00
|
|
|
|
|
|
|
|
func StringWidth(s string, col int) int {
|
|
|
|
|
sum := 0
|
|
|
|
|
i := 0
|
|
|
|
|
for _, r := range s {
|
|
|
|
|
if i >= col {
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
sum += RuneWidth(r)
|
|
|
|
|
i++
|
|
|
|
|
}
|
|
|
|
|
return sum
|
|
|
|
|
}
|