改一个词,其他什么都不动——听起来是PDF编辑器里最简单的功能,结果成了最耗时的那一个。一位开发者花了几个月做PDF编辑器,最后发现时间几乎全耗在这件事上。他的结论是:PDF根本不存文字,它存的是一堆绘图指令。
PDF里没有“句子”这回事
先要放弃的第一个假设是:PDF存的是文字。实际上,它存的是绘图指令。一行文字在PDF里通常长这样:
BT /F1 11 Tf 72 700 Td [(W) -30 (e) 15 (l) -20 (come)] TJ ET
那个TJ数组,是一个视觉上的单词被拆成了碎片,每个字母之间还带着字距微调参数。整个文件里根本找不到一个叫“Welcome”的字符串。没有段落,没有行,也没有任何“这些字形属于同一个词”的概念。人眼看到的所有结构,都是读文件的程序猜出来的。
所以“查找替换”根本不是字符串操作。它实际上是:从带位置的字形里重建出逻辑文本,在重建的文本里找到目标,再反推是哪几条绘图指令生成了那些字形,删掉它们,画上新的内容,还要让新内容看起来像本来就在那儿。
要替换文字,得先找回四样东西
要让替换结果看不出痕迹,你需要原始的字体、字号、基线和颜色。这四样没有一样是可靠可得的。
字号不是Tf后面那个数字那么简单。文本矩阵会缩放它。一个声明为Tf 1的标题,如果矩阵放大了24倍,实际渲染出来就是24磅。只看Tf值,你拿到的是1。
基线比人们以为的重要得多。高了两三个点,人眼立刻就能察觉,哪怕字体和字号都完美匹配。
颜色在扫描文档里尤其麻烦。取一页的背景色做参考,最后很可能在米黄色纸上刷出白色色块。
这位开发者的做法是:从周围行去估算——取邻近文本块的字号、字体和基线的加权中位数,只有在精确匹配不到时才用这个估算值。而几乎所有bug,都出在这条兜底路径上。
最让他长教训的那个bug
替换学术论文里的文字时,结果总是变成错误的字体:一篇衬线体文档里,混进了一个细无衬线体。读者一眼就能看出来,他却始终复现不了。
他造了自以为能匹配失败场景的测试PDF:内嵌字体、子集化、粗体标题,全都通过了。他对着这些文件“修”了三次bug,结果在真实文档上还是失败。
最后他不再造测试文件,直接从arXiv下载了一篇真实论文。立刻失败,每次都失败。原因是一行代码:
let serif = low.contains("times") || low.contains("roman") || ...
LaTeX嵌入的Times克隆字体叫NimbusRomNo9L。里面含有“Rom”,但不含“roman”。于是这个字体被判定为无衬线体,替换成了Helvetica。Computer Modern也有同样的问题:CMR10和CMBX12里同样找不到“roman”这个词。
同样的命名假设还搞坏了字重检测。Nimbus的粗体叫“-Medi”,不叫“-Bold”,于是……
热门跟贴