一切得从复制 iOS 通讯录联系人手机号说起,有同学发现复制的号码是 "(415)555-3695",长度应该是 13,但Debug 打印的长度却是 15,WTF?
通过断点发现是前后分别多了一个不知何用 Unicode 字符:
image-20180913152116622U+202D 和 U+202C 这两个是个啥?
其实这两个都是关于方向的 Unicode 控制字符,U+202D 简称 LRO
,U+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)
目前我们还没说到文章开始提到的 LRO
和 PDF
控制字符,下面我们先把这两个控制字符从号码中去掉,仅将 "(415)555-3695" 套用到阿拉伯文和中英文环境,观察会出现哪些问题:
可以看到在中英文环境中,文本、数字和标点符号都按照从左至右的顺序书写,展示正常。
但在阿拉伯文环境中,电话号码好像按符号分割分组并方向展示了,这是怎么回事?
这里要引入 方向串 (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>
هاتف: ‎(‎415‎)‎555‎-‎3695
</div>
image
简直完美,成功了!
但写这么多未免繁琐,毕竟 iOS 实现相同效果只用了 LRO
和 PDF
两个字符,这两个字符又有什么作用呢?
显式双向控制字符 (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 将号码部分文本方向改为从左至右 -->
هاتف: ‪(415)555-3695‬
</div>
image
<!-- 基础方向为从右至左 -->
<div dir=rtl>
<!-- 使用 LRO 将号码部分文本方向改为从左至右 -->
هاتف: ‭(415)555-3695‬
</div>
image
Bingo!同样实现了通讯录所需效果。
那么,LRE
/ RLE
和 LRO
/ RLO
有什么区别,用在什么不同场景呢?接着看例子:
<!-- 基础方向为从左至右 -->
<div dir=ltr>
<div> here left to right, here right to left. </div>
<div> here left to right‫, here right to left.‬ </div>
<!-- 后半部分因为 RLE 使得文本方向改变 ',''.' 符号书写顺序变为从右至左 -->
</div>
image
<!-- 基础方向为从左至右 -->
<div dir=ltr>
<div> here left to right, here right to left. </div>
<div> here left to right‮, here right to left.‬ </div>
<!-- 后半部分因为 RLO 使得字符方向属性被覆盖为强字符从右至左,英文字母也变成了从右至左书写 -->
</div>
image
iOS 对通讯录号码的处理
在从右至左的书写环境,虽然作为弱字符的数字还是按照从左至右的顺序书写,但是包含中性字符标点符号的电话号码,因为受到基础方向的影响,导致算法在不同环境下生成了不同的方向串,最终展示出错。
苹果为了避免这种错误产生,使用 LRO
和 PDF
控制字符包裹号码部分,使得其中的字符始终为强字符从左至右。
至此我们了解到,iOS 通讯录中复制电话号码都出的两个字符,并不是什么 bug,而是有意为之的,是为了避免不同语言环境下,电话号码的展示不一致。
总结
这里仅讨论了复制阿拉伯数字到输入框时可能遇到的坑。大家可知道阿拉伯语言环境下 iOS 通讯录中是不用 阿拉伯数字
的,用的是 阿拉伯文数字
,类似中文 一、二、三 和 1、2、3 的区别
如果中东用户复制阿拉伯文字数字到输入框,我们是自动转化为阿拉伯数字还是拒绝输入呢?
想做好全球化不容易啊~
相关文章
网友评论