使用 VS Code 徒手构建 PDF 文件

PDF 文件是广泛应用的页面描述文件格式,从本质上讲,文件内部的结构混合使用了文本格式描述和二进制格式描述,对于简单的文件,比如说我们今天要创建的第一个 PDF 文件来说,里面只包含一个 Hello, world 的字符串,其中主要的内容就可以使用文件来描述,只有很少的一部分需要特殊处理。借助于强大的的 VS Code,我们完全可以手工将这个简单的文件构建出来。

1. PDF 文件结构

从整体上讲,PDF 文件基本上可以分成 3 个顺序组成的部分:

  1. 头部
  2. 内容
  3. 尾部

1.1 头部

PDF 文件的头部从文件的字节 0 位置开始,至少包含 8 个字节,以及跟随的行结束符号。

PDF 文件的第一行为文件类型说明和该文件使用的 PDF 标准的版本号。开头的 5 个字符为:%PDF-。后面跟上版本号,目前一般使用 1.5 版本。这样第一行的内容就是一如下的文本:

%PDF-1.5

通常,PDF 文件内部不会只有文本内容,例如内部可能嵌入了图片,或者字体等等二进制内容。为了防止被误认为这是一个纯文本文件,那么文件的第二行就需要存在。第二行的第一个字符是 %,这是 PDF 中的注释符号,随后是至少 4 个字节的字符编码大于 127 的 ASCII 符号,尽管可以是符合的任意字符,通常使用的是二进制表示的 (0xE2,0xE3,0xCF,0xD3) 这 4 个字符,随后也有一个行结束符号。

1.2 尾部

PDF 文件的尾部使用 trailer 开始。直到最行一行的 %%EOF 最终结束。

trailer
<<
/Size 7
/Root 6 0 R
>>
startxref
478
%%EOF

这两行中间的内容又分为 2 个部分:

  • 尾部字典
  • 交叉索引表位置

1.2.1 尾部字典

在 PDF 内部,定义了用来描述各种数据结构的定义机制,为了方便,我们并不枯燥地介绍这些数据结构,而是按照我们遇到顺序来逐渐介绍它们。

这里我们遇到的第一个数据结构是字典。在 PDF 语法中,字典使用一对双尖括号包围起来。所以我们看到的从 << 到 >> 这一部分,实际上表示来一个字典结构。

在字典结构中,其构成元素是 key 与 value 对。在 PDF 定义中,每行表示一个 key/value 对,其中使用空格进行分隔,所以第一行中的 /Size 就是其中的一个 key,而数字 7 则是其值。第二行中的 /Root 则为其 key,而剩下的就是其值。

这里我们需要学到 2 种新的 PDF 数据类型:

  • Name 名称对象
  • 数值对象

在 PDF 定义中,名称对象表示一个唯一的名称,名称对象使用正斜线来引导,这就是 /Size 和 /Root 这两个名称前面的正斜线出现的原因。名称对象使用正斜线开始,后面是一个 UTF-8 的字符串。

在 PDF 中,已经预先定义了大量的名称对象,这里出现的 /Size 表示这个 PDF 文件中出现的对象数量。PDF 文件是使用对象树来描述的。/Root 表示其中的根对象是那个对象。这里的 6 0 R 表示根对象是在其它位置的一的 6 号对象。

1.2.2 交叉索引表的位置

上面已经提到了,PDF 文件是使用对象树来描述的,那么,这些对象在哪里可以找到呢?

交叉索引表就是该文件内部包含的所有 PDF 对象的索引,或者称为目录。所以,找到这些对象的第一步就是找到该索引表,这里的 478 表示可以从该文件的第 478 字节位置找到该交叉索引表。

1.3 内容部分

在头部和尾部之间的内容就是内容部分了。内容部分包含了 PDF 实际的详细定义。我们专门单列出来进行说明。

2. 内容部分

PDF 实际上是由一系列对象进行描述的,所以,在内容部分,就是一系列对象的定义。

作为入门,我们准备创建一个包含一个页面,页面上有一行 Hello, World 文本的 PDF 文件。我们就以此为例来说明涉及到的各种对象。

2.1 对象

在 PDF 内部,各种数据是通过对象来描述的。为了引用方便,PDF 内部使用唯一的数字编号来区分各个对象。描述形式如下:

1 0 obj

endobj

1 表示对象的编号,0 表示对象的版本号,一般不会使用,所以通常见到的就是 0 本身。最后的 obj 表示这是一个对象。这 3 个部分之间使用空格进行分隔。随后就是该对象的说明。

最后一行的 endobj 表示对象说明的结束,对象的说明我们随后就会看到。

2.2 字体对象

作为页面描述语言,我们不会不使用字体说明。字体描述也使用字典结构来描述。字典中的各个条目用来描述字体信息。

在 PDF 中支持 3 种类型的字体:

  • Type 1,这是 Adobe 原创的用于 Postscript 语言打印机的矢量字体格式。
  • TrueType,Windows 用户应该比较熟悉这种类型的字体,这是由 Apple 和 Microsoft 联合创建的矢量字体格式,
  • OpenType,尽管 Type1 和 TrueType 各有优势,却导致了字体标准的战争。OpenType 字体标准合并了上面两种,内部可能是 Type1 或者 TrueType。
  • Type 3,嵌入的点阵字体
  • Type 0,或者称为 CID 字体。对于像中文这样的字体来说,其中包含大量的字体,但是在 PDF 文件中却并不都会用到。可以从字库中抽取在该 PDF 文件中使用到的字体描述来构建一个字体的子集,以缩减文件的尺寸。

实际上,PDF 标准还定义了 14 种直接支持的字体,称为 Base14,它们可以直接在 PDF 文件中使用而不需要嵌入字体本身。

  • Times-Roman
  • Times-Bold
  • Times-Italic
  • Times-BoldItalic
  • Helvetica
  • Helvetica-Bold
  • Helvetica-Oblique
  • Helvetica-BoldOblique
  • Courier
  • Courier-Bold
  • Courier-Oblique
  • Courier-BoldOblique
  • Symbol
  • ZapfDingbats

下面就是字体描述的一个实例。

4 0 obj
<<
/Type /Font
/Subtype /Type1
/Name /F1
/BaseFont /Helvetica
>>
endobj

这个字体对象的编号是 4。它使用一个字典来进行描述,/Type 是预定义的名称,用来表示该对象的类型,这里是 /Font 字体类型的对象。

/Subtype 表示该字体的格式类型,值为 /Type1。

/BaseFont 表示使用的实际字体是 /Helvetica,从前面我们知道,这是一种预先定义,可以直接使用的字体。

/Name 表示重新命名该字体在 PDF 内部使用的名称,这里重新命名为 /F1。以后就可以使用 /F1 表示 /Helvetica 这种字体了。

2.3 资源对象

描述 PDF 中使用的资源,

3 0 obj
<<
/ProcSet [/PDF/Text]
/Font <</F1 4 0 R >>
>>
endobj

这里说明该资源是文本资源,资源中包含了前面定义的字体。

2.4 流对象

该介绍我们的文本 Hello, World 是如何描述了。

文本可以通过 stream 对象来描述。

流对象的开头是一个描述它的字典,/Length 表示该流对象的字节长度。

2 0 obj
<<
/Length 53
>>
stream
BT
/F1 24 Tf
1 0 0 1 260 600 Tm
(Hello World)Tj
ET
endstream
endobj

stream 和 endstream 表示流对象的开始与结束。

BT 的意思是:文本开始,即 Begin Text,当然 ET 就是文本结束,即 End Text。

其中的描述比较有意思,操作符在后面。

Tf 表示文本字体,/F1 24 Tf 表示使用 24 号的 /Helvetica 字体。

Tm 表示转换矩阵,1 0 0 1 260 600 Tm 表示将原点平移到 (260, 600)。

Tj 表示显示一个字符串,需要注意的是 PDF 中使用圆括号来表示字符串,而不是常见的双引号。

2.5 页面对象

万事俱备,我们可以创建一个页面了。

示例如下:

1 0 obj
<<
/Type /Page
/Parent 5 0 R
/MediaBox [ 0 0 612 792 ]
/Resources 3 0 R
/Contents 2 0 R
>>
endobj

页面也是使用字典来描述的,页面的类型是 /Type。

/Resources 表示页面使用的资源,该资源是前面的 3 号对象定义的。这里的 R 表示引用其它位置定义的对象。

/Contents 表示页面的内容,这里引用 2 号对象定义的字符串。

/MediaBox 比较重要,可以认为定义了页面的尺寸,

/Parent 表示该对象的父对象,我们马上就可以看到。

2.6 页面集

PDF 文件是多个页面所组成的,/Pages 对象表示页面的集合。

示例如下:

5 0 obj
<<
/Type /Pages
/Kids [ 1 0 R ]
/Count 1
>>
endobj

/Kids 表示其包含的子页面集合,数组是使用中括号来表示的。1 0 R 表示我们刚刚定义的 1 号页面对象。

2.7

目录对象的类型是 /Catalog。其中包含了页面集对象。

示例如下:

6 0 obj
<<
/Type /Catalog
/Pages 5 0 R
>>
endobj

这个 6 号对象就是我们在尾部看到的根对象。

2.8 交叉索引表

为了随机访问各个对象的便利,例如直接跳转到某个页面,我们需要一个包含每个对象位置的索引表,称为交叉索引表。

交叉索引表使用 xref 来开始。第二行由空格隔开的两个数字组成,第一个表示索引表中起始对象的编号,它总是 0,我们总是从编号 1 开始来进行定义,0 是特定的。第二个数字表示总共的对象数量,我们定义了 6 个对象,加上特殊的 0 号对象,合计为 7 个对象。

随后是相应数量的行,对象的编号从 0 开始递增。每行描述一个对象的起始字节数。虽然在定义对象的时候可以是无序的,但是,在交叉表中是按照编号依次排列的。

每行由 3 个部分组成:

  • 十进制的对象开始位置
  • 固定 00000
  • 对象是否被使用

其中第 1 行的内容是固定的。

示例如下:

xref
0 7
0000000000 65535 f
0000000072 00000 n
0000000257 00000 n
0000000416 00000 n
0000000459 00000 n
0000000357 00000 n
0000000023 00000 n

你需要检查每个对象以二进制字节计算的起始位置。

3 创建第一个 PDF 文件

将所有内容合在一起,就是一个 PDF 文件了。

%PDF-1.5
%����
6 0 obj
<<
/Type /Catalog
/Pages 5 0 R
>>
endobj
1 0 obj
<<
/Type /Page
/Parent 5 0 R
/MediaBox [ 0 0 612 792 ]
/Resources 3 0 R
/Contents 2 0 R
>>
endobj
4 0 obj
<<
/Type /Font
/Subtype /Type1
/Name /F1
/BaseFont /Helvetica
>>
endobj
2 0 obj
<<
/Length 53
>>
stream
BT
/F1 24 Tf
1 0 0 1 260 600 Tm
(Hello World)Tj
ET
endstream
endobj
5 0 obj
<<
/Type /Pages
/Kids [ 1 0 R ]
/Count 1
>>
endobj
3 0 obj
<<
/ProcSet [/PDF/Text]
/Font <</F1 4 0 R >>
>>
endobj
xref
0 7
0000000000 65535 f
0000000072 00000 n
0000000257 00000 n
0000000416 00000 n
0000000459 00000 n
0000000357 00000 n
0000000023 00000 n
trailer
<<
/Size 7
/Root 6 0 R
>>
startxref
478
%%EOF

需要注意的是交叉表中每个对象在 PDF 文件中的字节位置和交叉索引表本身在 PDF 文件中的字节位置。

你可以安装来自微软的 VS Code 扩展 HexEditor 扩展,它可以支持我们以十六进制格式打开文件,这样,我们可以很容易查到每个对象的起始字节值。然后,你可以切换回到文件格式,将这些值填入预留的地方。

参考资料:

使用 VS Code 徒手构建 PDF 文件的更多相关文章

  1. pdf文件内容查看器 -- 采用wpf开发

    前言 pdf是一种应用非常广的版式文档格式,已成为事实上的国际标准.关于pdf格式的文章汗牛充栋,本文也是关于pdf格式的文章,但是本文不是纸上谈兵:本人这几周一直研究pdf格式内容,不但对pfd格式 ...

  2. Pdf File Writer 中文应用(PDF文件编写器C#类库)

    该文由小居工作室(QQ:2482052910)    翻译并提供解答支持,原文地址:Pdf File Writer 中文应用(PDF文件编写器C#类库):http://www.cnblogs.com/ ...

  3. java操作office和pdf文件java读取word,excel和pdf文档内容

    在平常应用程序中,对office和pdf文档进行读取数据是比较常见的功能,尤其在很多web应用程序中.所以今天我们就简单来看一下Java对word.excel.pdf文件的读取.本篇博客只是讲解简单应 ...

  4. PDF 文件编写器 C# 类库(版本 1.28.0)使用详解

    PDF File Writer 是一个 C# .NET 类库,允许应用程序创建 PDF 文件. PDF File Writer C# 类库使 .NET 应用程序能够生成 PDF 文档.该库使应用程序免 ...

  5. 破解加密PDF文件pdfcrack

    破解加密PDF文件pdfcrack   PDF是常见的文档格式.它允许用户设置双重密码来保护文档.第一重是用户密码(user password),当打开PDF文档,输入该密码.第二重是所有者密码(ow ...

  6. itextpd f生成 pdf 文件

    一.简介 itextpdf 是一个开源的允许你去创建和操作PDF文档的库.它使的开发者可以提高web和其他应用来动态地生成或操作PDF文档.通过iText 中的Document和PdfWriter类, ...

  7. Android MuPDF 阅读PDF文件

    MuPDF是一款轻量级的开源软件,可以用来阅读PDF文件.下载完源代码以后,想要运行成功,除了Android SDK之外,还需要Android NDK环境,因此有点麻烦. 但是一旦安装完必须的环境以后 ...

  8. [轉載]史上最强php生成pdf文件,html转pdf文件方法

    之前有个客户需要把一些html页面生成pdf文件,然后我就找一些用php把html页面围成pdf文件的类.方法是可谓是找了很多很多,什么html2pdf,pdflib,FPDF这些都试过了,但是都没有 ...

  9. 用C#制作PDF文件全攻略

    用C#制作PDF文件全攻略 目  录 前    言... 3 第一部分 iText的简单应用... 4 第一章 创建一个Document 4 第一步 创建一个Document实例:... 5 第二步 ...

  10. 怎么用PHP在HTML中生成PDF文件

    原文:Generate PDF from html using PHP 译文:使用PHP在html中生成PDF 译者:dwqs 利用PHP编码生成PDF文件是一个非常耗时的工作.在早期,开发者使用PH ...

随机推荐

  1. Win10 LTSC 从 2019(1809) 升级到 2021(21H2) 后找回丢失的 WSL

    Win 10 LTST 2019 升级 2021 很简单,直接挂载 ISO 镜像以后,运行 setup.exe,剩下的就是耐心等待了. 升级完成后,用户信息和安装的软件基本上都在,VM15 启动的时候 ...

  2. 分析ueventd Coldboot耗时问题

    安卓go平台启动时间发现如下ueventd耗时1.907s问题: 01-11 00:20:02.854 0 0 I init : Parsing file /odm/etc/init... 01-11 ...

  3. Android Qcom USB Driver学习(四)

    VID/PID识别USB设备 CDC-ACM驱动介绍 CDC-ACM(Communication Device Class--Abstract Control Model)驱动实现以USB设备驱动和t ...

  4. HOG算法的笔记与python实现

    这两篇[1][2]博客写的都非常详细.这里做个笔记记录一下. HOG称为方向梯度直方图(Histogram of Oriented Gradient),主要是为了对图像进行特征提取.所以在传统目标检测 ...

  5. js中数据的基本类型

    有5种基本数据类型分类 : 1. 数字型  number 2. 字符型 string 3. 布尔型 boolean 4. undefined 未定义  就是声明了但是没有赋值 5. null 空指针 ...

  6. Awesome-Visual-Captioning

    目录 Table of Contents Paper Roadmap ACL-2021 CVPR-2021 AAAI-2021 ACMMM-2020 NeurIPS-2020 ECCV-2020 Vi ...

  7. grafana配置告警

    首先,进入grafana控制面板,选择需要监控指标的区域,然后点击编辑 此时进入Alert页面会发现提示 Template variables are not supported in alert q ...

  8. R语言经典统计分析

    经典统计分析包括了许多常用的统计方法和技术,用于数据的描述.推断和建模.本节将介绍经典统计分析方法(包括t检验.方差分析.卡方检验.线性回归)在R语言中的实现. 5.1.1  t检验 样本均值(sam ...

  9. PBA 商业分析师 考试心得

    2021年7月报名开始学习PBA,因为疫情,中间经历两次考试延期,虽然复习时间增多了,但是学习的节奏也被打乱.好在没有白努力,今天收到了邮件,5A通过考试.在这里整理学习经验,梳理一下自己的思路,也希 ...

  10. SpringBoot项目集成MinIO

    一.MinIO的下载安装以及基本使用 1.下载地址:https://dl.min.io/server/minio/release/windows-amd64/minio.exe 2.下载好后需要手动创 ...