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

news2024/11/18 23:40:44

已经很久没有写博客记录自己学到的一些东西了。但是在过去一年的时间中自己确实又学到了一些东西。一直攒着没有系统化成一篇篇的文章,所以今年的博客打算也是以去年学到的一系列内容为主。通过之前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文档需要在文件体中包含下列元素对象:

  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
我们先写上文件头:

%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文档都粘贴了出来,从这里我们可以看到,它已经为我们补全了交叉引用表。下面通过整个文档来说明一般读取过程

  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字符串

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1417573.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Springmvc-@RequestBody

SpringBoot-2.7.12 请求的body参数无法转换&#xff0c;服务端没有报错信息打印&#xff0c;而是响应的状态码是400 PostMapping("/static/user") public User userInfo(RequestBody(required false) User user){user.setAge(19);return user; }PostMapping("…

05 Redis之Benchmark+简单动态字符串SDS+集合的底层实现

3.8 Benchmark Redis安装完毕后会自动安装一个redis-benchmark测试工具&#xff0c;其是一个压力测试工具&#xff0c;用于测试 Redis 的性能。 src目录下可找到该工具 通过 redis-benchmark –help 命令可以查看到其用法 3.8.1 测试1 3.9 简单动态字符串SDS 无论是 Redis …

【面试】测试开发面试题

帝王之气&#xff0c;定是你和万里江山&#xff0c;我都护得周全 文章目录 前言1. 网络原理get与post的区别TCP/IP各层是如何传输数据的IP头部包含哪些内容TCP头部为什么有浮动网络层协议1. 路由协议2. 路由信息3. OSPF与RIP的区别Cookie与Session&#xff0c;Token的区别http与…

Redis学习——高级篇①

Redis学习——高级篇① Redis7高级之单线程和多线程&#xff08;一&#xff09; 一、Redis单线程VS多线程1.Redis的单线程部分1.1 Redis为什么是单线程&#xff1f;1.2 Redis所谓的“单线程”1.3 Redis演进变化1.3.1 Redis 3.x 单线程时代性能很快的原因1.3.2…

林浩然科学趣谈:妙解麦克斯韦方程的电磁奥秘

林浩然科学趣谈&#xff1a;妙解麦克斯韦方程的电磁奥秘 Lin Haoran’s Scientific Banter: Playful Insights into the Electromagnetic Mysteries of Maxwell’s Equations 在科学的璀璨星河中&#xff0c;林浩然如同一颗热爱探索的行星&#xff0c;以其独特的幽默和严谨的态…

latent-diffusion model环境配置--我转载的

latent-diffusion model环境配置&#xff0c;这可能是你能够找到的最细的博客了_latent diffusion model 训练 autoencoder-CSDN博客 前言 最近在研究diffusion模型&#xff0c;并对目前最火的stable-diffusion模型很感兴趣&#xff0c;又因为stable-diffusion是一种latent-di…

opencv#35 连通域分析

连通域分割原理 像素领域介绍: 4邻域是指中心的像素与它邻近的上下左右一共有4个像素&#xff0c;那么称这4个像素为中心像素的4邻域。 8邻域是以中心像素周围的8个像素分别是上下左右和对角线上的4个像素。 连通域的定义(分割)分为两种:以4邻域为相邻判定条件的连通域分割和…

C++笔记之RTTI、RAII、MVC、MVVM、SOLID在C++中的表现

C++笔记之RTTI、RAII、MVC、MVVM、SOLID在C++中的表现 —— 杭州 2024-01-28 code review! 文章目录 C++笔记之RTTI、RAII、MVC、MVVM、SOLID在C++中的表现1.RTTI、RAII、MVC、MVVM、SOLID简述2.RAII (Resource Acquisition Is Initialization)3.RTTI (Run-Time Type Informat…

steam幻兽帕鲁服务器配置费用报价,4核16G

幻兽帕鲁服务器价格多少钱&#xff1f;4核16G服务器Palworld官方推荐配置&#xff0c;阿里云4核16G服务器32元1个月、96元3个月&#xff0c;腾讯云换手帕服务器服务器4核16G14M带宽66元一个月、277元3个月&#xff0c;8核32G22M配置115元1个月、345元3个月&#xff0c;16核64G3…

某度Pan复活,突破限速,很强大!

软件简介&#xff1a; 软件【下载地址】获取方式见文末。注&#xff1a;推荐使用&#xff0c;更贴合此安装方法&#xff01; 作为国内领先的云存储服务提供商之一&#xff0c;某度Pan为用户提供了一个便捷的文件存储和分享平台。然而&#xff0c;用户普遍反映某度Pan的下载速…

简盒工具箱iapp源码

一款工具箱兼做软件库。 新增远程更新功能 修复了部分失效功能 修复了偶尔会卡在启动页的情况 源码下载&#xff1a;https://download.csdn.net/download/m0_66047725/88776737 更多资源下载&#xff1a;关注我。

漏洞原理MySql注入 Windows中Sqlmap 工具的使用

漏洞原理MySql注入 SQLmap是一款开源的自动化SQL注入工具&#xff0c;用于检测和利用Web应用程序中的SQL注入漏洞。以下是SQLmap工具的使用总结&#xff1a; 安装和配置&#xff1a;首先需要下载并安装SQLmap工具。安装完成后&#xff0c;可以通过命令行界面或图形用户界面来使…

2024幻兽帕鲁服务器,阿里云配置

阿里云幻兽帕鲁服务器Palworld服务器推荐4核16G配置&#xff0c;可以选择通用型g7实例或通用算力型u1实例&#xff0c;ECS通用型g7实例4核16G配置价格是502.32元一个月&#xff0c;算力型u1实例4核16G是432.0元/月&#xff0c;经济型e实例是共享型云服务器&#xff0c;价格是32…

案例分享:长沙红胖子公司内部评估高清内窥镜功能列表流程产出成果鉴赏

若该文为原创文章&#xff0c;转载请注明出处 本文章博客地址&#xff1a;https://hpzwl.blog.csdn.net/article/details/135898723 红胖子(红模仿)的博文大全&#xff1a;开发技术集合&#xff08;包含Qt实用技术、树莓派、三维、OpenCV、OpenGL、ffmpeg、OSG、单片机、软硬结…

多地多活与单元化架构

多地多活与单元化架构 背景 在业务发展到一定阶段之后&#xff0c;任何因故障而导致的服务中断都会带来巨大的损失。为了提高系统的伸缩能力与高可用能力&#xff0c;我们都不断的在努力消除系统单点瓶颈。如使用应用集群是为了解决服务层的单点问题&#xff0c;使用主从数据…

FreeRTOS任务知识详解

前言 本篇文章旨在记录我学习FreeRTOS实时操作系统中&#xff0c;有关于Free RTOS的任务知识的记录。由于RTOS系统的核心就是任务管理&#xff0c;而且我们大多数人学习RTOS的初衷就是为了使用RTOS的多任务处理功能&#xff01; 初步上手RTOS首先应该掌握的就是任务的创建、删除…

AlmaLinux上安装Docker

AlmaLinux上安装Docker 文章目录 AlmaLinux上安装Docker一、前言二、具体步骤1、Docker 下载更新系统包索引&#xff1a;添加Docker仓库&#xff1a;安装Docker引擎&#xff1a; 2、Docker服务启动启动Docker服务&#xff1a;设置Docker开机自启&#xff1a; 3、Docker 安装验证…

GitLab16.8配置webhooks、Jenkins2.4配置GitLab插件实现持续集成、配置宝塔面板实现持续部署

看本篇文章的前提是已经部署完GItlab和Jenkins服务器&#xff0c;已经可以手动构建成功&#xff0c;并且经过了很多次实践&#xff0c;对这两款软件基本熟悉。 建议大家按以下顺序看 前端自动化&#xff08;其一&#xff09;部署gitlab https://blog.csdn.net/weixin_45062076…

数据中心代理IP:最优性价比业务应用指南

数据中心代理IP在应对高速高并发的业务时&#xff0c;以独特的高速传输&#xff0c;游刃有余地应对多任务处理&#xff0c;适合于特定业务场景的高效加速。理性选用数据中心代理IP&#xff0c;可以为业务将迎来更加稳健和迅速的发展。今天&#xff0c;我们将揭示数据中心代理IP…

QT+VS实现Kmeans++

1、Kmeans的原理如下&#xff1a; &#xff08;1&#xff09;首先选取样本中任一数据点作为第一个聚类中心&#xff1b; &#xff08;2&#xff09;计算样本每一个数据点至现所有聚类中心的最近距离&#xff0c;并记录下来&#xff1b; &#xff08;3&#xff09;逐一挑选所…