BiDI大全

From : http://hi.baidu.com/%B2%A4%B2%CB%B1%F9%B1%F9/blog/item/9ba9a9131164d329dd540137.html

BiDi 算法的实现及应用

developerWorks
文档选项
<tr valign="top"><td width="8"><img alt="" height="1" width="8" src="//www.ibm.com/i/c.gif"/></td><td width="16"><img alt="" width="16" height="16" src="//www.ibm.com/i/c.gif"/></td><td class="small" width="122"><p><span class="ast">未显示需要 JavaScript 的文档选项</span></p></td></tr>
将打印机的版面设置成横向打印模式

打印本页

将此页作为电子邮件发送

将此页作为电子邮件发送

级别: 初级

张 顺 (shunzh@cn.ibm.com), 软件工程师, IBM

2009 年 5 月 14 日

软 件的国际化是软件发展的趋势和挑战,软件国际化要处理编码转换、日期、数字和货币格式以及双向阅读顺序(Bidirection)等问题。世界上有二十多 个国家和地区的超过 3 亿的人口使用从右到左的阅读顺序,因此很有必要在软件中加入对 BiDi 的支持。本文首先介绍软件国际化中 BiDi 的问题及算法,然后介绍 ICU4J 中 BiDi 算法的实现和使用。

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 还支持:

  • Text reordering
  • Shaping
  • Geometry mirroring, Right_to_Left geometry



回页首


ICU4J

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/ 网站上下载。




回页首


BiDi 算法

下面以 ICU4J 的 Bidi 算法实现为例,简要介绍 Bidi 实现中的概念和算法。

BiDi Level

一 段 BiDi 文本里可以有不同的文字方向,如在 RTL 方向的文本中包含 LTR 方向的字符串,或是在 LTR 方向的文本中包含 RTL 方向的字符串,在理论上还可能多重嵌套,但一般来说不会超过两层。 BiDi 算法使用 Level 来记录文本的方向。偶数为 LTR,奇数为 RTL,最外层一般规定为 0 或 1 。

如下面的文本是一个地址信息,大写字母代表阿拉伯语等 RTL 方向语言,全局方向为 RTL:

B ECNARTNE   25    TEERTS ELPAM 
 <-------------->   <->    <--------------------> 
     1            2             1

如果这段地址信息被一个使用英语的人引用,这时全局方向为 LTR,嵌套级别变为:

address is   B ECNARTNE    25  TEERTS ELPAM   today 
 <---------->  <----------------->  <->  <------------------>   <------> 
 0               1          2          1               0

通过计算每个字符的嵌套级别,BiDi 算法可以确定每个字符的文字方向,从而将逻辑顺序转换成视觉顺序,或是用于与遗留系统交换数据进行存储布局转换。

BiDi Run

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 对象根据调用参数的设置,计算每个字符的输出顺序和映射结果并依次输出。




回页首


使用 ICU4J 进行 BiDi 开发

构造函数


表 1. BiDi 构造函数表
函数签名详细信息
BiDi()默认构造函数,调用 this(0,0)
BiDi(int maxLength, int maxRunCount)以文本的最大长度和 Run 的最大个数构造 BiDi 对象,预先分配内存,运行时超出最大限制则出错,如果参数为 0 则根据输入文本自动分配内存。
BiDi(String paragraph, int flags)以文本和文本方向创建 BiDi 对象,flags 的取值范围见表 2
BiDi(AttributedCharacterIterator paragraph)以带属性的字符迭代器创建 BiDi 对象
BiDi(char[] text,int textStart,byte[] embeddings,int embStart,int paragraphLength,int flags)以字符数组的方式创建 BiDi 对象,
textStart:构造 BiDi 对象的字符起始位置
embeddings:级别数组
embStart:相对开始级别
paragraphLength:文本长度
flags:文本方向


表 2. 文本方向标志 flags 参数说明
DIRECTION_LEFT_TO_RIGHT从左到右
DIRECTION_RIGHT_TO_LEFT从右到左
DIRECTION_DEFAULT_LEFT_TO_RIGHT以第一个 BiDi 算法规定的强方向字符的方向作为文本方向,如果没有这种字符则使用从左到右方向
DIRECTION_DEFAULT_RIGHT_TO_LEFT以第一个 BiDi 算法规定的强方向字符的方向作为文本方向,如果没有这种字符则使用从右到左方向

下面的代码对 BiDi 的主要函数进行了测试:


清单1 BiDi测试用例
public void testBiDi(String text,int flag,int options){       
       BiDi BiDi = new BiDi(text, flag);
       byte paraLevel = BiDi.getParaLevel();
       int baseLevel = BiDi.getBaseLevel();
       
       boolean isBaseLeftToRight = BiDi.baseIsLeftToRight();
       boolean isLeftToRight = BiDi.isLeftToRight();
       boolean isRightToLeft = BiDi.isRightToLeft();
       boolean isMixed = BiDi.isMixed();
       boolean requiresBiDi = BiDi.requiresBiDi(text.toCharArray(), 0, text.length());
      
       int len = BiDi.getLength();
       int levels[] = new int[len];
       for (int i = 0; i < len; i++) {
          levels[i] = BiDi.getLevelAt(i) ;           
       }
       
       int runCount = BiDi.getRunCount();
       for (int i = 0; i < runCount; i++) {
           BiDi.getRunLevel(i);
           BiDi.getRunStart(i);
           BiDi.getRunLimit(i);
       }
       
       String writeReordered =  BiDi.writeReordered(options);
       
       StringBuffer sb = new StringBuffer(); sb.append("\n");
       sb.append("input text:" + text); sb.append("\n");
       sb.append("paraLevel:" + paraLevel); sb.append("\n");
       sb.append("baseLevel:" + baseLevel); sb.append("\n");
       sb.append("isBaseLeftToRight:" + isBaseLeftToRight); sb.append("\n");
       sb.append("isLeftToRight:" + isLeftToRight); sb.append("\n");
       sb.append("isRightToLeft:" + isRightToLeft); sb.append("\n");
       sb.append("isMixed:" + isMixed); sb.append("\n");
       sb.append("requiresBiDi:" + requiresBiDi);sb.append("\n");
       sb.append("levels:"); 
       for(int i = 0; i < levels.length; i++)
           sb.append(levels[i] + " ");
       sb.append("\n");
       sb.append("runCount:" + runCount); sb.append("\n");
       for(int i = 0; i < runCount; i++){
           sb.append("  run " + i + ":");
           sb.append("level-" + BiDi.getRunLevel(i));
           sb.append("  start-" + BiDi.getRunStart(i));
           sb.append("  limit-" + BiDi.getRunLimit(i));
           sb.append("\n");
       }      
       sb.append("writeReordered(" + options + "):"+ writeReordered);
       
       System.out.println(sb.toString());
}

writeReordered 函数根据 BiDi 对象设置的参数对文本进行重排序,该函数的 options 参数说明如表 3:


表 3. options 参数说明
DO_MIRRORING在 RTL 方向的 BiDi Run 中用镜像字符替换原字符,但是有的字符在 Unicode 里并没有镜像字符
INSERT_LRM_FOR_NUMERIC在必要的时候插入 LRM
KEEP_BASE_COMBINING在 RTL 方向的 BiDi Run 中保持组合的字符在基本字符之后
OUTPUT_REVERSE以逆向顺序输出字符
REMOVE_BIDI_CONTROLS移除 BiDi 控制字符,不影响 INSERT_LRM_FOR_NUMERIC
OPTION_STREAMING将输出作为一个未结束的流处理,指明是一个大文本的一部分,只有在最后一部分的时候关闭选项

假设输入文本如下:

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
测试结果 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
测试结果 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
测试结果 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 算法的实现和使用。

声明:

本文仅代表个人观点。



参考资料

学习

获得产品和技术


关于作者

 

张顺,现在 IBM 中国软件开发实验室 Lotus 开发中心工作,目前从事 Lotus Quickr 的开发定制以及客户支持工作。对 Web 服务,Web2.0 相关技术有浓厚的兴趣。

<img width="24" height="24" id="myFxSearchImg" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAADsElEQVR4nK2VTW9VVRSGn33OPgWpYLARbKWhQlCHTogoSkjEkQwclEQcNJEwlfgD/AM6NBo1xjhx5LyJ0cYEDHGkJqhtBGKUpm3SFii3vb2956wPB/t+9raEgSs52fuus89613rftdcNH8/c9q9++oe/Vzb5P+3McyNcfm2CcPj9af9w6gwjTwzvethx3Bx3x8xwd1wNM8dMcTNUHTfFLPnX6nVmZpeIYwf3cWD/PhbrvlPkblAzVFurKS6GmmGqqComaS+qmBoTI0Ncu3mXuGvWnrJ+ZSxweDgnkHf8ndVTdbiT3M7cQp2Z31dRTecHAfqydp4ejhwazh6Zezfnu98E1WIQwB3crEuJ2Y45PBTAQUVR9X4At66AppoEVO1Q8sgAOKJJjw6Am6OquDmvHskZ3R87gW+vlHz98zpmiqphkkRVbQtsfPTOC30lJKFbFTgp83bWh7Zx/uX1B6w3hI3NkkZTqEpBRDBRzG2AQHcwcYwEkOGkTERREbLQ/8HxJwuW7zdYrzfZ2iopy4qqEspKaDYravVm33k1R91Q69FA1VBRzFIVvXbx5AgXT44A8MWP81yfu0utIR2aVK3vfCnGrcUNxp8a7gKYKiLCvY2SUvo/aNtnM3e49ucK9S3p0aDdaT0UAVsKi2tVi6IWwNL9JvdqTdihaz79/l+u/rHMxmaJVMLkS2OoKKLWacdeE3IsSxctc2D5Qcl6vUlVVgNt+fkPPcFFmTw1xruvT7SCd7nuVhDQvECzJH90h0azRKoKFRkAmP5lKTWAGRdefoZL554FQNUxB92WvYeA5UN4PtSqwB2phKqsqMpBgAunRhFR3j49zuU3jnX8k6fHEQKXzh1jbmGDuYU6s4t1rt6socUeLLZHhYO2AHSHmzt19ihTZ48O8Hzl/AmunD/BjTvrvPfNX3hWsNpwJCvwYm+ngug4UilSCSq6k8YPtxDwfA+WRawIWFbgscDiULcCEaWqBFOlrLazurupOSHLqGnEKJAY8TwBEHumqUirAjNm52vEPPRV4p01XXMPAQhUBjcWm9QZwijwokgAeYHlHYA06KR1cT6ZvoV56pDUJQEjw0KeaMgj1hPEY4vz2A4eW0/e1qA7KtQdsxTYAG0H3iG4xyK1Y+xm7XmEPOJZDiENzLi2WZHngeOjj2Pe+sMg4GRYyLAsx7ME4FnsyTD9pr0PEc8zPGRAwKXBkYOPEd96cZRvf11g9MDe7e3R4Z4Q+vyEnn3P4t0XzK/W+ODN5/kPfRLewAJVEQ0AAAAASUVORK5CYII%3D" style="border: medium none ; position: absolute; z-index: 2147483647; opacity: 0.6; display: none;" hidden="true" alt="" />


  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值