Zalgo 文字看起来像字体从行框里逃了出来:
Z̴̡̛̫̹̿a̵̡̳̋l̴̙̓g̶̤̈o̷̜̽
它不是图片,通常也不是字体损坏。这种效果来自在普通基础字符后面添加大量组合附加符。
组合附加符在做什么?
许多文字系统需要重音、元音符号、声调、诵读符号以及其他和相邻字符有关的记号。Unicode 把其中很多编码成不占间距的组合字符。
以 é 为例,它可以表示为:
- 单个预组字符 U+00E9 LATIN SMALL LETTER E WITH ACUTE;
- 或者 U+0065 LATIN SMALL LETTER E,后跟 U+0301 COMBINING ACUTE ACCENT。
第二个代码点会修饰第一个。排版系统把重音放在基础字母上方,却不会像普通字母一样让光标再前进一个字符宽度。
Zalgo 生成器利用了同一机制,只是会在每个字母的上方、下方和中间添加远超正常拼写需要的附加符。
为什么它能堆得这么夸张?
组合字符具有规范组合类别,帮助系统决定它们在基础字符周围的顺序。字体也会为常见组合提供锚点和定位数据。当序列非常少见或特别长时,排版系统只能尽量处理。
一种字体可能把附加符叠得很整齐;另一种可能让它们碰撞、盖住相邻行,或者伸出按钮边界。文字序列本身仍然有效,只是界面从来没有为一个字母上挂几十个重音做过设计。
这也是同一效果会随浏览器和操作系统变化的原因。Unicode 规定字符身份与属性,字体决定具体形状和连接位置。
正规化不等于“删除重音”
Unicode 正规化让等价的文字序列更容易比较。NFC 会在存在预组字符时,把基础字母和附加符组合起来;NFD 则会把许多预组字符拆成基础字符加附加符。两者都会把组合符调整到规范顺序。
但它们都不是通用的 Zalgo 清理器。大多数极端组合根本没有预组等价字符,正规化后,这些附加符依旧是有意义的编码字符。
产品如果想提供“纯净文字”功能,就必须另外决定哪些符号属于语言、哪些属于装饰、哪些可以删除。粗暴移除所有组合符,会损坏越南文、阿拉伯文、希伯来文、印度文字等许多正常语言。
一个视觉字母可以包含很多代码点
Unicode 文本分段通常会把基础字符和后续组合符放进同一个扩展字素簇。所以即使 é 由两个代码点编码,光标也可以把它当成一个单位移动。
Zalgo 把这条规则推到了极端。一个看起来像单个字母的单位,底层可能有十个、二十个甚至更多代码点。这会影响:
- 数据库长度限制;
- 短信和通知截断;
- 光标移动与选区;
- 行高和内容裁切;
- 搜索与内容审核;
- 屏幕阅读器输出。
用户看到的长度,并不等于计算机存储的长度。
装饰效果背后是重要的文字基础设施
组合附加符不是“坏字符”。它们是数字书写不可缺少的一部分。故障效果之所以成立,正是因为 Unicode 和现代文字塑形系统支持复杂文字,而不是假定每个视觉单位都只是一个英文字母。
风险来自使用场景和数量。巨大的字符堆会让界面难以阅读、制造异常行高,或者隐藏人眼不容易发现但自动系统仍会处理的差异。有关 Unicode 隐形扰动的研究也发现,不可见字符、组合方式、同形字符和文字重排都可能干扰语言处理系统。
怎样使用才不容易破坏页面?
用于装饰性内容时:
- 把效果限制在一个短标题或一个单词;
- 使用低到中等强度;
- 在最终发布的应用中预览;
- 文字内容很重要时,附上一份普通版本;
- 不要用于账号、链接、邮箱和安全信息。
产品界面则应同时限制代码点数量和字素簇数量,处理溢出,并测试异常长的组合序列。不能把“看起来很短”理解成“存储和显示成本也很低”。
Zalgo 是靠累积产生的排版效果:基础字母仍然普通,真实的 Unicode 附加符却堆出了不普通的形状。
故障文字生成器可以控制添加多少附加符。建议先从轻度开始,再把结果放到上方显微镜里检查。