文字间的战斗与其救世主 Unicode
我们都知道如何从键盘输入文字，不是吗？
Unicode
117.181.102.154
外国人的电脑里有没有中文字体呢？
来自117.181.102.154的 Chrome 109.0|Windows 10 用户
2023-01-27T16:25:44.000
223.11.229.242
有的
来自山西的 Chrome 109.0|Windows 10 用户
2023-01-28T09:13:34.000
219.156.91.157
感觉这篇翻译的质量比上次来看某些翻译文章时要好很多，赞
来自河南郑州的 Firefox 109.0|Windows 10 用户
2023-01-30T13:56:59.000
112.44.84.179
你去试试英文版的 Windows 就知道了。带了中文字体的，但没有中文版系统带的那么全。<br />
<br />
参考: learn.microsoft.com/en-us/typography/fonts/windows_10_font_list
来自四川的 Chrome 108.0|Windows 10 用户
2023-01-31T09:14:34.000
我们都知道如何从键盘输入文字，不是吗？
![](/data/attachment/album/202301/27/123501fod5doujjgo5jfjk.jpg)


我们都知道如何从键盘输入文字，不是吗？


那么，请允许我挑战你在你最爱的文本编辑器中输入这段文字：


![«Ayumi moved to Tokyo in 1993 to pursue her career» said Dmitrii](/data/attachment/album/202301/27/123527d9c9ychhryc54rte.png)


这段文字难以被输入因为它包含着：


* 键盘上没有的印刷符号，
* 平假名日文字符，
* 为符合平文式罗马字标准，日本首都的名字中的两个字母 “o” 头顶带有长音符号，
* 以及最后，用西里尔字母拼写的名字德米特里。


毫无疑问，想要在早期的电脑中输入这样的句子是不可能的。这是因为早期电脑所使用的字符集有限，无法兼容多种书写系统。而如今类似的限制已不复存在，马上我们就能在文中看到。


### 电脑是如何储存文字的？


计算机将字符作为数字储存。它们再通过表格将这些数字与含有意义的字形一一对应。


在很长一段时间里，计算机将每个字符作为 0 到 255 之间的数字储存（这正好是一个字节的长度）。但这用来代表人类书写所用到的全部字符是远远不够的。而解决这个问题的诀窍在于，取决于你住在地球上的哪一块区域，系统会分别使用不同的对照表。


这里有一张在法国常被广泛使用的对照表 [ISO 8859-15](https://en.wikipedia.org/wiki/ISO/IEC_8859-15)：


![The ISO 8859-15 encoding](/data/attachment/album/202301/27/123527ewo76mwrhmw33v00.png)


如果你住在俄罗斯，你的电脑大概会使用 [KOI8-R](https://en.wikipedia.org/wiki/KOI8-R) 或是 [Windows-1251](https://en.wikipedia.org/wiki/Windows-1251) 来进行编码。现在让我们假设我们在使用后者：


![The Windows-1251 encoding is a popular choice to store text written using the Cyrillic alphabets](/data/attachment/album/202301/27/123528rs4w7fpuumjiwhmm.png)


对于 128 之前的数字，两张表格是一样的。这个范围与 [US-ASCII](https://en.wikipedia.org/wiki/ASCII) 相对应，这是不同字符表格之间的最低兼容性。而对于 128 之后的数字，这两张表格则完全不同了。


比如，依据 Windows-1251，字符串 “said Дмитрий” 会被储存为：



```
115 97 105 100 32 196 236 232 242 240 232 233

```

按照计算机科学的常规方法，这十二个数字可被写成更加紧凑的十六进制：



```
73 61 69 64 20 c4 ec e8 f2 f0 e8 e9

```

如果德米特里发给我这份文件，我在打开后可能会看到：



```
said Äìèòðèé

```

这份文件 *看起来* 被损坏了，实则不然。这些储存在文件里的数据，即数字，并没有发生改变。被显示出的字符与 *另一张表格* 中的数据相对应，而非文字最初被写出来时所用的编码表。


让我们来举一个例子，就以字符 “Д” 为例。按照 Windows-1251，“Д” 的数字编码为 196（c4）。储存在文件里的只有数字 196。而正是这同样的数字在 ISO8859-15 中与 “Ä” 相对应。这就是为什么我的电脑错误地认为字形 “Ä” 就是应该被显示的字形。


![When the same text file is written then read again but using a different encoding](/data/attachment/album/202301/27/123528ycym6nr2nm8rfers.png)


多提一句，你依然可以时不时地看到一些错误配置的网站展示，或由 [用户邮箱代理](https://en.wikipedia.org/wiki/Email_client) 发出的对收件人电脑所使用的字符编码做出错误假设的邮件。这样的故障有时被称为乱码（LCTT 译注：原文用词为 [mojibake](https://en.wikipedia.org/wiki/Mojibake)， 源自日语 *文字化け*）。好在这种情况在今天已经越来越少见了。


![Example of Mojibake on the website of a French movie distributor. The website name has been changed to preserve the innocent.](/data/attachment/album/202301/27/123528jpbpepvbmyrpml1b.png)


### Unicode 拯救了世界


我解释了不同国家间交换文件时会遇到的编码问题。但事情还能更糟，同一个国家的不同生产商未必会使用相同的编码。如果你在 80 年代用 Mac 和 PC 互传过文件你就懂我是什么意思了。


也不知道是不是巧合，[Unicode](https://en.wikipedia.org/wiki/Unicode) 项目始于 1987 年，主导者来自<ruby> 施乐 <rt>  Xerox </rt></ruby>和……<ruby> 苹果 <rt>  Apple </rt></ruby> 。


这个项目的目标是定义一套通用字符集来允许同一段文字中 *同时* 出现人类书写会用到的任何文字。最初的 Unicode 项目被限制在 65536 个不同字符（每个字符用 16 位表示，即每个字符两字节）。这个数字已被证实是远远不够的。


于是，在 1996 年 Unicode 被扩展以支持高达 100 万不同的 <ruby> <a href="https://en.wikipedia.org/wiki/Code_point">  代码点 </a> <rt>  code point </rt></ruby>。粗略来说，一个“代码点”可被用来识别字符表中的一个条目。Unicode 项目的一个核心工作就是将世界上正在被使用（或曾被使用）的字母、符号、标点符号以及其他文字仓管起来，并给每一项条目分配一个代码点用以准确分辨对应的字符。


这是一个庞大的项目：为了让你有个大致了解，发布于 2017 年的 Unicode 版本 10 定义了超过 136,000 个字符，覆盖了 139 种现代和历史上的语言文字。


随着如此庞大数量的可能性，一个基本的编码会需要每个字符 32 位（即 4 字节）。但对于主要使用 US-ASCII 范围内字符的文字，每个字符 4 字节意味着 4 倍多的储存需求以及 4 倍多的带宽用以传输这些文字。


![Encoding text as UTF-32 requires 4 bytes per character](/data/attachment/album/202301/27/123529l9rsusejro9j9ozr.png)


所以除了 [UTF-32](https://en.wikipedia.org/wiki/UTF-32)，Unicode 联盟还定义了更加节约空间的 [UTF-16](https://en.wikipedia.org/wiki/UTF-16) 和 [UTF-8](https://en.wikipedia.org/wiki/UTF-8) 编码，分别使用了 16 位和 8 位。但只有 8 位该如何储存超过 100,000 个不同的值呢？事实是，你不能。但这其中窍门在于用一个代码值（UTF-8 中的 8 位以及 UTF-16 中的 16 位）来储存最常用的一些字符。再用几个代码值储存最不常用的一些字符。所以说 UTF-8 和 UTF-16 是 *可变长度* 编码。尽管这样也有缺陷，但 UTF-8 是空间与时间效率之间一个不错的折中。更不用提 UTF-8 可以向后兼容大部分 Unicode 之前的 1 字节编码，因为 UTF-8 经过了特别设计，任何有效的 US-ASCII 文件都是有效的 UTF-8 文件。你也可以说，UTF-8 是 US-ASCII 的超集。而在今天已经找不到不用 UTF-8 编码的理由了。当然除非你书写主要用的语言需要多字节编码，或是你不得不与一些残留的老旧系统打交道。


在下面两张图中，你可以亲自比较一下同一字符串的 UTF-16 和 UTF-8 编码。特别注意 UTF-8 使用了一字节来储存拉丁字母表中的字符，但它使用了两字节来存储西里尔字母表中的字符。这是 Windows-1251 西里尔编码储存同样字符所需空间的两倍。


![UTF-16 is a variable length encoding requiring 2 bytes to encode most characters. Some character still requires 4 bytes though (for example](/data/attachment/album/202301/27/123529vkgg89rc6vd4e87a.png)


![UTF-8 is a variable length encoding requiring 1, 2, 3 or 4 bytes per character](/data/attachment/album/202301/27/123529zgpxzfp5atzapxdb.png)


### 而这些对于打字有什么用呢？


啊……知道一些你的电脑的能力与局限以及其底层机制也不是什么坏事嘛。特别是我们马上就要说到 Unicode 和十六进制。现在……让我们再聊点历史。真的就一点，我保证……


……就说从 80 年代起，电脑键盘曾经有过 [`Compose` 键](https://en.wikipedia.org/wiki/Compose_key)（有时候也被标为 `Multi` 键）就在 `Shift` 键的下边。当按下这个键时，你会进入 “<ruby> 组合 <rt>  Compose </rt></ruby>” 模式。一旦在这个模式下，你便可以通过输入助记符来输入你键盘上没有的字符。比如说，在组合模式下，输入 RO 便可生成字符 ®（当作是 O 里面有一个 R 就能很容易记住）。


![Compose key on lk201 keyboard](/data/attachment/album/202301/27/123530eu5pl5u59u5zllg4.jpg)


现在很难在现代键盘上看到 `Compose` 键了。这大概是因为占据主导地位的 PC 不再用它了。但是在 Linux 上（可能还有其他系统）你可以模拟 `Compose` 键。这项设置可以通过 GUI 开启，在大多数桌面环境下调用“键盘”控制面板：但具体的步骤取决于你的桌面环境以及版本。如果你成功启用了那项设置，不要犹豫，在评论区分享你在你电脑上所采取的具体步骤。


（LCTT 译注：如果有读者想要尝试，建议将 `Compose` 键设为大写锁定键，或是别的不常用的键，`Ctrl` 和 `Alt` 会被大部分 GUI 程序优先识别为功能键。还有一些我自己试验时遇到过的问题，在开启 `Compose` 键前要确认大写锁定是关闭的，输入法要切换成英文，组合模式下输入大小写敏感。我试验的系统是 Ubuntu 22.04 LTS。）


至于我自己嘛，我现在先假设你用的就是默认的 `Shift+AltGr` 组合来模拟 `Compose` 键。（LCTT 校注：`AltGr` 在欧洲键盘上是指右侧的 `Alt` 键，在国际键盘上等价于 `Ctrl+Alt` 组合键。）


那么，作为一个实际例子，尝试输入 “LEFT-POINTING DOUBLE ANGLE QUOTATION MARK（左双角引号）”（LCTT 译注：Guillemet，是法语和一些欧洲语言中的引号，与中文的书名号不同），你可以输入 `Shift+AltGr` `<<`（你在敲助记符时不需要一直按着 `Shift+AltGr`）。如果你成功输入了这个符号，你自己应该也能猜到要怎么输入 “RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK（右双角引号）” 了。


来看看另一个例子，试试 `Shift+AltGr` `---` 来生成一个 “EM DASH（长破折号）”（LCTT 译注：中文输入法的长破折号由两个 “EM DASH” 组成）。要做到这个，你需要按下主键盘上的的 [连字符减号](https://en.wikipedia.org/wiki/Hyphen-minus) 键而非数字键盘上的那个。


值得注意的是 `Compose` 键在非 GUI 环境下也能工作。但是取决于你使用的是 X11 控制台还是只显示文字的控制台，它们所支持的组合按键顺序并不相同。


在控制台上，你可以通过命令 `dumpkeys` 来查看支持的组合按键列表（LCTT 译注：可能需要 root 权限）：



```
dumpkeys --compose-only

```

在 GUI 下，组合键是在 Gtk/X11 层被实现的。想要知道 Gtk 所支持的助记符，可以查看页面：<https://help.ubuntu.com/community/GtkComposeTable>


### 我们可以避免对 Gtk 字符组合的依赖吗？


或许我是个纯粹主义者，但是我为 Gtk 这种对 Compose 键进行硬编码的方式感到悲哀。毕竟，不是所有 GUI 应用都会使用 Gtk 库。而且我如果想要添加我自己的助记符的话就只能重新编译 Gtk 了。


幸好在 X11 层也有对字符组合的支持。在以前则是通过令人尊敬的 [X 输入法（XIM）](https://en.wikipedia.org/wiki/X_Input_Method)。


这个方法在比起基于 Gtk 的字符组合能够在更加底层的地方工作，同时具备优秀的灵活性并兼容很多 X11 应用。


比如说，假设我只是想要添加 `-->` 组合来输入字符 `→` （U+2192，RIGHTWARDS ARROW（朝右箭头）），我只需要新建 `~/.XCompose` 文件并写入以下代码：



```
cat > ~/.XCompose << EOT
# Load default compose table for the current local
include "%L"

# Custom definitions
<Multi_key> <minus> <minus> <greater> : U2192 # RIGHTWARDS ARROW
EOT

```

然后你就可以启动一个新的 X11 应用，强制函数库使用 XIM 作为输入法，并开始测试：



```
GTK_IM_MODULE="xim" QT_IM_MODULE="xim" xterm

```

新的组合排序应该可以在你刚启动的应用里被输入了。我鼓励你通过 `man 5 compose` 来进一步学习组合文件格式。


在你的 `~/.profile` 中加入以下两行来将 XIM 设为你所有应用的默认输入法。这些改动会在下一次你登录电脑时生效：



```
export GTK_IM_MODULE="xim"
export QT_IM_MODULE="xim"

```

这挺酷的，不是吗？这样你就可以随意的加入你想要的组合排序。而且在默认的 XIM 设置中已经有几个有意思的组合了。试一下输入组合键 `LLAP`。


但我不得不提到两个缺陷。XIM 已经比较老了，而且只适合我们这些不太需要多字节输入法的人。其次，当你用 XIM 作为输入法的时候，你就不能利用 `Ctrl+Shift+u` 加上代码点来输入 Unicode 字符了。什么？等一下？我还没聊过那个？让我们现在来聊一下吧：


### 如果我需要的字符没有对应的组合键排序该怎么办？


组合键是一个不错的工具，它可以用来输入一些键盘上没有的字符。但默认的组合集有限，而切换 XIM 并为一个你一生仅用一次的字符来定义一个新的组合排序十分麻烦。


但这能阻止你在同一段文字里混用日语、拉丁语，还有西里尔字符吗？显然不能，这多亏了 Unicode。比如说，名字 “あゆみ” 由三个字母组成：


* [“HIRAGANA LETTER A（平假名字母 あ）” （U+3042）](http://www.fileformat.info/info/unicode/char/3042/index.htm)
* [“HIRAGANA LETTER YU（平假名字母 ゆ）” （U+3086）](http://www.fileformat.info/info/unicode/char/3086/index.htm)
* 以及 [“HIRAGANA LETTER MI（平假名字母 み）” （U+307F）](http://www.fileformat.info/info/unicode/char/307F/index.htm)


我在上文提及了 Unicode 字符的正式名称，并遵循了全部用大写拼写的规范。在它们的名字后面，你可以找到它们的 Unicode 代码点，位于括号之间并写作 16 位的十六进制数字。这让你想到什么了吗？


不管怎样，一旦你知道了的一个字符的代码点，你就可以按照以下组合输入：


* `Ctrl+Shift+u`，然后是 `XXXX`（你想要的字符的 *十六进制* 代码点）然后回车。


作为一种简写方式，如果你在输入代码点时不松开 `Ctrl+Shift`，你就不用敲回车。


不幸的是，这项功能的实现是在软件库层而非 X11 层，所以对其支持在不同应用间并不统一。以 LibreOffice 为例，你必须使用主键盘来输入代码点。而在基于 Gtk 的应用则接受来自数字键盘的输入。


最后，当我和我的 Debian 系统上的控制台打交道时，我发现了一个类似的功能，但它需要你按下 `Alt+XXXXX` 而 `XXXXX` 是你想要的字符的 *十进制* 的代码点。我很好奇这究竟是 Debian 独有的功能，还是因为我使用的语言环境（Locale） 是 `en_US.UTF-8`。如果你对此有更多信息，我会很愿意在评论区读到它们的！




| GUI | 控制台 | 字符 |
| --- | --- | --- |
| `Ctrl+Shift+u` `3042` `Enter` | `Alt+12354` | あ |
| `Ctrl+Shift+u` `3086` `Enter` | `Alt+12422` | ゆ |
| `Ctrl+Shift+u` `307F` `Enter` | `Alt+12415` | み |


### 死键


最后值得一提的是，想要不（必须）依赖 Compose 键来输入键组合还有一个更简单的方法。


你的键盘上的某些键是专门用来创造字符组合的。这些键叫做 <ruby> <a href="https://en.wikipedia.org/wiki/Dead_key">  死键 </a> <rt>  Dead Key </rt></ruby>。这是因为当你按下它们一次，看起来什么都没有发生，但它们会悄悄地改变你下一次按键所产生的字符。这个行为的灵感来自于机械打字机：在使用机械打字机时，按下一个死键会印下一个字符，但不会移动字盘。于是下一次按键则会在同一个地方印下另一个字符。视觉效果就是两次按键的组合。


我们在法语里经常用到这个。举例来说，想要输入字母 `ë` 我必须按下死键 `¨` 然后再按下 `e` 键。同样地，西班牙人的键盘上有着死键 `~`。而在北欧语系下的键盘布局，你可以找到 `°` 键。我可以念很久这份清单。


![hungary dead keys](/data/attachment/album/202301/27/123530gv17730jjk939jc4.png)


显然，不是所有键盘都有所有死键。实际上，你的键盘上是找不到大部分死键的。比如说，我猜在你们当中只有小部分人——如果真的有的话——有死键 `¯` 来输入 `Tōkyō` 所需要的长音符号（“平变音符”）。


对于那些你键盘上没有的死键，你需要寻找别的解决方案。好消息是，我们已经用过那些技术了。但这一次我们要用它们来模拟死键，而非“普通”键。


那么，我们的第一个选择是利用 `Compose` `-` 来生成长音符号（你键盘上有的连字符减号）。按下时屏幕上什么都不会出现，但当你接着按下 `o` 键你就能看到 `ō`。


Gtk 在组合模式下可以生成的一系列死键都能在 [这里](https://help.ubuntu.com/community/GtkDeadKeyTable) 找到。


另一个解决方法则是利用 Unicode 字符 “COMBINING MACRON（组合长音符号）”（U+0304），然后字母 `o`。我把细节都留给你。但如果你好奇的话，你会发现你打出的结果有着微妙的不同，你并没有真地打出 “LATIN SMALL LETTER O WITH MACRON（小写拉丁字母 O 带长音符号）”。我在上一句话的结尾用了大写拼写，这就是一个提示，引导你寻找通过 Unicode 组合字符按更少的键输入 `ō` 的方法……现在我将这些留给你的聪明才智去解决了。


### 轮到你来练习了！


所以，你都学会了吗？这些在你的电脑上工作吗？现在轮到你来尝试了：根据上面提出的线索，加上一点练习，现在你可以完成文章开头给出的挑战了。挑战一下吧，然后把成果复制到评论区作为你成功的证明。


赢了也没有奖励，或许来自同伴的惊叹能够满足你！




---


via: <https://itsfoss.com/unicode-linux/>


作者：[Sylvain Leroux](https://www.yesik.it/) 选题：[lkxed](https://github.com/lkxed) 译者：[yzuowei](https://github.com/yzuowei) 校对：[wxy](https://github.com/wxy)


本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出

