1、简介
PDF(英语:Portable Document Format,缩写:PDF)
2、文件结构
一般的PDF文件由四部分组成
- header部分:单行,标识文件所遵循的PDF规范的版本
- body部分:多行,包含了由多个文件对象构成的文件主体
- cross-reference table 部分:交叉引用表,包含文件中间接对象信息
- trailer部分:包含交叉引用表的位置以及文件正文中某些特殊对象的位置
另外,PDF支持文件增量更新,新增部分可以附加到原始文件之后,通过同样类似的结构追加进来。
按照规范标准约定,PDF文件按照一定的标记排列成行,每行的终结符可以是回车符( 、0x0D、ASCII 13),也可以是换行符( 、0x0A、ASCII 10),也可以两者同时存在( 、0x0D0x0A)表示一行结束。为了统一,行标记占两个字符,通常为空格+回车、空格+换行或者回车+换行这3种形式。包含二进制数据的部分可以是任意多行。
为了增强PDF格式文件解析处理程序的兼容性,不属于流对象数据的行的行数不得超过255个字符。 有个列外情况,从PDF 1.3开始,签名字典的Contents字符串不受行长的限制。
以上为基本的PDF格式,PDF在设计时考虑了扩展结构,以适用于在不同环境下的使用。例如线性化PDF文件是PDF文件的一种特殊格式,可以通过Internet更快地进行查看。线性化PDF文件包含允许的信息字节流服务器一次下载PDF文件一页。如果在服务器上禁用了字节流,或者PDF文件未禁用线性化后,必须先下载整个PDF文件才能查看。所有受支持的IDS版本都会生成线性化的PDF文件。对于PDF线性化来说,在页面数量很多的情况下,能突出表现出它快速网络浏览的优势。
2.1、Header
PDF文件的第一行,由5个字符组成,%PDF-后跟形式为1.N的版本号,其中N是介于0和7之间的数字。
%PDF–1.0 %PDF–1.1 %PDF–1.2 %PDF–1.3 %PDF–1.4 %PDF–1.5 %PDF–1.6 %PDF–1.7
PDF低于1.4版本时,唯一由Header确定版本。从1.4开始,版本标识可以从文档的目录字典中获取(文档目录字典位于位于文件Trailer部分的中的Root条目),通常PDF生成器遵循两个版本标记一致原则。
2.2、Body
PDF文件的body由代表文档内容的一系列间接对象组成。
- Boolean values
- Integer and Real numbers
- Strings
- Names
- Arrays
- Dictionaries
- Streams
- Null object
- Object Streams
PDF包括八种基本类型的对象:Boolean values, Integer and Real numbers, Strings, Names, Arrays, Dictionaries, Streams, and the null object。通过这8种基本对象可以用来描述文档的各种特征,例如字体、页面和采样的图像。
从PDF 1.5开始,Body还可以包含对象流(Object Streams),每个对象流可以包含一系列的间接对象(Indirect Objects)。
2.3、Cross-Reference Table(交叉引用表)
交叉引用表包含允许随机访问文件中间接对象的信息,因此无需读取整个文件即可找到任何特定对象。 该表应为每个间接对象包含一个单行条目,并指定该对象在文件正文中的字节偏移量。
从PDF 1.5开始,部分或全部交叉引用信息可能会包含在交叉引用流(Cross-Reference Streams)中。
交叉引用表是PDF文件中具有固定格式的唯一部分,它允许随意访问表中的条目。PDF文件可以包含一个或多个交叉引用部分。例如原始文件可能只有一个交叉引用表,如果文件是线性化的,则文件结构是由两部分组成,每次文件进行增量更新时,都应该增加一个交叉引用表结构。
每个交叉引用部分均应以包含关键字xref的行开头。此行之后是一个或多个交叉引用小节,这些引用小节可以以任何顺序出现。
对于从未进行过增量更新的文件,交叉引用节应仅包含一个子节,其对象编号从0开始。子节结构对于增量更新很有用,因为它允许将新的交叉引用节添加到PDF文件中,其中仅包含已添加或删除的对象的条目。
每个交叉引用小节均应包含对象编号连续范围的条目。该小节应以包含以空格隔开的两个数字的行开头,表示该小节中第一个对象的对象编号及该小节中的条目数。
例如以下,表示从28~32的连续5个对象为其中的一个小节
xref 28 5 ...
对于给定的对象编号只允许出现在某一个小节,不允许在其他小节重复引用出现。
在编号行之后是交叉引用条目本身,每行一个条目。每个条目为20个字节长,包括行尾标记。
交叉引用条目有两种:一种用于使用中的对象,另一种用于已删除并因此是空闲的对象。两种类型的条目都有类似的基本格式,以关键字n(in-use)或f(free)来区分。条目的格式为:
nnnnnnnnnn ggggg n eol
...
nnnnnnnnnn ggggg f eol
- nnnnnnnnnn为解码流中10位字节偏移量,绝对占位10,不够10位时用0做前导填充
- ggggg为5位字节生成编号,绝对占位5,不够5位时用0做前导填充
- n表示该对象处于使用中,f表示该对象已删除
- eol表示换行标记,可以是SP CR、SP LF或者CR LF
4节数据默认以空格为间隔,组成一行,每行固定占位20字节。
交叉引用表中的第1个编号为0的对象始终是(f),并且生成号为65535。
除了编号0的对象外,交叉引用表中的所有对象最初的生成号应为0。
删除间接对象时,应将其交叉引用条目标记为(f),并将其添加到free条目的链表中。
下次创建具有该对象编号的对象时,条目的生成号应增加1,最大生成号为65535;当交叉引用条目达到此值时,它将永远不会被重用。
连续的交叉引用表
下面显示了一个交叉引用表,该表由一个包含6个条目的小节组成:4个正在使用(对象编号1、2、4和5)和2个空闲(对象编号0和3)。 对象编号3已被删除,使用该对象编号创建的下一个对象的生成编号为7。
xref 0 6 0000000003 65535 f 0000000017 00000 n 0000000081 00000 n 0000000000 00007 f 0000000331 00000 n 0000000409 00000 n
非连续的交叉引用表
下面显示了具有4个子节的交叉引用表,总共包含5个条目。
第1个小节包含1个条目,对象编号为0,标记是f(free),默认的。
第2个小节包含1个条目,对象编号为3,正在使用(n)。
第3小节包含2个条目,对象编号为23、24,正在使用(n)。从对象编号23的生成编号为2可以看出,它已被重用(n)。
第4小节包含1个条目,对象编号30,正在使用中(n)。
xref 0 1 0000000000 65535 f 3 1 0000025325 00000 n 23 2 0000025518 00002 n 0000025635 00000 n 30 1 0000025777 00000 n
2.4、File Trailer(文件尾)
PDF文件的trailer文件尾使得应用程序可以快速找到交叉引用表和某些特殊对象。应用程序从文件末尾读取PDF文件。
文件的最后一行应仅包含文件结尾标记%%EOF。
倒数往前两行应每行依次包含关键字startxref和解码后的流中从文件起始位置到最后一个交叉引用节中xref关键字开头的字节偏移量。
startxref行之前必须是Trailer字典,该字典由关键字Trailer组成,后跟一系列用双尖括号(<< ... >>)括起来的键/值对。 总体结构如下:
trailer << key1 value1 key2 value2 ... keyn valuen >> startxref Byte_offset_of_last_cross-reference_section %%EOF
Trailer字典类型
Key | Type | Value |
Size | 数字型 | 所有间接对象的个数。一个PDF文件,如果被更新过,则会有多个对象集合、交叉引用表、trailer,最后一个trailer的这个字段记录了之前所有对象的个数。这个值必须是直接对象。 |
Prev | 数字型 |
当文件有多个对象集合、交叉引用表和trailer时,才会有这个键,它表示前一个相对于文件头的偏移位置。这个值必须是直接对象。 |
Root | 字典 | Catalog字典(文件的逻辑入口点)的对象号。必须是间接对象。 |
Encrypt | 字典 | 文档被保护时,会有这个字段,加密字典的对象号。 |
info | 字典 | 存放文档信息的字典,必须是间接对象。 |
ID | 数组 | 文件的ID |
一个未增量更新过的PDF原始文件的trailer文件尾如下格式,标志是没有Prev这个Key
trailer << /Size 22 /Root 20R /Info 10R /ID [ <81b14aafa313db63dbd6f981e49f94f4> <81b14aafa313db63dbd6f981e49f94f4> ] >> startxref 18799 %%EOF
2.5、Incremental Updates(增量更新)
PDF文件内容可以增量更新而无需重写整个文件。更新变化数据需要附加到文件尾段,而其原始内容可以保持不变。
PDF支持增量更新的优点:
- 可以快速保存对大型文档的较小更改。
- 在某些情况下,例如通过HTTP连接编辑文档或使用OLE嵌入(Windows特定技术)时,符合条件的编写器无法覆盖原始文件的内容时,增量更新可用于保存对文档的更改。
增量更新后的文件结构示例:
增量更新时新增的交叉引用表部分仅包含那些已经被修改,替换,或删掉的对象。删除的对象原封不动的留在文件中,需要通过交叉引用表把对应的交叉引用表项标记为已删除(f)。新增的trailer文件尾包括所有的选项包括之前的trailer文件尾部分,字典中的Prev项标记之前交叉引用部分的位置。被更新过多次的PDF文件通常包含了多个trailer文件尾;每个trailer文件尾都有自己的结束标识(%%EOF)。
由于更新的部分被添加到文件中,一个文件可能有一个相同对象的多个副本,而保持相同的对象标识(对象编号和生成编号)。可能出现以下情况,例如如果一个文本标注被修改多次并且文件保存了多次的修改。因为文本标注对象没删除,它仍保持着之前相同的对象号和生成号。但一个被修改过的对象副本会被包含在更新部分添加到文件中。更新的交叉引用表部分包含这个新的对象的字节偏移值,覆盖原先交叉引用表中旧的字节偏移值。当一个用户应用程序读取文件时,它必需用这种方法构建它自己的交叉引用表信息,即文件中每个对象的最新副本是被访问的那一个。
PDF 1.4之后的版本,版本号Version是支持在增量更新中修改。