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

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

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

    补充pdftk介绍信息

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

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

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

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

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

    这个词典包含五个条目:

    /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来填写细节。例如,我们手动编写交叉引用表是不切实际的。

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

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

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

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

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

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

    文件头

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

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

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

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

      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. >>
      7. 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 条目到流字典。

      页面上的图形运算符流的结果如图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

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

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

      我们可以使用免费的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标题中 - 这可确保文件被识别为二进制 (而不是文本),例如,通过FTP等文件传输程序。

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

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

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

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

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

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

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