From : http://hi.baidu.com/%B2%A4%B2%CB%B1%F9%B1%F9/blog/item/9ba9a9131164d329dd540137.html
BiDi 算法的实现及应用![]() | ![]() |
![]() |
级别: 初级 张 顺 (shunzh@cn.ibm.com), 软件工程师, IBM 2009 年 5 月 14 日 软 件的国际化是软件发展的趋势和挑战,软件国际化要处理编码转换、日期、数字和货币格式以及双向阅读顺序(Bidirection)等问题。世界上有二十多 个国家和地区的超过 3 亿的人口使用从右到左的阅读顺序,因此很有必要在软件中加入对 BiDi 的支持。本文首先介绍软件国际化中 BiDi 的问题及算法,然后介绍 ICU4J 中 BiDi 算法的实现和使用。 BiDi 的英文全称是 bi-directional language,即双向字符集语言。这种语言主要包括希伯来语、阿拉伯语和乌尔都语等。它们的最大特点就是允许双向文本—也就是说,他们的本土语言书写顺序是从右往左,而其中的英文单词或商标符号从左向右显示。 BiDi 算法是为了在计算机世界里实现 BiDi 效果的而产生的。 BiDi 算法用于指定文本的文字方向。 BiDi 文本是指通常在一段文本中包含两种文字方向,水平 LTR 方向(从左到右)的文本中包含 RTL 方向(从右到左)的文本,或是 RTL 方向的文本中包含 LTR 方向的文本,主方向称为全局方向。 使用 RTL 方向的语言主要是在中东地区,如阿拉伯语和希伯来语。在这些语言中全局方向是 RTL,但是文本中嵌入的数字和其它 LTR 方向语言的地址、缩写以及引用会使用 LTR 方向。实现 BiDi 算法和对字符串进行重排序的类库称为存储布局引擎(Storage Layout Engine)。 逻辑顺序(Logical Order) 和 视觉顺序(Visual Order) 逻辑顺序和视觉顺序是 BiDi 中两个重要的概念,逻辑顺序指的是人们阅读和从键盘上输入的文字顺序,文本在内存里也是以逻辑顺序存储的。视觉顺序则是文本在屏幕或是打印机中显示的顺序。 如下面的例子,小写字母代表英语等 LTR 方向语言,大写字母代表阿拉伯语等 RTL 方向语言,假设全局方向为 LTR 。 视觉顺序:English CIBARA text 逻辑顺序:English ARABIC text 在 输入的时候采用逻辑顺序,由于全局方向是 LTR,因此以 LTR 方向将 English 输入和显示之后,输入 ARABIC 时会以 RTL 方向对文本进行显示,即 CIBARA,但是会以 ARABIC 的顺序进行存储。输入 text 时又会以 LTR 方式进行输入和显示。 在 显示或打印文本的时候,需要对文本进行重排序,将逻辑顺序转换为视觉顺序,某些文本以 LTR 方向显示,某些文本以 RTL 方向显示。 Unicode 标准规定了一种从逻辑顺序到视觉顺序的转换算法,通常以整个段落作为输入,屏幕的换行也会影响 BiDi 算法输出文本的实际显示位置。重排序算法的输出也用于光标的移动和选择。 除了显示文本以外,Unicode 对于计算机内部文本的处理,如拷贝,排序,查找等,都是以逻辑顺序处理的。这些操作依赖于字符匹配的顺序,因此必须以统一的顺序进行存储和处理。有的遗留 系统为了避免在显示的时候对文本重排序,以视觉顺序对文本进行存储,在跟这些系统交换数据的时候,需要将数据从视觉顺序转换为逻辑顺序或是从逻辑顺序转换 为视觉顺序。这种不是为了显示目的的转换称为存储布局转换(Storage Layout transformation)。 除了对键盘 , Locale, 字体等 NLS(National Language Support)基本的支持之外,BiDi 还支持:
ICU(International Component for Unicode) 是 IBM 与开源组织合作研究 , 基于 "IBM 公共许可证 " 的用于支持软件国际化的开源项目。 ICU 实现了对数字、日期、货币等提供国际化支持,提供了强大的 BIDI 算法,对阿拉伯语和希伯来语等 BiDi 语言提供了完善的支持。 ICU 分为 ICU4J 和 ICU4C,分别对应 Java 和 c/c++ 平台。 ICU4J 被 Sun 的 JDK1.1 采用并随 JDK 版本更新。最新的 ICU4J 库可以从 http://icu-project.org/ 网站上下载。
下面以 ICU4J 的 Bidi 算法实现为例,简要介绍 Bidi 实现中的概念和算法。 一 段 BiDi 文本里可以有不同的文字方向,如在 RTL 方向的文本中包含 LTR 方向的字符串,或是在 LTR 方向的文本中包含 RTL 方向的字符串,在理论上还可能多重嵌套,但一般来说不会超过两层。 BiDi 算法使用 Level 来记录文本的方向。偶数为 LTR,奇数为 RTL,最外层一般规定为 0 或 1 。 如下面的文本是一个地址信息,大写字母代表阿拉伯语等 RTL 方向语言,全局方向为 RTL:
如果这段地址信息被一个使用英语的人引用,这时全局方向为 LTR,嵌套级别变为:
通过计算每个字符的嵌套级别,BiDi 算法可以确定每个字符的文字方向,从而将逻辑顺序转换成视觉顺序,或是用于与遗留系统交换数据进行存储布局转换。 BiDi Run 用来表示相同嵌套级别的字符序列,主要用途是为了避免单独记录每个字符的嵌套级别,节省内存空间。 BiDi 算法将一段文本根据嵌套级别分解为多个字符序列,同一个级别的相邻字符序列称为一个 BiDi Run 。 BiDi Run 记录了序列的开始和结束位置、嵌套级别以及一个标志位。 BiDi Run 没有公有构造函数,只能由 BiDi 算法解析文本的时候产生,并且没有 setter 方法,成员是不能被修改的。一个 BiDi Run 对象只需占用 8 个字节,通过 BiDi Run 来记录文本嵌套级别可以减少内存使用,只有在所有 BiDi Run 的平均字符数小于 2 个的情况下使用 BiDi Run 才会比单独记录每个字符的嵌套级别占用更多内存。 BiDi 算法实现了对输入文本的解析,构造 BiDi 对象以及对文本进行重排序,对数字及特殊字符的映射等操作。对于输入的字符串,BiDi 算法首先根据参数的设置解析每个字符的嵌套级别,可以显示设定文本的全局方向,也可以由程序自动扫描,以第一个遇到的强方向字符的方向作为文本全局方向。 解析完后,每个字符都会被设置级别,并通过 BiDi Run 来记录,解析之后创建的相关数据和原始文本都保存在 BiDi 对象中。在调用重排序操作的时候,BiDi 对象根据调用参数的设置,计算每个字符的输出顺序和映射结果并依次输出。
表 1. BiDi 构造函数表
表 2. 文本方向标志 flags 参数说明
下面的代码对 BiDi 的主要函数进行了测试: 清单1 BiDi测试用例
writeReordered 函数根据 BiDi 对象设置的参数对文本进行重排序,该函数的 options 参数说明如表 3: 表 3. options 参数说明
假设输入文本如下: String text = "\u006c\u0061\u0028\u0074\u0069\u006e\u0020\u05d0\u05d1\u0029\u05d2\u05d3"; 调用 testBiDi(text, BiDi.DIRECTION_LEFT_TO_RIGHT,BiDi.DO_MIRRORING) 输出如下: 图 1. 测试结果 1 ![]() 可 以看到由于设置 BiDi 文本方向为 DIRECTION_LEFT_TO_RIGHT,paraLevel 和 baseLevel 为 0,isBaseLeftToRight 为 true 。又由于文本中包含 LTR 和 RTL 字符,isLeftToRight 和 isRightToLeft 为 false,isMixed 为 true,requiresBiDi 为 true 。 requiresBiDi() 函数用来确定文本是否需要进行 BiDi 算法的转换,它可以避免额外的转换。由于 base level 为 0,RTL 的字符为 1 。该文本共有两个 BiDi Run,并记录了每个 BiDi Run 的起止位置。可以看到整个文本以 LTR 方向输出,RTL 的文本已经以从右到左的顺序输出,由于用 DO_MIRRORING 作为参数调用 writeReordered 函数,RTL 部分输出的“ ( ”变成了它的镜像字符“ ) ”,但是 LTR 部分的仍以原字符输出。 调用 testBiDi(text, BiDi.DIRECTION_RIGHT_TO_LEFT,BiDi.DO_MIRRORING) 输出如下: 图 2. 测试结果 2 ![]() 由 于设置 BiDi 文本方向为 DIRECTION_RIGHT_TO_LEFT,paraLevel 和 baseLevel 为 1,isBaseLeftToRight 为 false 。文本中包含 LTR 和 RTL 字符,isLeftToRight 和 isRightToLeft 为 false,isMixed 为 true,requiresBiDi 为 true 。由于 base level 为 1,RTL 的字符为 1,嵌套的 LTR 文本级别为 2 。整个文本以 RTL 的顺序输出,由于用 DO_MIRRORING 作为参数调用 writeReordered 函数,输出的“ ( ”变成了它的镜像字符“ ) ”,LTR 部分文本仍以原顺序输出,也未作镜像处理。 调用 testBiDi(text, BiDi.DIRECTION_DEFAULT_LEFT_TO_RIGHT,BiDi.DO_MIRRORING) 和 testBiDi(text, BiDi.DIRECTION_DEFAULT_RIGHT_TO_LEFT,BiDi.DO_MIRRORING) 输出如下: 图 3. 测试结果 3 ![]() 使 用 DIRECTION_DEFAULT_LEFT_TO_RIGHT 和 DIRECTION_DEFAULT_RIGHT_TO_LEFT 参数的时候以遇到第一个强方向字符作为文本的方向,因此对于该文本数据两种方式的调用结果是一致的,即以“ l ”的方向作为文本方向,与调用 testBiDi(text, BiDi.DIRECTION_LEFT_TO_RIGHT,BiDi.DO_MIRRORING) 一样。
本文介绍了 BiDi 的背景知识和相关概念,并介绍了开源项目 ICU4J 中 BiDi 算法的实现和使用。 声明: 本文仅代表个人观点。 学习
获得产品和技术
|