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文档需要在文件体中包含下列元素对象:

  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.mzph.cn/news/653262.shtml

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

相关文章

Day01-变量和数据类型课后练习-参考答案

文章目录 1、输出你最想说的一句话&#xff01;2、定义所有基本数据类型的变量和字符串变量3、用合适类型的变量存储个人信息并输出4、定义圆周率PI5、简答题 1、输出你最想说的一句话&#xff01; 编写步骤&#xff1a; 定义类 Homework1&#xff0c;例如&#xff1a;Homewo…

测试C#调用OpenCvSharp和IronOcr从摄像头中识别文字

学习了基于OpenCvSharp获取摄像头数据&#xff0c;同时学习了基于IronOcr的文字识别用法&#xff0c;将这两者结合即是从摄像头中识别文字。本文测试C#调用OpenCvSharp和IronOcr从摄像头中识别文字的基本用法、。   新版Winform项目&#xff0c;在Nuget包管理器中添加以下程序…

案例分析技巧-软件工程

一、考试情况 需求分析&#xff08;※※※※&#xff09;面向对象设计&#xff08;※※&#xff09; 二、结构化需求分析 数据流图 数据流图的平衡原则 数据流图的答题技巧 利用数据平衡原则&#xff0c;比如顶层图的输入输出应与0层图一致补充实体 人物角色&#xff1a;客户、…

告别繁琐!轻松创建旧版Spring Boot项目!

推荐文章 给软件行业带来了春天——揭秘Spring究竟是何方神圣&#xff08;一&#xff09; 给软件行业带来了春天——揭秘Spring究竟是何方神圣&#xff08;二&#xff09; 给软件行业带来了春天——揭秘Spring究竟是何方神圣&#xff08;三&#xff09; 给软件行业带来了春天—…

[Python图像处理] 使用OpenCV创建深度图

使用OpenCV创建深度图 双目视觉创建深度图相关链接双目视觉 在传统的立体视觉中,两个摄像机彼此水平移动,用于获得场景上的两个不同视图(作为立体图像),就像人类的双目视觉系统: 通过比较这两个图像,可以以视差的形式获得相对深度信息,该视差编码对应图像点的水平坐标的…

基于Python 网络爬虫和可视化的房源信息的设计与实现

摘 要 一般来说&#xff0c;在房地产行业&#xff0c;房源信息采集&#xff0c;对企业来说至关重要&#xff0c;通过人工采集数据的方式进行数据收集&#xff0c;既耗时又费力&#xff0c;影响工作效率&#xff0c;还导致信息时效性变差&#xff0c;可靠性偏低&#xff0c;不利…

QWT开源库使用

源代码地址&#xff1a;Qwt Users Guide: Qwt - Qt Widgets for Technical Applications Qwt库包含GUI组件和实用程序类&#xff0c;它们主要用于具有技术背景的程序。除了2D图的框架外&#xff0c;它还提供刻度&#xff0c;滑块&#xff0c;刻度盘&#xff0c;指南针&#xf…

matlab appdesigner系列-仪器仪表4-旋钮(离散)

旋钮&#xff08;离散&#xff09;&#xff0c;或叫分档旋钮&#xff0c;跟旋钮的连续性相区别&#xff0c;呈分档性。 示例&#xff1a;模拟空调档位切换 操作步骤&#xff1a; 1&#xff09;将旋钮&#xff08;离散&#xff09;、信号灯、标签拖拽到画布上&#xff0c;并设…

CSS 星空按钮

<template><button class="btn" type="button"><strong>星空按钮</strong><div id="container-stars"><div id="stars"></div></div><div id="glow"><div class=…

Kafka-服务端-GroupMetadataManager

GroupMetadataManager是GroupCoordinator中负责管理Consumer Group元数据以及其对应offset信息的组件。 GroupMetadataManager底层使用Offsets Topic,以消息的形式存储Consumer Group的GroupMetadata信息以及其消费的每个分区的offset,如图所示。 consumer_offsets的某Partiti…

每日一题——LeetCode1365.有多少小于当前数字的数字

方法一 暴力循环 对于数组里的没一个元素都遍历一遍看有多少元素小于当前元素 var smallerNumbersThanCurrent function(nums) {let n nums.length;let ret [];for (let i 0; i < n; i) {let count 0;for (let j 0; j < n; j) {if (nums[j] < nums[i]) {count…

菜谱的未来:SpringBoot, Vue与MySQL的智能推荐系统设计

✍✍计算机编程指导师 ⭐⭐个人介绍&#xff1a;自己非常喜欢研究技术问题&#xff01;专业做Java、Python、微信小程序、安卓、大数据、爬虫、Golang、大屏等实战项目。 ⛽⛽实战项目&#xff1a;有源码或者技术上的问题欢迎在评论区一起讨论交流&#xff01; ⚡⚡ Java实战 |…

算法学习记录:动态规划

前言&#xff1a; 算法学习记录不是算法介绍&#xff0c;本文记录的是从零开始的学习过程&#xff08;见到的例题&#xff0c;代码的理解……&#xff09;&#xff0c;所有内容按学习顺序更新&#xff0c;而且不保证正确&#xff0c;如有错误&#xff0c;请帮助指出。 学习工具…

PINN物理信息网络 | 全局自适应物理信息神经网络SA-PINN

概述 本文提出的自适应加权方法在于权重适用于不同损失组件中的个别训练点,而不是整个损失组件。之前的方法可以被看作是这个方法的一个特例,当所有针对特定损失组件的自适应权重同时更新时。在之前的方法中,独立开发的极小极大加权方案[16]与SA-PINNs最为相近,因为它也通过…

JavaScript浅拷贝和深拷贝

浅拷贝和深拷贝的区别 浅拷贝let a 10;let ba;a20console.log(b)//10 1&#xff0c;由于a和b基本类型并且都是在栈中的&#xff0c;它们分别进行保存&#xff0c;所以这里输出的b还是102&#xff0c;通过内存可以看出&#xff0c;它们的两个值是独立的&#xff0c;更改其中一…

CMake 完整入门教程(五)

CMake 使用实例 13.1 例子一 一个经典的 C 程序&#xff0c;如何用 cmake 来进行构建程序呢&#xff1f; //main.c #include <stdio.h> int main() { printf("Hello World!/n"); return 0; } 编写一个 CMakeList.txt 文件 ( 可看做 cmake 的…

深度学习之多分类问题

多分类问题&#xff1a; 我们在解决的时候会使用到一种叫做SoftMax的分类器。 前面我们在做糖尿病问题的时候&#xff0c;我们做出一个二分类网络&#xff0c;我们得到的是y1&#xff08;即一年后发病&#xff09;它的概率是多少&#xff0c;即P&#xff08;y1&#xff09;。这…

【Linux C | 网络编程】详细介绍 “三次握手(建立连接)、四次挥手(终止连接)、TCP状态”

&#x1f601;博客主页&#x1f601;&#xff1a;&#x1f680;https://blog.csdn.net/wkd_007&#x1f680; &#x1f911;博客内容&#x1f911;&#xff1a;&#x1f36d;嵌入式开发、Linux、C语言、C、数据结构、音视频&#x1f36d; &#x1f923;本文内容&#x1f923;&a…

JVM篇:垃圾回收算法

标记清除 通过遍历GC Root后得到不再被引用的对象&#xff0c;对没被引用的对象做一个标记处理&#xff0c;然后对其进行清除。 优点&#xff1a;速度快 缺点&#xff1a;会产生内存碎片&#xff0c;可能会导致空闲的内存足够保存对象&#xff0c;但由于不连续而保存失败。 标…

R语言【taxlist】——print_name():为发表文章准备可用的格式化名称

Package taxlist version 0.2.4 Description 在撰写关于生物多样性的文章时&#xff0c;可以使用名称自动插入到文件中&#xff0c;包括学名不同元素的典型斜体格式。函数print_name既可以应用于 Markdown 文档&#xff0c;也可以应用于图形。在 Rmarkdown 文档中&#xff0c;…