前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >PDF标准详解(一)——PDF文档结构

PDF标准详解(一)——PDF文档结构

作者头像
Masimaro
发布于 2024-01-29 00:28:35
发布于 2024-01-29 00:28:35
89100
代码可运行
举报
运行总次数:0
代码可运行

已经很久没有写博客记录自己学到的一些东西了。但是在过去一年的时间中自己确实又学到了一些东西。一直攒着没有系统化成一篇篇的文章,所以今年的博客打算也是以去年学到的一系列内容为主。通过之前Vim系列教程的启发,我发现还是写一些系列文章对自己的帮助最大。它能最大化自己的学习成果,并强迫自己深入了解一些内容。所以今年我想还是以系列文章为主,如果中间有需要穿插一些bug处理或者语言特性相关的,可能也会有这方面的内容吧。

好了,废话就到这里,下面开始正式介绍PDF相关的内容

PDF简介

PDF的全称是 Portable document format(可移植文档格式),是描述打印页面的世界领先语言。最早于1990年代由Adobe Systems创造。早期是Adobe专有格式,直到2008年作为开放标准发布。后续经过一系列的发展,目前已经发展到了2.0版本,由于PDF完全向后兼容,并且大部分都是向前兼容的,因此,这里不打算固定在某个具体的版本,而是介绍一些PDF通用的标准和规则。

PDF的文档结构

PDF主要由四个部分构成,文件头、文件体、交叉引用表以及文件尾 文件头将文件标识为PDF并给出它的版本号,例如

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
%PDF-1.0    % PDF 版本号为 1.0 的文件头

文件体是PDF文档的主体内容,主要由对象组成,它规定了页面信息和页面内容元素等信息

交叉引用表给出了每个对象距离文件首部的地址偏移,这样在解析PDF的时候就不用从头到尾解析每个对象,而是根据需要通过交叉引用表来寻址到具体的对象地址,只单独解析某个对象,提高了解析效率

文件尾给出交叉引用表的位置并且以

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
%%EOF

作为结尾

PDF文件的逻辑结构

一个标准的PDF文档需要在文件体中包含下列元素对象:

  1. 根节点元素,类似于xml的根节点,它是整个文档的根节点对象
  2. Pages对象,它包含了PDF文档的页面信息,一般通过它来定义整个PDF文档有多少页
  3. Page 页面对象,它用来描述每个具体的页
  4. Page Content 对象,它来描述每个具体页中都有哪些对象,一般是一个字节流用来表示将在页面中显示哪些内容
  5. Page Resource 对象,它是内容的资源字典,供Content对象引用,资源包括字体、画刷、画笔等等
  6. trailer 字典,可以将它看作pdf文档对象的入口,通过它我们可以知道当前PDF文档的一些具体信息,例如根节点的位置,交叉引用表的大小

它们之间的关系如下图:

PDF版的Hello World

说了这么多,我们来试试来自己编辑一个hello world文档,首先建立一个文本文件,将后缀改为.PDF 。 我们先写上文件头:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
%PDF-1.0    % PDF 版本号为 1.0 的文件头

主要对象

我们按照之前的分析的PDF文档中需要包含的对象,来逐一定义 首先给出Pages节点的定义

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
1 0 obj % 对象1
<< /Type /Pages     % 这是一个页面列表
   /Count 1         % 只有一页
   /Kids [2 0 R]    % 页面对象编号列表。这里只是对象2
>>
endobj  % 对象1结束

对象的内容我们在后续会专门介绍,所以这里不需要额外关注它的语法,这里只需要知道

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
1 0 obj

定义了一个对象1,后续通过1 这个编号可以找到这个对象。这个对象中定义了他的类型是 Pages表示它是一个pages对象,/Count表示整个PDF文档只有一页,Kids是一个数组,表示每一页的页面对象,这里它只有一个页面对象,就是对象2

接着我们定义页面对象

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
2 0 obj
<< /Type /Page              % 这是一个页面
   /MediaBox [0 0 612 792]  % 纸张尺寸为美国信肖像(612点x792点)
   /Resources 3 0 R         % 对象3的资源引用
   /Contents [4 0 R]        % 图形内容在对象4>>
endobj

页面对象中我们定义了页面纸张的大小,单位是磅。因为PDF是可移植文档,它需要在不同设备上显示同样的内容,这里不能使用像素,如果使用像素,在同样尺寸的显示器上如果显示器的像素分辨率不同,那么显示的结果将会不同。所以这里一般使用磅作为单位。

同时在页面对象中定义了页面中将要使用的资源以及将要显示的内容

接着我们来定义资源对象

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
3 0 obj
<< /Font  % 字体字典
     << /F0  % 只有一种字体,称为/F0
          << /Type /Font  % 这三行引用了内置字体Times Italic
            /BaseFont /Times-Italic
            /Subtype /Type1 >>
     >>
>>
endobj

资源对象中,我们定义了一个字体资源,字体为 Times Italic,并且定义了这种字体资源的名称为 F0, 后面可以通过F0 这个名称来直接引用这个字体

然后我们来定义页面内容对象

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
4 0 obj     % 页面内容流
<< >>
stream      % 流的开始
1. 0. 0. 1. 50. 700. cm % 位置在(50,700BT  % 开始文本块
 /F0 36. Tf         % 在36pt选择/F0字体
 (Hello, World!) Tj % 放置文本字符串
ET  % 结束文本块
endstream   % 流结束
endobj

通过stream来定义一个流对象,在这个流对象中,我们定义它在页面的 (50, 700) 坐标位置显示字符,显示字符内容通过后面的 (Hello, World!) Tj来定义,并且定义了字符采用F0 字体,也就是上面定义的Times-Italic字体

页面相关的内容我们已经定义完了,接着我们需要定义一些结构相关的对象,方便PDF解析器找到并解析页面内容。

我们来定义根节点

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
5 0 obj
<< /Type /Catalog %文件目录
   /Pages 1 0 R   %参考页面列表
>>
endobj

根节点包含了一个Pages定义,通过根节点就可以找到Pages节点

接着我们来定义交叉引用表

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
xref %这里我们跳过了交叉引用表的开始
0 6

交叉引用表包含一些偏移地址信息,我们单纯的通过文本文档很难计算各个对象的偏移,所以这里我们只给出文档中对象数量为6,具体的地址我们先不给出,这样PDF解析器也能解析出各个对象

之前我们给出了5个对象的定义,但是交叉引用表的条目却是6,这是因为交叉引用表的第一条一般是一个没有什么用处的,有效的对象从第二条定义开始。

下面给出 Trailer 字典的定义

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
trailer
<< /Size 6 %交叉引用表的行数
   /Root 5 0 R % 参考文档目录
>>

Trailer 字典以 trailer关键字开始。条目下面包括了交叉引用表的行数以及根节点的对象

最后我们给出交叉引用表在PDF文档中的偏移,由于交叉引用表的内容为空,所以这里我们直接给0

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
startxref
0			%xref表开始的字节偏移量,这里设置成0

最后我们以

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
%%EOF

结尾来表示整个PDF文档结束

到这里我们已经得到了一个PDF阅读器可以打开的PDF文档。我们使用PDF阅读器可以得到如下的页面

PDF文档一般的读取过程

不知道各位小伙伴们是否能看懂上面 Hello World 文档的定义。下面我们通过一个完整的 PDF文档来将上面所有定义的对象串起来,希望各位能对PDF文档有一个完整的认识。我们不用纠结各个部分的写法,以及为什么要这么写,只需要明白各个对象的功能即可。具体对象定义相关的语法和每个对象的详细解释将会在后面一系列文章中给出,相信那个时候再来看这个 Hello Word 文档一定会有一个更清晰的认识。

再说明文档读取的过程前,我们先使用一些工具来补全这个文档,这里使用 pdftk 工具。可以在这里 进行下载,完成之后,使用如下命令进行补全

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
pdftk hello.pdf output hello-full.pdf

成功后会得到如下内容

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
%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文档都粘贴了出来,从这里我们可以看到,它已经为我们补全了交叉引用表。下面通过整个文档来说明一般读取过程

  1. PDF解析程序,先通过文件头来确定是否是PDF文件,并且得到PDF文件的版本
  2. 在文件末尾找到%%EOF 关键子,确定文件尾。接着向上查找到 startxref 关键字,该关键字后面将会给出交叉引用表的偏移,通过这个偏移地址可以找到交叉引用表
  3. 接着查找trailer关键字,通过trailer关键字可以得到文档的一些信息,这里关键的是得到 Root 节点的对象。
  4. 根据交叉引用表可以很块定位到Root 节点对象,也就是对象5
  5. 根据Root 对象中的 Pages属性可以找到Pages对象,也就是PDF页面信息对象
  6. 根据Pages对象中的Kids 数组,可以找到PDF包含的所有页面对象,这个文档只有一个页面对象
  7. 找到Page 对象后可以根据 Resources 和Contents属性可以找到页面内容和页面引用的资源。例如该文档就可以使用Times-Italic字体显示 hello world字符串
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-01-28,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
实用 | 盘点几种解决 Chrome 占用内存大的实用方案!(文末送书)
Google Chrome 是笔者平时工作使用最多的浏览器,随着 Tab 窗口及插件的增多,内存占用几乎令人崩溃,甚者会出现页面卡死的状态
AirPython
2021/12/09
11.1K0
实用 | 盘点几种解决 Chrome 占用内存大的实用方案!(文末送书)
Chrome终于上线这项重磅功能,中国用户苦等多年!
但在Chrome上,标签页增多后,每个标签的宽度会自动缩小,用户无法阅读标题,甚至无法查看网站小图标。
龙哥
2020/10/27
2.7K0
Chrome终于上线这项重磅功能,中国用户苦等多年!
你的浏览器,何必是浏览器
工欲善其事,必先利其器,作为大学生或者从业人员,如果能熟练地使用各种工具来提高自己的工作学习效率必然是一件好事!!!
小孙同学
2022/01/17
3K0
你的浏览器,何必是浏览器
极力推荐的谷歌浏览器插件
今天有幸请教了 记得诚、小麦大叔、SoWhat、程序猿学社 等十位博客专家,给大家推荐一些谷歌浏览器插件,让你的谷歌浏览器更实用,成为真正的生活办公小助手!
Twcat_tree
2022/11/30
3.1K0
极力推荐的谷歌浏览器插件
超好用的谷歌浏览器、Sublime Text、Phpstorm、油猴插件合集
一、谷歌浏览器插件 二、Sublime Text 插件 三、Phpstorm 插件 四、油猴脚本 4.1 脚本网站 4.2 自用的脚本 五、相关链接 分享一些超好用的谷歌浏览器、Sublime Te
guanguans
2018/05/09
5.1K0
超好用的谷歌浏览器、Sublime Text、Phpstorm、油猴插件合集
浏览器插件:一款解决谷歌浏览器吃内存神器插件,你值得试一试!
Chrome浏览器是大部分开发者必备的浏览器,它的主要有点有便于调试、启动快、无广告。但是谷歌浏览器也有自己的缺点,Chrome浏览器对系统内存的占用太大了,每打开一个页面都会占用系统内存。如果你的浏览器一下子打开几十个网页,不一会儿你就会发现Chrome浏览器已经将系统大部分内存给占用了!
小明互联网技术分享社区
2021/08/13
1.4K0
Chrome快捷键整理
Ctrl+N 打开新窗口 Ctrl+T 打开新标签页 Ctrl+Shift+N 在隐身模式下打开新窗口 Ctrl+O,然后选择文件 在谷歌浏览器中打开计算机上的文件 按住 Ctrl 键,然后点击链接 从后台在新标签页中打开链接,但您仍停留在当前标签页中 按住 Ctrl+Shift 键,然后点击链接 在新标签页中打开链接,同时切换到新打开的标签页 按住 Shift 键,然后点击链接 在新窗口中打开链接 Alt+F4 关闭当前窗口 Ctrl+Shift+T 重新打开上次关闭的标签页。谷歌浏览器可记住最近关闭的 10 个标签页。 将链接拖动到标签页内 在指定标签页中打开链接 将链接拖动到两个标签页之间 在标签页横条的指定位置建立一个新标签页,在该标签页中打开链接 Ctrl+1 到 Ctrl+8 切换到指定位置编号的标签页。您按下的数字代表标签页横条上的相应标签位置。 Ctrl+9 切换到最后一个标签页 Ctrl+Tab 或 Ctrl+PgDown 切换到下一个标签页 Ctrl+Shift+Tab 或 Ctrl+PgUp 切换到上一个标签页 Ctrl+W 或 Ctrl+F4 关闭当前标签页或弹出式窗口 Alt+Home 打开主页
csxiaoyao
2019/02/18
6.8K0
idea和谷歌浏览器占用内存过高的处理方法
最近家里电脑打开浏览器页面过多,内存占用严重,而且idea启动一个项目就会把内存占满,最后查了一些资料顺利解决了这个问题。这里记录一下,方便后面直接使用。
jiankang666
2022/05/12
8.9K0
idea和谷歌浏览器占用内存过高的处理方法
【Chrome】谷歌浏览器常用的flags配置与插件介绍
就在最近,7月24日,谷歌浏览器Chrome终于迎来了最近最大的一次更新,Chrome68。身为软件颜控的我对隔壁Edge随着更新越来越好看的界面已经眼红很久了,甚至几次想要抛弃Chrome换成Edge,还好在这次更新中 ,Chrome挽回了我的信任:可以丢掉那祖传的落后界面设计,全面换上好看的质感设计(Material Design)了(笑
ZifengHuang
2020/07/29
15K1
【Chrome】谷歌浏览器常用的flags配置与插件介绍
实用:Google Chrome 键盘快捷键大全
窗口和标签页快捷方式 Ctrl+N 打开新窗口 按住 Ctrl‎ 键,然后点击链接 在新标签页中打开链接 按住 Shift 键,然后点击链接 在新窗口中打开链接 Alt+F4 关闭当前窗口 Ctrl+T 打开新标签页 Ctrl+Shift+T 重新打开上次关闭的标签页。 谷歌浏览器可记住您关闭的最后 10 个标签页。 将链接拖动到标签页内 在指定标签页中打开链接 将链接拖动到两个标签页之间 在新标签页横条上的指定位置打开链接 Ctrl+1 到 Ctrl+8 切换到指定位
joshua317
2018/04/16
1.7K0
开发中常用的一些Chrome插件介绍
工欲善其事,必先利其器。每个程序员都会有一套自己喜欢的,适用自己的提高工作效率的工具。之前每次换电脑总是要折腾一次,总会遗漏一些,这次就统一整理一下。这里主要介绍Chrome下的一些提高效率或者很好用的工具。 流程图:Gliffy Diagrams Gliffy Diagrams是一种全新类别的谷歌浏览器程序,它甚至可以脱机使用!适合于:基本绘图、流程图、 UML图表、网络图表、线框图和图样、网站地图、业务流程模型、组织机构图、平面图、文氏图、四点分析、技术图等。 特点: 使用HTML5创建的易于
子勰
2018/05/22
1.9K0
Google Chrome 插件,一直用,一直爽
C:\Users\Administrator\AppData\Local\Google\Chrome\Application\User Data\Default\Extensions
OwenZhang
2021/12/08
4310
Google Chrome 插件,一直用,一直爽
请停用以开发者模式运行的扩展程序?搞定谷歌浏览器插件弹窗
为什么我一直推荐使用谷歌浏览器呢,某些浏览器会自作主张封杀某些域名,还经常弹各种广告,当然更主要的是方便我使用谷歌搜索。
苏生不惑
2020/06/01
1.9K1
请停用以开发者模式运行的扩展程序?搞定谷歌浏览器插件弹窗
OneTab – 帮你节省 95% 的内存,让 Chrome / Firefox 重焕新生
浏览器作为我们上网的窗口,在我们的工作学习中担任着非常重要的角色,但人们想要浏览什么东西的时候,往往都是打开浏览器,打开网站,搜索关键词,尤其是你想要查某样东西的时候,同时打开7、8个甚至十几个网页是很正常的事。
课代表
2018/09/27
2.3K0
OneTab – 帮你节省 95% 的内存,让 Chrome / Firefox 重焕新生
浏览器快捷键大全
标签页和窗口快捷键 快捷键 说明 Ctrl + n 打开新窗口。 Ctrl + shift + n 在隐身模式下打开新窗口。 Ctrl + t 打开新的标签页。(常用) Ctrl + Shift + t 重新打开最后关闭的标签页。 Ctrl + Tab 或 Ctrl + Pgdn 跳转到下一个打开的标签页,如果当前为最后一个标签页,则跳转到第一个标签页。 Ctrl + Shift + Tab 或 Ctrl + Pgup 跳转到上一个打开的标签页。(常用) Alt + ←
十玖八柒
2022/08/01
1.4K0
程序猿的 Chrome 浏览器插件推荐
这是一款标签页插件,我使用 Chrome 浏览器的时候就开始使用这个插件,注册后可以使用 Pro 版本,它具有多种搜索引擎设计及类似书签页的功能,非常的实用,可以看一下我之前写的介绍 Infinity 插件的文章:
Meng小羽
2020/03/18
1.2K0
精选10款谷歌浏览器插件武装你的浏览器
最近看到的一段话很有感触,日复一日的低效率工作只会消磨你的热情,而巨大的时间成本会让你错失很多机会。
王小婷
2020/10/23
6460
8 款好用超赞的 Google Chrome 插件,一直用,一直爽
Github一个不好的地方就是代码是不能相互跳转的,所以阅读起来很累,如果我要引入一个库,那么就必须clone下来然后通过idea打开才行。这样的流程对于库的前期调研来说成本很高,所以我希望利用SourceGraph让在线阅读代码的体验提升一个量级,就像在强大的IDE中一样。
芋道源码
2019/07/30
7920
推荐一些chrome浏览器必装的插件!
Chrome浏览器已经作为开发者必不可少的工具,不仅仅使用其来搜索有价值的资料,解决各种难以解决的bug,同时,chrome浏览器的各种插件工具也可以大大的给我们带来便利。因此,今天就给大家推荐一些普通日常的插件。
好好学java
2021/07/28
2.6K0
推荐一些chrome浏览器必装的插件!
几款好用超赞的Google Chrome插件
Github一个不好的地方就是代码是不能相互跳转的,所以阅读起来很累,如果我要引入一个库,那么就必须clone下来然后通过idea打开才行。这样的流程对于库的前期调研来说成本很高,所以我希望利用SourceGraph让在线阅读代码的体验提升一个量级,就像在强大的IDE中一样。
小小詹同学
2019/07/19
1.3K0
推荐阅读
相关推荐
实用 | 盘点几种解决 Chrome 占用内存大的实用方案!(文末送书)
更多 >
LV.0
斑马网络资深开发工程师
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档