clipperhouse/displaywidth

GitHub: clipperhouse/displaywidth

高性能 Go 库,用于精确测量字符串、UTF-8 字节和 rune 在等宽终端中的显示列宽,支持 emoji、东亚字符和控制序列。

Stars: 17 | Forks: 2

# displaywidth 一个高性能的 Go 包,用于测量字符串、UTF-8 字节和 rune 的等宽显示宽度。 [![文档](https://pkg.go.dev/badge/github.com/clipperhouse/displaywidth.svg)](https://pkg.go.dev/github.com/clipperhouse/displaywidth) [![测试](https://static.pigsec.cn/wp-content/uploads/repos/cas/ce/ce733292a922c08274cf5a2096f8fa4cf01023bfa51a36ef6beecaaef371a9d9.svg)](https://github.com/clipperhouse/displaywidth/actions/workflows/gotest.yml) [![模糊测试](https://static.pigsec.cn/wp-content/uploads/repos/cas/aa/aa42500d814a7c8f54901ff31deb5aa14b56bab39beb967e4c516c5a122743b0.svg)](https://github.com/clipperhouse/displaywidth/actions/workflows/gofuzz.yml) ## 安装 ``` go get github.com/clipperhouse/displaywidth ``` ## 用法 ``` package main import ( "fmt" "github.com/clipperhouse/displaywidth" ) func main() { width := displaywidth.String("Hello, 世界!") fmt.Println(width) width = displaywidth.Bytes([]byte("🌍")) fmt.Println(width) width = displaywidth.Rune('🌍') fmt.Println(width) } ``` 在大多数情况下,你应该使用 `String` 或 `Bytes` 方法。它们会计算字符串或 byte 切片中字形簇宽度的总和。 ### 遍历字形 如果你需要单个的字形: ``` import ( "fmt" "github.com/clipperhouse/displaywidth" ) func main() { g := displaywidth.StringGraphemes("Hello, 世界!") for g.Next() { width := g.Width() value := g.Value() // do something with the width or value } } ``` ### 选项 创建你需要的选项,然后使用 options 结构体上的方法。 ``` var myOptions = displaywidth.Options{ EastAsianWidth: true, ControlSequences: true, } width := myOptions.String("Hello, 世界!") ``` #### ControlSequences `ControlSequences` 指定在计算显示宽度时是否忽略 ECMA-48 转义序列。当为 `false`(默认)时,ANSI 转义序列被视为一系列字符。当为 `true` 时,它们被视为一个零宽度的单个单元。 #### ControlSequences8Bit `ControlSequences8Bit` 指定在计算显示宽度时是否忽略 8 位 ECMA-48 转义序列。当为 `false`(默认)时,它们被视为一系列字符。当为 `true` 时,它们被视为一个零宽度的单个单元。 注意:该选项会被 `Truncate` 方法忽略,因为字符串拼接可能会导致非预期的 UTF-8 语义。 #### EastAsianWidth `EastAsianWidth` 定义了如何处理[东亚歧义字符](https://www.unicode.org/reports/tr11/#Ambiguous)。 当为 `false`(默认)时,东亚歧义字符的宽度被视为 1。 当为 `true` 时,它们的宽度被视为 2。 你可能希望根据环境变量或 locale 来配置此项。 例如,`go-runewidth` 就是在[包初始化期间](https://github.com/mattn/go-runewidth/blob/master/runewidth.go#L26C1-L45C2)进行此设置的。`displaywidth` 不会自动执行此操作,我们更倾向于将其留给你来决定。 ## 技术标准与兼容性 该包实现了 Unicode 东亚宽度标准 ([UAX #11](https://www.unicode.org/reports/tr11/tr11-43.html)),并处理了 [版本选择器](https://en.wikipedia.org/wiki/Variation_Selectors_(Unicode_block)), 以及[区域指示符对](https://en.wikipedia.org/wiki/Regional_indicator_symbol) (旗帜)。我们实现了 [Unicode TR51](https://www.unicode.org/reports/tr51/tr51-27.html) 以支持 emojis。我们正在持续关注 [新兴标准](https://www.jeffquast.com/post/state-of-terminal-emulation-2025/)。 对于控制序列,我们针对 7 位和 8 位控制序列实现了 [ECMA-48](https://ecma-international.org/publications-and-standards/standards/ecma-48/) 标准。 `clipperhouse/displaywidth`、`mattn/go-runewidth` 和 `rivo/uniseg` 在处理大多数真实世界的文本时会给出相同的输出。更多详细信息请参阅 [兼容性分析](comparison/COMPATIBILITY_ANALYSIS.md)。 ## 无效的 UTF-8 该包不验证 UTF-8。如果你传入无效的 UTF-8,结果将是 未定义的。我们对无效的 UTF-8 进行了模糊测试,以确保不会发生 panic 或 无限循环。 `ControlSequences8Bit` 选项意味着我们将分割有效的 8 位 控制序列,这些序列通常 _不是_ 有效的 UTF-8。8 位控制字节 恰好也是 UTF-8 的后续字节。请谨慎使用。 ## 前期工作 [mattn/go-runewidth](https://github.com/mattn/go-runewidth) [rivo/uniseg](https://github.com/rivo/uniseg) [x/text/width](https://pkg.go.dev/golang.org/x/text/width) [x/text/internal/triegen](https://pkg.go.dev/golang.org/x/text/internal/triegen) ## 基准测试 ``` cd comparison go test -bench=. -benchmem ``` ``` goos: darwin goarch: arm64 pkg: github.com/clipperhouse/displaywidth/comparison cpu: Apple M2 BenchmarkString_Mixed/clipperhouse/displaywidth-8 6085 ns/op 277.23 MB/s 0 B/op 0 allocs/op BenchmarkString_Mixed/mattn/go-runewidth-8 9970 ns/op 169.21 MB/s 0 B/op 0 allocs/op BenchmarkString_Mixed/rivo/uniseg-8 19060 ns/op 88.51 MB/s 0 B/op 0 allocs/op BenchmarkString_EastAsian/clipperhouse/displaywidth-8 6118 ns/op 275.76 MB/s 0 B/op 0 allocs/op BenchmarkString_EastAsian/mattn/go-runewidth-8 13917 ns/op 121.22 MB/s 0 B/op 0 allocs/op BenchmarkString_EastAsian/rivo/uniseg-8 19263 ns/op 87.58 MB/s 0 B/op 0 allocs/op BenchmarkString_ASCII/clipperhouse/displaywidth-8 54.54 ns/op 2347.10 MB/s 0 B/op 0 allocs/op BenchmarkString_ASCII/mattn/go-runewidth-8 125.5 ns/op 1020.32 MB/s 0 B/op 0 allocs/op BenchmarkString_ASCII/rivo/uniseg-8 1478 ns/op 86.62 MB/s 0 B/op 0 allocs/op BenchmarkString_Emoji/clipperhouse/displaywidth-8 3265 ns/op 221.74 MB/s 0 B/op 0 allocs/op BenchmarkString_Emoji/mattn/go-runewidth-8 5110 ns/op 141.69 MB/s 0 B/op 0 allocs/op BenchmarkString_Emoji/rivo/uniseg-8 7137 ns/op 101.44 MB/s 0 B/op 0 allocs/op BenchmarkRune_Mixed/clipperhouse/displaywidth-8 3517 ns/op 479.72 MB/s 0 B/op 0 allocs/op BenchmarkRune_Mixed/mattn/go-runewidth-8 4746 ns/op 355.48 MB/s 0 B/op 0 allocs/op BenchmarkRune_EastAsian/clipperhouse/displaywidth-8 3454 ns/op 488.36 MB/s 0 B/op 0 allocs/op BenchmarkRune_EastAsian/mattn/go-runewidth-8 11432 ns/op 147.56 MB/s 0 B/op 0 allocs/op BenchmarkRune_ASCII/clipperhouse/displaywidth-8 255.5 ns/op 500.88 MB/s 0 B/op 0 allocs/op BenchmarkRune_ASCII/mattn/go-runewidth-8 264.7 ns/op 483.48 MB/s 0 B/op 0 allocs/op BenchmarkRune_Emoji/clipperhouse/displaywidth-8 1320 ns/op 548.44 MB/s 0 B/op 0 allocs/op BenchmarkRune_Emoji/mattn/go-runewidth-8 2286 ns/op 316.72 MB/s 0 B/op 0 allocs/op BenchmarkTruncateWithTail/clipperhouse/displaywidth-8 2495 ns/op 70.94 MB/s 192 B/op 14 allocs/op BenchmarkTruncateWithTail/mattn/go-runewidth-8 4569 ns/op 38.74 MB/s 192 B/op 14 allocs/op BenchmarkTruncateWithoutTail/clipperhouse/displaywidth-8 2456 ns/op 93.25 MB/s 0 B/op 0 allocs/op BenchmarkTruncateWithoutTail/mattn/go-runewidth-8 5182 ns/op 44.19 MB/s 0 B/op 0 allocs/op ``` 这里有一些关于[如何让 Unicode 操作变快](https://clipperhouse.com/go-unicode/)的说明。
标签:EVTX分析, 日志审计