压缩 PDF 时实际发生什么
PDF123 压缩执行 qpdf 流重压缩与对象流打包,不降采样图像,也不子集化字体。页面外观不变,节省来自更紧的对象打包。线性化只重排对象,几乎不缩小文件。

两份 PDF 在屏幕上可以完全一样,体积却差三个数量级:50 KB 对 50 MB。差距几乎从不出在文字上。一份二十页合同的文字运算符通常只占几十 KB,其余全是图像采样数据、整份嵌入的字体,以及流与对象打包得有多松。
这就带来一个后果:“压缩 PDF”不是一个动作,而是四个。它们缩小的对象不同、代价不同,混在一起谈,就会得出互相矛盾的经验:压过了却没变小,或者小是小了但糊了。
体积是从哪来的
多数“这份 PDF 为什么这么大”的情况,源头是仍按采集尺寸嵌入的高分辨率扫描件或照片。这个量级可以做一次算术:信纸大小按 8.5 × 11 英寸、300 DPI 采集,得到约 2550 × 3300 像素;每个像素三个字节,未压缩就是 25 MB 上下。二十页这样的扫描,在任何人碰压缩之前常已到 60 到 80 MB。
第二类来源是字体。PDF 可以把整个字体程序嵌进文件,好让任何机器都能原样渲染,嵌入开销按字形表大小计价,不会被阅读量摊薄。这也是为什么字体子集化是另一个独立的体积杠杆。
第三类是打包本身。同样的内容,流可以带着冗余的压缩设置存储,对象可以逐个散落在文件里,每个都附带一份簿记开销。这一部分不改变任何像素或字形,却是流重压缩能真正吃到的东西。
“压缩”是四件事,代价各不相同
把体积当目标时,可动的杠杆有四个。它们作用在不同层次,收益与代价几乎不重叠,所以选错杠杆是“压了没效果”最常见的原因。
| 杠杆 | 动的是什么 | 体积收益 | 代价 |
|---|---|---|---|
| 流重压缩与对象流打包 | 内容流的压缩方式、对象的存放与打包 | 取决于原文件有多松 | 页面外观不变 |
| 图像降采样 | 像素数量,即分辨率 | 大 | 分辨率永久降低,不可逆 |
| 有损图像重编码 | 位图的字节表示 | 大 | 画质下降,不可逆 |
| 字体子集化 | 嵌入的字形表 | 中 | 未用到的字形不再可编辑 |
| 线性化 | 对象的排列顺序 | 几乎为零 | 换来的是首屏加载速度 |
前四行常被笼统叫作“压缩”,但只有第一行不改变内容。收益最容易被误判的也是第一行:一份体积主要来自原始图像采样的扫描件,用它能挤出的空间有限,因为那些字节里没有冗余可去。真正能把它砍掉大半的是降采样或有损重编码,代价是你永久失去了对应的分辨率与画质。
PDF123 压缩做的是哪一件
压缩 PDF(POST /api/v1/misc/compress-pdf)只做第一行:它让 qpdf 压缩未压缩的流(--compress-streams=y)、对已用 Flate 压缩的流再压一遍(--recompress-flate),并把对象收进对象流(--object-streams=generate)。
它不降采样图像,不把位图重编码成更低质量的 JPEG,也不子集化字体。所以页面外观应当保持一致,节省来自 Flate 重压缩与对象流打包。
失效条件同样要说清楚。如果文件的体积主要来自已经用 JPEG 这类格式压过的图像数据,Flate 对它们没有可挤的余地;图像字节不在 qpdf 这三个开关的作用范围内。这也是为什么一份扫描件的压缩结果可能只小几个百分点,而同样大小的文本型 PDF 收益明显。
反向路径是解压 PDF,它展开流供检查(--qdf,对象流关闭),同样不改变页面外观。
什么时候该走别的路径
| 你的目标 | 该用的路径 |
|---|---|
| 外观完全不变,只想去掉打包冗余 | 压缩 |
| 扫描件确实太大,可以接受画质损失 | 降采样或有损重编码,不是这条压缩 |
| 需要继续编辑,缺字形打不出字 | 换导出设置或源文件,见字体子集化 |
| 想让浏览器先看到第一页 | 线性化解决首屏,不解决体积 |
| 文件根本打不开 | 修复,那是结构问题,不是体积问题 |
最后一行有个容易踩的坑:本站的线性化 PDF不是独立实现,而是压缩的别名。它与压缩指向同一个操作(规范 id 是 misc/compress-pdf,linearize-pdf 只是别名),门户在这个工具上固定发送 linearize=true 与 optimizeLevel=1,而服务端这两个字段都不读,qpdf 也从不被传 --linearize。真正会让 qpdf 线性化的是给文件加密。
所以如果你要的是“外观不变、体积更小”,用压缩就够了;如果你要的是有损图像缩小或字体子集化,这条路径不是对的杠杆。服务端跑完直接返回文件,无需账号。想看清对象、流与交叉引用表在文件里的位置,可以接着读PDF 里面有什么。