查看原文
其他

Unicode双向算法(bidi算法)详解(一)

黄邦勇帅 脚本之家 2022-04-23

脚本之家

你与百万开发者在一起


作者 | 黄邦勇帅(原名:黄勇)

出品 | 脚本之家(ID:jb51net)


第1节 理解字符


一、字体的设计原理(字符集、编码、字体三者的关系)

1、字符集:就是各种字符的集合,比如Unicode就是一个字符集,它使用2个字节(即最多65535个)来表示所有的字符。

2、编码:一个字符要能被计算机所接受,需要进行两次编码,因为计算机只能表示二进制,对于人们常使用的10进制来讲不是很方便,因此字符的第一次编码就是把相应的字符使用一个整数值与其相对应,比如ASCII码字符集把字符'a'编码为10进制的61,就是一次编码,Unicode字符集也是一次编码。为了能让计算机的二进制识别,需要把第一次编码后的整数值再次编码为二进制值,比如使用一个字节来表示字符'a'一次编码后的整数值61,再如中文汉字使用两个字节进行表示,再如对于Unicode字符集有3种不同的二次编码方案,分别是UTF-8,UTF-16和UTF-32,目前使用较多的是使用UTF-8来存储的Unicode字符集。

3、字形(glyph):用于表示字符的外形,比如字母a的ASCII码为61,但这个字母可以以多种外形对其进行书写,再如中文字符中的每一笔画都是一个字形。注:glyph也翻译为图元,图像。其实对象的外形都是使用图元进行描述的。

4、字形与字符的关系:一个字形可以用于表示多个字符,一个字符也可以由多个字形组成,比如中文字符,就经常共享字形,而且是由多个字形组成的。字符的衬线、粗细等都是字形设计的元素。

5、字体:是一个拥有相同设计风格的字形及从字符到字形映射关系的集合,字体使字符能被显示出来,字体是计算机显示文字的一种方式,比如早期电报就把字符表示为一长串数字,这一长串数字就相当于是字符集,当接收到电报后,是使用宋体、草书或者其他形式显示出来,就需要使用字体了,每种字体都有一个相应的名字,比如“Times New Roman”、“宋体”等,相同的字体显示的字符具有相似的风格,比如以宋体显示的字体,其风格都是相似的。另外,字体名通常有版权,是受到法律保护的。

6、计算机显示字符的原理简述:当计算机接收到一串二进制之后,表示的具体是什么字符,需要由字符集来决定,然后字符需要被显示出来(即字体以什么外观进行显示),这时就需要寻找相应的字体,若字库中没有相应字符的字体,则可能会被显示为乱码,所以要让计算机正确显示文字,不仅编码应正确,还要有相应的字体才行。


二、Unicode对字符的理解

1、用户感知字符(user-perceived character)与字形族 (grapheme cluster)

用户感知字符就是指的人们认为的一个字符,字形族是用于近似表示用户感知字符的。计算机中的一个“字符”通常就是一个Unicode代码值,但是,现实中人们认为的一个“字符”,可能并不是由一个Unicode代码值组成,而是由多个Unicode代码值组成的。比如   

该字符会被人们认为是一个单个的字符,但实际上他是由两个Unicode代码值(即,两个字符)组成的,即由Unicode字符g (u+0067)和 (U+0308)组合而成。

2、字符的成形 (shaping)   

在Unicode中,把字符以最终形状显示出来的过程称为成形。渲染引擎不一定能正确的对用户感知的字符成形,比如,有些渲染引擎可能会把显示为两个字符,有可能不会显示。

3、对字符的处理

由以上可知,用户感知字符与计算机理解的字符是不同的,而人们通常是以用户感知字符对字符进行理解的,因此,就有很多问题需要处理,比如,一个字符串中有多少个用户感知字符(即,怎样确定一个用户感知字符的边界),怎样对用户感知字符断行,怎样显示等,Unicode标准对这些方面都有详细的描述,有兴趣的读者可参阅Unicode有关这些方面的内容,本文重点介绍Unicode双向排序算法(bidi算法),bidi算法影响各个Unicode字符的排序顺序,至于字符能否最终成形为用户感知字符、怎样断行等内容不属于bidi算法的范围。


第2节 Unicode字符的分类与定向格式化字符

 

一、文本显示顺序与Unicode双向算法
1、文本的方向
大多数语言的文本在水平方向都是按从左到右(Left To Right,简称LTR)的顺序显示字符的,但也有不少语言是按从右到左(Right To Left,简称RTL)的顺序显示字符的(比如阿拉伯语、希伯来语)。当然还有按垂直方向书写的文本,比如中国古代的汉字就是从上到下从右到左书写的,本文只讨论水平方向书写的文本,垂直方向书写的文本不予讨论。
双向文本是指一个字符串中同时包含LTR和RTL的文文,既包含从左到右的文本又包含从右到左的文本。现实中,从右向左书写的语言通常会夹杂着从左向右的文本(比如外语、引用、数字、符号等),因此,像阿拉伯语、希伯来语这些语言通常都是双向文本。另外,当在从左向右的文本中插入从右向左的文本时也会产生双向文本的问题。
2、逻辑顺序与显示顺序
逻辑顺序在Unicode标准内规定为文本在内存中表示的顺序,而显示顺序就是最终显示在我们面前所看到的文本的顺序,文本的逻辑顺序和显示顺序并不一定会一致,比如对于从右向左显示的文本,显示顺序应是从右向左的,而逻辑顺序则可能是从左向右的。逻辑顺序是属于计算机底层的问题,不属于本文讨论的范围,我们需要解决的是文本的显示顺序题。
3、Unicode双向算法(bidi)与定向格式化字符
1)、对于双向文本,若不明确的确定文本的显示顺序,在显示时就可能会出现歧义,为此,需要为双向文本的显示定义一种算法(或者一种规则),用于规范双向文本的显示顺序。
2)、通常有一种隐式算法(或称为隐式双向排序或隐式布局算法)来定义双向文本的显示,但是隐式算法并不足以产生可供理解的文本,为此,对某些字符的显示顺序需要明确地进行控制,就是使用一系列的控制符(类似于HTML中的元素)来控制字符的显示顺序,这些控制符被Unicode称为定向格式化字符。比如,使用RLO控制符来控制字符从右向左显示,PDF表示RLO的终止字符,那么ab cd RLO EF GH PDF x,将被显示为ab cd HG FE x,可见,Unicode控制符的原理与HTML的元素是相似的。
3)、定向格式化字符只影响文本的显示顺序,在其它方面会被忽略,也就是说定向格式化字符不会对文本的比较、断句、词法分析、数值分析等方面造成影响。
4)、Unicode双向算法(也称为bidi)是对隐式算法的扩展, Unicode双向算法定义了定向格式化字符(即控制符),并且定义了一套算法,用于规定这些控制符对需要显示的字符产生怎样的影响。
二、Unicode对字符的分类及输入方法
1、字符的属性

Unicode为字符定义了很多属性,以用于描述该字符,比如Bidi_Paired_Bracket_Type属性用于描述该字符是开括号(值为open)还是闭括号(值为close),再如General_Category描述了该字符的通用类别,比如若该字符是行分隔符,则值为Line_Separator,是控制符,则值为Control等。

2、字符的类型
Unicode为每个Unicode字符都定义了一种类型(称为双向字符类型或bidi类型),双向字符类型被分为:强字符(强类型)、弱字符(弱类型)、中性字符(中性类型)、定向格式化字符,表1为具体的分类方法(定向格式化字符的分类见后文)

 

字符的类型


分类

类型

简述

范围

强字符(strong)

L

left to right

LRM(见表2),大部分字母、音节、汉字、非欧洲或非阿拉伯数字

R

right to left

RLM(见表2),希伯来字母和相关的标点符号

AL

right to left Arabic

ALM(见表2),阿拉伯语(Arabic)、它拿字母(Thaana)、叙利亚字母,及大多数特定于这些文字的标点符号

弱字符(weak)

EN

欧洲数字(European Number)

欧洲数字、东阿拉伯-印度数字,经常使用的数字1,2,3等就是属于EN类型

ES

欧洲数字分隔符

加号,减号

ET

欧洲数字终止符

度的符号,货币符号,比如,$(美元),¥(人民币)

AN

阿拉伯数字

阿拉伯-印度数字,阿拉伯小数和千位分隔符,平时使用的数字虽然叫做阿拉伯数字,但阿拉伯拥有自已的数字,比如,4的阿拉伯数字字符为 ٤ (u+0664)

CS

普通数字分隔符

冒号,逗号,句点(即小数点),不间断空格(no-break space)等,注意:单引号、双引号、分号不属于该类型,中文的句号也不属于该类型

NSM

无间距标记(Nonspacing mark)

属性General_Category为以下值的字符:Mn(Nonspacing_Mark)和Me(Enclosing_Mark)比如,组合用发音字符的上左角  ̚ (u+031A),西非书面文中的ࣾ (u+08FE) 。详见后文对组合字符的讲解

BN

中性边界

不是明确给定类型的字符,比如:可忽略的默认值,非字符,控制字符等。比如,广义标点中的不可见乘号(u+2062)就是BN类型

中性字符(neutral)

B

段落分隔符

段落分隔符(u+2029),适当的换行符函数,高级别确定段落的协议

S

节分隔符(Segment Separator)

Tab

WS

空白(Whitespace)

空格,图形空格,行分隔符,换页符,常用标点符号的空格等

ON

其他中性符

所有其他字符,包括对象替换字符,比如,[、]、(、)、"、'、@&、*、<>|{}、;(分号)、!、?、~、=。注意:属于CS类型、%、#属于ET类型

定向格式化字符

见后文


3、定向格式化字符的分类


字向格式化字符分为隐式定向格式化字符和显示定向格式化字符两大类,显示定向格式化字符又分为显示定向嵌入格式化字符、显示定向重写格式化字符、显示定向隔离格式化字符,分别简称为嵌入格式化字符、重写格式化字符、隔离格式化字符。其中隔离格式化字符是在Unicode 6.3中引入的。具体的分类规则详见表2

 

定向格式化字符的分类


类型

控制符

Unicode代码

简述

说明

隐式定向格式化字符

LRM 

U+200E

left to right mark

从左到右的零宽度字符

RLM 

U+200F

right to left mark

从右到左的零宽度非阿拉伯字符

ALM 

U+061C

arabic letter mark

从右到左的零宽度阿拉伯语字符

显示定向嵌入和重写格式化字符

LRE

U+202A

left to right embedding

嵌入。把后面的文本看作是从左到右(LRE)或从右到左(RLE)的嵌入

RLE

U+202B

right to left embedding

LRO

U+202D

left to right override

重写。强制改变其后的文本的方向为从左向右(LRO)或从右向左(RLO)

RLO

U+202E

right to left override

PDF

U+202C

pop directional formatting

嵌入和重写终止符。用于终止LRE、RLE、LRO、RLO的作用范围

显示定向隔离格式化字符

LRI

U+2066

left to right isolate

从左到右(LRI)或从右到左(RLI)的隔离之后的文本

RLI

U+2067

right to left isolate

FSI

U+2068

first strong isolate

隔离之后的文本,文本的方向由第一个非嵌套在隔离中的强字符决定

PDI

U+2069

pop directional isolate

隔离终止符。用于终止LRI、RLI、FSI的作用范围,该终止符还会同时终止LRE、RLE、LRO、RLO的作用范围

4、组合字符

1)、组合字符(Combining character)是指General_Category属性的值为Mc (Spacing Combining Mark,间距组合标记)、Mn (Nonspacing Mark,无间距标记)、Me(Enclosing Mark,嵌入标记)的所有字符。

2)、组合字符通常用于与它的基本字符组合为一个字符,比如

以上字符由字符g (u+0067)和 (U+0308)组合而成,其中字符g就是基本字符,而U+0308就是组合字符。

3)、无间距标记(Mn)通常不单独占据空间位置,其占据的位置取决于它的基本字符。


5、零宽度字符 ( LRM、RLM、ALM )

表2中的LRM、RLM、ALM是一种零宽度字符,可将其理解为在该处插入了一个相应方向的强字符,但该字符是不可见的(宽度为零所以不可见)。比如aLRMb,相当于在a和b之间插入了一个从左向右的强字符,但该字符宽度为零且不可见(即不会被显示)。


6、与定向格式化字符对应的HTML5元素和CSS等效项

1)、HTML5没有提供对LRE、RLE、LRO、RLO的精确等效项,可使用CSS来获取LRE、RLE、LRO、RLO、LRI、RLI、FSI的精确等效项。表3为HTML和CSS与bidi算法的等效情形。由表3可见,在HTML5中,bdi元素更多用于对字符的隔离,以避免文本被周围字符方向性所影响,或避免隔离的文本影响周围字符的方向性,而bdo元素主要用于强制改变文本的方向性。

2)、注意:HTML5与HTML4.0不同,早期版本的bdi元素与隔离(LRI、RLI)相对应,bdo元素与重写(LRO、RLO)相对应。

 

各平台对bidi算法的实现


bidi算法

HTML5的等效项

CSS的等效项

RLI ... PDI

dir = "rtl" (任意元素的dir属性)

direction:rtl; unicode-bidi:isoloate;

LRI ... PDI

dir = "ltr"(任意元素的dir属性)

direction:ltr; unicode-bidi:isoloate;

FSI ... PDI

<bdi>dir = "auto"

unicode-bidi:plaintext;

RLE ... PDF

无对应元素

direction:rtl; unicode-bidi:embed;

LRE ... PDF

无对应元素

direction:ltr; unicode-bidi:embed;

RLO ... PDF

无对应元素

direction:rtl; unicode-bidi:bidi-override;

LRO ... PDF

无对应元素

direction:ltr; unicode-bidi:bidi-override;

FSI RLO ... PDF PDI

<bdo dir = "rtl">

direction:rtl; unicode-bidi:isoloate-override;

FSI LRO ... PDF PDI

<bdo dir = "ltr">

direction:ltr; unicode-bidi:isoloate-override;

 

7、以Unicode代码(即数字)的形式输入字符


1)、注意:定向格式化字符是Unicode字符集中的一个字符,只是不会被显示出来,每个定向格式化字符都有其对应的Unicode代码,比如RLO的Unicode代码为U+202E

2)、Unicode字符可在Word、浏览器、记事本等应用上以Unicode代码的形式输入,定向格式化字符同样可以被输入,但是不会被显示

3)、HTML的格式如下:

&#x+mmmm;(16进制),或   &#+mmmm;(10进制) 注意:末尾有个分号

      示例:&#x0061;表示字符a

4)、CSS的格式为:+mmmm(16进制)

      示例:0061表示字符a

5)、Word的方法如下(注:记事本采用的是Word的第2种方法):
  • 方法1:输入相应的Unicode代码,然后选中该代码或在该代码末尾按下ALT+x,这时会把相应代码转换为字符,同时在Word中还可使用ALT+x把相应的字符转换为相应的Unicode代码。

  • 方法2:按住ALT键不放,然后以10进制的形式输入Unicode代码,然后释放ALT键,便会产生相应的字符,比如,按住ALT键,同时输入97,然后释放ALT时便会产生字符a。

  • 方法3:选择【插入】---【符号】---【其他符号】,弹出一个对话框,在该对话框中插入Unicode字符,其界面和使用方法如下图所示


第3节 运行等级与隔离运行序列

 

一、基本名词
1、嵌入等级(或嵌入水平) ((level,可翻译为:等级、水平、级别):表示字符的嵌入层次,数字越大嵌入得越深,需要注意的是,在bidi算法中,字符串中的每个字符都有一个嵌入等级。
2、基础方向(base direction):分段的方向被称为基础方向,基础方向决定了该段文本从浏览器的左侧还是右侧开始书写。
3、隔离启动器:是对LRI、RLI、FSI的统称,注意:隔离启动器不包括PDI
4、嵌入启动器:是对LRE、RLE、LRO、RLO的统称,注意:嵌入启动器不包括PDF
二、运行等级和隔离运行序列
1、运行等级(level run):也称为定向运行(directional run),是指具有相同嵌入等级的字符所形成的最大子串,该子串与其直接接触的前后字符的嵌入等级不相同,比如ab cd RLE ef gh PDF kk mm,假设分段的嵌入等级为0,则字符a、bcd(含其中的空格)的嵌入等级都为0,字符e、fgh的嵌入等级都为1,字符k、kmm的嵌入等级为0,因此,该字符串共有3个运行等级,分别是子串ab cd,子串ef gh,子串kk mm
2、隔离运行序列(简称为运行序列或序列):是由一系列运行等级组成的序列,其规则如下:

1)、含有隔离启动器时:除最后一个运行等级外,隔离运行序列中运行等级的最后一个字符是隔离启动器,与该隔离启动器匹配的PDI是序列中下一个运行等级的第一个字符,也就是说,序列中的运行等级是以隔离启动器结束的(最后一个运行等级除外),以PDI开始的(除第一个运行等级外)。

2)、无隔离启动器时:此时每个运行等级构成一个独立的隔离运行序列。
3、隔离运行序列具有如下特点:

1)、每个运行等级只属于一个隔离运行序列,也就是说,不存一个运行等级属于两个序列的情形。

2)、在同一个隔离运行序列中所有的运行等级具有相同的嵌入等级,因为隔离运行序列是以隔离启动器开始一个运行等级,又以与其匹配的PDI开始另一个运行等级,很明显,这两个运行等级具有相同的嵌入等级。

3)、紧随着隔离启动器之后的运行等级会开启一个新的隔离运行序列,与之匹配的PDI之前的运行等级会结束它的隔离运行序列。

4、隔离启动器的重要规则:
隔离启动器和与其匹配的PDI拥有的嵌入等级是提升之前的原始嵌入等级,而不是提升之后的嵌入等级。
5、示例
  • 以下示例的“文本”表示实际输入的内容,其中的符号“”不属于文本的内容,该符号只是为了提高示例的清晰度,以方便阅读。
  • 以下示例均假设分段的嵌入等级为0

 

示例1(含隔离启动器): 

分析如下文本的运行等级和隔离运行序列

文本1 ⋄ RLI ⋄ 文本2 ⋄ PDI ⋄ 文本3 ⋄ RLI ⋄ 文本4 ⋄ PDI ⋄ RLI⋄ 文本5 ⋄ PDI ⋄ 文本6 

 

1、运行等级的分析方法:

  • |文本1 ⋄ RLI |构成一个动行等级,因为在RLI之后的 |文本2 | 与 |文本1 ⋄ RLI | 的嵌入等级不相同,|文本1 ⋄ RLI |的嵌入等级为0,而 |文本2 | 的嵌入等级为1,因此 |文本1 ⋄ RLI | 构成一个运行等级,其等级为0。此处应用了规则:RLI和与之匹配的PDI的嵌入等级是提升之前的嵌入等级。
  • |文本2 | 构成一个运行等级,因为在 |文本2 | 之前的RLI和之后的PDI与 |文本2 | 的嵌入等级不相同,|文本2 | 之前的RLI和之后的PDI的嵌入等级都为0,而 |文本2 | 的嵌入等级为1,因此,|文本2 | 构成一个运行等级。
  • 其余运行等级的分析方法与以上类似,不再重述。
2、隔离运行序列的分析方法:
  • |文本1 ⋄ RLI | 为隔离运行序列中的一个运行等级(以RLI结束),与该RLI匹配的PDI,即 |文本2 | 之后的PDI是该序列中的下一个运行等级的开始,因此 | PDI ⋄ 文本3 ⋄ RLI | 是该隔序运行序列中的下一个运行等级(以RLI结束),同理,|文本4 | 之后的   | PDI ⋄ RLI | 是该序列中的再下一个运行等级,| PDI ⋄ 文本6 | 是该序列中的最后一个运行等级,因此,第一个隔离运行序列中的运行等级包含 |文本1 ⋄ RLI | 、| PDI ⋄ 文本3 ⋄ RLI |、 | PDI ⋄ RLI | 、| PDI ⋄ 文本6 | 四个运行等级。

  • |文本2 | 独自构成一个隔离运行序列,因为,紧随着隔离启动器之后的运行等级会开启一个新的隔离运行序列,与之匹配的PDI之前的运行等级会结束它的隔离运行序列。|文本2 | 位于 |文本1 ⋄ RLI | 中的RLI之后,同时位于 | PDI ⋄ 文本3 ⋄ RLI | 中的PDI之前,因此 |文本2 | 独自构成一个隔离运行序列。

  • 其余隔离运行序列的分析方法与以上相同,不再重述。

3、最终的运行等级和隔离运行序列如下所示:

运行等级(共有7个):

|文本1 ⋄ RLI |  → 等级0

|文本2 | → 等级1

| PDI ⋄ 文本3 ⋄ RLI | → 等级0

|文本4 | → 等级1

| PDI ⋄ RLI | → 等级0

|文本5 | → 等级1

| PDI ⋄ 文本6 | → 等级0

隔离运行序列(共有4个)

|文本1 ⋄ RLI | | PDI ⋄ 文本3 ⋄ RLI | | PDI ⋄ RLI | | PDI ⋄ 文本6 | → 等级0

|文本2 | → 等级1

|文本4 | → 等级1

|文本5 | → 等级1

 

4、图形法表示运行等级和隔离运行序列

1为运行等级和隔离运行序列的图形表示法,等级=0的隔离运行序列由4个运行等级组成(虚线上方对应的运行等级),小括号范围内的运行等级不属于该隔离运行序列,等级=1的隔离运行序列虽然是画在同一行上的,但表示的是3个等级=1的隔离运行序列而不是一个由多个运行等级组成的等级=1的隔离运行序列,等级=0的隔离运行序列才表示的是一个由多个运行等级组成的隔离运行序列。

 

 

 

示例2(不含隔离启动器): 

 

分析如下文本的运行等级和隔离运行序列,本示例主要是要明白以下分段1的文本与分段2和分段3的文本的隔离运行序列的区别

分段1:文本1 ⋄ RLE ⋄ 文本2 ⋄ PDF ⋄ RLE ⋄ 文本3 ⋄ PDF ⋄ 文本4    

分段2文本1 ⋄ RLE ⋄ 文本2 ⋄ PDF ⋄ 文本3 ⋄ RLE ⋄ 文本4 ⋄ PDF ⋄ 文本5    

分段3文本1 ⋄ RLI ⋄ 文本2 ⋄ PDI ⋄ RLI ⋄ 文本3 ⋄ PDI ⋄ 文本4    

 

1、各分段的图形表示法分别如图2、图3、图4所示:


 

 

 

2、运行等级的分析方法:

  • 注:根据bidi的算法,字符RLE、LRE、PDF会被移除,因此,分析时可忽略。但是,RLI、LRI、FSI、PDI不会被移除,因此,分析时不可忽略。

  • 分段1:很明显文本1、文本4的嵌入等级为0,文本2和文本3的嵌入等级1,因此,分段1共有3个运行等级,如下所示

|文本1 | → 等级0

|文本2 | |文本3 | → 等级1

|文本4 | → 等级0

  • 分段2:文本1、文本3、文本5的嵌入等级0,文本2和文本4的嵌入等级都为1,因此,分段2的运行等级如下所示
|文本1 | → 等级0
|文本2 | → 等级1
|文本3 | → 等级0
|文本4 | → 等级1
|文本5 | → 等级0
  • 分段3:文本1 ⋄ RLIPDI ⋄ RLIPDI ⋄ 文本4的嵌入等级为0,文本2和文本4的嵌入等级为1,因此,分段3的运行等级如下所示

|文本1 ⋄ RLI | → 等级0
|文本2 | → 等级1
| PDI ⋄ RLI | → 等级0
|文本3| → 等级1
| PDI ⋄ 文本4 | → 等级0

3、隔离运行序列的分析方法:

  • 分段1:没有隔离启动器,因此每个运行等级构成一个隔离运行序列,因此,其隔离运行序列共有3个,如下:

序列1:|文本1 | → 等级0
序列2|文本2 | |文本3 | → 等级1
序列3|文本4 | → 等级0

分段2:与分段1相同,每个运行等级构成一个隔离运行序列,因此,分段2共有5个隔离运行序列,如下:

序列1:|文本1 | → 等级0
序列2|文本2 | → 等级1
序列3|文本3 | → 等级0
序列4|文本4 | → 等级1
序列5||文本5 | → 等级0
分段3:因含有隔离启动器(其分析方法见示例1),其隔离运行序列如下(共有3个)
序列1:|文本1 ⋄ RLI | | PDI ⋄ RLI | | PDI ⋄ 文本4 | → 等级0
序列2|文本2 | → 等级1
序列3|文本3| → 等级1

 


第4节 bidi算法总览

 

对Unicode算法的完整测试,建议在以下由Unicode推荐的网站进行https://www.unicode.org/cldr/utility/bidic.jsp?

一、bidi算法基本规则及思想
1、再次提醒:在bidi算法中定向格式化字符是作为一个字符处理的,比如a LRE b,这里共有3个字符,分别是a、LRE、b。
2、强字符的方向是确定的,要么为从左向右(称为L类型),要么为从右向左(称为R类型),其中AL类型视为R类型。
3、最终类型(在不引起混淆的情况下,本文有时会将其简称为类型):bidi算法会把每个字符(包括定向格式化字符)都转换为L、R、EN、AN四种类型之一,因此,最终类型是指字符的L、R、EN、AN类型。
4、bidi算法规定(这是强制规定):L类型字符的嵌入等级必须是偶数,R类型字符的嵌入等级必须是奇数,若不满足以上要求,需对字符的嵌入等级进行调整(调整规则见I1~I2算法)或作硬性规定,此规则,通常可以反过来理解(注:字符串含有EN、AN时就不能这样理解了),即,最终的嵌入等级若是奇数,则该字符是R类型,若是偶数,则该字符是L类型。同理,运行等级,分段的嵌入等级也需要满足此规定。
5、bidi算法的一个基本思想是,首先确定各个字符的嵌入等级,然后,把所有字符都调整为L、R、EN、AN四种类型之一,并根据字符的这些类型调整字符的嵌入等级为偶数或奇数,然后对调整后具有相同嵌入等级组成的子串进行重排序并显示。也就是说,所有的字符类型,比如ON、LRI、WS、NSM等最后都会被调整为L、R、EN、AN四种类型之一。
6、总体来讲,bidi算法分为4大步,即,
  • 初次确定各字符的嵌入等级(P、X系列算法)

  • 调整字符为L、R、EN、AN四种类型之一(W、N系列算法),即,把字符的类型调整为最终类型

  • 调整嵌入等级 ( I系列算法),调整后的嵌入等级为最终嵌入等级

  • 重排序(L系列算法)。

二、周围字符的类型对字符最终类型的影响及bidi调整字符类型的方法
1、bidi算法调整字符类型的方法是根据该字符周围字符的类型对该字符的类型进行调整,也就是说,周围字符的类型会对该字符的最终类型产生影响,具体的调整算法详见W和N系列算法。
2、强字符、弱字符、中性字符对周围字符的影响
1)、强字符会对其前后的中性字符的最终类型产生影响,除L类型的强字符会对之后的EN字符(欧洲数字,弱类型)的最终类型产生影响外,强字符不会对弱字符的最终类型产生影响。从表1可见,大部分的字符都是强字符,比如,阿拉伯字符(从右到左),汉字(从左到右)、英文字符(从左向右)等。
2)、中性字符不会影响周围文本的最终类型,大部分的标点符号和空格都是中性字符,比如符号“;”,“[”,“)”等都是中性字符
3)、弱字符不会对其前后字符的最终类型产生影响,数字就是弱字符。
3、定向格式化字符对周围字符的影响
1)、隔离(LRI、RSI、FSI)范围内的字符不会影响到外部字符的排序,反之亦然。隔离范围内的字符作为一个整体对周围字符的影响与中性字符相同。
2)、嵌入(LRE、RLE)范围内的字符会影响到外部字符的排序,反之亦然。嵌入内的字符作为一个整体对周围字符的影响与强字符相同。由于嵌入会对周围字符产生很强的影响从而导致新的问题,因此在支持隔离的平台上,建议使用隔离而不是嵌入。
3)、重写格式化字符可以强制改变文本的书写方向,因此存在着安全问题,除非特殊情况,否则不应使用重写格式化字符RLO、LRO。重写范围内的字符作为一个整体对周围字符的影响与强字符相同。
三、bidi算法总体概述(详细规则见下一节)
1、bidi算法由多个算法组成,每个算法都有一个名称,每个算法又包含多个子算法或步骤,比如P系列算法,共有P1、P2、P3三个步骤,P系列算法是解析字符顺序时的第一步。下面为bidi的各种算法及其作用:
  • P系列算法用于解析分段的嵌入等级
  • X系列算法用于解析分段中各个字符的嵌入等级,此步骤解析出的字符并不一定满足L类型的嵌入等级为偶数,R类型为奇数的规则,需在I系列进行调整。
  • W系列算法用于解析弱字符的类型,即调整弱字符的类型为最终类型
  • N系列算法用于中性和隔离格式化字符的类型为最终类型
  • I系列算法用于调整各字符的嵌入等级为奇数或偶数。
  • L系列算法用于重排序
2、以下为使用bidi算法的步骤,及各系列算法的主要规则(详细规则见下一节)
1)、步骤1:算法编号P1~P3
把文本分成段,然后以段为作用域进行后续算法的处理。若段的方向为从左向右(即L),则分段的嵌入等级为0(偶数),若段的方向为从右向左(即R),则分段的嵌入等级为1(奇数)。通常,在分段的嵌入等级确定后,还需要解析出各字符在调整之前的bidi类型
2)、步骤2:算法编号X1~X10
计算出各字符原始的、调整之前的嵌入等级,在该系列算法中,还会确定出运行等级和隔离运行序列,该步骤的几种基本规则如下:
  • RLE、LRE、RLI、LRI、FSI、LRO、RLO会提升之后的字符的嵌入等级,具体怎样提升的,详见下一小节对X系列算法的讲解。

  • PDF、PDI会把之后字符的嵌入等级还原到之前的嵌入等级。

  • 算法X9规定,RLE、LRE、RLO、LRO、PDF、BN会被移除,移除后这些了符只会作为占位符,在之后的分析中,这些字符表现得就像不存在一样,但是FSI、LRI、RLI、PDI不会被移除,由于对嵌入等级和字符类型的调整都是在该步骤之后,所以RLE、LRE、RLO、LRO、PDF、BN这些字符对整个字符排序的影响可忽略,其嵌入等级可不考虑,当使用图形法分析时,这些字符的嵌入等级使用x代替

   3)、步骤3:算法编号W1~W7
把弱字符调整为L、EN、AN、ON四种类型之一
   4)、步骤4:算法编号N0~N2
把中性和隔离格式化字符调整为L或R类型
   5)、步骤5:算法编号I1~I2
根据各个字符的L或R类型调整各字符的嵌入等级,在此步骤之前确定的字符的L类型不一定是偶数,同理R类型也不一定是奇数,该步骤的主要作用是把L类型的嵌入等级调整为偶数,R类型的嵌入等级调整为奇数,其方法比较简单,就是把不符合要求的字符的嵌入等级加1即可。该步骤的调整并不会影响之前确定的运行等级
   5)、步骤6:算法编号L1~L4
重排序具有相同嵌入等级的子串,并显示。该步骤的主要算法为:从嵌入层次最深的子串开始将所有字符进行反转(即,所有字符按从右向左排序输出),嵌入多少层就重复多少次该操作,比如,"ab cd ef"嵌入的层次为3层(即嵌入等级为3),则第1次反转为fe dc ab,第2次在第1次反转后的结果上反转为ab cd ef,第3次在第2次的基础上反转为fe dc ba,最后显示为fe dc ba
3、总结:bidi算法需要对各字符的bidi类型、嵌入等级进行调整,但运行等级、隔离运行序列是不会被调整的,一旦确定会保持不变,因此,bidi算法的关键是要正确的调整各字符的bidi类型和嵌入等级,最后再进行反转。
 示例3分析如下字符串的显示顺序,
 

 
说明:
  • 控制字符RLE和PDF不会被显示

  • 基础方向假设为L,即分段的嵌入等级为0

  • 第11和12的个字符是希伯来文(控制字符RLE和PDF分别算一个字符)

  • 注意:因字符串中包含有从右向左的希伯来文,字处理软件(比如word)显示这些字符串的顺序可能与这些字符的顺序不一致,为避免显示上的混乱,上面的字符串是以图片形式给出的。

  • 以下解题步骤中的Unicode代码为16进制,嵌入等级中的x表示该字符的嵌入等级不会被考虑,由算法调整后的选项以粗体加灰色背景显示。

  • 本示例暂不讲解空格的处理方法,

1、步骤1:处理分段:由题知基础方向为L,即分段的嵌入等级为0。
2、步骤2(X1~X10)
分析各字符bidi类型、计算嵌入等级、运行等级及隔离运行序列(以下简称为序列)

 

 

3、步骤3和步骤4:解析弱字符、中性字符、隔离格式化字符

4、步骤5:调整各字符的嵌入等级

 5、步骤6:反转各子串

  • 反转等级2:

  • 反转等级1,即,在上步基础上的第7~11(含空格,不含控制符)的个字符,该次反转是最终显示顺序,结果为:

 

参考文献:http://www.unicode.org/reports/tr9/

本文作者:黄邦勇帅(原名:黄勇)

声明:本文为 脚本之家专栏作者 投稿,未经允许请勿转载。

●  程序员35岁,依然可以“横行职场”

●  脚本之家粉丝福利,请查看!

●  那些裸辞的程序员,都干嘛去了?

● 致敬经典:Linux/UNIX必读书单推荐给你

● 9月份Github上最热门的JavaScript开源项目

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存