第2章 - 构建一个简单的PDF

在本章中,我们将在文本编辑器中手动构建PDF内容。然后我们将使用 免费的pdftk程序将其转换为有效的PDF文件,并在PDF查看器中查看输出。

此示例以及本书中的所有PDF文件都可以从中这本书的网页下载。

我们将同时考虑很多新概念,所以不要担心, 如果看起来压倒一切 - 我们将在以后的章节中回到这一切。

补充pdftk介绍信息

基本PDF语法

PDF文件至少包含三种不同的语言:

  • document content文档内容,是在它们之间具有链接的多个对象,形成有向图。这些对象描述了文档的结构(页面,元数据,字体和资源)。
  • page content页面内容,描述了使用一系列操作符将文本和图形放在一个页面上。
  • file structure文件结构,包括header(文件头),trailer(文件尾)和交叉引用表,帮助程序找到并读取文件的内容。

Document Content

文档内容包括由以下元素构建的对象:

  • 名称,写为 /Name
  • 整数,如 50
  • 带括号的字符串,如 (The Quick Brown Fox)
  • 引用其他对象,如 2 0 R,对对象2的引用。
  • 对象的数组(有序集合),如 [50 30 /Fred],是一个包含三个项目的数组,按顺序:50, 30/Fred
  • 字典(从名称到对象的无序映射),如 <</Three 3 /Five 5>>,映射 /Three3/Five5
  • stream(流),它由字典和一些二进制数据组成。这些用于存储PDF图形运算符的流,以及其他二进制数据,如图像和字体。

例如,这是一个页面对象,它是一个包含许多项目的字典,每个与名称相关联:

  1. << /Type /Page
  2. /MediaBox [0 0 612 792]
  3. /Resources 3 0 R
  4. /Parent 1 0 R
  5. /Contents [4 0 R]
  6. >>

这个词典包含五个条目:

/Type /Page 名称 /Page 与字典键 /Type 相关联。

/MediaBox [0 0 612 792] 四个整数 [0 0 612 792] 的数组与字典键 /MediaBox 相关联。

/Resources 3 0 R 对象编号3与字典键 /Resources 相关联。

/Parent 1 0 R 对象编号1与字典键 /Parent 对象相关联。

/Contents [4 0 R] 间接引用 [4 0 R] 的单元素数组与字典键 /Contents 相关联。

Page Content

页面内容是运算符列表,每个运算符前面都有零个或多个 操作数。这是一系列操作符,用于在36号字体选择/F0字体并放置 当前位置的文字:

  1. /F0 36.0 Tf
  2. (Hello, World!) Tj

这里,TfTj 是运算符,而 /F0, 36.0(Hello, World!) 是操作数。 你可以看到一些语法元素(例如,名称和字符串)是共享的跨页面内 容和文档内容使用的语言。

文件结构

文件结构包括:

  • 用于将文件区分为PDF文档的header(文件头)。
  • 一个交叉引用表,列出了文档中每个对象的字节偏移量 - 这个 允许任意访问对象,而不是必须按顺序读取。
  • trailer(文件尾),包括交叉引用表的字节偏移,后跟文件结束标记。

在编写我们的示例文件时,我们将对许多文件结构使用不完整的值, 依靠pdftk来填写细节。例如,我们手动编写交叉引用表是不切实际的。

Document 结构

我们将要构建的示例只是最简单有意义的PDF文件。然而, 它需要大量的元素。除了我们的文件结构 如上所述,最小的PDF文档必须包含许多基本部分:

  • trailer字典,提供有关如何阅读其余内容的信息文件中的对象
  • 文档目录,它是对象图的根。
  • 页面树,它枚举文档中的页面。
  • 至少有一页。每个页面必须具有:
      • resources(资源),包括例如字体。
      • 其页面内容,其中包含绘制文本和图形的说明在页面上。

这种安排如图2-1所示。

第2章 - 构建一个简单的PDF - 图1

构建元素

我们将PDF数据输入到文本文件中。 文本编辑器选择的行结尾并不重要([Unix和Mac OS X]和

[Microsoft Windows]都很好)。 我们将跳过一些信息(手动难以解决的数据),依靠pdftk来填充它。我们会:

  • 使用简短的header。
  • 跳过了页面内容流的长度,因此我们不必手动计数字节数。
  • 省略几乎所有的交叉引用表
  • 使用0表示交叉引用表的字节偏移量,以避免必须计数它手动。

首先,我们将查看文件的各个部分(按照它们出现的顺序)然后查看 我们将它们放在一起并运行pdftk来制作有效的PDF文件。

文件头

文件头通常由两行组成。第一行将文件标识为PDF和 给出它的版本号:

  1. %PDF-1.0 % PDF 版本号为 1.0 的文件头

第二行很难输入文本编辑器,因为它包含不可打印的字符。 我们将有pdftk为我们这样做。

主要对象

到文件的主体-对象。第一个是Page列表,它是链接到文档中页面对象的字典。

  1. 1 0 obj % 对象1
  2. << /Type /Pages % 这是一个页面列表
  3. /Count 1 % 只有一页
  4. /Kids [2 0 R] % 页面对象编号列表。这里只是对象2
  5. >>
  6. endobj % 对象1结束

接下来是页面。再次,它是一个字典。它包含纸张大小,间接 引用返回页面列表,以及图形内容和资源。

  1. 2 0 obj
  2. << /Type /Page % 这是一个页面
  3. /MediaBox [0 0 612 792] % 纸张尺寸为美国信肖像(612x792点)
  4. /Resources 3 0 R % 对象3的资源引用
  5. /Parent 1 0 R % 引用备份到父页面列表
  6. /Contents [4 0 R] % 图形内容在对象4
  7. >>
  8. endobj

现在,资源(resource)。在这里,只有一个条目,字体字典,在我们的 示例包含单个字体,我们将使用该字体在页面上写入一些文本。

  1. 3 0 obj
  2. << /Font % 字体字典
  3. << /F0 % 只有一种字体,称为/F0
  4. << /Type /Font % 这三行引用了内置字体Times Italic
  5. /BaseFont /Times-Italic
  6. /Subtype /Type1 >>
  7. >>
  8. >>
  9. endobj

图形内容

页面内容流包含用于放置文本和图形的一系列运算符 在页面上。它通过页面字典中的 /Contents 条目链接。

流对象由字典后跟原始数据流组成,包含一个 一系列PDF操作数和运算符。通常,这将被压缩以减少 文件大小,但我们手动输入,所以我们不压缩它。 我们还必须以字节为单位指定流的长度-pdftk将为我们添加所需的 /Length 条目到流字典。

  1. 4 0 obj % 页面内容流
  2. << >>
  3. stream % 流的开始
  4. 1. 0. 0. 1. 50. 700. cm % 位置在(50,700
  5. BT % 开始文本块
  6. /F0 36. Tf % 36pt选择/F0字体
  7. (Hello, World!) Tj % 放置文本字符串
  8. ET % 结束文本块
  9. endstream % 流结束
  10. endobj

页面上的图形运算符流的结果如图2-2所示。 第2章 - 构建一个简单的PDF - 图2

目录,交叉引用表和文件尾

文件的最后一部分以文档目录开头,该目录是文档目录的根对象 对象图。接下来是交叉引用表,它给出了字节偏移量 文件中的每个对象。我们将pdftk为我们填写此内容。最后两行:一行 给出交叉引用表开始的字节偏移量(我们写0和pdftk将 替换它为我们)。最后,文件结束标记 %%EOF

  1. 5 0 obj
  2. << /Type /Catalog % 文件目录
  3. /Pages 1 0 R % 参考页面列表
  4. >>
  5. endobj
  6. xref % 我们跳过了交叉引用表的开始
  7. 0 6
  8. trailer
  9. << /Size 6 % 交叉引用表中的行数(对象数加1
  10. /Root 5 0 R % 参考文档目录
  11. >>
  12. startxref
  13. 0 % xref表开始的字节偏移量,我们将其设置为0
  14. %%EOF % 文件结束标记

现在我们准备将这些部分放在一起了

把它放在一起

可以在此在线资源中找到书此文件的源(示例2-1), 或者你可以自己输入。将其保存为hello-broken.pdf

例2-1。无效的hello-broken.pdf PDF文件适合手动创建

  1. %PDF-1.0 % 文件header
  2. 1 0 obj % 主要对象
  3. << /Type /Pages
  4. /Count 1
  5. /Kids [2 0 R]
  6. >>
  7. endobj
  8. 2 0 obj
  9. << /Type /Page
  10. /MediaBox [0 0 612 792]
  11. /Resources 3 0 R
  12. /Parent 1 0 R
  13. /Contents [4 0 R]
  14. >>
  15. endobj
  16. 3 0 obj
  17. << /Font
  18. << /F0
  19. << /Type /Font
  20. /BaseFont /Times-Italic
  21. /Subtype /Type1 >>
  22. >>
  23. >>
  24. endobj
  25. 4 0 obj % 图形内容
  26. << >>
  27. stream
  28. 1. 0. 0. 1. 50. 700. cm
  29. BT
  30. /F0 36. Tf
  31. (Hello, World!) Tj
  32. ET
  33. endstream
  34. endobj
  35. 5 0 obj % 目录,交叉引用表和trailer
  36. << /Type /Catalog
  37. /Pages 1 0 R
  38. >>
  39. endobj
  40. xref
  41. 0 6
  42. trailer
  43. << /Size 6
  44. /Root 5 0 R
  45. >>
  46. startxref
  47. 0
  48. %%EOF

就目前而言,hello-broken.pdf不是有效的PDF文件,甚至也不是Adobe Reader(其中 相当容忍格式错误的文件)将无法应对。

译注: Adobe Acrobat 2018 已经可以直接打开 hello-broken.pdf 文件。 打开后关闭时,会提示是否需要保存。查看保存的 PDF 发现使用 PDF 1.6 规范并且已经线性化。

开源的 SumatraPDF v3.2 也可以直接打开。

我们可以使用免费的pdftk工具来修复hello-broken.pdf文件,其中包含缺少的细节, 将输出写入hello.pdf

pdftk hello-broken.pdf output hello.pdf

pdftk读取文件及其对象,并为缺失或计算正确的数据 我们写的错误部分,并生成示例2-2中显示的有效文件。注意 一些语法的间距和格式已经改变 - 每个PDF制做人对此有不同的选择。

例2-2。完成的PDF文件hello.pdf,由pdftk修复

  1. %PDF-1.0
  2. %âãÏÓ %
  3. 1 0 obj
  4. <<
  5. /Kids [2 0 R]
  6. /Count 1
  7. /Type /Pages
  8. >>
  9. endobj
  10. 2 0 obj
  11. <<
  12. /Rotate 0
  13. /Parent 1 0 R
  14. /Resources 3 0 R
  15. /MediaBox [0 0 612 792]
  16. /Contents [4 0 R]
  17. /Type /Page
  18. >>
  19. endobj
  20. 3 0 obj
  21. <<
  22. /Font
  23. <<
  24. /F0
  25. <<
  26. /BaseFont /Times-Italic
  27. /Subtype /Type1
  28. /Type /Font
  29. >>
  30. >>
  31. >>
  32. endobj
  33. 4 0 obj
  34. <<
  35. /Length 65 %
  36. >>
  37. stream
  38. 1. 0. 0. 1. 50. 700. cm
  39. BT
  40. /F0 36. Tf
  41. (Hello, World!) Tj
  42. ET
  43. endstream
  44. endobj
  45. 5 0 obj
  46. <<
  47. /Pages 1 0 R
  48. /Type /Catalog
  49. >>
  50. endobj xref
  51. 0 6 %
  52. 0000000000 65535 f
  53. 0000000015 00000 n
  54. 0000000074 00000 n
  55. 0000000192 00000 n
  56. 0000000291 00000 n
  57. 0000000409 00000 n
  58. trailer
  59. <<
  60. /Root 5 0 R
  61. /Size 6
  62. >>
  63. startxref
  64. 459 %
  65. %%EOF

1: 一些不可打印的字符已添加到PDF标题中 - 这可确保文件被识别为二进制 (而不是文本),例如,通过FTP等文件传输程序。

2: 已填写流的字节长度。

3: 交叉引用表已填入了每个对象的字节偏移量文件。

4: 已填写交叉引用表开头的字节偏移量。

该文件现在可以加载到PDF查看器中。Microsoft Windows上的Acrobat Reader的结果如图2-3所示。

第2章 - 构建一个简单的PDF - 图3

备注

我们已经看到了如何从头开始构建一个简单的PDF文件,使用pdftk来帮助我们,以及 我们已经看了一些构成PDF文档的基本语法。

你也可以使用文本编辑器查看现有的PDF文件。但是,有些 数据(例如构成页面内容的图形运算符)很可能被压缩,因此不可读。 pdftk命令可用于解压缩这些 部分以便于阅读 - 请参阅第114页的“压缩”。

在以后的章节中,我们将详细介绍典型PDF文件的各个部分以及如何进行 程序读取,写入和编辑PDF文件。在每个阶段,都有机会 通过改变和扩展我们在本章中构建的示例来构建示例文件。

目录 |上一章:介绍下一章:文件结构