登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  Golang >  Go教程

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)。

Paragraph.Order 计算后由 Ordering、NumRuns 和 Run 取得文本方向片段的结构说明图
图1:结构说明图,展示从原始段落到 Ordering 片段遍历的关系,不是运行截图。

用 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 假装计算成功。

混合方向文本中 Run 原始位置、视觉顺序、Paragraph.Line 行范围和旧 Ordering 失效边界说明图
图2:边界说明图,展示整段与行级 Ordering 的范围差异,以及重新设置文本后旧 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,避免把整段片段误用于单行。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>