-
-
Save bvk/aee4bff5b76131c22f4441ab22e59a02 to your computer and use it in GitHub Desktop.
`wcwidth` implementation for Go. Uses the `unicode` and `golang.org/x/text/width` packages.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package textwidth | |
import ( | |
"unicode" | |
"golang.org/x/text/width" | |
) | |
// IsComb returns true if r is a Unicode combining character. Alias of: | |
// | |
// unicode.Is(unicode.Mn, r) | |
// | |
func IsComb(r rune) bool { return unicode.Is(unicode.Mn, r) } | |
// RuneWidth returns fixed-width width of rune. | |
// https://en.wikipedia.org/wiki/Halfwidth_and_fullwidth_forms#In_Unicode | |
func RuneWidth(r rune) int { | |
if r == '\x00' || !unicode.IsPrint(r) || IsComb(r) { | |
return 0 | |
} | |
k := width.LookupRune(r) | |
switch k.Kind() { | |
case width.EastAsianWide, width.EastAsianFullwidth: | |
return 2 | |
case width.EastAsianNarrow, width.EastAsianHalfwidth, width.EastAsianAmbiguous, width.Neutral: | |
return 1 | |
default: | |
return 0 | |
} | |
} | |
// StringWidth returns fixed-width width of string. | |
// https://en.wikipedia.org/wiki/Halfwidth_and_fullwidth_forms#In_Unicode | |
func StringWidth(s string) (n int) { | |
for _, r := range s { | |
n += RuneWidth(r) | |
} | |
return n | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package textwidth | |
import ( | |
"testing" | |
"github.com/mattn/go-runewidth" | |
) | |
// test cases copied from https://github.com/mattn/go-runewidth/raw/master/runewidth_test.go | |
var stringwidthtests = []struct { | |
in string | |
out int | |
eaout int | |
}{ | |
{"■㈱の世界①", 10, 12}, | |
{"スター☆", 7, 8}, | |
{"つのだ☆HIRO", 11, 12}, | |
} | |
func BenchmarkStringWidth(b *testing.B) { | |
for i := 0; i < b.N; i++ { | |
StringWidth(stringwidthtests[i%len(stringwidthtests)].in) | |
} | |
} | |
func BenchmarkStringWidthOriginal(b *testing.B) { | |
for i := 0; i < b.N; i++ { | |
runewidth.StringWidth(stringwidthtests[i%len(stringwidthtests)].in) | |
} | |
} | |
func TestStringWidth(t *testing.T) { | |
for _, tt := range stringwidthtests { | |
if out := StringWidth(tt.in); out != tt.out { | |
t.Errorf("StringWidth(%q) = %d, want %d", tt.in, out, tt.out) | |
} | |
} | |
//c := runewidth.NewCondition() | |
//c.EastAsianWidth = false | |
//for _, tt := range stringwidthtests { | |
// if out := c.StringWidth(tt.in); out != tt.out { | |
// t.Errorf("StringWidth(%q) = %d, want %d", tt.in, out, tt.out) | |
// } | |
//} | |
//c.EastAsianWidth = true | |
//for _, tt := range stringwidthtests { | |
// if out := c.StringWidth(tt.in); out != tt.eaout { | |
// t.Errorf("StringWidth(%q) = %d, want %d (EA)", tt.in, out, tt.eaout) | |
// } | |
//} | |
} | |
var runewidthtests = []struct { | |
in rune | |
out int | |
eaout int | |
nseout int | |
}{ | |
{'世', 2, 2, 2}, | |
{'界', 2, 2, 2}, | |
{'セ', 1, 1, 1}, | |
{'カ', 1, 1, 1}, | |
{'イ', 1, 1, 1}, | |
{'☆', 1, 2, 2}, // double width in ambiguous | |
{'☺', 1, 1, 2}, | |
{'☻', 1, 1, 2}, | |
{'♥', 1, 2, 2}, | |
{'♦', 1, 1, 2}, | |
{'♣', 1, 2, 2}, | |
{'♠', 1, 2, 2}, | |
{'♂', 1, 2, 2}, | |
{'♀', 1, 2, 2}, | |
{'♪', 1, 2, 2}, | |
{'♫', 1, 1, 2}, | |
{'☼', 1, 1, 2}, | |
{'↕', 1, 2, 2}, | |
{'‼', 1, 1, 2}, | |
{'↔', 1, 2, 2}, | |
{'\x00', 0, 0, 0}, | |
{'\x01', 0, 0, 0}, | |
{'\u0300', 0, 0, 0}, | |
{'\u2028', 0, 0, 0}, | |
{'\u2029', 0, 0, 0}, | |
{'a', 1, 1, 1}, // ASCII classified as "na" (narrow) | |
{'⟦', 1, 1, 1}, // non-ASCII classified as "na" (narrow) | |
{'👁', 1, 1, 2}, | |
} | |
func BenchmarkRuneWidth(b *testing.B) { | |
for i := 0; i < b.N; i++ { | |
RuneWidth(runewidthtests[i%len(runewidthtests)].in) | |
} | |
} | |
func BenchmarkRuneWidthOriginal(b *testing.B) { | |
for i := 0; i < b.N; i++ { | |
runewidth.RuneWidth(runewidthtests[i%len(runewidthtests)].in) | |
} | |
} | |
func TestRuneWidth(t *testing.T) { | |
for i, tt := range runewidthtests { | |
if out := RuneWidth(tt.in); out != tt.out { | |
t.Errorf("case %d: RuneWidth(%q) = %d, want %d", i, tt.in, out, tt.out) | |
} | |
} | |
//c := runewidth.NewCondition() | |
//c.EastAsianWidth = false | |
//for _, tt := range runewidthtests { | |
// if out := c.RuneWidth(tt.in); out != tt.out { | |
// t.Errorf("RuneWidth(%q) = %d, want %d (EastAsianWidth=false)", tt.in, out, tt.out) | |
// } | |
//} | |
//c.EastAsianWidth = true | |
//for _, tt := range runewidthtests { | |
// if out := c.RuneWidth(tt.in); out != tt.eaout { | |
// t.Errorf("RuneWidth(%q) = %d, want %d (EastAsianWidth=true)", tt.in, out, tt.eaout) | |
// } | |
//} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment