这篇文章上次修改于 616 天前,可能其部分内容已经发生变化,如有疑问可询问作者。 一切得从复制 iOS 通讯录联系人手机号说起,有同学发现复制的号码是 “(415)555-3695”,长度应该是 13,但Debug 打印的长度却是 15,WTF? 通过断点发现是前后分别多了一个不知何用 Unicode 字符: ![image-20180913152116622](https://blog.37hi.com/usr/uploads/2023/03/934855323.png) ![](https://blog.37hi.com/usr/uploads/2023/03/1307349976.png) U+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”` 套用到阿拉伯文和中英文环境,观察会出现哪些问题: ``` هاتف: (415)555-3695 phone: (415)555-3695 座机: (415)555-3695 ``` 可以看到在中英文环境中,文本、数字和标点符号都按照从左至右的顺序书写,展示正常。 但在阿拉伯文环境中,电话号码好像按符号分割分组并方向展示了,这是怎么回事? 这里要引入 **方向串 (Directional Run)** 的概念,是指在一段文字中具有相同方向性的连续字符,并且其前后没有相同方向性的其它方向串。 **全局方向、文本中的字符强弱类型** 决定了如何分割方向串,以上面的例子做分析: ![](https://blog.37hi.com/usr/uploads/2023/03/1932047397.png) 文本被分为 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 )`: ``` هاتف: (415)555-3695 ``` هاتف: (415)555-3695 简直完美,成功了! 但写这么多未免繁琐,毕竟 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 字符的方向性将被覆盖为 从左至右强字符 / 从右至左强字符。 还以上面的通讯录文本为例: ``` هاتف: (415)555-3695 ``` هاتف: (415)555-3695 ``` هاتف: (415)555-3695 ``` هاتف: (415)555-3695 Bingo!同样实现了通讯录所需效果。 那么,LRE / RLE 和 LRO / RLO 有什么区别,用在什么不同场景呢?接着看例子: ``` here left to right, here right to left. here left to right, here right to left. ``` here left to right, here right to left. here left to right, here right to left. ``` here left to right, here right to left. here left to right, here right to left. ``` here left to right, here right to left. here left to right, here right to left. #### iOS 对通讯录号码的处理 在从右至左的书写环境,虽然作为弱字符的数字还是按照从左至右的顺序书写,但是包含中性字符标点符号的电话号码,因为受到基础方向的影响,导致算法在不同环境下生成了不同的方向串,最终展示出错。 苹果为了避免这种错误产生,使用`LRO` 和 `PDF` 控制字符包裹号码部分,使得其中的字符始终为强字符从左至右。 至此我们了解到,iOS 通讯录中复制电话号码都出的两个字符,并不是什么 bug,而是有意为之的,是为了避免不同语言环境下,电话号码的展示不一致 ### 总结 这里仅讨论了复制阿拉伯数字到输入框时可能遇到的坑。大家可知道阿拉伯语言环境下 iOS 通讯录中是不用 阿拉伯数字 的,用的是 阿拉伯文数字,类似中文 一、二、三 和 1、2、3 的区别 作者:Sagi 链接:https://juejin.cn/post/6844903683935698952 来源:稀土掘金
没有评论