PDF解析(一)

pdf1.7标准见http://www.adobe.com/content/dam/Adobe/en/devnet/acrobat/pdfs/pdf_reference_1-7.pdf

本文将在阅读pdf标准和研究poppler项目的代码之后的一些心得记录下来。

 

pdf文档的总体结构如下图所示,按照字节顺序依次是:

 

Header

Body

Cross-reference table

Trailer

 

 

pdf文件是从文件最后开始读的,我们以某一pdf为例:

示例pdf文档的地址在https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/AnimationGuide/AnimationGuide.pdf

这个pdf文件的最末端是:

 startxref

 174134

 %%EOF

 

在pdf文件中存在一个交叉引用表(cross reference table),这个表在pdf文件中非常重要,它的具体作用在后文介绍。

 startxref下一行的数字174134是什么含义呢?这是一个十进制数字,它表示交叉引用表开始的位置,于是定位到文件中的174134字节

 xref

 0 264

 0000000000 65535 f

 0000159830 00000 n

 0000160001 00000 n

 0000069518 00000 n

 0000168211 00000 n

 0000168265 00000 n

 0000170551 00000 n

 0000170416 00000 n

 0000170293 00000 n

 0000169222 00000 n

 …

 

 在pdf_reference_1-7.pdf的3.4.3节中专门介绍了Cross-reference table的结构。

 

 在 cross reference table的最后紧跟着的是Trailer。

 

 trailer

 <</Size 264

 /Info 3 0 R

 /Root 1 0 R

 /ID[<6098D5A8656696149B31E51178B3DD1F><6098D5A8656696149B31E51178B3DD1F>]

 >>

 

 

根据/Root 1 0 R在cross reference table中找到generation 为0,object number为1的entry。

就是

0000159830 00000 n

这个家伙。

0000159830 就是这个ref 对象在文件中的地址。定位到此处,把Root抓出来。

 

 1 0 obj

 <</Type/Catalog

 /Pages 2 0 R

 /PageLabels << /Nums  [0 <</S/D>>] >>

 /PageMode /UseOutlines

 /OpenAction [22 0 R /FitV 0]

 /Outlines 4 0 R

 /Names 193 0 R

 >>

 endobj

 

这是一个类型是dictionary的对象。

在pdf_reference_1-7.pdf的3.2节中专门介绍了对象类型。

 

我感兴趣的是/Pages 2 0 R,在cross reference table中找到generation 为0,object number为2的entry。是

0000160001 00000 n

定位到第0000160001字节处

 

 2 0 obj

 <</Type/Pages

 /Count 18

 /Kids [ 195 0 R 196 0 R]

 >>

 endobj

 

可以看出这个pdf共有18页,/Kids [ 195 0 R 196 0 R]是一个Array类型,为什么count是18而却只有两个kids呢,按照前面的方法定位到 195 0 R对象

 

 195 0 obj

 <</Type/Pages

 /Count 10

 /Parent 2 0 R

 /Kids [22 0 R 28 0 R 49 0 R 75 0 R 85 0 R 93 0 R 97 0 R 102 0 R 107 0 R 111 0 R ]

 >>

 endobj

 

发现195 0 R的Type 是Pages,猜测 196 0 R对象的Type也是Pages。

195 0 R对象的Count是10,猜测196 0 R对象的Count是8,加起来是18。

 

定位到 196 0 R对象

 196 0 obj

 <</Type/Pages

 /Count 8

 /Parent 2 0 R

 /Kids [115 0 R 119 0 R 123 0 R 127 0 R 131 0 R 135 0 R 139 0 R 143 0 R ]

 >>

 endobj

 

发现猜测是正确的。

 

 数了一下195 0 R对象的Kids里面对象的个数为10,其实这个个数不一定必须是10,也可以按照上面的方式像树一样组织。只要它的后代的总数为10就可以了。

 

 至于为什么要将Pages组织成树结构?这种结构可以使得pdf标准的消费者程序,如pdf阅读器,仅仅耗费有限的内存,快速打开包含有上千页的pdf文档。

 

 接下来看看具体的page对象是什么样子的,定位到Page Tree的叶子结点 22 0 R对象

 

 22 0 obj

 <</Type/Page

 /Parent 195 0 R

 /MediaBox [0 0 612 792]

 /CropBox [0 0 612 792]

 /BleedBox [0 0 612 792]

 /TrimBox [0 0 612 792]

 /Resources 25 0 R

 /Contents [197 0 R 23 0 R ]

 >>

 endobj

 

没错,Type是Page,对于这个Page,MediaBox,CropBox,BleedBox的值都是相同的。

 

在pdf_reference_1-7.pdf中的10.10.1节 Page Boundaries详细介绍了每种Box的具体含义。

 

继续追寻这个pdf page的具体内容,从/Contents [197 0 R 23 0 R ]这一项开始。

找到 197 0 R 对象,由于这个对象当中有一段FlateDecode的Stream,如果用文本显示的话会出现乱码,所以我截了一个在HexEdit中打开的图。

 

 
 

 

找到 23 0 R对象

 

 

由于Stream里面的内容是经过编码的,所以需要通过一些手段将这里面的内容进行解码。

时间不早了今天就写到这里,未完待续。

posted @ 2013-09-15 22:43  Raymoe  阅读(2038)  评论(0编辑  收藏  举报