`
kyo100900
  • 浏览: 634119 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

PDF源文件浅析

阅读更多

 

应该项目需要,初步研究了一下 PDF 的格式,打算和大家分享一下,如果有想做 PDF PDF 阅读器开发的同学们,可以看看。

 

 

PDF 格式规范,语法简洁。但初次查看 PDF 却容易给人错觉 ——PDF 的二进制流太多了,原本清晰的数据格式被杂乱无章的二进制流给淹没了。

 

例如:

 

stream
5di?倯? 貓?匚{o绝幾Te:馎HG
嵶?W}?妮雥??隱q?屸鷭鴊狳!赭€?鼜 ??烖K柽z煜?w? ??鴚¬D¬h?斜?醱釓?x?i佲匃???灗  Iu叁剪?
…………………………

 

<!----><!---->

<!---->

像这样一大堆乱码的东西很多,所以,心理上无法耐心的继续看下去。

 

 

实际上 PDF 就是由四部分组成:

 

 

 

 

<!----><!----> <!---->

数据流的读写操作一般是从文件末尾开始的:

 

 

 

 

<!----><!----><!----> <!---->

PDF 所有的数据类型都是 obj ,强调的也是一切皆对象。我简要的把 PDF 常见的几种数据类型给大家说说:

 

注意“ % ”表示注释

<!---->1.       <!---->Boolean Objects :只有 true false

<!---->2.       <!---->Numeric Objects

<!---->l         <!---->整型: 123

<!---->l         <!---->浮点型 -43.54 6.02E23

3.  String Objects

<!---->l         <!---->文字型,用圆括号括起来,如: (Hello World)

<!---->l         <!---->十六进制型,用尖括号括起来,如: <901FA3> 分别为 90 1F A3

4.  Name Objects :除 null 以外的任意 8 位字符组成的字符串,出现在 PDF 中,用“ / ”作前导符号,如: /Name1 。其实这与编程中的常量很类似。

5.  Array Objects PDF 中的数组用方括号“ [] ”括起来,里面的数据可以完全不相同,如: [549 3.14 false (Ralph) /SomeName] 该数组里面分别装有 5 种不同的类型。

 

<!---->6.        <!---->Dictionary Objects :其实就类似于 Java Map 对象——即以 key value 的形式成对出现。用的时候要用“ <<…>> ”把 key value 括起来。例如: <</Name (leo) >> 表示 key 为“ name ”的 Name Objects ,而 value 为“ leo ”字符串。 <<>> 内部也可以嵌套使用,在 PDF 源文件中应用的非常广泛。

<!---->7.        <!---->Stream Objects :顾名思义,肯定有流有关。这就是 PDF 中专门用于存放二进行流的对象。 PDF 的实现者对 String Objects 通常会做限制,但是 Stream Objects 却没有这样的限制。因此 stream object 用在图片,页面描述信息上是非常的适合的。 <!---->

看看使用示例:

 

 

7 0 obj 		 			%某一个Image类型的对象
<<
/Type /XObject
/Subtype /Image
/Name /Im7
/Width 189
/Height 189
/BitsPerComponent 8
/ColorSpace [/Indexed /DeviceRGB 255 …>]
/Mask [255 255]
/Length 3832
/Filter /FlateDecode
>>						%紧接着这个对象定义后的Dictionary Objects对象后面
stream 					%定义它的Stream Objects
%这里放二进制流
endstream
 

 

<!----><!----> <!---->

 

<!---->8.        <!---->Null Object :区别与其它任何对象,用关键字 null 标注的对象就是 null object

<!---->9.        <!---->Indirect Objects :当然就是类似 Java 中的“引用”概念啦,举个例子就说明一切了。

 

 

7 0 obj  				%有一个索引号为7 0的对象
<< /Length 8 0 R >>  	% 在它的Length属性引用了一个索引号为8 0的对象
             %在这里“8 0 R”表示对8 0的对象引用(Reference)
stream
………….
endstream
endobj


8 0 obj				%被上面7 0对象所引用的那个索引号为8 0的对象
77 					%7 0对象的二进制流的长度
endobj

 

 

注意:   <!---->obj …… endobj PDF 文档组成的最小 组成部分。表示一个完整对象的定义

 

 

 

 

<!----><!----> <!---->

够了,说了这么多,让我们实战吧。现在我们来看一个完整的示例,该示例可以在附件中下载。看的时候,可以按照下面的步骤一步一步来分析 PDF 源文件:

 

 

 

%PDF-1.3
%
2 0 obj                                 %2 0 obj,里面是一些PDF的基本信息。比如
<<				
/CreationDate (D:20081029093606+08'00')  %创建时间
/ModDate (D:20081029093606+08'00')		  %最后修改时间		
/Producer (leo 4.30 \(0615\))        %创建人
/Creator (easyPDF SDK 4.3)        %制作人
>>                   %你还可以自由的编写任何信息
endobj

1 0 obj
<<
/Count 0
>>
endobj

3 0 obj									%这是PDF的根结点(不是起始位置)
<<
/Type /Catalog
/Pages 4 0 R								%这是所有页面的集合,我们主要关心这个
/Outlines 1 0 R
>>
endobj

7 0 obj
<<
/Type /XObject
/Subtype /Image
/Name /Im7
/Width 189
/Height 189
/BitsPerComponent 8
/ColorSpace [/Indexed /DeviceRGB 255 <%这里放每个像素点的值>]
/Mask [255 255]
/Length 3832
/Filter /FlateDecode
>>
stream
%这里放的全是图片的二进制流……..
endstream
endobj

8 0 obj
<<
/Length 51
/Filter /FlateDecode
>>
stream
%这里放的全是图片的二进制流……..
endstream
endobj

6 0 obj									%重点分析一下这个
<<
/Type /Page
/Parent 4 0 R
/MediaBox [ 0 0 595.276 841.89 ]
/Resources <<
/XObject <<
/Im7 7 0 R
>>
/ProcSet [/PDF /Text /ImageB /ImageC] >>
/Contents 8 0 R
>>
endobj

4 0 obj
<<
/Type /Pages
/Kids [
6 0 R							%继续查找6 0 obj来看看具体的页面情况
]
/Count 1
>>
endobj

xref						%索引,f表示该地址未被使用,n表示已经被占用。
0 9								%索引的顺序与对象在文中出现的序号是一致的,比如
0000000000 65535 f               %默认占位符,不使用。
0000000177 00000 n        %表示0 1 obj的偏移地址
0000000015 00000 n        %表示0 2 obj的偏移地地 下面的以些类推
0000000208 00000 n 
0000006159 00000 n 
0000000000 00000 n        %表示0 5 obj的偏移为0,可能被删除操作给干掉了。
0000005981 00000 n 
0000000274 00000 n 
0000005858 00000 n 
trailer
<<
/Size 9
/Root 3 0 R						%这个属性信息最重要,我们可以直接找到根结点
/Info 2 0 R
/ID[<112cbcc8320742d1cbe148d4ec0d7dba><112cbcc8320742d1cbe148d4ec0d7dba>]
>>
startxref							%PDF文件解析的起点
6219
%%EOF

 

 

<!----><!---->

<!---->

 

第一步: 首先是先看文件末尾,找到它的“ xref ”和“ trailer ”部分。 Xref 表示对象索引,其实就是对象在文件中的偏移地址 PDF 源文件中是以 10 进制表示的,你转换成 16 进制后,用 UE 或者其它工具查看即可)

 

Trailer 虽然从字面上看是“尾部”的意思,但是解析却从它开始。“ startxref ”表示文件的入口地址,像 java main() 方法。后面紧跟着一个 10 进制的数字,指向的是索引“ xref ”的位置。

 

Trailer 里面的值分别表示:

 

<!---->1.       <!---->Size 9 :表示 9 个索引值

<!---->2.       <!---->Root 3 0 R :根结点是 3 0 obj 的引用

<!---->3.       <!---->Info 2 0 R :相关信息是 2 0 obj 的引用

<!---->4.       <!---->ID :类似于 Java Serializable 机制,给本 PDF 文件生成一个独一无二的标识

 

 

第二步: 按照 Root 3 0 R ,我们向上查找,找到了我们要找的 Root 对象,里面的信息从字面上很容易理解:

 

<!---->1.       <!---->/Type /Catalog :表示该对象的类型是目录

<!---->2.       <!---->/Pages 4 0 R :表示所有的 PDF 页面在一个叫 4 0 R 的对象上

<!---->3.       <!---->/Outlines 1 0 R PDF 文件的大纲,也就是左侧展示的东西。

 

继续查找 4 0 obj ,果然与预期所想的一样,的确是所有页面的集合,并且还有一个附加属性 Count 统计页面对象的个数:

 

/Type /Pages :表示该对象的类型是所有页面的集合

/Kids […] :这是一个数组,里面装的是每个页面的具体引用。因为这个 PDF 就一页,所有只有一个“ 6 0 R ”。下一步,就从这里入手。

/Count 1 :页的总数大小,因为这个 PDF 就一页,所以值为 1 。编程时可能会用到。

 

 

第三步: 来重点分析一下页对象

 

 

6 0 obj              	%索引编号为6的对象,0表示该对象的修改次数
<<                 	% << 表示是这一个map,里面的所有数据都以key,value形式出现
/Type /Page  			%其类型为Page,表示PDF的每一页
/Parent 4 0 R			%其父结点为4 0对象(R表示引用,对象之间是可以双向引用的)
/MediaBox [ 0 0 595.276 841.89 ]	%当前页画布为MediaBox样式,后面的表示左上角与右下角坐标,这样就形成了一个矩形。
/Resources <<			% Resources 又为一个map的嵌套
/XObject <<			% XObject 也是一个map的嵌套
/Im7 7 0 R		% 在Xobject中定义了一个叫Im7的名称,引用了叫Im7的7 0对象(在我的PDF中,这是一个图片对象)
>>					
/ProcSet [/PDF /Text /ImageB /ImageC] >> % 打印时的参数设置
/Contents 8 0 R        % 这里是实际页面上的内容信息,引用的是8 0对象
>>
endobj				% 对象定义结束

 

 

 

<!----><!---->

<!---->

对于我们来说,以下是项目开发的重点。因为要实现 PDF 文件盖章的添加或删除功能,必须进行流操作,并且图片在 PDF 文件的位置也要写入。

 

<!---->l         <!---->/XObject<<>> 里面持有图片等二进制的引用。通过上述的方法,当你再查看 7 0 obj 时,就会看到大量的二进制文件了。

<!---->l         <!---->/Contents 是文件的内容信息。比如说,页面上的文本,图片的坐标位置等。

 

 

 

通过这个完整的流程,你应该明白 PDF 的格式其实就是一个层次分明的树形结构。不过,想完全了解 PDF 的格式,进行相关的操作。你还得参看 PDF 规范文档。

 

 

31
3
分享到:
评论
18 楼 hdlbypf 2012-03-04  
lz你好能否留个联系方式,我有pdf解析的几个问题请教你,我邮箱hdlbypf@126.com qq421610654
17 楼 zhangbug 2009-03-27  
有研究word格式的吗
16 楼 zqwwjj 2008-12-13  
  
15 楼 elf8848 2008-12-05  
引用
14 楼 john2007 2008-12-04  
以前研究过一段时间。
有开源的java库,可以把pdf源文件的所有object解析为java object,很爽的。
13 楼 hanjs 2008-12-04  
lz写的这些都是java代码??

12 楼 sheandwei 2008-12-03  
不错
还真没去研究过
11 楼 harsh 2008-12-02  
哇塞,太好了,偶也在研究pdf呢
10 楼 cyfgod 2008-12-02  
分析的不错,不过对一般应用来说,不用研究这么底层,用itext就足够了。
9 楼 kakaluyi 2008-12-01  
eyejava 写道

kakaluyi 写道
kyo100900 ,关注你挺久的了,感觉你是那种一步一个脚印做事的人,身为高手但是为人谦虚 加油,日后必成大器啊ps:我对你封面上的美女挺感兴趣的 请使用留言簿发表你的敬仰好像以前有个敬仰表情的,咋没了,Quake同学确认下

这是私人博客啊,敬仰一个人也有错了  ,这个我就不服了,就当是两个个朋友聊聊嘛,
8 楼 eyejava 2008-12-01  
kakaluyi 写道

kyo100900 ,关注你挺久的了,感觉你是那种一步一个脚印做事的人,
身为高手但是为人谦虚 加油,日后必成大器啊
ps:我对你封面上的美女挺感兴趣的


请使用留言簿发表你的敬仰
好像以前有个敬仰表情的,咋没了,Quake同学确认下
7 楼 kakaluyi 2008-12-01  
kyo100900 ,关注你挺久的了,感觉你是那种一步一个脚印做事的人,
身为高手但是为人谦虚 加油,日后必成大器啊
ps:我对你封面上的美女挺感兴趣的
6 楼 kyo100900 2008-12-01  
Quake Wang 写道

kyo100900 写道
我们实际上想做的是一个类似于Adobe Reader的PDF阅读器。用户只能通过我们的阅读器才能实现盖章的添加,删除,虚化等功能。当别的PDF阅读器打开时,只能是只读的。我之前遇到类似的需求(公文电子签名),如果你采用开放规范的PDF来实现,是很难达到高安全性的要求,正是由于PDF格式是公开的,导致破解签名比较容易,我们是对PDF格式作了改动和加密,不过这样的文件就不能在其他阅读器打开了。


嗯,方法和你类似 , 并且要想使用我们的PDF阅读器,还得用USE KEY。
5 楼 QuakeWang 2008-12-01  
kyo100900 写道

我们实际上想做的是一个类似于Adobe Reader的PDF阅读器。用户只能通过我们的阅读器才能实现盖章的添加,删除,虚化等功能。当别的PDF阅读器打开时,只能是只读的。

我之前遇到类似的需求(公文电子签名),如果你采用开放规范的PDF来实现,是很难达到高安全性的要求,正是由于PDF格式是公开的,导致破解签名比较容易,我们是对PDF格式作了改动和加密,不过这样的文件就不能在其他阅读器打开了。
4 楼 chamborghini 2008-12-01  
用iText做pdf的加密, 好像不管怎么做都很容易被破解, 解决中。

g到这里,问下,有没有办法将pdf文件设置为不能被修改的?(很难被破解的那种, 目前的做法是将pdf文件设置为仅能被打印的,并建权限密码设置为随机数,但是这种做法很容易就被PDF Password Remover给破解了)
3 楼 kyo100900 2008-12-01  
iText主要还是一个应用类库。远远无法满足我们的需求。
我们实际上想做的是一个类似于Adobe Reader的PDF阅读器。用户只能通过我们的阅读器才能实现盖章的添加,删除,虚化等功能。当别的PDF阅读器打开时,只能是只读的。
2 楼 c_mingze@163.com 2008-12-01  
看了一头雾水  好乱 果然是高手中的高手  个人感觉itext较好用
1 楼 QuakeWang 2008-12-01  
PDF规范很麻烦,如果要通过2进制实现PDF文件盖章/添加/删除的功能要作不少工作,你可以试试看itext,它应该可以简化一些工作。

相关推荐

Global site tag (gtag.js) - Google Analytics