Go x/text/unicode/bidi Ordering 怎样取得文本方向片段
来源:17golang原创
时间:2026-10-27 04:53:29 439浏览 收藏
处理混合中文、英文或阿拉伯文时,直接按字符串下标切片并不能得到浏览器或排版引擎使用的视觉片段。golang.org/x/text/unicode/bidi 的做法是先把段落交给 Paragraph.Order,再从返回的 Ordering 中逐个读取 Run。每个 Run 是一个连续方向的文本片段,既可以读方向,也可以读它在原文中的位置和原始顺序文本。
官方地址:https://pkg.go.dev/golang.org/x/text/unicode/bidi
Paragraph.SetString设置一个段落,必要时用DefaultDirection提供默认方向。Paragraph.Order计算整段的视觉顺序,返回Ordering和错误。Ordering.NumRuns给出片段数量,Run(i)取得第i段,再用Direction、Pos和String读取信息。- 位置是原始输入中的字节区间;段落重新设置后,旧的 Ordering 不能继续使用。
先分清 Paragraph、Ordering 和 Run 的职责
这组 API 可以按三个层次理解:Paragraph 保存待处理的单个段落,Ordering 保存计算后的视觉顺序,Run 则是其中一段连续方向的字符序列。Ordering 不是文本副本,也不是可以跨越下一次计算长期缓存的业务对象。
下面的最小流程先设置段落,再计算整段排序。示例只展示 API 的职责关系,不把图示当作运行截图;包的公开接口仍以官方文档为准。
package main
import (
"fmt"
"golang.org/x/text/unicode/bidi"
)
func main() {
var paragraph bidi.Paragraph
// SetString 接收一个段落;混合方向文本由 bidi 算法处理。
if _, err := paragraph.SetString("Go 文本 A العربية"); err != nil {
panic(err)
}
// Order 计算整段的视觉顺序,不能忽略它可能返回的错误。
ordering, err := paragraph.Order()
if err != nil {
panic(err)
}
fmt.Println("direction:", ordering.Direction())
fmt.Println("runs:", ordering.NumRuns())
}
Order 的结果只说明“怎样分成方向片段并按视觉顺序组织”,它不会自动把所有片段拼成一个已经排版好的新字符串。要取得每一段,继续使用 Run(i)。

用 NumRuns 和 Run 逐段读取方向片段
最实用的遍历方式是先读取 NumRuns,再用下标调用 Run(i)。每个 Run 都提供四类信息:Direction() 是该片段的方向,Pos() 是它在输入文本中的起止位置,String() 和 Bytes() 返回片段在原始顺序中的内容。
func printRuns(ordering bidi.Ordering) {
// NumRuns 是片段总数,Run 的下标范围是 0 到 NumRuns()-1。
for i := 0; i
这里的 start 和 end 不是“第几个汉字”的索引,而是原始输入的字节位置。对于 UTF-8 字符串,一个中文字符通常占多个字节,所以如果要用位置切片或建立高亮区间,必须继续把它当作字节边界处理;不要把它直接当作 rune 下标。
String() 返回片段原始顺序的文本。视觉顺序由 Ordering 的片段排列和每个 Run 的方向共同表达,业务层如果要绘制、分行或建立光标映射,应保存片段位置,而不是只保存拼接后的字符串。
整段 Order 与行级 Line 要分开使用
如果需求是取得一个段落所有方向片段,使用 Order()。如果排版引擎已经决定了一行在原始文本中的范围,则使用 Line(start, end),它会针对给定的起止位置计算这一行的 Ordering。行级结果不应被误当成整段结果。
func orderLine(paragraph *bidi.Paragraph, start, end int) (bidi.Ordering, error) {
// start 和 end 必须来自原始文本的范围,具体边界由排版阶段决定。
line, err := paragraph.Line(start, end)
if err != nil {
// 保留错误,让调用者处理非法范围或输入状态。
return bidi.Ordering{}, err
}
return line, nil
}
一个常见的工程分工是:先用 RunAt(pos) 做行切分或查找某个原始位置所属的 Run,再对确定的行范围调用 Line;需要整段导航时则调用 Order。无论哪种入口,错误都应向上返回,不要用空 Ordering 假装计算成功。

重新设置 Paragraph 后,旧 Ordering 不能复用
官方文档对 Ordering 有一个容易被忽略的限制:它来自某个 Paragraph 的一次输入,调用该 Paragraph 的 SetBytes 或 SetString 后,原来的 Ordering 就失效。正确做法是把“设置文本”和“计算 Ordering”视为同一轮处理,文本变化就重新计算。
func orderText(text string) (bidi.Ordering, error) {
var paragraph bidi.Paragraph
// 每个新文本都重新设置段落,避免沿用上一次的 Ordering。
if _, err := paragraph.SetString(text); err != nil {
return bidi.Ordering{}, err
}
// 只有当前段落没有再次修改时,返回的 Ordering 才属于这次输入。
return paragraph.Order()
}
同一个 Ordering 的方法还应由一个 goroutine 一次使用。需要并发处理多段文本时,为每段任务准备自己的 Paragraph/Ordering,或在明确的同步边界内访问,不要把同一个 Ordering 当作无锁共享缓存。
把 API 信息整理成一张决策表
| 目标 | 入口 | 得到什么 | 注意点 |
|---|---|---|---|
| 计算整段视觉片段 | Paragraph.Order() | 整段 Ordering | 处理错误,段落修改后重新计算 |
| 计算单行片段 | Paragraph.Line(start,end) | 该原始范围的 Ordering | 范围应来自原始文本位置 |
| 遍历片段 | NumRuns + Run(i) | 连续方向的 Run | 下标从 0 开始,不能超出数量 |
| 读取片段信息 | Direction、Pos、String | 方向、字节区间、原始顺序文本 | Pos 是字节位置,不是 rune 下标 |
如果你的目标只是判断整段主方向,可以先看 Paragraph.Direction 或 Ordering.Direction;如果要真正取得文本方向片段,仍然要遍历 Ordering。这两个层次不要混用。
常见问题
Ordering.Run(i) 返回的是视觉顺序文本吗?
它返回视觉排序结果中的第 i 个 Run,但 Run 自己的 String() 仍是该片段的原始顺序文本。若要渲染,应结合 Run 的 Direction 和 Ordering 的排列处理。
Pos() 能直接拿来当字符串的字符下标吗?
不能直接这样假设。官方 API 把它定义为原始输入位置,Go 字符串通常是 UTF-8 字节序列;需要 rune 下标时应建立自己的转换关系。
Paragraph.SetString 后为什么不能继续使用旧 Ordering?
旧 Ordering 对应的是上一次段落输入的计算结果。重新设置输入会使它失效;应在新文本上重新调用 Order 或 Line。
Order 和 Line 应该选哪个?
没有行范围时用 Order 得到整段结果;排版器已经确定原始起止位置时用 Line,避免把整段片段误用于单行。
-
860 收藏
-
843 收藏
-
826 收藏
-
809 收藏
-
792 收藏
-
306 收藏
-
102 收藏
-
130 收藏
-
394 收藏
-
123 收藏
-
463 收藏
-
351 收藏
-
475 收藏
-
173 收藏
-
153 收藏
-
151 收藏
-
332 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习