unicode 控制字符
unicode 官网:https://home.unicode.org/
unicode 各种类型详细介绍:https://www.compart.com/en/unicode/category/Cf
对零宽度字符完全没有头绪的可以先玩下这个Demo
在綫解密:https://330k.github.io/misc_tools/unicode_steganography.html
1 前言
在所有主要的Web浏览器中内存中的字符顺序(逻辑)与它们显示的顺序(可视)是不同的。
Unicode 定义了它其中每个字符的方向属性,浏览器应用的一组规则(通过这个来进行自动判断文本Unicode方向属性应该使用哪种方向)在显示时产生正确的顺序由Unicode双向算法进行描述,也可简称为BIDI算法。
控制字符,有时候也称非打印字符,是出现在特定的信息文本中,表示某一控制功能的字符。这类字符并不显示,只包含某种特定的功能。
2 Unicode方向属性
Unicode方向属性包含以下三种类型:
- 强字符:大部分的字符都属于强字符,比如英文字母、汉字和阿拉伯字母。它们的方向性是确定的(英文字母是从左到右,而阿拉伯字母则从右到左),和其上下文的bidi属性无关。并且,强字符在bidi算法中可能会影响其前后字符的方向性;
- 弱字符:数字和数字相关的符号属于弱字符。它们的方向性是确定的,但对其前后字符的方向性不会产生影响;
- 中性字符:大部分的标点符号和空格属于中性字符。它们的方向性是不确定的,由上下文的bidi属性来决定其方向。
方向性 | 相关字符 | 效果 |
---|---|---|
Left-to-Right (LTR) | 强字符从左至右(英文字母、汉字以及世界上大部分从左到右书写的文字) | 方向性确定,LTR,和上下文无关。并且可能会影响其前后字符的方向性。 |
Right-to-Left (RTL) | 强字符从右至左(阿拉伯文字、波斯语以及大部分从右到左书写的文字) | 方向性确定,RTL,和上下文无关。并且可能会影响其前后字符的方向性。 |
Left-to-Right (LTR) / Right-to-Left (RTL) | 弱字符(数字和数字相关的符号) | 和强字符一样方向性也是确定的,但是不会影响前后字符的方向性,也不会受强方向性影响。 |
Neutral | 中性字符(大部分标点符号和空格) | 方向性不确定,由上下文环境决定其方向。 |
2.1 全局方向(也叫基础方向)
全局方向是一个区域中的总体方向,例如一个页面、一个段落或一个句子。中英文环境一般是 (LTR) 从左至右,而阿拉伯文环境则为 (RTL) 右至左的书写顺序。我们可以通过dir
属性或者direction
样式,设置指定方向。
2.2 方向串
目前我们还没说到文章开始提到的 LRO 和 PDF 控制字符,下面我们先把这两个控制字符从号码中去掉,仅将 “(415)555-3695” 套用到阿拉伯文和中英文环境,观察会出现哪些问题:
1 |
|
可以看到在中英文环境中,文本、数字和标点符号都按照从左至右的顺序书写,展示正常。
但在阿拉伯文环境中,电话号码好像按符号分割分组并方向展示了,实际上不是故意这样输入的,而是输入 阿拉伯文字+电话号码
之后自动就变成这样了。这是怎么回事?
这里要引入 方向串 (Directional Run) 的概念,是指在一段文字中具有相同方向性的连续字符,并且其前后没有相同方向性的其它方向串。
全局方向、文本中的字符强弱类型 决定了如何分割方向串,以上面的例子做分析:
文中首个强类型字符是阿拉伯文字,因此其所在的文本区域的全局方向为RTL,但弱类型的数字则保持了原方向LTR,而中性字符”-“没有被强字符包围则跟随了全局方向。
对于以上含有阿拉伯文字的段落,我们可以得到6个不同的方向串。正是因为中性符号被全局方向影响,使得原本的号码被拆分成不同的方向串,从而重新排列。
3 Unicode控制字符
为了解决上面的问题你,Unicode标准中定义了一系列方向性控制字符,这些字符在页面上并不显示,也不占用展示空间。他们像是一些标记,影响着BIDI算法对文字书写方向的判断。下面介绍一些常用的控制字符。
3.1 隐式控制字符
名称 | 方向 | Unicode Code | HTML Code |
---|---|---|---|
Left-To-Right Mark (LRM) | 左->右 | U+200E | &lrm、‎、‎ |
Right-To-Left Mark (RLM) | 右->左 | U+200F | &rlm、‏、‏ |
隐式控制字符的概念比较简单,可以理解为一个不会展示出来的强字符,LRM 为从左到右的强字符,而 RLM 为从右到左的强字符。
那我们如何利用隐式控制解决上面号码的问题?两种解决方案:
- 把影响全局方向的强字符左右用 LTR 包裹起来,强制改变他们的方向
- 把每个中性字符
-、()
左右用 LTR 包裹起来,中性字符被左至右的强字符包裹,它的方向也应该会变为从左至右。 - 还有一种是使用显示控制字符,这里也先写一下,后面会详细介绍。使用 LRO 可以修改强字符方向。
1 |
|
再举个例子:
1 |
|
但写这么多未免繁琐,所以 iOS 实现相同效果只用了 LRO 和 PDF 两个字符,这两个字符又有什么作用呢?
3.2 显式控制字符
显式控制字符需要成对使用,前四个字符 LER RLE LRO RLO 为开始字符,最后一个 PDF 为结束字符。
- LRE & RLE : 接下来的文字片段内的方向变为 从左至右 / 从右至左。效果类似基础方向,将一段文本中的基础方向变更。
- LRO & RLO : 顾名思义 override,接下来的所有 Unicode 字符的方向性将被覆盖为 从左至右强字符 / 从右至左强字符。
3.2.1 Embedding
名称 | 方向 | Unicode Code | HTML Code |
---|---|---|---|
Left-To-Right Embedding (LRE) | 左->右 | U+202A | ‪, dir='ltr' |
Right-To-Left Embedding (RLE) | 右->左 | U+202B | ‫, dir='rtl' |
Pop Directional Formatting (PDF) | 结束标记 | U+202C | ‬ |
使用LRE或者RLE,可以改变控制字符后的文本区域的全局方向。但不影响强字符和弱字符的方向。
效果和 dir
属性或者direction
样式 相同。
3.2.2 Override
名称 | 方向 | Unicode Code | HTML Code |
---|---|---|---|
Left-To-Right Override (LRO) | 左->右 | U+202D | ‭, <bdo dir='ltr'>..</bdo> |
Right-To-Left Override (RLO) | 右->左 | U+202E | ‮, <bdo dir='rtl'>..</bdo> |
LRO或者RLO会强制将控制符后的字符的方向属性覆盖为对应的方向,各种字符都会被影响,包括强字符。
结束标记符使用的也是PDF,在此处PDF 表示为</bdo>
。
3.2.3 Isolate
名称 | 方向 | Unicode Code | HTML Code |
---|---|---|---|
Left-To-Right Isolate (LRI) | 左->右 | U+2066 | ⁦, <bdi dir='ltr'> |
Right-To-Left Isolate (RLI) | 右->左 | U+2067 | ⁧, <bdi dir='rtl'> |
First Strong Isolate (FSI) | 自适应 | U+2068 | ⁨, <bdi dir='auto'> |
Pop Directional Isolate (PDI) | 结束标记 | U+2069 | ⁩, </bdi> |
Isolate控制符用来在双向文字中加入脱离与其父元素的全局方向的方向串,可以使用<bdi>
标签实现。<bdi>
标签有点类似与<span>
标签的作用,但不同的是<bdi>
标签本身带有默认方向属性,dir默认值为auto。
虽然控制符和相应的HTML元素能够达到相同的控制效果,但需要注意的是,有些浏览器现阶段并不支持<bdi>
等新标签,比如IE。
4 相关的CSS属性
除了上文提到的控制文本方向,我们还可以使用css中unicode-bidi
和direction
属性决定文字渲染方向、书写方向。
具体使用方法可以参考以下教程:
direction: CSS direction 属性
unicode-bidi: CSS unicode-bidi 属性
5 iOS 对通讯录号码的处理
在从右至左的书写环境,虽然作为弱字符的数字还是按照从左至右的顺序书写,但是包含中性字符标点符号的电话号码,因为受到基础方向的影响,导致算法在不同环境下生成了不同的方向串,最终展示出错。
苹果为了避免这种错误产生,使用 LRO 和 PDF 控制字符包裹号码部分,使得其中的字符始终为强字符从左至右。
所以iOS 通讯录中复制电话号码都出的两个字符,并不是什么 bug,而是有意为之的,是为了避免不同语言环境下,电话号码的展示不一致。
6 Python 代码
在Python中,可以使用unicodedata模块来判断一个字符串是否包含RLO或LRO字符,并将其删除或替换为其他字符
1 |
|
7 结语
虽然前面介绍了几种改变文本方向的方法,但实际情况会复杂得多。大家可根据实际情况使用。