文章目錄
- 1. 拆分文章成一行一行
- 2. 拆分一行為不同層次的句子
- 3. 決定每個句子的層次和方向
- 4.確定一行中每個句子開始的方向和結束的方向
- 5. 標記每個句子中每個強字元的方向
- 6. 標記弱字元的方向
- 7. 標記中性字元的方向
- 8.標記每個字元的層次
- 9. 根據層次反轉字元
- 1. 例子一
說明
本文內容參考 The Bidiretional Algorithm
本文並不是對The Bidirection Algorithm的翻譯,而是將比較常碰到的問題摘錄下來。同時從比較簡單的例子來說明這個演算法是應用的。因此也並沒有覆蓋裡面所講的每一個細節。同時比較可以借鑒的是我對演算法的理解(當然,也有可能因理解的不準確而有偏差)。
目前網頁的排版基本上分成LTR(從左至右,left-to-right)和RTL(從右至左,right-to-left)。比較多的語言預設的排版是LTR,比較典型的RTL有阿拉伯文和希伯來文。這裡就是介紹如果兩種不同的排版的文字在同一段文章裡面是如何排版的。本文主要基於網頁的排版,當然,很多其他軟體的排版也是基於這種演算法來排版的。
以下的例子都以阿拉伯文和英文來舉例。其中大寫字母表示阿拉伯文,比如AB C。小寫字母表示英文,比如abc。同時因為LTR的排版比較簡單,將基本上以RTL為例子。
術語介紹
首先是RLE和LRE。RLE是指嵌入在一段LTR文字中的RTL文字。例如 english text "I AM ARABIC" at the end,其中的"I AM ARABIC"就可以認為是RLE。反之為LRE。RLE和LRE是用來把一段文章分成不同層次的句子來處理的。比如剛才的句子中,在演算法的處理過程中,會分成三個句子來分別處理,english text作為第0層的句子,I AM ARABIC作為第1層的句子,at the end 作為第2層的句子。
上面談到的層是演算法中比較關鍵的一個概念。其中分成嵌入層次(Embedding Level)和演算法處理得將要結束的時候得到的每個字元的層次(Resolved Implicit Level)。嵌入層次由上面提到的RLE和LRE決定。
然後是RLM和LRM。英文裡面叫Right-to-Left-Mark和Left-to-Right-Mark,是不可見的特殊字元。RLM可以認為是RTL排版的語言裡面的一個強字元,而LRM可以認為是LTR排版的語言裡面的一個強字元。
上面提到的強字元是也是一種很關鍵的概念。所謂強字元表示排版的系統知道這個字元是RTL的字元,任何一個阿拉伯的字元和希伯來的字元都可以認為是強字元(其類型為RTL),其他LTR語言的字母,文字也是強字元(其類型為LTR)。相對應的是弱字元,即排版系統不知道這個字元屬於RTL還是LTR的字元,這種字元的排版取決於很多的因素(就是下面的演算法的關鍵了)。比如數字 - 1,2,3...。還有一種是中性字元,比如標點符號 - 括弧,逗號,句號...;空格。注意:引號並不總是(其實我還不太確定不成雙成對的引號的處理辦法,但如果引號是成雙成對的話,那肯定是作為嵌入句子的分符號)代表中性字元,因為引號用來區分開不同的各個不同層的句子,以後會出現回各個不同句子的分割處。
排版最後要區分出來的是每個字元的方向,分成R和L兩種。R表示向右,L表示想左。RTL強字元的方向是R,LTR強字元的方向是L。
演算法步驟1. 拆分文章成一行一行
排版的開始需要將一段話分配到不同的行,不管是從左至右,還是從右至左,每行能夠容納的字元的數量是定的。因此系統會從第一個字元開始,一個一個字元計算寬度,當累積的寬度超過一行時,就從當前的字元斷開,將之前所有的字元作為下面處理的基礎。根據斷行的不同,最終的排版的效果可能會有很根本的區別。
比如:
abc "I AM ARABIC" 2 ARABIC
如果到2的時候就斷行,則abc "I AM ARABIC" 2會作為一行句子處理,這時候2會認為是前面I AM ARABIC" 2 的一部分,第一行會分成abc 和 I AM ARABIC" 2兩句話(注意第一個引號作為嵌入的標誌而移出了,第二個引號因為後面跟的是2,並不認為是嵌入的結束,因此作為句子的一部分進行處理了);而如果是到2前面斷行,則第一行會分成 abc 和 I AM ARABIC兩句(兩個引號都認為是嵌入的標誌)。
2. 拆分一行為不同層次的句子
根據引號(包括單雙引號),把句子分成不同層次的句子。
比如:
abc "ARABIC english text" HEBREW TEXT
分成三個句子,
abc
ARABIC english text
HEBREW TEXT
3. 決定每個句子的層次和方向
首先要從段落的初次層次說起。段落的初始層次可以是0和1,取決於當前段落的基本方向,如果基本方向是L,則為0,或者為1。當前段落的基本方向由段落中第一個強字元的方向決定,比如abc ARABIC 的初始層次為0,而(23) A abc的初始層次為1。但是如果在段落的更高層次的地方聲明了段落的方向,則由聲明的方向決定。
比如HTML中的dir屬性,就可以定義段落的方向。<div dir="rtl">abc</div>將段落的方向定義為R,其初始層次為1。
然後每個句子的初始層次分別遞增。
比如以上abc "ARABIC english text" HEBREW TEXT會分層以下的層次
0: abc
1: ARABIC english text
2: HEBREW TEXT
4.確定一行中每個句子開始的方向和結束的方向
句子開始的方向和結束的方向是用來決定句子的開頭或者結尾沒有的弱字元。如123ab()這裡的123就要根據句子開始的方向來決定(如何決定下面會提到),()則要根據句子結束的方向輔助來判斷。
演算法大概是這樣的,一行最開始的句子的方向為段落的方向,結束的方向與開始的方向相反,以此類推下去。但一行最後一個句子結束的方向也和段落的方向一樣。
我們用sor (start of level run)來表示句子的開始的方向,eor表示句子結束的方向。
比如abc "ARABIC english text" HEBREW TEXT如果段落的方向為R,則
sor=R abc eor=L
sor=L ARABIC english text eor=R
sor=R text eor=R
最後一個eor=R是因為段落的方向為R。
5. 標記每個句子中每個強字元的方向
強字元的方向是很容易確定的,RTL強字元的方向是R,LTR強字元的方向是L。
比如:
| 步驟 |
0 |
b |
c |
空格 |
1 |
2 |
3 |
空格 |
A |
( |
4 |
d |
5 |
) |
B |
C |
6 |
. |
|
| 第5步後 |
|
L |
L |
|
|
|
|
|
R |
|
|
L |
|
|
R |
R |
|
|
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
6. 標記弱字元的方向
弱字元主要是數字。從弱字元往前面查抄,知道找到上一步標記的L,R或sor為止。如果找到L(如果sor為L,也算),則將弱字元標記成L。
比如 (當前句子的sor為R)
| 步驟 |
0 |
b |
c |
空格 |
1 |
2 |
3 |
空格 |
A |
( |
4 |
d |
5 |
) |
B |
C |
6 |
. |
|
| 第5步後 |
|
L |
L |
|
|
|
|
|
R |
|
|
L |
|
|
R |
R |
|
|
|
| 第6步後 |
|
L |
L |
|
L |
L |
L |
|
R |
|
|
L |
L |
|
R |
R |
|
|
|
上面例子中,0找到的sor(為R),不標記。1,2,3都找到字元c,其為L,因此標記為L。5找到d,也為L。4找到A(為R),不標記。6找到C(為R),不標記。
7. 標記中性字元的方向
兩個R之間的中性字元標記成R,兩個L之間的中性字元標記成L。注意,因為在第6步的時候所有的數字,如果表現為L的話,都已經標記成了L了,所以現在剩下的所有的數字都表現(這裡用表現表示其行為上一致,但因為後面有和R不同的行為,所以不標記成R)為R,我們可以將數字表示成EN(European Number)(實際上有AN和EN之分,AN表示Arabic Number,為現在阿拉伯文字中用的數字-不同於我們熟悉的阿拉伯數字;EN表示歐洲標準的數字,其實就是我們熟悉的阿拉伯數字。但因為這兩者沒有排版上的根本區別,這裡都用EN表示)。同時在這一步的時候,sor作為第一個字元的相鄰字元,eor作為最後一個字元的相鄰字元。
在上面的步驟之後,其他剩下的與sor的方向一樣。
比如 (當前句子的sor為R)
| 步驟 |
0 |
b |
c |
空格 |
1 |
2 |
3 |
空格 |
A |
( |
4 |
d |
5 |
) |
B |
C |
6 |
. |
|
| 第5步後 |
|
L |
L |
|
|
|
|
|
R |
|
|
L |
|
|
R |
R |
|
|
|
| 第6步後 |
|
L |
L |
|
L |
L |
L |
|
R |
|
|
L |
L |
|
R |
R |
|
|
|
| 第7步後 |
R |
L |
L |
L |
L |
L |
L |
R |
R |
R |
EN |
L |
L |
R |
R |
R |
EN |
R |
|
0因為相鄰的sor和b分別為R和L,取sor方向。第一個空格因為相鄰的都為L,方向為L。第二個空格相鄰的3和A分別為L和R,取sor方向。"("相鄰的A和4都為R(4也認為是R),方向為R。")"相鄰的5和B分別為L和R,取sor方向。"."相鄰為6和eor,都為R,取R。
8.標記每個字元的層次
根據當前句子的層次(用EL表示),標記每個字元的層次:
字元的 方向(類型) |
句子的層次 |
| 偶數 |
奇數 |
| L |
EL |
EL+1 |
| R |
EL+1 |
EL |
| EN |
EL+2 |
EL+1 |
如果當前的層次為1,則
| 步驟 |
0 |
b |
c |
空格 |
1 |
2 |
3 |
空格 |
A |
( |
4 |
d |
5 |
) |
B |
C |
6 |
. |
|
| 第5步後 |
|
L |
L |
|
|
|
|
|
R |
|
|
L |
|
|
R |
R |
|
|
|
| 第6步後 |
|
L |
L |
|
L |
L |
L |
|
R |
|
|
L |
L |
|
R |
R |
|
|
|
| 第7步後 |
R |
L |
L |
L |
L |
L |
L |
R |
R |
R |
EN |
L |
L |
R |
R |
R |
EN |
R |
|
| 第8步後 |
1 |
2 |
2 |
2 |
2 |
2 |
2 |
1 |
1 |
1 |
2 |
2 |
2 |
1 |
1 |
1 |
2 |
1 |
|
如果當前的層次為0,則
| 步驟 |
0 |
b |
c |
空格 |
1 |
2 |
3 |
空格 |
A |
( |
4 |
d |
5 |
) |
B |
C |
6 |
. |
|
| 第5步後 |
|
L |
L |
|
|
|
|
|
R |
|
|
L |
|
|
R |
R |
|
|
|
| 第6步後 |
|
L |
L |
|
L |
L |
L |
|
R |
|
|
L |
L |
|
R |
R |
|
|
|
| 第7步後 |
R |
L |
L |
L |
L |
L |
L |
R |
R |
R |
EN |
L |
L |
R |
R |
R |
EN |
R |
|
| 第8步後 |
1 |
0 |
0 |
0 |
0 |
0 |
0 |
1 |
1 |
1 |
2 |
0 |
0 |
1 |
1 |
1 |
2 |
1 |
|
9. 根據層次反轉字元
從當前句子的最高的層次開始,反轉當前和以上層的字元。一直到第1層為止。
比如
如果當前的層次為1,則
| 步驟 |
0 |
b |
c |
空格 |
1 |
2 |
3 |
空格 |
A |
( |
4 |
d |
5 |
) |
B |
C |
6 |
. |
|
| 第5步後 |
|
L |
L |
|
|
|
|
|
R |
|
|
L |
|
|
R |
R |
|
|
|
| 第6步後 |
|
L |
L |
|
L |
L |
L |
|
R |
|
|
L |
L |
|
R |
R |
|
|
|
| 第7步後 |
R |
L |
L |
L |
L |
L |
L |
R |
R |
R |
EN |
L |
L |
R |
R |
R |
EN |
R |
|
| 第8步後 |
1 |
2 |
2 |
2 |
2 |
2 |
2 |
1 |
1 |
1 |
2 |
2 |
2 |
1 |
1 |
1 |
2 |
1 |
|
| 開始 |
0bc 123 A(4d5)BC6. |
| 反轉第2層 |
0321 cb A(5d4)BC6. |
| 反轉第1層以上 |
.6CB(4d5)A bc 1230 |
如果當前的層次為0,則
| 步驟 |
0 |
b |
c |
空格 |
1 |
2 |
3 |
空格 |
A |
( |
4 |
d |
5 |
) |
B |
C |
6 |
. |
|
| 第5步後 |
|
L |
L |
|
|
|
|
|
R |
|
|
L |
|
|
R |
R |
|
|
|
| 第6步後 |
|
L |
L |
|
L |
L |
L |
|
R |
|
|
L |
L |
|
R |
R |
|
|
|
| 第7步後 |
R |
L |
L |
L |
L |
L |
L |
R |
R |
R |
EN |
L |
L |
R |
R |
R |
EN |
R |
|
| 第8步後 |
1 |
0 |
0 |
0 |
0 |
0 |
0 |
1 |
1 |
1 |
2 |
0 |
0 |
1 |
1 |
1 |
2 |
1 |
|
| 開始 |
0bc 123 A(4d5)BC6. |
| 反轉第2層 |
0bc 123 A(4d5)BC6. |
| 反轉第1層以上 |
0bc 1234(A d5.6CB) |
例子及修正方法1. 例子一
注意:一下的例子中大寫字母不再表示阿拉伯文,這種字元 ينمتم 才是阿拉伯文
在阿拉伯的網頁中,我們有以下的文字要顯示 English (UK),因為當前的網頁由以下的聲明<div dir="rtl">,因此我們的段落的基本方向為R,這句直接作為一個句子進行處理。其sor為R,eor為R。
| 步驟 |
E |
n |
g |
l |
i |
s |
h |
空格 |
( |
U |
K |
) |
| 第5步後 |
L |
L |
L |
L |
L |
L |
L |
|
|
L |
L |
| 第6步後 |
L |
L |
L |
L |
L |
L |
L |
|
|
L |
L |
| 第7步後 |
L |
L |
L |
L |
L |
L |
L |
L |
L |
L |
L |
R |
| 第8步後 |
2 |
2 |
2 |
2 |
2 |
2 |
2 |
2 |
2 |
2 |
2 |
1 |
| 開始 |
English (UK) |
| 反轉第2層 |
KU( hsilgnE) |
| 反轉第1層以上 |
)English (UK |
因此字元會顯示成 ")English (UK" (實際上會顯示成 "(English (UK" 因為顯示的時候會根據字元的特性作一些自身的反轉,比如")"會被反轉成"(").
如何修正呢?
我們可以在左括弧的前面几上一個RLM字元(‏),右括弧的後面加上一個RLM字元。RLM字元並不會顯示。這樣計算就變成
| 步驟 |
E |
n |
g |
l |
i |
s |
h |
空格 |
RLM |
( |
U |
K |
) |
RLM |
| 第5步後 |
L |
L |
L |
L |
L |
L |
L |
|
R |
|
L |
L |
|
R |
| 第6步後 |
L |
L |
L |
L |
L |
L |
L |
|
R |
|
L |
L |
|
R |
| 第7步後 |
L |
L |
L |
L |
L |
L |
L |
R |
R |
R |
L |
L |
R |
R |
| 第8步後 |
2 |
2 |
2 |
2 |
2 |
2 |
2 |
1 |
1 |
1 |
2 |
2 |
1 |
1 |
| 開始 |
English (UK) |
| 反轉第2層 |
hsilgnE (KU) |
| 反轉第1層以上 |
(UK) English |
因此字元會顯示成 (UK) English,就是我們在RTL裡面想要的排版。