PDF 1.5 引入了两种以前文件格式无法表达的存储结构:对象流(object stream)和交叉引用流(cross-reference stream)。对象流是一个经过 Flate 压缩的容器,标记为 /Type /ObjStm,它将许多小的间接对象首尾相接打包在一起,而不是将它们分散在整个文件体中。交叉引用流则是将文件的查找表重写为具有可变宽度字段的压缩二进制数据,取代了直至 1.4 版本在每个 PDF 结尾处闭合的固定宽度 ASCII 查找表。这两者总是结伴而行。因为一旦对象被折叠进流里,旧的文本表就无法再定位它们,所以必须采用二进制的交叉引用流搭配使用
将其与经典的布局作对比,它所免去的开销显而易见。在 PDF 1.4 文件中,每个间接对象都是不压缩地放在其自己的 obj 头部之后,并且尾部的表对每个条目花费整整 20 个字节的 ASCII,禁止任何形式压缩。一个有 20 万个对象的文档在画下哪怕一个字形前就已经带有约 4 MB 的交叉引用数据,外加所有未压缩字典主体所叠加上去的尺寸。PDF 1.5 瞬间攻克了这两种消耗:字典被折叠进 Flate 容器里边,而这 4 MB 的引用表缩减成只有几百 KB 的二进制数据。在 ISO 32000-1 中的 §7.5.7 和 §7.5.8 对这两种结构进行了定义
实际节省的是哪些开销
对象流仅仅针对非流对象(non-stream objects)产生作用,所以它压缩的是结构,而非像素。因为早在 1.5 以前页面内容就已经过 Flate 压缩了,且图像数据携带了自己的一套编解码器(codecs),这就是为何一份图多文本少的宣传册不会在这项设置下缩多少。那些能在体积上剧减的是重度依赖结构的文件:带有成千上万个字段字典(field dictionaries)的 AcroForm 表单、深深交错排布的文档大纲树(outline trees)、标签化 PDF(tagged-PDF)中的结构元素。那些对象小巧、数目繁多且彼此极度相似,这重复的特质一旦全放置在同一个缓冲池中,而非在中间楔上标头散落在整个主文之间,正是 Flate 所能拿捏得最如鱼得水的场景
老旧文件中存在的巨大隐形成本常被轻易低估。历经数年反复修改存档下来的表单,可能将其超过一半的文件容量都耗在了字典标头、xref 的填充留白位,还有那些读者压根永远不会去查看的版本记录之上。前面的这两项特色回收了这两部分空间的消耗。至于第三部分,日积月累修定改版的记录,唯有在实施紧缩,即该文件不再必须保留它的修改历史之后才能得以消除
在 HotPDF 之中你借着下面这两条属性即可开启这两个选项,并且它们两者间牵连的作用机制比你把它们写出来的先后次序更重要:
var
Pdf: THotPDF;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.FileName := 'catalog-2026.pdf';
Pdf.UseXRefStream := True; // binary xref, prerequisite for ObjStm
Pdf.UseObjectStreams := True; // pack objects into /Type /ObjStm
Pdf.BeginDoc;
Pdf.CurrentPage.SetFont('Arial', [], 11);
Pdf.CurrentPage.TextOut(50, 760, 0, 'Compressed structure demo');
Pdf.EndDoc; // emits XRefStm + ObjStm containers
finally
Pdf.Free;
end;
end;
UseObjectStreams 必须与 UseXRefStream 一起设为 True。压缩对象通过类型 2 的 xref 条目定位,该条目记录对象流编号和对象在流中的索引;传统的 20 字节文本条目无法保存这对信息。因此单独启用 UseObjectStreams 不会产生可见效果。请在 BeginDoc 之前设置两个属性;调用 BeginDoc 后,HotPDF 已经确定了文件布局
为什么默认关闭这两个选项
HotPDF 默认将两个属性设为 False,主要是为了兼容旧的下游程序。只支持 PDF 1.4 的读取器通常不会明确说明自己不支持压缩对象,遇到 xref 流后可能报告交叉引用表损坏,甚至直接拒绝打开文件。如果输出要进入旧传真网关、带嵌入式解释器的打印机,或按照 PDF 1.4 编写的解析器,请为该通道保留默认设置并接受更大的文件。对于归档和网页交付,主流查看器早已支持 PDF 1.5,启用这两个选项通常可以用很小的兼容性代价换取明显的结构压缩
还得捎上句告知给你那伙后端后勤队的话做留心提醒它其中蕴藏的次序性附带后果连带现象:这些字表字典们被成簇成片打包裹卷对象流当中了以后,拿着整整前后两次跑出来的成品想要生凑着去做字字对应的纯数据比对这事情从此失去了其一切判定内涵根据,因为区区只消更改一丁点地内文便极有能全包重头给重新 Flate 加一次缩而继往地连根把整个内部一切全推洗排列掉一通的后果出来。去做文件鉴对需要是以里面的成分也就是各物件实情上作拆判比对而不是硬对着全套二进码去做较真了
增量更新和它们保护的字节偏移量
数字签名覆盖了一个明确的 /ByteRange:这是构成物理文件的两段跨度范围,以绝对字节偏移量的形式给出,CMS 摘要就是在这两段范围上提取的。重写文件时,即便是在屏幕上看起来一模一样的内容,所有的这些偏移量也全都会移位。摘要将不再匹配,而且签名会被读取为已被破坏。这就是 ISO 32000-1 §7.5.6 使用增量更新所确切解决的问题。新的和发生改变的对象被附加在现有的 %%EOF 之后,然后写入一个全新的交叉引用区段,该区段的 /Prev 条目回指到它之前的那个。原始字节永远不会受到扰动,因此带有签名的版本始终保持可验证状态,Acrobat 则可以在签名面板中将其每个已签名的版本单独呈现出来
HotPDF 通过其自己的入口点暴露了这个功能:
Pdf.BeginIncrementalUpdate('contract-signed.pdf');
Pdf.AddPage;
Pdf.CurrentPage.SetFont('Arial', [], 10);
Pdf.CurrentPage.TextOut(50, 760, 0, 'Addendum recorded 2026-06-11');
Pdf.SaveIncrementalUpdate('contract-updated.pdf'); // appends the delta only
有两点经常让人绊脚。BeginIncrementalUpdate 必须接收原始的文件名,因为附加的 xref 区段所记录的偏移量仅相对于那些确切的原始字节才有意义;如果将它指向一个重命名过或是另存过的副本,那些偏移量描述的就是一个已不复存在的文件了。再者,按照其构建方式,保存操作只能是追加式的(append-only),因此输出文件的体积永远都比输入文件大。这种体积增长并不是应当被优化掉的浪费。正是依靠这一特性,才使得早期带签名的版本完好无缺
对已加载文件的修改通过 LoadFromFile 进行
那些最先通过生成功能 API 接触到 HotPDF 的开发者很容易撞在一堵特定的墙上。BeginDoc 打开的是一个全新的文档,如果你原本打算去修改一个已经存在的文件,这可就用错了工具。编辑现有文件的流程应使用针对已加载文档(loaded-document)的调用集:
PageCount := Pdf.LoadFromFile('base.pdf');
Pdf.InsertPagesFromDocument(OtherDoc, '1-3', 5); // pages 1-3 after page 5
Pdf.MovePage(2, 5);
Pdf.SaveLoadedDocument('modified.pdf');
把这两种混合用,表现出来的症状就是输出文件里面只有你的新内容,而原有的东西统统不见了,原因在于 BeginDoc 在你以为正去加以编辑的地方一转头给你建了一个全新的空档。把 LoadFromFile 连同着 SaveLoadedDocument 当作一拨语汇词库;把 BeginDoc 带上 EndDoc 看作是另外一套。如果一通流程里同时向一个文件身上伸出向这两套的手时几乎断定是做错了
什么时候应压缩追加后的文件
只会越叠越厚光出不进性质的纯增档(append-only)带有缓慢蚕食成本。要是做个日巡检班工作:天天光往那份老 PDF 底底儿上按新状态留只这么一道墨字那就等于滚积成三百来多六十的叠加版本年终盘点案,且这每一次累叠版都在其屁股后方拖个更新交叠索引新段出来。待至那一整坨老底子往事的参考意义完全丧退到了头儿、不再在乎是否需强撑里边某个当初数字签名存世与否之时,那你大可以完全痛下狠手给彻底熨个扁去:重通过那个承制装读取存现存件路子:
Pdf.LoadFromFile('stamped.pdf');
Pdf.SaveLoadedDocument('compacted.pdf');
如此一倒腾实打实是一次满盘从推重写的全面彻底置换操作。它是主观放弃所有之前历历过往重写之并会粉碎尚存在这档案里边所有残全遗存认证所以只当把它安置那和实行其它等同绝灭断交同毁属性前的一致门监条款之后再施用。有个放任执行规则也蛮抗打通用:每等到复翻累计版本突破了一定量警戒大限亦或者这种往尾巴接拼搭堆生占的比率撑爆出了根基母文件特定份量之际做它紧缩且坚绝戒止严忌在明眼见到签名框仍带任何在项事关物里施行做这种紧缩动作
发布前检查输出
印证这对特色拍档着实是一门令人宽慰省事确有凭实据的手艺。放 Acrobat 跑跑验真三点这般即妥明鉴见底;打点属性窗那儿是否打出了是 PDF 1.5 及以上一旦是上了个流物件打底开端之后之事;验增累存过之更新案在拉通后在签字栏那仍然通报其每一次历史曾上留名批按之实皆完整健妥有效;然后确保页数和各个标栏经得起这种导回重调更修改重存流程毫不败损落空等就大体是妥贴之举。针对当用于正牌存档规程去发行流传这类档,也须顺送它受受 veraPDF 那类货的苛挑检查,因这种加编带密形式正是该此般把死角究死理苛厉校验器抓它个眼红盯死细勘远超普适查阅宽纵度上心死查死问所在。若你们的手头上活里还搀带着超级大份个头料档进出那么,在这个涉及用于大型 PDF 行走作业工序下的 Direct File API 全流程领带说明里 有提那类检察门路和追加存留增修自然也是极顺套般结合匹配且有讲法;而对在上面此等基于范围界定这底基机能牵及出的实权电子验明正签大义则在 有关 HotPDF 加码数字化验签 PAdES 实论里头 作极尽详尽彻论发扬
这双大两门技术悉尽包纳置在这件用与供给在 Delphi 及 C++Builder 上行其事的 HotPDF Component 所自带附属配置系统里面出让随给而至。这功能位列该博客它地之列出生成的、打着表的,还有涉及秘加上锁与授权的之那些的各种同列当中存在。产品面首页早已链接有全线完整操作调指方法总揽只要想要比对此些前面列明号文用于你家的档案输作线上直接调参皆在那处了