写在最前面:
gnupdf 已经不存在了,不过这篇文章对 pdf 结构的介绍还是很好的。
原链接: https://web.archive.org/web/20141010035745/http://gnupdf.org/Introduction_to_PDF
PDF,Portable Format Document,可移植格式文档。这种格式意味着在所有平台和媒介(屏幕,打印机...)上显示完全一致的内容。
我们从一个非常简单的 "Hello,world!" PDF 开始。你可以复制/粘贴到文本编辑器中(换行用 "LF"),将其另存为hello.pdf,或者从这里下载:File:Hello.pdf。使用 xpdf 或 Evince 等 PDF 阅读器 打开它:
%PDF-1.7
1 0 obj % entry point
<<
/Type /Catalog
/Pages 2 0 R
>>
endobj
2 0 obj
<<
/Type /Pages
/MediaBox [ 0 0 200 200 ]
/Count 1
/Kids [ 3 0 R ]
>>
endobj
3 0 obj
<<
/Type /Page
/Parent 2 0 R
/Resources <<
/Font <<
/F1 4 0 R
>>
>>
/Contents 5 0 R
>>
endobj
4 0 obj
<<
/Type /Font
/Subtype /Type1
/BaseFont /Times-Roman
>>
endobj
5 0 obj % page content
<<
/Length 44
>>
stream
BT
70 50 TD
/F1 12 Tf
(Hello, world!) Tj
ET
endstream
endobj
xref
0 6
0000000000 65535 f
0000000010 00000 n
0000000079 00000 n
0000000173 00000 n
0000000301 00000 n
0000000380 00000 n
trailer
<<
/Size 6
/Root 1 0 R
>>
startxref
492
%%EOF
本 PDF 是手动编写的,出于介绍的目的进行了简化。实际的 PDF 通常更复杂。
练习 1:用 OpenOffice.org 写一个 "Hello, world!" 文档并将其导出为PDF。和 hello.pdf 比较。
注意:如果使用命令行工具 less 查看 PDF ,你可能需要添加 -L 选项(该选项将禁用默认运行 pdftotext 的输入预处理器,pdftotext 的处理会屏蔽原始文件内容)。
如我们所见,该文档具有基于文本的常规结构,这样研究起来是很方便的。不过,PDF 文件通常会包含非 ASCII("二进制")数据,一般都应该把PDF当做二进制文件。
一个简单的 PDF 包含 4 个部分:
%PDF-1.7
1 0 obj
...
endobj
2 0 obj
...
endobj
...
xref
0 6
0000000000 65535 f
0000000010 00000 n
0000000079 00000 n
0000000173 00000 n
0000000301 00000 n
0000000380 00000 n
trailer
<<
/Size 6
/Root 1 0 R
>>
startxref
492
%%EOF
在完整的文档结构中,可以附加其他 body + cross-reference + trailer 元素来完善现有文档,但是在我们的示例中不会看到这种情况。
在主体(对象列表)中,我们可以看到各种定义:
有9种类型的对象:
还有在此示例中未使用的:
表现和组织这些对象构成了 GNUpdf 库的对象层。
xref
0 6
0000000000 65535 f
0000000010 00000 n
0000000079 00000 n
0000000173 00000 n
0000000301 00000 n
0000000380 00000 n
交叉引用表就是对象的顺序列表(#2,#3,#4 ...),更确切地说是对象偏移量(距文件开头的字节位置)。交叉引用表允许按其编号轻松快速地访问任何给定对象。而 HTML 恰好相反,HTML 纯粹是顺序的并且不能很好地处理大型文档。
前两个数字表示“我将引入6个对象偏移,从0开始计数”。
每行包含对象定义的偏移量,修订号(此处未使用)和开/关标记 f(free,空闲)或 n(in use,正在使用)。
现在我们可以先忽略第一个偏移量。
解释下面的含义:
如果你修改了测试文档,要记住更新所有这些偏移量以及描述 xref 偏移量的 startxref 行。
交叉引用表还可以包含更复杂的声明,我们将在后面介绍。
% page content
注释以百分比(%)字符开头,并在下一个换行符处结束。从效果来讲,注释等效于空格字符,并且不包括结尾的换行符。
PDF阅读器不会按顺序(从上到下)分析文档,而是以更复杂的方式访问文件:
版本
%%EOF
标记,再向上一行读取 492,这是交叉引用表的 偏移量
(上一部分介绍过)。现在你就会明白为什么通常无法阅读尚未下载完成的大 PDF。当 trailer 缺失或损坏时,某些阅读器会尝试通过扫描整个文件来重建索引,但这要慢得多。对象偏移量列表
。Catalog
(意思是目录),即文档的开始。它是通过一个指向对象 1 的间接引用:1 0 R 来指定的。到目前为止,阅读器可以随机(非顺序)访问 PDF 文件,并获取文档的整体结构。
Catalog
对象。在我们的例子里,它仅包含对 Pages
对象(编号2)的引用。树状
数据结构。它是一个节点,可以引用叶子(pages)或其他节点(它们本身可以引用叶子和节点)。在我们的例子里,Pages 对象仅引用了 1 个 Page(/Count 1),该页面在 Kids 列表中指定,包含对对象 3:3 0 R 的间接引用。Pages 对象还定义了存放子对象的介质的大小,该大小由 Page(叶)对象继承,可以重新定义。在这里,我们定义了一个 200x200 的小盒子。Page
对象引用其父对象(/Parent 2 0 R),呈现页面所需的一组资源(此处是字体,对象 4)及其实际内容(对象 5)。流
对象,其中包含呈现页面的指令。这些指令与本文档的其余部分有很大不同,完全可以视为另一种语言。在BT 和 ET 之间,指令描述了 3 个操作。它们使用了后缀运算符,这是 PDF 的前身 PostScript 的缩影。
大功告成!
我们例子中的 流
直接表示为明文。实际上这并不常见,大多数流都经过压缩,其内容(在 stream 和 endstream 之间)是二进制数据。
本部分的目标是将 PDF 中的纯文本流替换为压缩流,可以在这里查看结果: File:Hello-stream.pdf。
要编码或解码流,可以使用 GNUpdf 的 pdf-filter 组件。
尝试用一下 FlateDecode 过滤器:
# Encode
$ ./pdf-filter --flateenc <<EOF > filtered.bin
BT
70 50 TD
/F1 12 Tf
(Hello, world!) Tj
ET
EOF
# Check size:
$ ls -l filtered.bin
-rw-r--r-- 1 me me 52 Jan 26 23:59 filtered.bin
# It‘s binary:
$ file filtered.bin
filtered.bin: data
# Decode it back:
./pdf-filter --flatedec < filtered.bin
BT
70 50 TD
/F1 12 Tf
(Hello, world!) Tj
ET
注意:如果从现有的 PDF 复制/粘贴流以进行解码,请确保没有编码问题。验证一下你提取的流文件(上面的 filtered.bin)具有与 /Length 中指定的大小相同的大小。如果你使用 Emacs,请尝试在缓冲区之间进行复制/粘贴之前尝试使用 raw-text 编码(使用 M-x set-buffer-file-coding-system 或 C-x RET f)。
这是对象 5 的新版本("Hello, world!" 的文本显示):
5 0 obj % page content
<<
/Length 52
/Filter /FlateDecode
>>
stream
x?s
á27P05P?qáòw3T04R?I?òeHíéé×Q(?/êIQ?T?éar
á?á·
á
endstream
endobj
由于我们要在文件中引入非 ASCII 内容,因此 PDF 格式要求我们添加至少4个以注释符开头的二进制字符(ASCII值 >= 128),以便通用工具可以将其检测为二进制文件:
%PDF-1.7
%éééé
最好使用 [128, 159] 中的字符,这些字符不是 ASCII 或 Latin-1 的一部分。不过,这不一定奏效,因为 diff(1)和 mercurial 之类的程序仅在文件包含 NUL(0)字符时才将其视为二进制文件。
我们还需要更新交叉引用表和 startxref 中的偏移量:
xref
0 6
0000000000 65535 f
0000000016 00000 n
0000000085 00000 n
0000000179 00000 n
0000000307 00000 n
0000000386 00000 n
trailer
<<
/Size 6
/Root 1 0 R
>>
startxref
530
%%EOF
注意:要快速确定 Emacs 下文件中某个位置的偏移量,可以从文档的开头选择到该位置,使用 M-=(count-lines-region)命令 查看 "characters" 状态即可:
Region has 52 lines, 530 characters
这里有更多的过滤器:
如果要研究现有的PDF,可以使用 pdftk 工具的 uncompress 命令将所有压缩的流转换为明文:
pdftk hello-stream.pdf output hello-clear.pdf uncompress
PDF 最初是 Adobe 的专有格式,但自 2008 年以来,就成了 ISO-32000 标准(更确切地说是 ISO 32000-1:2008)。
你可以从 ISO 花费 380 瑞士法郎(约250欧元)获得电子副本。Adobe发布的副本虽然不是官方的,但具有相同的技术内容和页码。
要下载的是 "Document management – Portable document format – Part 1: PDF 1.7, First Edition (July, 2008)"。
请注意,ISO-32000 是非免费文档,请勿在此 Wiki 上转载相关内容。位于 gnupdf.org 的 PDF知识 部分,旨在提供免费的 PDF 格式文档,你可以阅读,修改,共享和重新分发。
你还可以看到一些扩展文档:这些是不属于 ISO-32000 的 PDF 特性,可能在该格式的下一个修订版中提出,也可能不会。保持 PDF 的开放标准是一场长期的战斗。
原文:https://www.cnblogs.com/-rvy-/p/13096576.html