PDF标准详解(一)——PDF文档结构
已经很久没有写博客记录自己学到的一些东西了。但是在过去一年的时间中自己确实又学到了一些东西。一直攒着没有系统化成一篇篇的文章,所以今年的博客打算也是以去年学到的一系列内容为主。通过之前Vim系列教程的启发,我发现还是写一些系列文章对自己的帮助最大。它能最大化自己的学习成果,并强迫自己深入了解一些内容。所以今年我想还是以系列文章为主,如果中间有需要穿插一些bug处理或者语言特性相关的,可能也会有这方面的内容吧。
好了,废话就到这里,下面开始正式介绍PDF相关的内容
PDF简介
PDF的全称是 Portable document format(可移植文档格式),是描述打印页面的世界领先语言。最早于1990年代由Adobe Systems创造。早期是Adobe专有格式,直到2008年作为开放标准发布。后续经过一系列的发展,目前已经发展到了2.0版本,由于PDF完全向后兼容,并且大部分都是向前兼容的,因此,这里不打算固定在某个具体的版本,而是介绍一些PDF通用的标准和规则。
PDF的文档结构
PDF主要由四个部分构成,文件头、文件体、交叉引用表以及文件尾
文件头将文件标识为PDF并给出它的版本号,例如
%PDF-1.0 % PDF 版本号为 1.0 的文件头
文件体是PDF文档的主体内容,主要由对象组成,它规定了页面信息和页面内容元素等信息
交叉引用表给出了每个对象距离文件首部的地址偏移,这样在解析PDF的时候就不用从头到尾解析每个对象,而是根据需要通过交叉引用表来寻址到具体的对象地址,只单独解析某个对象,提高了解析效率
文件尾给出交叉引用表的位置并且以
%%EOF
作为结尾
PDF文件的逻辑结构
一个标准的PDF文档需要在文件体中包含下列元素对象:
- 根节点元素,类似于xml的根节点,它是整个文档的根节点对象
- Pages对象,它包含了PDF文档的页面信息,一般通过它来定义整个PDF文档有多少页
- Page 页面对象,它用来描述每个具体的页
- Page Content 对象,它来描述每个具体页中都有哪些对象,一般是一个字节流用来表示将在页面中显示哪些内容
- Page Resource 对象,它是内容的资源字典,供Content对象引用,资源包括字体、画刷、画笔等等
- trailer 字典,可以将它看作pdf文档对象的入口,通过它我们可以知道当前PDF文档的一些具体信息,例如根节点的位置,交叉引用表的大小
它们之间的关系如下图:

PDF版的Hello World
说了这么多,我们来试试来自己编辑一个hello world文档,首先建立一个文本文件,将后缀改为.PDF 。
我们先写上文件头:
%PDF-1.0 % PDF 版本号为 1.0 的文件头
主要对象
我们按照之前的分析的PDF文档中需要包含的对象,来逐一定义
首先给出Pages节点的定义
1 0 obj % 对象1
<< /Type /Pages % 这是一个页面列表
/Count 1 % 只有一页
/Kids [2 0 R] % 页面对象编号列表。这里只是对象2
>>
endobj % 对象1结束
对象的内容我们在后续会专门介绍,所以这里不需要额外关注它的语法,这里只需要知道
1 0 obj
定义了一个对象1,后续通过1 这个编号可以找到这个对象。这个对象中定义了他的类型是 Pages表示它是一个pages对象,/Count表示整个PDF文档只有一页,Kids是一个数组,表示每一页的页面对象,这里它只有一个页面对象,就是对象2
接着我们定义页面对象
2 0 obj
<< /Type /Page % 这是一个页面
/MediaBox [0 0 612 792] % 纸张尺寸为美国信肖像(612点x792点)
/Resources 3 0 R % 对象3的资源引用
/Contents [4 0 R] % 图形内容在对象4中
>>
endobj
页面对象中我们定义了页面纸张的大小,单位是磅。因为PDF是可移植文档,它需要在不同设备上显示同样的内容,这里不能使用像素,如果使用像素,在同样尺寸的显示器上如果显示器的像素分辨率不同,那么显示的结果将会不同。所以这里一般使用磅作为单位。
同时在页面对象中定义了页面中将要使用的资源以及将要显示的内容
接着我们来定义资源对象
3 0 obj
<< /Font % 字体字典
<< /F0 % 只有一种字体,称为/F0
<< /Type /Font % 这三行引用了内置字体Times Italic
/BaseFont /Times-Italic
/Subtype /Type1 >>
>>
>>
endobj
资源对象中,我们定义了一个字体资源,字体为 Times Italic,并且定义了这种字体资源的名称为 F0, 后面可以通过F0 这个名称来直接引用这个字体
然后我们来定义页面内容对象
4 0 obj % 页面内容流
<< >>
stream % 流的开始
1. 0. 0. 1. 50. 700. cm % 位置在(50,700)
BT % 开始文本块
/F0 36. Tf % 在36pt选择/F0字体
(Hello, World!) Tj % 放置文本字符串
ET % 结束文本块
endstream % 流结束
endobj
通过stream来定义一个流对象,在这个流对象中,我们定义它在页面的 (50, 700) 坐标位置显示字符,显示字符内容通过后面的 (Hello, World!) Tj来定义,并且定义了字符采用F0 字体,也就是上面定义的Times-Italic字体
页面相关的内容我们已经定义完了,接着我们需要定义一些结构相关的对象,方便PDF解析器找到并解析页面内容。
我们来定义根节点
5 0 obj
<< /Type /Catalog %文件目录
/Pages 1 0 R %参考页面列表
>>
endobj
根节点包含了一个Pages定义,通过根节点就可以找到Pages节点
接着我们来定义交叉引用表
xref %这里我们跳过了交叉引用表的开始
0 6
交叉引用表包含一些偏移地址信息,我们单纯的通过文本文档很难计算各个对象的偏移,所以这里我们只给出文档中对象数量为6,具体的地址我们先不给出,这样PDF解析器也能解析出各个对象
之前我们给出了5个对象的定义,但是交叉引用表的条目却是6,这是因为交叉引用表的第一条一般是一个没有什么用处的,有效的对象从第二条定义开始。
下面给出 Trailer 字典的定义
trailer
<< /Size 6 %交叉引用表的行数
/Root 5 0 R % 参考文档目录
>>
Trailer 字典以 trailer关键字开始。条目下面包括了交叉引用表的行数以及根节点的对象
最后我们给出交叉引用表在PDF文档中的偏移,由于交叉引用表的内容为空,所以这里我们直接给0
startxref
0 %xref表开始的字节偏移量,这里设置成0
最后我们以
%%EOF
结尾来表示整个PDF文档结束
到这里我们已经得到了一个PDF阅读器可以打开的PDF文档。我们使用PDF阅读器可以得到如下的页面

PDF文档一般的读取过程
不知道各位小伙伴们是否能看懂上面 Hello World 文档的定义。下面我们通过一个完整的 PDF文档来将上面所有定义的对象串起来,希望各位能对PDF文档有一个完整的认识。我们不用纠结各个部分的写法,以及为什么要这么写,只需要明白各个对象的功能即可。具体对象定义相关的语法和每个对象的详细解释将会在后面一系列文章中给出,相信那个时候再来看这个 Hello Word 文档一定会有一个更清晰的认识。
再说明文档读取的过程前,我们先使用一些工具来补全这个文档,这里使用 pdftk 工具。可以在这里 进行下载,完成之后,使用如下命令进行补全
pdftk hello.pdf output hello-full.pdf
成功后会得到如下内容
%PDF-1.0
%忏嫌
1 0 obj
<<
/Kids [2 0 R]
/Count 1
/Type /Pages
>>
endobj
2 0 obj
<<
/Resources 3 0 R
/MediaBox [0 0 612 792]
/Contents [4 0 R]
/Type /Page
>>
endobj
3 0 obj
<<
/Font
<<
/F0
<<
/BaseFont /Times-Italic
/Subtype /Type1
/Type /Font
>>
>>
>>
endobj
4 0 obj
<<
/Length 202
>>
stream
% 娴佺殑寮€濮?
1. 0. 0. 1. 50. 700. cm % 浣嶇疆鍦紙50,700锛?
BT % 寮€濮嬫枃鏈潡
/F0 36. Tf % 鍦?6pt閫夋嫨/F0瀛椾綋
(Hello, World!) Tj % 鏀剧疆鏂囨湰瀛楃涓?
ET % 缁撴潫鏂囨湰鍧?
endstream
endobj
5 0 obj
<<
/Pages 1 0 R
/Type /Catalog
>>
endobj xref
0 6
0000000000 65535 f
0000000015 00000 n
0000000074 00000 n
0000000168 00000 n
0000000267 00000 n
0000000523 00000 n
trailer
<<
/Root 5 0 R
/Size 6
>>
startxref
573
%%EOF
这个我将整个PDF文档都粘贴了出来,从这里我们可以看到,它已经为我们补全了交叉引用表。下面通过整个文档来说明一般读取过程
- PDF解析程序,先通过文件头来确定是否是PDF文件,并且得到PDF文件的版本
- 在文件末尾找到%%EOF 关键子,确定文件尾。接着向上查找到 startxref 关键字,该关键字后面将会给出交叉引用表的偏移,通过这个偏移地址可以找到交叉引用表
- 接着查找trailer关键字,通过trailer关键字可以得到文档的一些信息,这里关键的是得到 Root 节点的对象。
- 根据交叉引用表可以很块定位到Root 节点对象,也就是对象5
- 根据Root 对象中的 Pages属性可以找到Pages对象,也就是PDF页面信息对象
- 根据Pages对象中的Kids 数组,可以找到PDF包含的所有页面对象,这个文档只有一个页面对象
- 找到Page 对象后可以根据 Resources 和Contents属性可以找到页面内容和页面引用的资源。例如该文档就可以使用
Times-Italic字体显示hello world字符串
PDF标准详解(一)——PDF文档结构的更多相关文章
- 详解在Word文档中常见的各种公式编辑问题
正常情况下,我们在安装完成MathType之后会直接加载在Word文档中,Word文档中的MathType比较复杂,新手操作遇到麻烦也是常有的事,今天就来给大家详解下Word文档中常见的MathTyp ...
- 详解微信开发者文档——5 access_token管理
写在前面的话:前几篇博客详细讲解了如何获取用户发送的消息并进行回复,这里的回复是一种被动的回复,而被动回复的方式便是通过echo返回信息给微信服务器的POST请求,因此,其实我们并没有算的上调用了微信 ...
- PDF文件可以转换成txt文档吗
PDF是一种便携式的文件格式,传送和阅读都非常方便,是Adobe公司开发的跨平台文件格式,它无论在哪种打印机上都可以保证精确的颜色和准确的打印效果.可是有点遗憾的是PDF格式一般不能在手机上打开,或者 ...
- TestNG详解-深度好文
转自: https://blog.csdn.net/lykangjia/article/details/56485295 TestNG详解-深度好文 2017年02月22日 14:51:52 阅读数: ...
- HTML的概念和三大基石以及标准文档结构
HTML的概念: 概念: HTML:超文本标记语言 作用: 需要将java在后台根据用户请求处理的请求结果在浏览器中显示给用户. 在浏览器中数据需要使用友好的格式展示给用户. HTML是告诉浏 ...
- 浏览器对象模型(BOM)是什么?(体系结构+知识详解)(图片:结构)
浏览器对象模型(BOM)是什么?(体系结构+知识详解)(图片:结构) 一.总结 1.BOM操作所有和浏览器相关的东西:网页文档dom,历史记录,浏览器屏幕,浏览器信息,文档的地址url,页面的框架集. ...
- HTML5的文档结构和新增标签
一.HTML5 文档结构1.第一步:打开 开发工具,打开指定文件夹:2.第二步:保存 index.html 文件到磁盘中,.html 是网页后缀:3.第三步:开始编写 HTML5 的基本格式.< ...
- Win 10 开发中Adaptive磁贴模板的XML文档结构,Win10 应用开发中自适应Toast通知的XML文档结构
分享两篇Win 10应用开发的XML文档结构:Win 10 开发中Adaptive磁贴模板的XML文档结构,Win10 应用开发中自适应Toast通知的XML文档结构. Win 10 开发中Adapt ...
- Mongodb:修改文档结构后出现错误:Element '***' does not match any field or property of class ***.
Mongodb:修改文档结构后出现错误:Element '***' does not match any field or property of class ***. Mongodb是一种面向文档的 ...
- 读取XML文档结构并写入内容
1.在项目中新建XML文档结构.xsd文件,在其中添加相应的节点. 2.读取文档结构并写入内容 string initFileName = @"D:\Config.xml"; Da ...
随机推荐
- 跟着字节AB工具DataTester,5步开启一个实验
更多技术交流.求职机会,欢迎关注字节跳动数据平台微信公众号,回复[1]进入官方交流群 火山引擎A/B测试平台DataTester孵化于字节跳动业务内部,在字节跳动,"万事皆A/B,一切可 ...
- 以 Golang 为例详解 AST 抽象语法树
前言 各位同行有没有想过一件事,一个程序文件,比如 hello.go 是如何被编译器理解的,平常在编写程序时,IDE 又是如何提供代码提示的.在这奥妙无穷的背后, AST(Abstract Synta ...
- Problem B - Card Constructions (构造)
题意: 你可以用图示的方法建造金字塔,但是每一次都要建最大的金字塔,问最后能建几个金字塔. 思路: 我们可以发现对于每一个金字塔都是两边增加了两天边,然后中间行数− 1 -1−1个三角形,所以就可以求 ...
- var _ I = (*T)(nil)
学习的时候看到这样一行代码 var _ Codec = (*GobCodec)(nil) 查了一下后,得到该语句的作用为:检查GobCodec这个结构体是否实现了Codec这个接口 空白标识符_代表变 ...
- vue学习笔记 十八、父子组件相互传递参数
系列导航 vue学习笔记 一.环境搭建 vue学习笔记 二.环境搭建+项目创建 vue学习笔记 三.文件和目录结构 vue学习笔记 四.定义组件(组件基本结构) vue学习笔记 五.创建子组件实例 v ...
- P2895
本题用时:01:44:20. 算法:BFS 期间固然去逛了逛淘宝买了两个东西,但毕竟还是太久了.我因为忘记判断是否出界而浪费了好多时间,后来才半天想起来,这便是用了这么长时间的原因. 之后提交代码只有 ...
- 理解 Kubernetes volume 和 共享存储
1. Kubernetes volume 文章 介绍了 Docker volume.与 docker volume 类似的,在 kubernetes 中存在 Pod 级别的 volume,Pod 的 ...
- docker 安装 ETCD 及 etcd 使用
本文为博主原创,未经允许不得转载: etcd 是 CoreOS 团队发起的一个开源项目(Go 语言,其实很多这类项目都是 Go 语言实现的,只能说很强大),实现了分布式键值存储和服务发现,etcd 和 ...
- MySQL复习——20211027
MYSQL MySQL创建数据库 我们可以在登录MySQL服务后,使用create命令创建数据库,语法如下: CREATE DATABASE 数据库名; 使用root用户登录,root用户拥有最高权限 ...
- SpringMVC06——数据绑定——2021-05-09
数据绑定介绍 在执行程序时,SpringMVC会根据客户端请求参数的不同, 将请求信息中的信息以一定的方式转换并绑定到控制器类的方法参数中. 在数据绑定过程中,SpringMVC框架会通过数据绑定组件 ...