如果你使用文本编辑器打开PDF文件,那你就会发现,.pdf文件与.html文件类似,都有自己的一套结构,无论 PDF 多么复杂(包含注释、多媒体、3D 对象),它的底层结构都遵循一个基本框架:
Header // 文件头
Body // 文件体
Xref // 交叉引用表
Trailer // 文件尾
是不是与html的结构很相似?
理解这四个部分,就等于掌握了 PDF 文件的骨架。
1. 文件头(Header)
PDF 文件的开头总是以一个标识行开始:
%PDF-1.7
%PDF表明这是一个 PDF 文件。1.7表示 PDF 的版本号。
有些 PDF 文件在开头还会包含一些二进制字符(例如 %âãÏÓ),这是为了确保文件不会被错误地识别为纯文本。
%PDF-1.7
%âãÏÓ
👉 文件头的作用很简单:告诉解析器这是不是 PDF,以及遵循哪个版本规范。
2. 文件体(Body)
紧接着文件头的是 文件体,这里存放了 PDF 的所有对象,每一个对象均以如下格式存在:
1 0 obj // 对象开始标识
... // 对象内容
endobj // 对象结束标识
- 对象开始标识格式有
[数字] [数字] obj组成,第一个数字表示对象的编号,这是第1个对象;第二个数字标识对象版本号,对象被更新过后会递增;obj是固定标识。 - 对象内容是对象实际数据,可以是字典、流、数组、字符串等。
- 对象结束关键字,表示对象内容结束。
2.1 对象内容基础类型
常见对象类型包括:
-
布尔值:
true/false -
数字:整数或浮点数
-
字符串:文本或二进制数据
-
名称(Name) :以
/开头,如/Author -
数组:例如
[1 2 3 /Page] -
字典(Dictionary) :键值对集合,例如:
<< /Type /Page /Parent 1 0 R /Resources 2 0 R /Contents 3 0 R >> -
流对象(Stream) :字典 + 二进制数据块(存储图片、压缩内容等)
2.2 直接对象和间接对象
PDF对象之间存在引用关系,比如一个Page对象(标识某一页)下面就引用了1个或多个Contents对象(页面的内容):
12 0 obj
<< /Type /Page
/Parent 1 0 R
/Contents [13 0 R 14 0 R]
>>
endobj
这里 12 0 obj的/Type为/Page,表明这是一个page对象,他的父级(/Parent)是 1 0 obj,他下面有两个内容对象(/Contents) 13 0 obj和14 0 obj。
这样对象就可以通过“编号 + 引用”来互相指向,构建出整棵文档树。引用其他对象的对象是直接对象,被引用的对象就是间接对象。
👉 文件体就像是 零件仓库,里面存放了文档所需的各种对象(页面、字体、图像等)。
有点像html的body里面的元素标签,但是这里的对象不会嵌套,而是平铺的,通过对象编号之间的引用构建树关系。
3. 交叉引用表(Cross-reference Table, xref)
为了快速定位对象,PDF 在文件体之后维护了一个 xref 表。
它记录了每个对象在文件中的偏移位置。
示例:
xref
0 6
0000000000 65535 f
0000000017 00000 n
0000000081 00000 n
0000000145 00000 n
0000000200 00000 n
0000000275 00000 n
含义:
-
0 6表示表中包含从 0 到 5 的 6 个对象。 -
每一行记录一个对象的位置、版本号、是否在用。
f表示对象已释放(free)n表示对象正在使用(in use)
👉 交叉引用表就是 目录,让 PDF 阅读器可以快速找到对象的位置。
这里需要特别解释的是每一行中对象位置、版本号的含义,以0000000145 00000 n为例,0000000145表示将文件以二进度读取为Uint8Array字节流的时候,3 0 obj的3这个字符的起始下标为145;00000表示这个对象的修改次数为0,与3 0 obj中的0是一致的。
4. 文件尾(Trailer)
在交叉引用表后面是 Trailer,它告诉解析器文档的入口在哪里。
示例:
trailer
<< /Size 6
/Root 1 0 R
/Info 2 0 R >>
startxref
289
%%EOF
说明:
/Size:对象数量(至少比最大对象号大 1)。/Root:指向 文档结构树的根目录(Catalog 对象)。/Info:指向 文档信息字典(作者、标题等)。startxref:交叉引用表的起始偏移量。%%EOF:文件结束标记。
👉 Trailer 的作用是 让阅读器知道从哪里开始解析整个文档。
startxref后面的数字(289)与上面交叉引用表中的对象位置(0000000145)是一个意思,就是xref这几个词在文档二进制流中的下标位置。
5. 基础结构示例
一个最小的 PDF 文件可能长这样:
%PDF-1.4
1 0 obj
<< /Type /Catalog
/Pages 2 0 R >>
endobj
2 0 obj
<< /Type /Pages
/Kids [3 0 R]
/Count 1 >>
endobj
3 0 obj
<< /Type /Page
/Parent 2 0 R
/MediaBox [0 0 300 144]
/Contents 4 0 R >>
endobj
4 0 obj
<< /Length 44 >>
stream
BT
/F1 24 Tf
100 100 Td
(Hello, PDF!) Tj
ET
endstream
endobj
xref
0 5
0000000000 65535 f
0000000010 00000 n
0000000079 00000 n
0000000178 00000 n
0000000303 00000 n
trailer
<< /Size 5
/Root 1 0 R >>
startxref
420
%%EOF
这份文件只有一页,页面上显示一句话:“Hello, PDF!”。
总结
PDF 文件的基础结构非常清晰:
- Header:声明版本号。
- Body:存放对象(文档的所有零件)。
- xref:目录,快速定位对象。
- Trailer:文档入口,指向根对象。
这四个部分共同构成了 PDF 文件的“骨架”。
下一篇,我将深入介绍 PDF 对象与语法,理解这些零件的类型和作用。