PDF文档物理结构概述

本文详细介绍了PDF文件的基本结构,包括header(版本标识)、body(对象集合)、cross-referencetable(交叉引用表)和trailer(元数据)。讲解了PDF的更新机制、标记和编码规则,以及对象流、对象编码和跨引用表的不同形式。
摘要由CSDN通过智能技术生成

基础

PDF(Portable Document Format,便携式文档结构)是一种平台无关而且功能强大(支持文字/图象/表单/链接/音乐/视频等)文档结构,它提供有效随机存取和增量更新的功能,一个基本的PDF文件将会包含四种元素:

  • header部分:标识PDF文件使用的版本;
  • body部分:包含PDF文件中的所有obj对象;
  • cross-reference table 部分:交叉引用表,包含文件中间接对象的信息;
  • trailer部分:包含交叉引用表和文件正文中某些特殊对象的位置;

注意:PDF文件内容被修改的时候,原始结构中的数据可能会被更新,而额外的元素可以附件到文件末尾,相关内容参见章节“增量更新”种的描述;

PDF File Structure
header
Original body
Original cross-reference section
Original trailer
body Update 1
cross-reference section 1
trailer 1
body Update n
cross-reference section n
trailer n

按照惯例,PDF文件中的标记(tokens)按行排列,每行都会使用一个行结尾标记(EOL),例如 carriage return(回车符,字符编码0x0D)或line feed(换行符,字符编码0x0A),或两者兼有;而PDF文件中包含的二进制数据可以是任意多行(不根据行结尾标记来判断数据是否结束);

注意:为了提高PDF文件的兼容性,不属于流对象的数据行限制为不超过255个字符,但有一个例外,签名字典的内容字符串不受行长度限制。

这些规则保证了PDF文件的处理基本一致,而其他的规则可以满足网络环境中对PDF文档组件进行高效的增量访问,这类组织方式被称为线性化PDF(Linearized PDF)

标记符号

空白字符标记

十进制十六进制名称
00x00NULL(NUL),空字符
90x09HORIZONTAL TAB(HT),水平(行)Tab
100x0ALINE FEED(LF),换行符
120x0CFORM FEED(FF),
130x0DCARRIAGE RETURN(CR),回车符
320x20SPACE(SP) ,空格

分界符标记

符号十进制十六进制名称
400x28LEFT PARENTHESIS
410x29RIGHT PARENTHESIS
<600x3CLESS-THAN SIGN
>620x3EGREATER-THAN SIGN
[910x58LEFT SQUARE BRACKET
]930x5DRIGHT SQUARE BRACKET
{1230x78LEFT CURLY BRACKET
}1250x7DRIGHT CURLY BRACKET
/470x2FSOLIDUS
%370x25PERCENT SIGN

Header 部分

PDF文件的第一行应是由5个字符“%PDF-”后跟“1.N”的版本号组成的标记,其中N是0到7之间的数字。例如下面的:

%PDF–1.0
%PDF–1.1
%PDF–1.2
%PDF–1.3
%PDF–1.4
%PDF–1.5
%PDF–1.6
%PDF–1.7

从PDF 1.4开始,应使用“文档目录字典(document’s catalog dictionary)”中的Version 条目(通过文件Trailer部分的Root条目指定版本),而不是标题中指定的版本。

如果PDF文件中包含了二进制的数据,版本号标记后必须立即跟随一个注释行,其中至少需包含4个二进制字符,而且这些字符的字符编码必须大于等于128;这可以保证文件在传送的过程中的正确性,也便于确定所处理的文件是文本类型还是二进制类型;

注意:在很多PDF文件中,我们会看到 25 E2 E3 CF D3 0A 跟随在“版本号标记(%PDF–1.N)”后,使用这些字符来标识此PDF文档中包含二进制数据,但 无需使用相同的二进制字符(字符编码只需要大于等于128),使用这些值只是“Adobe软件生成的示例文件”是这么取值的,所以很多第三方开发包也沿用此习惯,没有理由不使用它们,为什么要和“Adobe软件生成的示例文件”不一致呢?

Boby 部分

PDF文件的正文应由表示文件内容的一系列间接对象组成,例如字体、页面和采样图像。从PDF 1.5开始,Body还可以包含对象流,每个对象流包含一系列间接对象。

Object Streams 对象流

对象流为一种特殊的流对象,类型(Type)为“ObjStm”的流对象,从PDF-1.5开始出现,一个对象流中可以保存多个对象,并通过压缩技术来优化文件大小;

注意:在对象流中无法保存的对象如下:

  • 流数据对象
  • 重复使用次数不为0的对象
  • 文档的加密目录
  • 在对象流的目录中,使用了Length属性进行描述的对象
  • 在线性化文件中,文档分类,线性化目录和页面对象,都不能出现在对象流中
15 0 obj % The object stream
<</Type/ObjStm
  /Length 1856
  /N 3 % The number of objects in the stream
  /First 24 % The byte offset of the first object
>>
stream
% The object numbers and offsets of the objects, relative to the first
11 0 12 547 13 665
<</Type/Font
  /Subtype/TrueType
  ...other keys...
  /FontDescriptor 12 0 R
>>
<</Type/FontDescriptor
  /Ascent 891
  ...other keys...
  /FontFile2 22 0 R
>>
<</Type/Font
  /Subtype/Type0
  ...other keys...
  /ToUnicode 10 0 R
>>
...
endstream
endob
键名类型说明
Type
Name(名称)
(必选)指PDF对象的类型,对于对象流来说,即“ObjStm”
NNumber(整型)(必选)流中保存的间接对象的个数
FirstNumber(整型)(必选)解码流中第一个对象的字节偏移量
ExtendsStream(数据流)包含另一个对象流的引用

对象流中的流数据所包含的内容

  • N个键值对(整型)会使用空格分隔,每个键值对的第一个整型将描述压缩对象编码,第二个整型将描述该对象在解码流中的字节偏移量(从First属性的数值开始计算),偏移量将使用“Big Endian”编码
  • First属性将给出解码流中第一个对象的字节偏移量
  • 可连续保存N个对象,而在流中只会包含对象的值,obj和endobj关键字将不会使用

注意

  • 对象流中的对象并无次序限制,因此对象无需按照对象编号的大小进行排序
  • 字典和数组可包含间接引用

Cross-Reference Table 交叉引用表部分

交叉引用表包含文件中间接对象的信息,以便允许对这些对象进行随机访问,因此无需读取整个文件即可定位任何特定对象,交叉引用表可以有两种描述方式:字符和流(PDF 1.5开始);

Cross-Reference Table(字符形式)

xref eol 
0 1
0000000000 65535 f  
4 1
0000000009 00000 n  
8 3
0000000074 00000 n  
0000000120 00000 n  
0000000179 00000 n 

交叉引用表以“xref”关键字开始,后跟交叉引用表的内容,每个交叉引用表又可以分为若干个子段,每个子段的第一行是两个以空格隔开的数字,第一个数字是对象起始编号,后面的数字是连续的对象个数,接着每行是这个子段的各对象的具体信息,对象信息的格式如下:

nnnnnnnnnn ggggg n eol
  • nnnnnnnnnn 长度10个字节,表示对象在文件的偏移地址;
  • ggggg 长度5个字节,表示对象的生成号;
  • n (in-use)表示对象被引用,如果此值是f (free),表示对象未被引用;
  • eol 行结尾标记

交叉引用表中的第一个编号为0的对象始终是f(free)的,并且生成号为65535;除了编号0的对象外,交叉引用表中的所有对象最初的生成号应为0。删除间接对象时,应将其交叉引用条目标记为“free”,并将其添加到free条目的链表中。下次创建具有该对象编号的对象时,条目的生成号应增加1,最大生成号为65535;当交叉引用条目达到此值时,它将永远不会被重用。

Cross-Reference Streams(流形式)

从 PDF-1.5 版本开始,交叉引用表将以流的形式保存,而不再使用字符信息;使用流形式可以提供以下优势:

  • 交叉引用信息的描述更加紧凑
  • 可以访问保存再对象流中的压缩对象,并允许加入新的交叉引用属性

交叉引用表使用流形式,可以包含一个字典对象和一个数据流,数据流中的信息可以等价于文本形式,在字典对象可以携带Trailer中的信息(及可以在PDF文件结构中不需要包含“trailer”信息)

... objects ...
12 0 obj % Cross-reference stream
<< /Type /XRef % Cross-reference stream dictionary
/Size ...
/Root ...
>>
stream
... % Stream data containing cross-reference information
endstream
endobj
... more objects ...
startxref
byte_offset_of_cross-reference_stream % Points to object 12
%%EOF

关键字“startxref”后跟的数值,将是交叉引用流的偏移量,而不是“xref”关键字的偏移量,对于完全使用交叉引用的文件(且文件不是一个混合引用文件),“xref”和“trailer”将不再使用,因此除了“startxref address %%EOF”段和注解之外,一个文件可视为一个对象序列;

注意:在线性化PDF文件中,文档分类、线性化目录和页面对象不会出现在一个对象流中;

字典对象属性描述
数据流相关属性描述
键名类型说明
Length
Numeric(数值)
(必选) 关键字“stream”和“endstream ”之间的字节数(在endstream 之前,可有其他的EOL标记,但不会包含在计数中,并且也不会是流数据的逻辑部分)
FilterName(名称)或  Array(数组)(可选) 在“stream”和“endstream” 之间数据使用的过滤器,可以包含了0个、1个或多个过滤器
DecodeParmsDictionary(字典)或Array(数组)(可选) 过滤器参数列表, 用于配置过滤器
F文件数据(可选,支持PDF-1.2),即文件中包含的流数据,如果出现该属性,“stream”和“endstream”之间的字节将被忽略;Lenght 属性可指定该数据流的字节数(如无数据,Lenght将为0),这些数据所使用的过滤器将由“FFilter”指定,过滤器的参数将由“FDecodeParms” 指定
FFilterName(名称)或  Array(数组)(可选,支持PDF-1.2),同“Filter”
FDecodeParmsDictionary(字典)或Array(数组)组(可选,支持PDF-1.2),同“Filter”
DLNumeric(数值)(可选,支持PDF-1.5),是一个非负整型,用于描述解码后流的字节数,它可用于确认磁盘空间是否满足写入数据流到文件中
交叉引用表相关属性描述
键名类型说明
Type
Name(名称)
(必选) XRef
SizeNumeric(数值)(必选) 交叉引用表中的对象数,果该数值大于在段中出现的所有对象编号,则表明该段已经更新过了,它与“trailer”目录的“Size”属性是等价的
IndexArray(数组)(可选) 用来描述该交叉引用表中所有的子段索引,数组的长度等于子段数乘以二,及一个子段索引有两个数字表示,第一个数字是对象起始编号,后面的数字是连续的对象个数;数组将以对象起始编号升序排列,子段不能进行覆盖,每个子段中必须包含一个对象;如果只有一个字段,那么他的取值为[0 Size]
PrevNumeric(数值)如果文件中包含多个交叉引用流,将出现“Prev”属性,且文件不是混合引用文件,该属性将包含解码流中文件起始处到前一个交叉引用流起始处的字节偏移量,并等同于“trailer”目录的“Prev”属性
WArray(数组)(必选) 该整型数组可描述单个交叉引用属性中位域尺寸,W 会包含三个整型,每个整型都将给出对应位域的字节数,例:[1 2 1]意味着对应的位域为1个字节、2个字节和1个字节;对所有元素值求和可得属性的总字节数(如上例1+2+1=4byte),这时可使用Index数组确认每个子段的起始位置;如果位域长度大于一个字节,将使用“Big Endian”编码
注意
  • 交叉引用表相关属性都是直接对象,且不允许使用间接对象,对于数组(Index和W属性)来说,其中的元素也必须是直接对象,如果数据流被编码,数据流相关属性中的Filter和DecodeParms属性也需要是直接对象
  • 其他交叉引用流的属性,可以是间接对象,事实上有些属性就是间接对象,例如“Root”
  • 交叉引用表中的数据流不会被加密,字典属性中出现的值也不能加密
交叉引用表数据流中定义的引用属性

引用属性由三部分组成,每个部分的尺寸由交叉引用表相关属性“W”定义

  • 第一部分(位域 1)为类型
  • 第二部分(位域 2)为根据类型定义
  • 第三部分(位域 3)为对象的生成号
类型位域描述
0位域 1即属性的类型0,它可定义释放对象的链表(字符形式的f属性)
0位域 2下一个释放对象的对象编号
0位域 3如果对象编号重复使用,将给出重复使用的次数
1位域 1即属性的类型1,它可定义未压缩的使用对象(字符形式的n属性)
1位域 2从文件起始处开始的对象偏移量
1位域 3对象的重复使用次数,默认值为0
2位域 1即属性的类型2,它可定义压缩的使用对象(字符形式的n属性)
2位域 2包含对象的对象流的编号(对象流的重复使用次数必须为0)
2位域 3对象流中对象的索引

Trailer 部分

trailer 可使读取器快速查找交叉引用表和某些特殊对象;

trailer
<< key1 value1
key2 value2
…
keyn valuen
>>
startxref
Byte_offset_of_last_cross-reference_section
%%EOF

trailer 由三部分组成

  • trailer: 字典类型(PDF-1.5版本起由“交叉引用表(流形式)”中的字典类型代替
  • startxref: “交叉引用表(xref)”的位置,从文件起始处到交叉引用表偏移量
  • eof 结尾标识符,取值“%%EOF”

trailer 属性

键名类型说明
Size
Numeric(数值)
(必选,非间接引用),将给出文件的交叉引用表中所有属性的总数,交叉引用表中任意对象编码大于Size,则表明它的定义已经丢失,并且会被读取器忽略
PrevNumeric(数值)(若文件中包含多个交叉引用段则必选,非间接引用),它将标记文件起始处与前一个交叉引用段之间的字节偏移量
RootDictionary(字典)(必选,间接引用),即为文件中PDF文档的分类目录(Document Catalog)
EncryptDictionary(字典)(若文件被加密则必选,PDF-1.1开始支持)文档的加密属性
InfoDictionary(字典)(可选,间接引用),即为文档的信息目录
IDArray(数组)(如果Encrypt属性出现将包含,否则可选,PDF-1.1开始支持),该数组中包含的两个字节将构成一个文件标识符,当Encrypt属性存在时,该数组将是直接对象且不会加密
注意
  • 由于ID未被加密,因此可检查ID键,以确定加密前的文件是能被访问的,所以该数组不能被加密
  • 由于这个属性是可选的,它的缺失也可避免一些依赖于文件唯一标识符的工作流的运行
  • 若ID数据作为加密算法的输入,如果这些数据是间接引用或是ID数组为间接引用,这些数据在写入时必须加密,而这在读取器中将变成一个死锁条件:ID数据必须在解密时,使用自身进行解密,而自身也是加密的,又必须解密,如此将出现死锁,所以之前的限制就是为了预防这个死锁
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值