在PDF-1.5版本之前,对象的交叉引用信息是存储在交叉引用表(cross-reference table)中的。在PDF-1.5版本之后,引进了交叉引用流(cross-reference stream)对象,可以用它来存储对象的交叉引用信息,就像交叉引用表的功能一样。
采用交叉引用流对象有以下几点好处:
1) 存储的信息更紧凑,并且可以引入压缩算法进行压缩
2) 提供了访问存储于对象流(ObjectStreams)中的被压缩的对象的功能
3) 提供了将来的可扩展的交叉引用流的表项类型,以便存储更多不同信息
PDF交叉引用表是PDF的重要组成部分,本文介绍的是交叉引用流(cross-reference stream)对象,这种引用表的格式是PDF的obj格式,内容是被压缩存放在obj下的stream中,因此比常规的引用表格式复杂。下面就开始介绍这种交叉引用表的格式和解析的方法:
1 定位最初始位置:
交叉引用表方法一样,需要到文件尾部找到"startxref"后面的数字,就是第一个交叉引用表的位置(PDF交叉引用表是倒序的,应该说是最后一个)。
2 解析交叉引用表obj内容:
取出obj内DecodeParms,W, Index,size,Prev ,stream内容
DecodeParms是解压缩参数,针对当前Obj有stream内容的情形,解压缩stream内容使用(解压缩部分后续再说)
W的值是数组,数组里面有三个数字,表示的是交叉引用表三个元素(type,file offset,generation)信息在stream内容需要读取的长度;
Index的值也是数组,数量是偶数个,两两一组,表示交叉引用对应obj的起始obj号和范围;
Prev的值是下一个交叉引用表的位置(准确的说应该叫上一个,因为PDF交叉引用表是倒序的,为了描述方便);
stream的内容是交叉引用表的实际内容,交叉引用相关信息被处理,再被压缩
3 多个交叉引用表
当文档内有多个交叉引用表时,当前引用表obj的prev内容保存的是下一个交叉引用表的位置,如果当前引用表时最后一个,那么当前obj找不到Prev。
在解析多个交叉引用表时,需要不断查找当前obj下是否存在prev,如果不存在,则停止查找,说明交叉引用表到此是最后一个,在交叉引用表尾都会部有startxrf字段,后面的数字就是当前交叉引用表位置。因此,在最开头去文件尾部查找startxrf,也是在查找第一个交叉引用表尾部的startxrf。
这里第一个引用表的Prev是541882,找到541882(0x844ba)位置如下:
105 0 obj仍然是交叉引用表obj,里面还有Prev,在endobj后面跟着startxrf(当前交叉引用表位置)。
注意:有时候startxrf记录的不是当前obj的位置,准确的方式是使用引用表obj内的Prev判断。
下面结合实例,看一下交叉引用表时如何被解析出来的:
这是一段PDF文档内容:
可以看到,在PDF尾部的startxrf的是572618,对应的位置是8bcca,正好是116 0 obj,这里的Index是[1 1 4 1 12 1 106 11],表示这段交叉引用表保存的是1号obj,4号obj,12号obj,106号至117(106+11)号obj(106,107,108,109....117);W是[1 3 0];接下来是stream内容,下面是一段解压之后的stream内容:
第一个字节是type内容,0表示f,1表示n,2表示o;2到4字节是offset值,二进制存储,得到结果是542265,最后由于W数组最后一个是0,所以不读取stream数据,generation值为0;从stream起始位置对应Index的顺序(两两一组)最后得出交叉引用表的信息:1号obj,type是n,位置在542265,generation是0,;好了现在找到了交叉引用表的1号obj,我们到542265位置看一下:
可以看到,0x84639对应的位置正是1 0 obj,这样一个完整的交叉引用表流程解析完毕。