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里面的内容是经过编码的,所以需要通过一些手段将这里面的内容进行解码。
时间不早了今天就写到这里,未完待续。