ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

使用 VS Code 徒手构建 PDF 文件

2022-03-05 19:32:03  阅读:264  来源: 互联网

标签:文件 Code obj 对象 VS 字体 PDF 页面


使用 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 扩展,它可以支持我们以十六进制格式打开文件,这样,我们可以很容易查到每个对象的起始字节值。然后,你可以切换回到文件格式,将这些值填入预留的地方。

参考资料:

标签:文件,Code,obj,对象,VS,字体,PDF,页面
来源: https://www.cnblogs.com/haogj/p/15969054.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有