美文网首页程序员
Unicode 中的 BIDI 双向性算法

Unicode 中的 BIDI 双向性算法

作者: Sagi | 来源:发表于2019-11-08 15:17 被阅读0次

    一切得从复制 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 中的实现

    相关文章

      网友评论

        本文标题:Unicode 中的 BIDI 双向性算法

        本文链接:https://www.haomeiwen.com/subject/rztwbctx.html