Obeta

Unicode控制字符及其有关的双向算法

前端开发过程中经常会使用到各种Unicode 字符,但是使用的都是一些常见的空格,@等可视化符号,这次来了解一下Unicode中的控制字符及其有关的双向算法.

控制字符(有时候也称非打印字符),是出现在特定的信息文本中,表示某一控制功能的字符.这类字符并不显示,只包含某种特定的功能.

前段时间介绍了ASCII 控制字符,但是不止 ASCII 有这类控制字符,Unicode 也有,而且应用的更广泛.如果你使用 Safari 浏览器,建议换成 Chrome 打开控制台中Elements tab 阅读更佳.

介绍

我们需要理解的是:在所有主要的 Web 浏览器中内存中的字符顺序(逻辑)与它们显示的顺序(可视)是不同的.Unicode 定义了它其中每个字符的方向属性,浏览器应用的一组规则(依据规则判断文本 Unicode 方向属性应该使用哪一种)在显示时产生正确的顺序由 Unicode 双向算法进行描述,也可以简称为“bidi 算法”.

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

方向性相关字符效果
Left-to-Right (LTR)强字符从左至右(英文字母、汉子以及世界上大部分左->右书写的文字)方向性确定,LTR 或 RTL,和上下文无关.并且可能会影响其前后字符的方向性.
Right-to-Left (RTL)强字符从右至左(阿拉伯文字、希伯来文字以及右->左书写的文字)同上
Left-to-Right (LTR) / Right-to-Left (RTL)弱字符(数字和数字相关的符号)和强字符一样方向性也是确定的,但是不会影响前后字符的方向性.
Neutral中性字符(大部分标点符号和空格)方向性不确定,由上下文环境决定其方向

这个表格至关重要,涉及到后面所讲的所有知识点.

全局方向

也可以称为基础方向.全局方向是一个文本中的总体方向,文本在页面上显示方向运行的顺序取决于主要的全局方向,确定一个文本的全局方向主要靠以下几点:

  • 默认从文档(HTML)的左->右继承.
  • 如果有相关的dir属性或者direction样式,则根据相应的值指定方向.
<p>ولدت obeta 12 34 56 ولدت</p>
<p dir="rtl">ولدت 12 34 56 ولدت</p>

ولدت obeta 12 34 56 ولدت

ولدت 12 34 56 ولدت

方向串

方向串是指在一段文字中具有相同方向性的连续字符,这是由于 bidi 算法产生具有相同方向性的每个连续字符序列的单独方向运行,并且其前后没有相同方向性的其它方向串.

听起来可能很绕,下面看一个例子:

<p>ولدت (+86)138-3456-1000</p>
<p dir="ltr">ولدت (+86)138-3456-1000</p>
<p dir="rtl">ولدت (+86)138-3456-1000</p>

ولدت (+86)138-3456-1000

ولدت (+86)138-3456-1000

ولدت (+86)138-3456-1000

手机号并不是故意反着输入,而是输入完成后最后再后面加了一个阿拉伯文字就变成这样的,正确的手机号是(+86)138-3456-1000.

由于文中有包含阿拉伯文字(强类型 RTL),因此整个文本中的全局方向为右->左(即使手动设置了全局方向dirltr,也无法覆盖被强字符影响的中性字符).

弱类型的数字保持了自己本来的方向,而中性类型的字符()-+跟随了全局方向(首个强类型文字方向给出的).

方向串
方向串

上面文本被分为 7 个方向串,由于中性符号被全局方向影响,使得原本号码被拆分成不同方向串,被重新排序.为此 Unicode 标准中定义了一系列方向性控制字符,这些字符在界面上并不会显示.比如U+202E,可以强制文本右->左

<p>&#8238;welcome to my obeta blog.&#8236;</p>
<p>welcome to my &#8238;obeta&#8236; blog.</p>
<!-- U+202E 使它之后的所有方向串的方向被强制定向为 右->左 -->

‮welcome to my obeta blog.‬

welcome to my ‮obeta‬ blog.

但是为何上面代码块能显示这些控制字符呢?这是因为我所使用的 markdown 编译器自动帮我将所有的#转义成了 Unicode 中的&amp;来替换,你可以打开控制台选中代码块,右击选择Edit as HTML就能看到它.

如何使用?

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

下面列举了常用的一些控制字符:

名称方向Unicode CodeHTML Code模式
Left-To-Right Mark(LRM)左->右U+200E&#8206;(实体是&lrm;)隐性
Right-To-Left Mark(RLM)右->左U+200F&#8207;(实体是&rlm;)隐性
Left-To-Right Embedding(LRE)左->右U+202A&#8234;ordir ="ltr"显性
Right-To-Left Embedding(RLE)右->左U+202B&#8235;ordir ="rtl"显性
Left-To-Right Override(LRO)左->右U+202D&#8237;or<bdo dir ="ltr">显性
Right-To-Left Override(RLO)右->左U+202E&#8238;or<bdo dir ="rtl">显性
Left-To-Right Isolate(LRI)左->右U+2066&#8294;or<bdi dir ="ltr">显性
Right-To-Left Isolate(RLI)右->左U+2067&#8295;or<bdi dir ="rtl">显性
First Strong Isolate(FSI)U+2068&#8296;ordir ="auto"显性
Pop Directional Formatting(PDF)结束标记U+202C&#8236;or</bdo>显性
Pop Directional Isolate(PDI)结束标记U+2069&#8297;or</bdi>显性

显性、隐性字符都不会显示出来,他们的区别如下:

隐性

当使用实体时必须成对使用,LRM为从左到右的强字符,而RLM为从右到左的强字符,使用他们可以影响被包裹起来的中性字符方向,达到控制方向串的目的.

我们可以使用隐性字符&lrm;包裹住下面中性字符()+-,使他们的方向为左->右,让手机号正确显示.

<!-- 使用实体 -->
<p>ولدت &lrm;(+&lrm;86&lrm;)&lrm;138&lrm;-&lrm;3456&lrm;-&lrm;1000</p>
<p>ولدت &lrm;(+86)138-3456-1000&lrm;</p>

<!-- 更常规的做法是使用一个内联元素区分开强字符并设置中性字符的方向 -->
<p>ولدت <span dir="ltr">(+86)138-3456-1000</span></p>
<p><span dir="ltr">ولدت</span> (+86)138-3456-1000</p>

<!-- bdi标签能隔离外面的方向,默认值为auto,自动判断文本应该使用哪种方向 -->
<p>ولدت <bdi dir="auto">(+86)138-3456-1000</bdi></p>
<p><bdi dir="auto">ولدت</bdi> (+86)138-3456-1000</p>

ولدت ‎(+‎86‎)‎138‎-‎3456‎-‎1000

ولدت ‎(+86)138-3456-1000‎

ولدت (+86)138-3456-1000

ولدت (+86)138-3456-1000

ولدت (+86)138-3456-1000

ولدت (+86)138-3456-1000

不管使用上面哪种方式,你都可以正确的设置他们的全局方向,当然除了实体外,其它的都是一些 web 中常用的方法使用例子.

显性

需要成对使用,开始字符为它们自己的HTML Code,结束字符中RLE、LRE、RLO、LROPDF,而RLI,LRI,FSIPDI

<!-- 使用LRE, LRO将阿拉伯字符改成 左->右 -->
<p dir="ltr">&#8234;ولدت&#8236; (+86)138-3456-1000</p>
<p dir="ltr">&#8237;ولدت&#8236; (+86)138-3456-1000</p>

你会发现上面的代码显示到非常奇怪,根本没有将阿拉伯字符包裹起来,其实这是处理后的样子,真实的样子是在我的源文件里,我试着很多方法都没能让他在上面那段代码里显示正常,甚至你打开控制台看这段代码也是一样的,因此我只能截图如下:

截图,编辑器中显示样式
截图,编辑器中显示样式

&#8237;是带有Override的替换符,强制文字使用全局方向,因此整个阿拉伯字母被转换为左->右方向,下面这段就是浏览器显示的效果:

‪ولدت‬ (+86)138-3456-1000

‭ولدت‬ (+86)138-3456-1000

需要注意的是,我们应该尽量避免使用这些 Unicode 控制符,应该使用能产生相同结果的 HTML 标签,具体可以看我这篇文章css3 中的 unicode-bidi 与 direction 使用.只有当某些特定的情况下才能使用这些控制符,比如title标签以及标签的一些属性值.

引用

个人随笔记录,内容不保证完全正确,若需要转载,请注明作者和出处.