PDF文档物理结构概述
基础
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)
标记符号
空白字符标记
十进制 | 十六进制 | 名称 |
---|---|---|
0 | 0x00 | NULL(NUL),空字符 |
9 | 0x09 | HORIZONTAL TAB(HT),水平(行)Tab |
10 | 0x0A | LINE FEED(LF),换行符 |
12 | 0x0C | FORM FEED(FF), |
13 | 0x0D | CARRIAGE RETURN(CR),回车符 |
32 | 0x20 | SPACE(SP) ,空格 |
分界符标记
符号 | 十进制 | 十六进制 | 名称 |
---|---|---|---|
( | 40 | 0x28 | LEFT PARENTHESIS |
) | 41 | 0x29 | RIGHT PARENTHESIS |
< | 60 | 0x3C | LESS-THAN SIGN |
> | 62 | 0x3E | GREATER-THAN SIGN |
[ | 91 | 0x58 | LEFT SQUARE BRACKET |
] | 93 | 0x5D | RIGHT SQUARE BRACKET |
{ | 123 | 0x78 | LEFT CURLY BRACKET |
} | 125 | 0x7D | RIGHT CURLY BRACKET |
/ | 47 | 0x2F | SOLIDUS |
% | 37 | 0x25 | PERCENT 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” |
N | Number(整型) | (必选)流中保存的间接对象的个数 |
First | Number(整型) | (必选)解码流中第一个对象的字节偏移量 |
Extends | Stream(数据流) | 包含另一个对象流的引用 |
对象流中的流数据所包含的内容
- 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标记,但不会包含在计数中,并且也不会是流数据的逻辑部分) |
Filter | Name(名称)或 Array(数组) | (可选) 在“stream”和“endstream” 之间数据使用的过滤器,可以包含了0个、1个或多个过滤器 |
DecodeParms | Dictionary(字典)或Array(数组) | (可选) 过滤器参数列表, 用于配置过滤器 |
F | 文件数据 | (可选,支持PDF-1.2),即文件中包含的流数据,如果出现该属性,“stream”和“endstream”之间的字节将被忽略;Lenght 属性可指定该数据流的字节数(如无数据,Lenght将为0),这些数据所使用的过滤器将由“FFilter”指定,过滤器的参数将由“FDecodeParms” 指定 |
FFilter | Name(名称)或 Array(数组) | (可选,支持PDF-1.2),同“Filter” |
FDecodeParms | Dictionary(字典)或Array(数组)组 | (可选,支持PDF-1.2),同“Filter” |
DL | Numeric(数值) | (可选,支持PDF-1.5),是一个非负整型,用于描述解码后流的字节数,它可用于确认磁盘空间是否满足写入数据流到文件中 |
交叉引用表相关属性描述
键名 | 类型 | 说明 |
---|---|---|
Type
|
Name(名称)
|
(必选) XRef |
Size | Numeric(数值) | (必选) 交叉引用表中的对象数,果该数值大于在段中出现的所有对象编号,则表明该段已经更新过了,它与“trailer”目录的“Size”属性是等价的 |
Index | Array(数组) | (可选) 用来描述该交叉引用表中所有的子段索引,数组的长度等于子段数乘以二,及一个子段索引有两个数字表示,第一个数字是对象起始编号,后面的数字是连续的对象个数;数组将以对象起始编号升序排列,子段不能进行覆盖,每个子段中必须包含一个对象;如果只有一个字段,那么他的取值为[0 Size] |
Prev | Numeric(数值) | 如果文件中包含多个交叉引用流,将出现“Prev”属性,且文件不是混合引用文件,该属性将包含解码流中文件起始处到前一个交叉引用流起始处的字节偏移量,并等同于“trailer”目录的“Prev”属性 |
W | Array(数组) | (必选) 该整型数组可描述单个交叉引用属性中位域尺寸,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,则表明它的定义已经丢失,并且会被读取器忽略 |
Prev | Numeric(数值) | (若文件中包含多个交叉引用段则必选,非间接引用),它将标记文件起始处与前一个交叉引用段之间的字节偏移量 |
Root | Dictionary(字典) | (必选,间接引用),即为文件中PDF文档的分类目录(Document Catalog) |
Encrypt | Dictionary(字典) | (若文件被加密则必选,PDF-1.1开始支持)文档的加密属性 |
Info | Dictionary(字典) | (可选,间接引用),即为文档的信息目录 |
ID | Array(数组) | (如果Encrypt属性出现将包含,否则可选,PDF-1.1开始支持),该数组中包含的两个字节将构成一个文件标识符,当Encrypt属性存在时,该数组将是直接对象且不会加密 |
注意
- 由于ID未被加密,因此可检查ID键,以确定加密前的文件是能被访问的,所以该数组不能被加密
- 由于这个属性是可选的,它的缺失也可避免一些依赖于文件唯一标识符的工作流的运行
- 若ID数据作为加密算法的输入,如果这些数据是间接引用或是ID数组为间接引用,这些数据在写入时必须加密,而这在读取器中将变成一个死锁条件:ID数据必须在解密时,使用自身进行解密,而自身也是加密的,又必须解密,如此将出现死锁,所以之前的限制就是为了预防这个死锁
PDF文档物理结构概述相关推荐
- asp登录页面跳转到注册页面_Java 添加页面跳转按钮到PDF文档
概述 当我们在查阅含有大量页面的PDF时,可通过在页面上添加跳转按钮来实现页面转换,以达到节约时间,提高效率的目的.本文将通过Java程序来演示如何给PDF文档添加页面跳转按钮.通常来说跳转可分为两种 ...
- 14天学会安卓开发(附PDF文档和全部示例代码)
前言: 本人也是菜鸟,老鸟看了此文有哪里不好之处敬请指点,本书是根据<<Android应用开发揭秘>>攒写的,如何把一本书读薄,是一件值得思考的问题.相信看过那本书的都知道有5 ...
- datatables 添加时间按钮_Java 添加页面跳转按钮到PDF文档
概述 当我们在查阅含有大量页面的PDF时,可通过在页面上添加跳转按钮来实现页面转换,以达到节约时间,提高效率的目的.本文将通过Java程序来演示如何给PDF文档添加页面跳转按钮.通常来说跳转可分为两种 ...
- mac多个html合并,如何在Mac上将多页文件扫描合成一个PDF文档
您想要扫描杂志.报纸或文档的多个页面,并将其保存合并到一个PDF文件中吗?现在,不需要手动逐页扫描,也很容易做到.在Mac上将多个页面扫描合成一个PDF文件,你所需要的只是正确的软件.有很多软件可供选 ...
- python读取pdf文档书签 bookmark_用Python为PDF文件批量添加书签
平时看一些大部头的技术书籍,大多数都是PDF版的,而且有一些书籍是影印扫描版的,几百上千页的书,没有任何书签,想要找到一个章节的位置非常费劲.那么就想,能不能搞一个工具,来自动地为这些大部头的PDF书 ...
- 【教程】如何使用Java生成PDF文档?
在如今数字化时代,越来越多的人使用PDF文档进行信息传递和共享.而使用Java生成PDF文档也成为了一个非常重要的技能,因为Java作为一种通用的编程语言,可以在不同的操作系统和平台上运行.下面,我们 ...
- Java PDF文档转换 — PDF转Excel、SVG转PDF
概述 Spire.PDF for Java支持将PDF文档高质量地转换为XPS.图片.SVG.Word.HTML和PDF/A格式,以及支持将XPS.HTML文档转换为PDF格式.本文将通过代码演示来介 ...
- matlab使用pdfbox,PDFBox编写PDF文档
本文概述 PDF文档是纸质文档的流行替代品.它们在每个平台上具有相同的外观.与纸质文档一样, 某些PDF文档是机密的.我们可以使用密码保护来保护PDF文档.我们还可以在PDF文档中指定权限和加密类型. ...
- iOS 开发之 pdf 文档的加载与浏览的 4 种方式
原文链接:http://www.jianshu.com/p/1d4305a02ea5 在我们的开发中,有些像电子书类型的 app 的开发会涉及到 pdf 文档的加载与展示.由于笔者项目中正好涉及到这块 ...
最新文章
- PyCharm+QT Designer整合
- 【消息中间件】浅谈中间件优缺RabbitMQ基本使用
- php常用函数time
- jzoj3189-解密【字符串hash】
- 小白Linux下安装mysql
- Git 安装和使用教程(更加详细)
- JSONP和CORS两种跨域方式的介绍和方案实例
- 2.Jenkins 权威指南 --- 配置Jenkins 服务器
- MyEclipse 中各种 libraries 的含义
- 计算机四级网络工程师等级考试题库软件---百度云分享
- 已经解决ProcfsMetricsGetter: Exception when trying to compute pagesize
- PHP叫号系统,排队叫号系统
- DBeaver-Driver-All ( DBeaver驱动包,所有JDBC驱动整合包)
- 03_CSS字符属性
- 如何评价腾讯云游戏平台 START ?
- ASDFZ 3633 -- 排兵布阵
- vi中方向键和删除键
- 一文详尽解释CatBoost
- 程序员租女友被骗 揭秘“租友”市场背后那些坑
- python中ndim是什么_使用Python中的ndim和shape属性获取darray数据的维度、长度、形状和其他参数,python,ndarray,等...
热门文章
- 你是弱者,又有什么了不起
- 读现代操作系统第一二章笔记
- MFC--利用Haru库生成PDF文件
- 蓝牙电话协议HFP(Hands-Free Profile) 传输手机状态信息(信号/漫游/电量/运行商/电话状态)
- 【MySQL】MySQL建表与常见类型设计陷阱(MySQL专栏启动)
- 将github上的项目同步到本地
- 2022 计算机网络面试题整理(二)
- ACM-ICPC 2018 南京赛区网络预赛 E AC Challenge(状压dp)
- 八股文-- 2022.08.31
- 第 218 场周赛阿里巴巴专场(只做出了前三道)设计 Goal 解析器+K 和数对的最大数目+连接连续二进制数字