PDF底层格式之obj对象与语法规则

80 阅读4分钟

引言

在上一章介绍了 PDF 的基础结构:Header、Body、xref、Trailer,其中 Body(文件体) 是最核心的部分,它存放了 PDF 的全部内容。但和一般的纯文本文件不同,PDF 的 Body 不是“平铺直叙”的数据,而是由一个个 对象(Object) 组成的。 这些对象就像 乐高积木

  • 每个对象定义了文档的一部分内容(比如一页纸、一个字体、一张图像、一个注释);
  • 不同对象之间通过 引用 彼此关联;
  • 最终拼装成一棵完整的 结构树(Document Tree) ,让阅读器能够正确显示文档。

要真正理解 PDF,必须先看懂对象:

  • 它们长什么样?(语法规则)
  • 它们分哪几类?(常见对象分类)
  • 它们之间是如何组织的?(对象结构树)

对象语法规则

上文说过,PDF对象的内容是7种基本类型(布尔值、数字、字符串、字典、列表、流等),在实际的PDF文件中,最常见的是内容是字典和流,以一个常见的Page对象为例:

3 0 obj
  << /Type /Page
     /Parent 2 0 R
     /MediaBox [0 0 595 842]
     /Contents 4 0 R
     /Resources 5 0 R >>
endobj

这个对象的内容(在obj和endobj中间的部分)是由<< 和 >> 包围的字典对象,内部是键值对:

  • /Type /Page:说明该对象是一个 页面对象
  • /Parent 2 0 R:父级是对象 2 0 obj(即 Pages 对象)。
  • /MediaBox [0 0 595 842]:页面尺寸(595×842 pt ≈ A4 纸)。
  • /Contents 4 0 R:页面的内容流,包含绘制指令。
  • /Resources 5 0 R:资源字典(字体、图片等)。

PDF的字典对象与常规语言中的字典一致,是一系列的键值对,转换为字典是:

{
    Type: 'Page',
    Parent: '2 0 R',
    MediaBox: [0 0 595 842],
    Contents: '4 0 R',
    Resources: '5 0 R'
}

字典的key一般是以 / 开头的名称(如 /Type),值可以是任意PDF对象类型(名称、字符串、数组、字典、间接引用等)。所以会存在字典嵌套字典、列表嵌套字典的情况。

常见对象类型

对象的/Type字段会标识这个对象的类型,表明他的作用。在实际的PDF文件中,常见的对象类型有:

文档结构对象

  • Catalog(/Type /Catalog) :文档根对象,入口。
  • Pages(/Type /Pages) :页面树节点,管理所有页面。
  • Page(/Type /Page) :单个页面,包含尺寸、内容、资源。

元信息对象

  • Info:文档元信息(标题、作者、创建时间等)。
  • Metadata:基于 XML 的文档元数据(PDF/A、XMP 使用)。

资源对象

  • Font:字体对象,定义字体信息。
  • XObject:外部对象,可以是图像(Image XObject)、表单(Form XObject)。
  • ColorSpace:颜色空间对象。
  • Pattern:图案填充对象。
  • Shading:渐变对象。

内容相关对象

  • Stream:流对象,存放二进制数据(文本指令流、图片数据、压缩数据)。
  • Contents:页面的内容流,描述绘制操作。

导航与交互对象

  • Outline(书签) :文档目录结构。
  • Annotation(注释) :文档批注、高亮、评论。
  • Action:触发操作(跳转页面、打开链接)。

PDF文档结构

综合上述内容,我们可以知道,PDF文件中的对象有不同的类型,并且PDF中对象是互相引用的。分析一篇实际的PDF文件,可以发现,PDF 文档通过 对象引用关系 构建成一棵结构树,构造流程如下:

  • 首先找到Catalog对象,这是页面的入口
123 0 obj
<< 
    /Type /Catalog 
    /Outlines 124 0 R 
    /Pages 2 0 R 
    /PageLayout /OneColumn
>>
endobj

可以看到,里面标识了Pages对象为2 0 R

  • 找到Pages对象,这里标记了所有页面
2 0 obj
<< 
/Type /Pages 
/Kids [ 3 0 R 8 0 R 13 0 R 18 0 R 23 0 R 28 0 R 33 0 R 38 0 R 
43 0 R 48 0 R 53 0 R 58 0 R 63 0 R 68 0 R 73 0 R 78 0 R 
83 0 R 88 0 R 93 0 R 98 0 R 103 0 R 108 0 R 113 0 R 118 0 R 
] 
/Count 24 
>> 
endobj

可以看到,Pages对象中标识了这个PDF共有(/Count 24)24页,Kids中指向每一页分别是3 0 R8 0 R...

  • 找到第一页3 0 obj对象,这是第一页的入口
3 0 obj
<<
/Type /Page 
/Parent 2 0 R 
/MediaBox [0.0000 0.0000 595.0000 842.0000] 
/Contents 4 0 R 
/Resources << 
    /XObject << /Im1 6 0 R >>
    /ProcSet [ /ImageB ]
    >>
>>
endobj

这一页的对象中指定了内容(Contents)在4 0 R,页面宽高MediaBox和引用资源Resources等。

  • 在后续的4 0 obj中又会引用字体对象、字符串对象、流对象等等。如此不断引用,构成了整个PDF树。

总结

理解了对象和结构树,PDF 就不再是“黑盒文件”,而是一个由积木搭建的有序系统。下一篇,将继续深入 交叉引用表和 Trailer,看看阅读器是如何快速找到这些对象的。