下面以 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 |
将输出作为一个未结束的流处理,指明是一个大文本的一部分,只有在最后一部分的时候关闭选项 |