Unicode 中的 BIDI 双向性算法

一切得从复制 iOS 通讯录联系人手机号说起,有同学发现复制的号码是 "(415)555-3695",长度应该是 13,但Debug 打印的长度却是 15,WTF?

通过断点发现是前后分别多了一个不知何用 Unicode 字符:

image-20180913152116622

U+202DU+202C 这两个是个啥?

其实这两个都是关于方向的 Unicode 控制字符,U+202D 简称 LROU+202C 简称 PDF 。那它们是做什么用,如何控制所谓方向的呢?

Unicode 的方向性

基础方向

定义的是一个区域的整体方向,例如一个页面、一个段落或一个句子。中英文环境一般是 (LTR) 从左至右,而阿拉伯文环境则为 (RTL) 右至左的书写顺序

字符方向

日常我们书写文字会知道,书写的方向是决定于所书写的文字,汉子、拉丁文字是从左至右,阿拉伯文、希伯来文则是从右至左。相应的,Unicode 字符在设计时就考虑了不同文字方向性的问题,因此定义了每个 Unicode 字符的方向属性。

每个 Unicode 编码都被赋予一个方向性,并且有强弱之分:

方向性 字符举例
Strong Left-to-Right (LTR) 强字符从左至右(英文字母、汉子都属于此类)
Strong Right-to-Left (RTL) 强字符从右至左(阿拉伯文字、希伯来文字属于此类)
Neutral 中性字符(大部分标点符号和空格属于此类)
Weak Left-to-Right (LTR) / Right-to-Left (RTL) 弱字符(数字和数字相关的符号属于此类)

Strong 强字符: 方向性确定,LTR 或 RTL,和上下文无关。并且可能会影响其前后字符的方向性。

Weak 弱字符: 和强字符一样方向性也是确定的,但是不会影响前后字符的方向性。

Neutral 中性字符: 方向性不确定,由上下文环境决定其方向。

Unicode 字符方向串 (Directional Run)

目前我们还没说到文章开始提到的 LROPDF 控制字符,下面我们先把这两个控制字符从号码中去掉,仅将 "(415)555-3695" 套用到阿拉伯文和中英文环境,观察会出现哪些问题:

image

可以看到在中英文环境中,文本、数字和标点符号都按照从左至右的顺序书写,展示正常。

但在阿拉伯文环境中,电话号码好像按符号分割分组并方向展示了,这是怎么回事?

这里要引入 方向串 (Directional Run) 的概念,是指在一段文字中具有相同方向性的连续字符,并且其前后没有相同方向性的其它方向串。

全局方向、文本中的字符强弱类型 决定了如何分割方向串,以上面的例子做分析:

image-20180913174843950

文本被分为 6 个不同的方向串,问题显而易见,由于中性符号被全局方向影响,使得原本号码被拆分成不同方向串,被重新排序。

关于方向性的 Unicode 控制字符

为了解决上面的问题,Unicode 标准中定义了一系列方向性控制字符,这些字符在界面上不显示,也不占用任何展示空间。它们像是一些标记,影响着 BIDI 双向算法对文字书写方向的判断。

隐式双向控制字符 (Implicit Markers)

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

隐式控制字符的概念比较简单,可以理解为一个不会展示出来的强字符,LRM 为从左到右的强字符,而 RLM 为从右到左的强字符。

思考如何利用隐式控制解决上面号码的问题?

我们可以在每个中性字符 '-'、'('、'')' 左右用 LTR 字符包裹,这样中性字符被左至右的强字符包裹,它的方向也应该会变为从左至右。

来吧,尝试一下 (阿拉伯文手机的 Unicode 编码为 U+0647 U+0627 U+0062a U+0641 ):

<!-- dir=rtl 设置 div 中的基础方向为从右至左 -->
<div dir=rtl>
    &#x647;&#x627;&#x062a;&#x641;: &#x200e;(&#x200e;415&#x200e;)&#x200e;555&#x200e;-&#x200e;3695
</div>
image

简直完美,成功了!

但写这么多未免繁琐,毕竟 iOS 实现相同效果只用了 LROPDF 两个字符,这两个字符又有什么作用呢?

显式双向控制字符 (Explicit Markers)

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)

显式控制字符需要成对使用,前四个字符 LER RLE LRO RLO 为开始字符,最后一个 PDF 为结束字符。

  • LRE & RLE : 接下来的文字片段内的方向变为 从左至右 / 从右至左。效果类似基础方向,将一段文本中的基础方向变更。
  • LRO & RLO : 顾名思义 override,接下来的所有 Unicode 字符的方向性将被覆盖为 从左至右强字符 / 从右至左强字符。

还以上面的通讯录文本为例:

<!-- 基础方向为从右至左 -->
<div dir=rtl>
    <!-- 使用 LRE 将号码部分文本方向改为从左至右 -->
    &#x647;&#x627;&#x062a;&#x641;: &#x202a;(415)555-3695&#x202c;
</div>
image
<!-- 基础方向为从右至左 -->
<div dir=rtl>
    <!-- 使用 LRO 将号码部分文本方向改为从左至右 -->
    &#x647;&#x627;&#x062a;&#x641;: &#x202d;(415)555-3695&#x202c;
</div>
image

Bingo!同样实现了通讯录所需效果。

那么,LRE / RLELRO / RLO 有什么区别,用在什么不同场景呢?接着看例子:

<!-- 基础方向为从左至右 -->
<div dir=ltr>
    <div> here left to right, here right to left. </div>
    <div> here left to right&#x202b;, here right to left.&#x202c; </div>
    <!-- 后半部分因为 RLE 使得文本方向改变 ',''.' 符号书写顺序变为从右至左 -->
</div>
image
<!-- 基础方向为从左至右 -->
<div dir=ltr>
    <div> here left to right, here right to left. </div>
    <div> here left to right&#x202e;, here right to left.&#x202c; </div>
    <!-- 后半部分因为 RLO 使得字符方向属性被覆盖为强字符从右至左,英文字母也变成了从右至左书写 -->
</div>
image

iOS 对通讯录号码的处理

在从右至左的书写环境,虽然作为弱字符的数字还是按照从左至右的顺序书写,但是包含中性字符标点符号的电话号码,因为受到基础方向的影响,导致算法在不同环境下生成了不同的方向串,最终展示出错。

苹果为了避免这种错误产生,使用 LROPDF 控制字符包裹号码部分,使得其中的字符始终为强字符从左至右。

至此我们了解到,iOS 通讯录中复制电话号码都出的两个字符,并不是什么 bug,而是有意为之的,是为了避免不同语言环境下,电话号码的展示不一致。

总结

这里仅讨论了复制阿拉伯数字到输入框时可能遇到的坑。大家可知道阿拉伯语言环境下 iOS 通讯录中是不用 阿拉伯数字 的,用的是 阿拉伯文数字,类似中文 一、二、三 和 1、2、3 的区别

image-20180913200500301
img

如果中东用户复制阿拉伯文字数字到输入框,我们是自动转化为阿拉伯数字还是拒绝输入呢?

想做好全球化不容易啊~


相关文章

  1. Understanding Bidirectional (BIDI) Text in Unicode
  2. bidi 算法及 HTML 中的实现
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,039评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,223评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,916评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,009评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,030评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,011评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,934评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,754评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,202评论 1 309
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,433评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,590评论 1 346
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,321评论 5 342
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,917评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,568评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,738评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,583评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,482评论 2 352