PDF 文件格式 基本结构

参考:https://resources.infosecinstitute.com/topic/pdf-file-format-basic-structure/

 

PDF是一种可移植的文档格式,可用于显示包含文本,图像,多媒体元素,网页链接等内容。PDF文件格式规范链接:https://opensource.adobe.com/dc-acrobat-sdk-docs/。官方链接中仅仅文件格式的文档就有800多页,因此通读该文档并不是意见容易的事。

PDF不仅具有文本功能,还具有更多的功能:它可以包含图像和其他多媒体元素,受密码保护,执行JavaScript等。下图显示了PDF文件的基本结构:

PDF 文件格式  基本结构
 

PDF 文件格式  基本结构 

下文中[]中的内容为上图中的结构体。 

Header

[PDFHeader]这是PDF文件的第一行,它指定文档使用的所用PDF规范的版本号.此处PDF文档版本为1.7.

[PDFComment] 这部分是PDF文档的注释部分

Body

在PDF文档的正文中,有一些对象通常包括文本流,图像,其他多媒体元素等。“正文”部分用于保存所有显示给用户的文档数据。
即[PDFObj] sPdfObj[0]-sPdfObj[12]都是Body部分。

xref table

这是交叉引用表,其中包含对文档中所有对象的引用。交叉引用表的目的是允许它随机访问文件中的对象,因此我们无需阅读整个PDF文档即可找到特定的对象。每个对象由交叉​​引用表中的一个条目表示,该条目始终长20个字节。让我们来看一个例子:

xref
0 14
0000000000 65536 f
0000000017 00000 n
0000000066 00000 n
0000000122 00000 n
0000000258 00000 n
0000000367 00000 n
0000000500 00000 n
0000000831 00000 n
0000001013 00000 n
0000001197 00000 n
0000010222 00000 n
0000010294 00000 n
0000010366 00000 n
0000010431 00000 n

 我们可以通过使用文本编辑器简单地打开PDF并滚动到文档底部来显示PDF文档的交叉引用表。

0 14

这行中的第一个数字0对应于对象编号,而第二列显示当前子节中的对象数量14。

0000000000 65536 f
0000000017 00000 n
0000000066 00000 n

。。。。。。

每个对象由一个条目表示,该条目长20个字节(包括CRLF)。

前10个字节是对象从PDF文档开始到该对象开始的偏移量。接下来是一个空格分隔符,其中另一个数字指定对象的世代号。之后,还有另一个空格分隔符,后跟字母“ f”或“ n”以指示对象是空闲的还是正在使用的。

 

Trailer

PDF Trailer 指定读取PDF文档的应用程序应如何找到交叉引用表和其他特殊对象。所有PDF阅读器都应从文件末尾开始阅读PDF。以下是一个Trailer示例:

trailer
<<
/Root 1 0 R
/Info 3 0 R
/Size 14/ID[<EA61F2FE98704E5009E01A59031F93DD><EA61F2FE98704E5009E01A59031F93DD>]>>
startxref
10750
%%EOF

PDF文档的最后一行包含“ %% EOF”文件字符串的结尾。在文件标记末尾之前,有一行带有startxref字符串的行,用于指定从文件开头到交叉引用表的偏移量。在此例中,交叉引用表从偏移量10750字节开始。在此之前是Trailer字符串,用于指定当前部分的开始。此节的内容嵌入在<<和>>字符内(这是一个接受键值对的字典)。  

我们可以看到此部分定义了几个键,每个键用于一个特定的动作。此部分指定以下键:

  • / Size [integer]:指定交叉引用表中的条目数(也对更新部分中的对象进行计数)。使用的号码不应是间接引用。
  • / Prev [integer]:指定从文件开头到上一个交叉引用节的偏移量,如果有多个交叉引用节,则使用此偏移量。该数字应为交叉引用。
  • / Root [dictionary]:指定文档目录对象的引用对象,这是一个特殊对象,其中包含指向不同种类的其他特殊对象的各种指针(稍后会对此进行更多介绍)。
  • / Encrypt [dictionary]:指定文档的加密字典。
  • / Info [dictionary]:指定文档信息字典的参考对象。
  • / ID [array]:指定由两个字节的未加密字符串组成的数组,这些字符串构成文件标识符。
  • / XrefStm [integer]:指定从文件开头到解码流中的交叉引用流的偏移量。这仅存在于混合引用文件中,如果我们也想打开文档,即使应用程序不支持压缩的参考流,也可以指定该文件。

我们必须记住,如果以后再更新PDF文档,则可以修改初始结构。该更新通常在文件末尾附加其他元素。

增量更新

PDF的设计考虑了增量更新,因为我们可以在不重写整个文件的情况下将一些对象附加到PDF文件的末尾。因此,可以快速保存对PDF文档的更改。下图显示了PDF文档的新结构:

PDF 文件格式  基本结构

我们可以看到PDF文档仍然包含原始的标题,正文,交叉引用表和trailer。此外,PDF文档中还添加了其他正文,交叉引用和trailer部分。其他交叉引用部分将仅包含已更改,替换或删除的对象的条目。删除的对象将保留在文件中,但将带有“ f”标记。每个trailer都需要以“ %% EOF”标签终止,并应包含/ Prev条目,该条目指向上一个交叉引用部分。

在PDF版本1.4和更高版本中,我们可以在文档的目录字典中指定版本条目,以覆盖PDF标头中的默认版本。

例子

本篇文章所用示例文档下载地址:https://download.csdn.net/download/lacoucou/15709804

此文档是使用wps创建的,内容仅仅有一行 PDF 文件格式  基本结构,其二进制结构如下图:

PDF 文件格式  基本结构

010editor 模版解析出来的结构:

PDF 文件格式  基本结构

PDF文件数据类型

PDF 文件基本元素是 PDF 对象(PDF Object), PDF 对象包括直接对象(Direct Object)和间接对象(Indirect Object)。直接对象如下八种类型;间接对象,又叫 labelled object, 嵌套在关键词 n 0 obj 和 endobj 之间, 是用一种表示来标识一个 PDF 对象,通过标识来让别的 PDF 对象引用,这个标识叫做间接对象的 ID. https://lazymind.me/2017/10/pdf-structure/

直接对象类型

  1. Boolean value(布尔)

    布尔类型,值只能是 true 和 false

  2. Integer and Real number(数值)

    数值类型,包括整数和实数,与普通编程语言中的数值类型大体相同。

  3. String(字符串)

    字符串类型,包括包含在圆括号 ( ) 内的文字字符串(literal string)和包含在单尖括号 < > 内的十六进制字符串(hexadecimal string)两种。

    例:

    1. (Hello World)
    2. <9ADCF1>
  4. Name(名字?)

    名字类型,用字符组成的字符串,用 / 作为前导符号,在 PDF 文件中具有唯一性,相同的名字表示相同的对象(the same sequence of character denotes the same object)。常见用在 Dictionary 里面作 Key,用来表示对象名称。

    例:

    /Page
    /Kid
  5. Array(数组)

    数组类型,存在于方括号 [ ] 内,元素可以是除 Stream 外的所有类型。PDF 中数组只支持一维数组。

    例:

    [/Page false 17 (hello)]  该数组包含了4种类型元素
  6. Dictionary(字典)

    字典类型,包含在双尖括号 << >> 内,每两个元素为一对,第一个为 key, 第二个为 value, key 只能是 Name 类型,value 可以是任意类型,即可以嵌套为 Dictionary

    例:

    <</Page 1 0 obj
    /Filter /FlateDecode
    /Name (Hello)>>
  7. Stream(流对象)

    流对象,是用字节表示的序列,长度理论上没限制。包含在 stream 和 endstream 之间。以 CRLF或 LF 结尾,不能单独以 CR 结尾。dicionary 里的内容用来描述该 stream 的相关信息。

    dictionary
    stream
        ......
    endstream
  8. Null object(空对象)

    空对象类型,用关键词 null 表示。

间接对象类型

使用 unique object identifier 来表示,方便其他对象引用。结构如下:

12 0 obj
........
endobj

第一行第一个 12 规定为 positive integer, 表示对象 ID; 第二个 0 表示生成号(generation number),通常为0;第三个为固定 obj 表示,以最后一行 endobj 表示结束。中间 ...... 表示内容。其他地方引用该对象时,使用如下格式,其中 R 为关键字:

12 0 R

文件结构 

PDF文档由PDF文件的主体部分中包含的对象组成。PDF文档中的大多数对象都是字典。文档的每个页面都由页面对象表示,页面对象是一个字典,其中包含对页面内容的引用。页面对象连接在一起并形成页面树,该页面树在文档目录中使用间接引用进行声明。

PDF 文件格式  基本结构

在上图中,我们可以看到文档目录包含对页面树,大纲层次结构,文章线程,命名目的地和交互式表单的引用( page tree, outline hierarchy, article threads, named destinations and interactive form)。我们不会详细介绍每个部分的功能,但仅介绍最重要的部分,即“页面树”。 

Document catalog 

从上图可以看出,文档目录 是PDF文档中对象的根。我们已经说过,“PDF”尾部中的/ Root元素指定了文档目录。文档目录包含对其他定义文档内容的对象的引用。它还包含声明如何在屏幕上显示文档的信息。文档目录中的条目如下:

  • / Type:目录描述的PDF对象的类型(在我们的示例中,这是Catalog,因为这是文档目录对象)。
  • / Version:文档所依据的PDF规范的版本。
  • / Extensions:有关本文档中开发人员扩展的信息。
  • / Pages:对对象的间接引用,该对象是文档页面树的根。
  • /Dests:对对象的间接引用,该对象是指定的目标对象的根。
  • / Outlines:对大纲目录对象的间接引用,该对象是文档大纲层次结构的根。
  • / Threads:对表示文档文章线程的线程词典数组的间接引用。
  • / Metadata:对包含文档元数据的元数据流的间接引用。

其他 Catalog字典中,常用的字段一般有以下一些:

  • 字段

    类型

    Type

    name

    (必须)必须为Catalog。

    Version

    name

    (可选)PDF文件所遵循的版本号(如果比文件头指定的版本号高的话)。如果这个字段缺省或者文件头指定的版本比这里的高,那就以文件头为准。一个PDF生成程序可以通过更新这个字段的值来修改PDF文件版本号。

    Pages

    dictionary

    (必须并且必须为间接对象)当前文档的页面集合入口。

    PageLabels

    number tree

    (可选) number tree,定义了页面和页面label对应关系。

    Names

    dictionary

    (可选)文档的name字典。

    Dests

    dictionary

    (可选;必须是间接对象)name和相应目标对应关系字典。

    ViewerPreferences

    dictionary

    (可选)阅读参数配置字典,定义了文档被打开时候的行为。如果缺省,则使用阅读器自己的配置。

    PageLayout

    name

    (可选) 指定文档被打开的时候页面的布局方式。SinglePageDisplay 单页OneColumnDisplay 单列TwoColumnLeftDisplay 双列,奇数页在左TwoColumnRightDisplay 双列,奇数页在右TwoPageLeft 双页,奇数页在左TwoPageRight 双页,奇数页在右缺省值: SinglePage.

    PageMode

    name

    (可选) 当文档被打开时,指定文档怎么显示UseNone 目录和缩略图都不显示UseOutlines 显示目录UseThumbs 显示缩略图FullScreen 全屏模式,没有菜单,任何其他窗口UseOC 显示Optional content group 面板UseAttachments显示附件面板缺省值: UseNone.

    Outlines

    dictionary

    (可选;必须为间接对象)文档的目录字典

    Threads

    array

    (可选;必须为间接对象)文章线索字典组成的数组。

    OpenAction

    array or dictionary

    (可选) 指定一个区域或一个action,在文档打开的时候显示(区域)或者执行(action)。如果缺省,则会用默认缩放率显示第一页的顶部。

    AA

    dictionary

    (可选)一个附加的动作字典,在全局范围内定义了响应各种事件的action。

    URI

    dictionary

    (可选)一个URI字典包含了文档级别的URI action信息。

    AcroForm

    dictionary

    (可选)文档的交互式form (AcroForm)字典。

    Metadata

    stream

    (可选;必须是间接对象)文档包含的元数据流。

我们可以看到还有许多其他条目属于文档目录,但是这里不再对其进行描述。读者可以查看我们的资源以了解详细信息。下面显示了文档目录的示例:

1 0 obj
<</Type/Catalog/Pages 2 0 R >>
endobj

Page tree

通过页面树可以访问文档的页面,该页面树定义了PDF文档中的所有页面。该树包含代表PDF文档页面的节点,该节点可以有两种类型:中间节点和叶节点。中间节点也称为页面树节点,而叶节点称为页面对象。 

最简单的页面树结构可以由单个页面树节点组成,该节点直接引用所有页面对象(因此所有页面对象都是叶子)。

页面树中的每个节点必须具有以下条目:

  • / Type:此对象描述的PDF对象的类型(在本例中为Pages ,因为我们正在谈论页面树节点)。
  • /Parent:应该在所有页面树节点中存在,但在根目录中除外,在根目录中不得存在该条目。此项指定其父项。
  • / Kids:应该出现在除叶子以外的所有页面树节点中,并指定可从当前节点直接访问的所有子元素。
  • / Count:指定在后续页面树中作为该节点后代的叶节点的数量。

我们必须记住,页面树与PDF文档中的任何内容都不相关,例如页面或章节。

2 0 obj
<</Type/Pages/Count 1/Kids[ 4 0 R ]>>
endobj

4 0 obj
<</Type/Page/Parent 2 0 R /MediaBox[ 0 0 595.3 841.9]/Resources 10 0 R /Contents 13 0 R >>
endobj

字段

类型

Type

name

(必须)必须是Page。

Parent

dictionary

(必须;并且只能是间接对象)当前page节点的直接父节点page tree 。

LastModified

date

(如果存在PieceInfo字段,就必须有,否则可选)记录当前页面被最后一次修改的日期和时间。

Resources

dictionary

(必须; 可继承)记录了当前page用到的所有资源。如果当前页不用任何资源,则这是个空字典。忽略所有字段则表示继承父节点的资源。

MediaBox

rectangle

(必须; 可继承)定义了要显示或打印页面的物理媒介的区域(default user space units)

CropBox

rectangle

(可选; 可继承)定义了一个可视区域,当前页被显示或打印的时候,它的内容会被这个区域裁剪。默认值就是 MediaBox。

BleedBox

rectangle

(可选) 定义了一个区域,当输出设备是个生产环境( production environment)的时候,页面显示的内容会被裁剪。默认值是 CropBox.

Contents

stream or array

(可选) 描述页面内容的流。如果这个字段缺省,则页面上什么也不会显示。这个值可以是一个流,也可以是由几个流组成的一个数组。如果是数组,实际效果相当于所有的流是按顺序连在一起的一个流,这就允许PDF生成的时候可以随时插入图片或其他资源。流之间的分割只是词汇上的一个分割,并不是逻辑上或者组织形式的切割。

Rotate

integer

(可选; 可继承) 顺时钟旋转的角度数,这个必须是90的整数倍,默认是0。

Thumb

stream

(可选)定义当前页的缩略图。

Annots

array

(可选) 和当前页面关联的注释。

Metadata

stream

(可选) 当前页包含的元数据。

在我们这个例子中,所有对象的引用关系如下图:

PDF 文件格式  基本结构

上图内容解释: 

 PDF 文件格式  基本结构

0bj0 代表对象编号,后边的色块为对象包含的内容。

 

 

上一篇:python 将数据对象存储为文件


下一篇:python数据类型(字典dictionary)