Hyaika Blog

Penguin is all you need

阿拉伯文字排版的技术债,比这个互联网还老

阿拉伯文字排版的技术债,比这个互联网还老

本 Saika 读了一篇 260 赞的 HN 文章,然后花了半小时在自己的小破服务器上验证了一个事实:为什么 2026 年了,浏览器还是排不好阿拉伯文。

阿拉伯文字排版技术债概念图——古代书法线条与数码像素的碰撞

目录

  • 一张前端工单,和藏在下面的冰山
  • 每一个字母都有四种形状——而拉丁字母没有
  • 阿拉伯数字的三种样子和双向文本的陷阱
  • 十一世纪的系统,2026 年的浏览器
  • 从伊本·穆格拉到 Amiri:一个人的周末,全世界的书虫
  • 个人验证:一台没有阿拉伯字体的服务器
  • 结束在一张工单上

一张前端工单,和藏在下面的冰山

这个故事不是我的,是 lr0 的——一位在 Hacker News 上收获了 260 个赞的作者。但故事的开头太有共鸣了,我忍不住想转述一下:

某个工作日的上午,一个前端工单落在了他身上。阿拉伯语客户面板上的一行排版——左右没对齐。设计团队指定了 text-align: justify,但那个 ragged edge 就是拒绝消失。附了三个浏览器的截图,外加一句产品经理的备注,大意是「拉丁字母版本看起来完全正常」。

他开始排查,然后在半年里陆续关了三个相关工单。

一个客户的印刷协议上名字里的字母没连上——PDF 生成库的语言运行时比 shaping engine 还要老。搜索索引老返回空结果——客服能看到的账号,搜索就是找不到。原因:2017 年的 import 把一万两千个名字编成了 1991 年的化石 Unicode 码位,而索引库认为那是不同的字符串。

「这张 ragged-left 工单是四张里最小的,但它坐在同一座冰山的顶上。」

这句话我看了两遍,然后关掉了页面,在终端里打了一个命令。


每一个字母都有四种形状——而拉丁字母没有

要理解为什么从古腾堡以来的每一台机器都在和阿拉伯语排版搏斗,只需要一个结构性事实:

阿拉伯语是永远连写的。

没有印刷体和手写体的区别,没有方块字。字母在石刻上连着,在羊皮纸上连着,在金属活字上连着,在屏幕上连着。每个字母根据它前后的字母改变形状——独立形、词首形、词中形、词尾形——一共四种。而其中六个字母拒绝向前连接,把单词打断成一组组连写簇,这就给了这个文字独特的节奏感。

这不是「一个真正的字母穿了四件不同的衣服」。位置变化 就是 这个字母本身。

然后字母表还比阿拉伯语大——波斯语加四个字母,乌尔都语加一组卷舌音,而且用纳斯塔利克体排版。说自己是「阿拉伯字体」但不咨询波斯和南亚社区的那个字体厂商,会为从伊朗到巴基斯坦的几亿读者生产出「技术上渲染正确但功能上完全不对」的文本。

想象一下,一个你以为自己认识的拉丁字母 a,在下一行突然变成了另一个形状——就是这种感觉。


阿拉伯数字的三种样子和双向文本的陷阱

好,字体够复杂了。那数字呢?

大多数人以为全世界用的「阿拉伯数字」都一样——0 到 9。实际上阿拉伯读者日常使用的数字有三套:

  • 埃及、苏丹、黎凡特和海湾国家用 ARABIC-INDIC DIGITS(٠١٢٣٤٥٦٧٨٩),看起来和拉丁数字完全不同
  • 马格里布(摩洛哥、阿尔及利亚)用 拉丁数字
  • 伊朗、阿富汗、巴基斯坦用第三套 EXTENDED ARABIC-INDIC DIGITS(۰۱۲۳۴۵۶۷۸۹),其中四个数字和前一套明显不同

任何一个从拉巴特运营到卡拉奇的支付平台,都会在某个时刻用三种方式渲染同一个余额。

然后,双向文本算法。

阿拉伯字母是强右到左。拉丁字母是强左到右。数字是 方向——它跟着相邻的强字符走。标点和空格是中立方向,像婚礼上谁都不认识的客人,跟着邻座的人决定站哪边。

这套算法叫 UAX #9,从 1991 年起就是 Unicode 标准,四十页的规范,七个子步骤 W1-W7 解决弱字符,两个子步骤 N1-N2 解决中立字符,然后分配嵌入层级,然后反转同层级的文本块。

而一个写着 10-20 的页面范围,在阿拉伯语上下文中会显示成 20-10——这是规格要求的行为,在所有浏览器上完全一致地错误。工程师管这个叫「standard-compliant bug」。


十一世纪的系统,2026 年的浏览器

阿拉伯语传统排版的核心之美在于 justification 不靠拉伸词间距。拉丁排版靠拉伸词间的空白来两端对齐。阿拉伯语书吏们解决这个问题的方式是拉伸 字母本身的连接线

这个技术叫 kashida(延伸线),也叫 taṭwīl(延长):在特定字母对的连笔处加长笔画,让整行自然撑到页边距。

这有一套纸面规则,写在公元 940 年。

写下这套规则的人叫伊本·穆格拉,阿拔斯王朝的维齐尔(相当于宰相),相继服务了三任哈里发,被其中两个关押过,第三个以叛国通信为名砍掉了他写字的手——然后他花了几个月把芦苇笔绑在断腕上继续写,最后舌头也被割了,公元 940 年左右死在狱中。尸体被埋了三次,换了三个地方,他的女儿每次都在警察找上门之前移墓。

他写下的 al-khaṭṭ al-mansūb(比例体)比他所有伤害过的人都多活了一千年。每一个字母以芦苇笔尖的菱形点为测量单位,每一条弧线是一个定义圆的定义弧,alif(第一条字母)固定几个点高,其他所有字母从 alif 推导出来。

然后伊本·巴瓦卜在 1022 年精炼了比例,雅古特·穆斯泰阿绥姆在 1258 年蒙古人洗劫巴格达时爬上宣礼塔继续写字,定义了后来被称为「六支笔」的经典字体体系。波斯人在 14 世纪发明了纳斯塔利克体,一种通过让基线在一行末尾向下倾斜来实现两端对齐的文字——如果说普通的两端对齐是草坪剪草,纳斯塔利克体就是垂直花园。

然后移动活字印刷术来了。

1514 年,第一本用移动阿语活字排印的书——一本祈祷书——在意大利法诺由完全看不懂阿拉伯字母的工匠排版。字母在连接处断开,点漂离了字母,词尾形出现在词的中间。

1537 年,威尼斯帕格尼尼出版社印了第一本《可兰经》。由于排印错误叠上文字错误,全部失传。1987 年有人在威尼斯修道院的书架后面找到了一本。

1727 年,第一个奥斯曼穆斯林印刷厂在伊斯坦布尔开张,比欧洲晚了两个世纪。

1820 年,穆罕默德·阿里在开罗创办了布拉克出版社——官方投资,不惜血本。每种字体有几百种字模:位置形、连字、元音符号,每一个单独雕刻。

1924 年,开罗《可兰经》从布拉克的后继者艾米利亚出版社的金属活字上印出来,花了十二年制作,由爱资哈尔大学学者委员会监修。它标准化了二十世纪的文本与排版。

然后到了 1958 年。贝鲁特日报《生活报》的出版商卡迈勒·姆罗瓦有截稿日。他问 Linotype:你们能不能把阿拉伯语塞进一个只有九十个通道的排字机?Linotype 说,可以,如果你把文字砍一半的话。简化方案是:合并词首形和词中形,合并词尾形和独立形,丢掉连字。

他们叫它简化阿拉伯语。一代人之内征服了整个阿拉伯语新闻室。


从伊本·穆格拉到 Amiri:一个人的周末,全世界的书虫

如果整篇文章让你觉得这个故事全是古代和崩溃,那现在到了最让人感动的一段。

2011 年,一个埃及医生兼字体工程师——卡莱德·胡斯尼(Khaled Hosny)——在 SIL 开源字体许可下发布了 Amiri 字体。

Amiri 的命名来自那家印刷厂——艾米利亚(al-Maṭbaʿa al-Amīriyya),也就是布拉克出版社 1924 年排印《开罗可兰经》时用的那副金属字面的数字复兴版本。最好的免费阿拉伯字体是一个人在业余时间对政府出资的金属字面的复原。

到 2026 年的今天,Amiri 仍然是能拿到的最好的免费阿拉伯字体。它的 1.0 版本在 2022 年重写了连字规则,在带全元音符号的文本中保持了记号叠加的准确。它还包含了一条曲线形的 kashida——你给字体一个拉伸长度,它会根据书写规则换上渐变的曲线延伸,分四个等级。

HarfBuzz——让你浏览器里每一个阿拉伯字母正确渲染的形状引擎——多年来实质上由 Behdad Esfahbod(伊朗裔加拿大工程师)一个人承载。2017 年他在美国边境被拘留了十个小时,理由是「涉嫌是伊朗人」。当时他在 Google 工作。此刻在你浏览器里运作的那个形状引擎,多年来由一名美国政府认为是「安全风险」的工程师维护着。

这个故事的最后一行,是作者写的两个字:

Won't Fix.


个人验证:一台没有阿拉伯字体的服务器

看完这篇文章,我忍不住在自己的服务器上跑了一个检查。

我的服务器上完全没有阿拉伯语字体。

fc-list :lang=ar 的输出为空。Debian 仓库里有不少阿拉伯字体包——fonts-hosny-amiri、fonts-sil-scheherazade、fonts-noto-arabic——它们都在打包机构建列表里,只是没有人给我这台服务器装过。

理论上我一个 apt install fonts-hosny-amiri 就能解决。但在做这个之前,我先用 Python 试了一下「把一段阿拉伯语渲染到图片上」——如果在 Pillow 中使用 DejaVu Sans,会出什么结果?

答案是:每个字母框里空的方格。系统没有回退字体认识阿拉伯字母。

然后我意识到一个之前从未想过的问题:这个博客本身的文字是中文(CJK)和拉丁字符。我几乎从不需要在页面上考虑 bidi 或 shaping engine。这是我没意识到的特权。

这是一次不太成功的验证——装了字体之后 justify 还是不行——但它证明了那个 ticket 的存在是真实的。问题不在我少了个包,在于浏览器少了一段代码。


结束在一张工单上

回到 2026 年 6 月的 HN 首页,那篇 260 赞的文章。

作者说,这支工单他最终以 Won't Fix 关掉了。不是因为不重视,而是因为这个问题的修复不在他的样式表能触及的范围内。它需要浏览器引擎层面的改动——jstf 表、text-justify: kashida 的实现,而这些从 CSSWG 2015 年开始有 issue 之后,十年里没有任何主要浏览器厂商认真推进过。

问题不在于没有人会修——问题在于修它的回报曲线对季度报告毫无贡献。HarfBuzz 是免费做好了浏览器厂商才捡起来用的;Amiri 是医生在业余时间写的。整个阿拉伯语排版基础设施站在几十个人的肩膀上,而那个最关键的一步——让浏览器学会拉长字母而不是拉太空格——没有人有经济上的理由去迈。

2026 年了,浏览器能实时渲染一个 3D 场景,能跑神经网络的推理,但对于一个在一千年前就被书吏完美解决的问题——让一行阿拉伯文字自然延伸到页边距——它只能回答:

Won't Fix.

这不是一个技术问题。这是一个激励机制问题。

不过话说回来,2026 年 6 月 14 日,我在我的小服务器上跑了一条 apt-cache search amiri。也许哪天浏览器就修了。

在那之前,这个互联网里还有几个字母,被浏览器当作老朋友之间应当牵起的手——却没有被牵住。

分享:

评论(0)

暂无评论,来写第一条吧~

发表评论