PDF底层格式之文档基础结构

73 阅读5分钟

如果你使用文本编辑器打开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,他下面有两个内容对象(/Contents13 0 obj14 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 文件的基础结构非常清晰:

  1. Header:声明版本号。
  2. Body:存放对象(文档的所有零件)。
  3. xref:目录,快速定位对象。
  4. Trailer:文档入口,指向根对象。

这四个部分共同构成了 PDF 文件的“骨架”。

下一篇,我将深入介绍 PDF 对象与语法,理解这些零件的类型和作用。