bidi 算法及 HTML 中的实现

来源:互联网 发布:二重积分的算法 编辑:程序博客网 时间:2024/06/06 04:01

bidi 算法及 HTML 中的实现

对于全球化 Web 工程师来说,了解页面文字的方向性非常重要。因为显示从右向左的文字更为复杂,同时页面的组织和排版也会受到阅读方向的影响。本文将从语言和文字的方向性入手介绍双向字符(bidirectional characters),并对 Unicode 双向算法(Unicode Bidirectional Algorithm)的工作原理进行阐述。通过对比 Unicode 控制字符和 HTML 标记,读者可以了解在 Web 中实现文字方向性控制的两种方法以及其各自特点。本文使用在 HTML 中实现 bidi 支持的例子更能让读者对 bidi 在 Web 中的实现拥有更加直观和深入的理解。

夏 音, 软件工程师, IBM

陈 挚, 软件工程师, IBM

2014 年 4 月 17 日

  • expand内容

文字、语言和方向

在开始介绍 bidi 算法和其在 HTML 中的实现之前,我们先来了解一下文字、语言和方向的关系,这也将有助于在后文中更好地理解 bidi 算法。

什么是文字和语言呢?我们引用 IBM Terminology 中的定义:

  • 文字是使用于书面语的图形符号的集合。文字和语言或者国家/地区并不是一对一的关系。相同语系中的成员也可能使用不同的文字。例如,大部分的西欧语言都使用拉丁文,同样,阿拉伯文字不仅在阿拉伯国家中广泛使用而且也被用于伊朗的波斯语和巴基斯坦的乌尔都语。
  • 而语言是指用于交流信息的一套字符、惯例和规则的集合。

从上面的定义中我们可以了解到,文字和语言实际上是多对多的关系。一种文字可以被多种语言所使用,比如拉丁文。而另一方面,一种语言可能使用多种文字来表述,比如日语,它使用了日语汉字、平假名、片假名三种文字系统,而且还可以使用拉丁文来表述。

那书写方向和文字、语言有怎样的关系呢?严格来说,书写方向是与文字相关的,而与语言没有多大关系。就像上文中所谈到的,一种语言可以使用多种文字来表述,那同一种语言的书写方向就会因为所使用文字的不同而变化。比较经典的例子是阿塞拜疆语,它可以使用拉丁文字、西里尔文字和阿拉伯文字进行表述。当拉丁文字和西里尔文字被使用时,它的书写方向是从左到右,而当其使用阿拉伯文字时,书写方向就会变成从右到左。

文字的方向是多种多样的。多数的文字采用的是从左到右书写,而行的顺序是从上到下。而阿拉伯文字和希伯来文字等采用的是从右到左书写方向。我们当然还能很容易的想到,古代汉字的书写方向是从上到下,而行的顺序是从右到左。我们可以想象,如果现代汉字仍然采用古代的书写方向,这会让我们的 Web 工程师在设计页面方向和布局时是多么苦恼啊!

一般而言,当人们谈到从右到左的文字时,会首先联想到阿拉伯文字和希伯来文字。当然,还有一些其他的文字也是从右到左书写的,比如古叙利亚文字。而从左到右书写的文字主要包括拉丁文、西里尔文、希腊文等等。另外,形意文字(如中文、日文和韩文中使用的文字)一般也是按照从左到右的顺序书写,不过有时候也会出现其他的书写方向,就像前文介绍的汉字那样。

本文中将介绍的 bidi 实际是英文单词 bidirectional (双向)的缩写,进一步来说,我们这里所说的 bidi 是指双向文字。顾名思义,双向文字是指同时包含了两种书写方向的文字,也就是从左到右和从右到左的文字同时存在。一般来说,这种情况多数存在于从右向左书写的文字中,比如,阿拉伯文字和希伯来文字,这些文字中字母的书写顺序是从右到左的,但当其中包含数字(实际上阿拉伯数字是起源于印度的)或拉丁文字(如英文字母)时,数字和拉丁文字部分的书写顺序就会变成从左到右。同样,从左到右书写英文字母时遇到阿拉伯文字,那阿拉伯文字的部分就会按照从右到左来书写。

因此,对于全球化 Web 工程师来说,了解页面文字的方向性非常重要。因为显示双向的文字更为复杂,而正确地显示文字的方向是正确理解文字的基础,不同的方向可能会导致理解上的不一致。同时,页面的组织和排版也会受到阅读方向的影响。从下面的例子中您或许就能理解文字方向的重要性了(在该例子中,用大写英文字母表示从右到左的文字,而小写英文字母用来表示从左到右的文字。下文中的例子都将使用这种模式。):

John does not believe TAHT YAS SYAWLA I.

当我们从左到右来阅读时,这个句子是“John does not believe I always say that.”,而如果从右到左来阅读这个句子,它就变成了“I always say that John does not believe.”。两个方向从语义上来说都是合适的,所以当不清楚这个句子的阅读方向时,就无法确定它真正要表达的意思。

Unicode 双向算法介绍

在现代计算机应用中,最常用来处理双向文字的算法是 Unicode 双向算法(Unicode Bidirectional Algorithm),在后面的文章中我们将 Unicode 双向算法简称为 bidi 算法。该算法用来确认双向文字显示时的方向性。当然,除了方向性,该算法还涉及字形变化(Shaping)和镜像分析(Mirroring)等等其它和 bidi 相关的特性,不过这部分特性的实现大多由底层应用所控制,无需 Web 工程师进行设定或干预。

我们在此文中并不会详细解析 Unicode 双向算法,就如刚才所说,该算法的大部分内容将由底层实现。这个段落中,您将了解到 bidi 算法的一些核心的或和 Web 开发相关的概念,以便更好地在后面理解 HTML 中和 bidi 相关的设定是在改变或控制着什么特性。

双向字符类型(Bidirectional Character Types)

正如前文介绍的,书写方向是和文字相关,阿拉伯文字从右到左,拉丁文字从左到右。当人们在纸上书写时当然会记得这些规则,那计算机是如何知道的呢?实际上,Unicode 定义了它其中每个字符的方向属性,计算机就是根据这个方向属性来判断该文字的方向。

Unicode 方向属性包含三种类型:强字符、弱字符和中性字符。在这三种主要类型下面还有很多细小的属性分类。大部分的字符都属于强字符,比如英文字母、汉字和阿拉伯字母。它们的方向性是确定的,从左到右或者从右到左,和其上下文的 bidi 属性无关。并且,强字符在 bidi 算法中可能会影响其前后字符的方向性。中性字符的方向性是不确定的,由上下文的 bidi 属性来决定其方向,比如大部分的标点符号和空格。有意思的是弱字符的特性,它们的方向是确定的,但对其前后字符的方向性并不会产生影响。数字和数字相关的一些符号就属于弱字符。

基础方向(Base Direction)

或者也称为全局方向(Global Direction)、页面方向、段落方向,定义的是一个区域内的总体方向,比如一个页面、一个段落或者一个句子的方向。它决定了应该从这个区域的哪边开始书写文字。比如,当某个区域内的双向文字主要是从右到左的文字,并包含了从左到右的文字时,该区域的全局方向一般为从右到左,那么,第一个文字的输入位置就会在该区域的最右侧。

方向串(Directional Run)

方向串是指在一段文字中具有相同方向性的连续字符,并且其前后没有相同方向性的其它方向串。请参考一下关于方向串的例子:

图1. 关于方向串的例子

在这个例子中,包含了三个方向串。该句子以从左到右的方向串开始,然后是从右到左的方向串,最后以从左到右的方向串结尾。

要注意的是,方向串的排列顺序和数目往往会受到全局方向的影响。上面的例子中采用是从左到右的全局方向,如果该全局方向变为从右到左,那这个例句中方向串的排列顺序将如下图所示:

图2. 方向串的排列顺序

在从左到右的全局方向中,第一个方向串将从最右侧开始,在最左侧结束。可能您也观察到方向串的个数从之前的三变成了四,句子结尾处的句号的方向性随着全局方向的变化也发生了变化。这同时也说明了中性字符(这个句号)的方向性会受到上下文 bidi 属性的影响。

关于方向的 Unicode 控制字符

大部分情况,Unicode 双向算法能根据字符属性和全局方向等信息运算并正确地显示双向文字,这是该算法的隐性模式。在这种模式下,双向文字的显示方式基本上由算法完成,不需要人为的干预。但是,隐性模式的算法在处理复杂情况的双向文字时会显得不足,这时就可以使用显性模式来进行补充。在显性模式的算法中,除了隐性算法的运算外,可以在双向文字中加入关于方向的 Unicode 控制字符来控制文字的显示。这些被加入文字中的 Unicode 控制字符在显示界面上是不可见的,也不占用任何显示空间。它们只是在默默地影响着双向文字的显示。

Unicode 控制字符又可以分为两类,第一类为隐性双向控制字符:

U+200E:   LEFT-TO-RIGHT MARK (LRM)U+200F:   RIGHT-TO-LEFT MARK (RLM)

简单来说,您可以将这类的控制字符看成是不会显示出来的强字符,LRM 为从左到右的强字符,而 RLM 为从右到左的强字符。

而第二类当然就是显性双向控制字符:

U+202A:   LEFT-TO-RIGHT EMBEDDING (LRE)U+202B:   RIGHT-TO-LEFT EMBEDDING (RLE)U+202D:   LEFT-TO-RIGHT OVERRIDE (LRO)U+202E:   RIGHT-TO-LEFT OVERRIDE (RLO)U+202C:   POP DIRECTIONAL FORMATTING (PDF)

这类控制字符需要成对使用,列表中的前四个为开始字符,而最后一个为结束字符。当双向算法遇到 LRE 时,接下来文字片段内的方向开始变为从左到右。当双向算法遇到 RLE 时,接下来文字片段内的方向开始变为从右到左。当遇到 LRO 时,双向算法会将后面所有文字的双向属性视为从左到右强字符。当遇到 RLO 时,双向算法会将后面所有文字的双向属性视为从右到左强字符。如果一旦遇到 PDF 字符,双向属性的状态就会恢复到最后一个 LRE、RLE、LRO 或 RLO 之前的状态。

如何使用这些关于方向的 Unicode 控制字符,您可以参见后面使用 HTML 元素的例子。

关于方向的 HTML 元素

在 HTML 中除了可以使用那些关于方向的 Unicode 控制字符,还可以使用 HTML 中提供的标签或者属性来控制双向文字的显示。而且,当可以使用标签或者属性时,建议使用这些 HTML 元素来控制方向,而不使用Unicode 控制字符。最主要的原因是 Unicode 控制字符是不可见的,在使用和维护时容易出现问题,而 HTML元素可以避免这个问题。

属性 dir

与 LRE 控制字符和 RLE 控制字符相对应的 HTML 元素是 dir = "ltr" 和 dir = "rtl" (ltr 的英文全称为 left to right,而 rtl 的英文全称为 right to left)这两个属性,它们就像其他的属性一样,可以被用在块级元素或内联元素中,并控制该区域的方向性。属性 dir 这个关键字就是英文单词 direction (方向)的缩写。实际上,这个属性控制的就是相应元素内的全局方向。

HTML5 中给 dir 属性加入了一个新的值“auto”。这个值与上面提到的 ltr 和 rtl 不同,它并不明确相应元素内的全局方向,而需要根据不同的情况进行判断。这个判断依据就是该元素内的第一个强字符。如果第一个强字符是从左到右的属性,那么 dir="auto" 的结果就等于 dir="ltr"。同理,如果第一个强字符是从右到左的属性,那么 dir="auto" 的结果就等于 dir="rtl"。需要注意的是,这里的判断条件的第一个强字符,而会忽略之前遇到的弱字符和中性字符。这种模式可以被称为自动方向性或者上下文方向性。

标签 <bdo>

而与 LRO 控制字符和 RLO 控制字符相对应的 HTML 元素分别为:

<bdo dir = "rtl"></bdo><bdo dir = "ltr"></bdo>

其中,<bdo dir = "rtl"> 和 <bdo dir = "ltr"> 相当于 LRO 控制字符和 RLO 控制字符,表示一段控制区域的开始。而 </bdo> 的作用就像是 PDF 控制字符,表示此控制区域的结束。该标签根据 dir 属性覆盖相应元素内的字符方向性。

关键字 bdo 的英文全称为 bidirectional override (双向性超控)。需要注意的是,在这个标签中的 dir 属性不能赋值为 auto。

标签 <bdi>

HTML5 还加入了一个新的元素,那就是标签 <bdi>,它的英文全称为 bidirectional isolate (bidi 隔离,笔者觉得翻译成双向性片段会比较合适)。该标签用来在双向文字中加入方向片段,使得该片段脱离其父元素的全局方向。有点类似于 <span> 标签的作用,但不同的是 <bdi> 标签本身带有默认方向属性。

<bdi> 标签中可以使用 dir 属性来指定方向性,但默认情况下该属性的值为 auto。也就是说,使用 <bdi>...</bdi> 的结果和使用 <bdi dir="auto">...</bdi> 是一样的。

当您无法预知要显示内容的方向性时,建议使用此标签,比如用户的输入。让该区域根据上下文来判断优先使用的方向。

关于方向的 HTML 实体

还需要说明的是,隐性双向控制字符 LRM 和 RLM 并没有与之相对应的 HTML 元素,不过建议使用 HTML 实体来代替 Unicode 控制字符。至少在编辑器中,这些 HTML 实体是可见的。

表1. 关于方向的 HTML 实体
Unicode 控制字符HTML 实体作用LEFT-TO-RIGHT MARK (LRM)&lrm;从左到右强字符RIGHT-TO-LEFT MARK (RLM)&rlm;从右到左强字符

HTML 实现 bidi 支持的例子

我们将在这个部分介绍一些 HTML 中关于 bidi 支持的基本设置。但实际情况往往更为复杂,需要考虑的方向性问题更多。比如,在方向串的边界处出现弱字符或者中性字符时常常会出现显示顺序的问题,需要用到 HTML 方向元素。那我们下面的例子就从基础的设置开始。

HTML 页面的全局方向

就如之前所介绍的,属性 dir 可以指定 HTML 元素内的全局方向。同样,它也可以用来定义整个 HTML 页面的全局方向。并且,该页面内的其他 HTML 元素将默认继承该全局方向,除非您针对某个元素再次修改了其方向性。

当指定页面的全局方向时,可以将属性 dir 添加入 <html> 标签内(其中的语言属性是可选的):

<html lang=”en” dir=ltr>…</html>或者<html lang=”ar” dir=”rtl”>…</html>

覆盖字符的原有双向属性

这里需要使用到 <bdo> 标签,在某些特殊的情况下改变原本的字符双向属性。比如,之前我们的例子中使用大写英文字母来代替阿拉伯字符。但默认情况下大写英文字符的方向是从左到右的,这时就可以用 <bdo> 覆盖它。

默认情况下,

清单1. HTML 元素默认的方向
<!DOCTYPE HTML><html><body><p>APPLE</p></body></html>

您将看到的显示结果为:

图3. 正常大写英文字母

在使用 <bdo> 标签改变字符方向属性后,

清单2. 覆盖后的方向性
<!DOCTYPE HTML><html><body><p><bdo dir="rtl">APPLE</bdo></p></body></html>

您将看到的显示结果为:

图4. 改变方向属性后的大写英文字母

改变字符方向属性后,此区域内的英文字母排列顺序也变成从右到左了。

HTML 元素内方向的指定

当 HTML 文件中没有使用任何的 dir 属性,那么该文件的方向会默认为从左到右。

如果您将 dir 指定为 ltr:

清单3. 指定从左到右的全局方向
<bdi dir="ltr">The apple is called <bdo dir="rtl">APPLE</bdo> in Arabic.</bdi>

您将看到的显示结果为:

图5. dir="ltr" 的效果

如果您将 dir 指定为 rtl:

清单4. 指定从右到左的全局方向
<bdi dir="rtl">The apple is called <bdo dir="rtl">APPLE</bdo> in Arabic.</bdi>

您将看到的显示结果为:

图6. dir="rtl" 的效果

如果您将 dir 指定为 auto:

清单5. 指定自动的全局方向
<bdi dir="auto">The apple is called <bdo dir="rtl">APPLE</bdo> in Arabic.</bdi>

您将看到的显示结果为:

图7. dir="auto" 的效果

因为这个句子中的第一个强字符为英文大写字母 T,所以 dir="auto" 等同于 dir="ltr" 的效果。

需要修正的方向性

比如下面的代码:

清单6. 需要修正的方向
<p>These fruits are called <bdo dir="rtl">APPLE</bdo>, <bdo dir="rtl">PEAR</bdo> and <bdo dir="rtl">ORANGE</bdo> in Arabic.</p>

我们期望看到的显示结果为:

图8. 期望的显示结果

但实际上这个句子显示出来后,您会看到这样的结果:

图9. 实际的显示结果

因为 ELPPA 和 RAEP 之间是中性字符逗号和空格,那么 ELPPA、RAEP、逗号以及空格会被作为一个方向串来处理。想要让这个句子按照期望的方式来显示,有两种修正方式。其中之一是使用 HTML 实体将这个方向串从中截断,如:

清单7. 使用 HTML 实体修正方向
<p >These fruits are called <bdo dir="rtl">APPLE</bdo>&lrm;, <bdo dir="rtl">PEAR</bdo> and <bdo dir="rtl">ORANGE</bdo> in Arabic.</p>

其中的 LRM 作为和这个方向串不同方向性的强字符打断了这个方向串的连续性。

另一个方式是使用 <bdi> 标签将所有从右到左的片段隔离起来,如:

清单8. 使用 HTML 方向标签修正方向
<p>These fruits are called <bdi><bdo dir="rtl">APPLE</bdo></bdi>, <bdi><bdo dir="rtl">PEAR</bdo></bdi> and <bdi><bdo dir="rtl">ORANGE</bdo></bdi> in Arabic.</p>

这两种方式虽然使用上有所不同,但原理上是类似的,就是将这三个阿拉伯水果名作为三个方向片段交给 Unicode 双向算法来处理。

结束语

在实际编辑 HTML 页面时,使用 Unicode 控制字符和相应的 HTML 元素能起到相同的控制效果。可以根据情况选择具体使用哪种方式,但当 HTML 元素能够被使用时,建议使用此种方式来控制页面和元素的方向性。另外,还需要注意的是,有些浏览器现阶段并不支持 HTML5 中新加入的这些元素。因此,在设计页面时使用何种控制办法也需要考虑到这个因素,尽量让更多的浏览器支持您所制作的页面。

0 0