技術文章

瞭解 PDF 檔案結構:技術概述

· PDF 結構

PDF檔案內部結構:完整解析

可移植文件格式(PDF)已成為文件交換和歸檔的實際標準。瞭解其內部結構對於開發人員、系統管理員以及所有參與文件處理工作流程的人員至關重要。本綜合指南深入探討PDF檔案的複雜佈局和內容,考察其四個主要部分以及構成每個元件的物件的詳細語法。

PDF檔案佈局:四個關鍵元件

每個有效的PDF檔案都遵循嚴格的架構模式,由四個主要部分組成,並按特定的順序排列。這些元件協同工作,建立了一種既結構化又非常適合隨機訪問的格式。

  1. 標題 (Header) – 標識PDF版本號和二進位制特性
  2. 正文 (Body) – 包含所有文件物件,包括頁面、字型、影像和圖形內容
  3. 交叉引用表 – 提供精確的位元組偏移量對映,用於隨機物件訪問
  4. 預告片。 包含重要的後設資料和導航指標。

剖析完整的PDF檔案: “Hello, World” 示例。

為了理解這些元件如何協同工作,讓我們來分析一個完整的、最小的PDF檔案,該檔案顯示“Hello, World!”文本。這個示例展示了PDF結構中的所有關鍵元素:

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

💡 理解PDF物件圖。

PDF物件形成一個有向圖結構,其中節點是PDF物件,連結是間接引用。這種圖表示允許高效地隨機訪問內容,而無需進行順序的檔案處理。文件目錄(物件5)作為根節點,連線到頁面樹(物件1),後者引用單個頁面及其資源。

頭部:版本控制和二進位制標識。

PDF頭部具有兩個關鍵功能,可確保在不同的系統和應用程式中正確處理檔案:

1
2
%PDF-1.0
%âãÏÓ

第一行指定PDF版本(在此示例中為1.0)。PDF具有出色的向後相容性,這意味著較新的閱讀器可以無縫處理較舊的版本。它在一定程度上也具有向前相容性,因為大多數PDF應用程式會嘗試讀取檔案,而不管其宣告的版本號。

第二行包含二進位制字元,其ASCII碼值高於127。這一點至關重要,因為PDF檔案幾乎總是包含二進位制資料,如果在檔案傳輸過程中修改了行尾,這些資料可能會損壞。這些高ASCII字元有助於舊的檔案傳輸程式將檔案識別為二進位制檔案,從而防止自動進行行尾轉換,這會損壞文件。

百分號 (%) 在PDF語法中表示註釋行,而特定的字元 âãÏÓ 是任意的位元組,其值超過ASCII 127,用作傳輸協議的二進位制標記。

Body:所有內容都位於此處。

檔案主體構成主要內容儲存庫,由一系列物件組成。每個物件都遵循嚴格的語法結構:

1
2
3
[object_number] [generation_number] obj
[object_content]
endobj

每個物件都以物件編號、生成編號以及 obj 關鍵字開始,位於同一行,然後是物件內容,最後以 endobj 關鍵字結束。生成編號允許在更新交叉引用條目時重用物件,對於大多數情況,該值保持為零。

例如,檢視我們示例中的物件1:

1
2
3
4
5
6
7
1 0 obj
<<
/Kids [2 0 R]
/Count 1
/Type /Pages
>>
endobj

此物件(編號 1,第 0 代)包含一個字典,定義了頁面樹。 /Type /Pages 該條目標識其為一個頁面樹節點, /Count 1 指示它包含一個頁面,並且 /Kids [2 0 R] 引用物件 2 作為其子頁面。

交叉引用表:導航骨幹

交叉引用表代表 PDF 中用於效能最佳化的最巧妙的功能。它提供從物件編號到檔案內位元組位置的直接對映,從而可以在不進行順序掃描的情況下實現隨機訪問:

1
2
3
4
5
6
7
8
xref
0 6                              % Six entries starting at object 0
0000000000 65535 f               % Special entry for free objects
0000000015 00000 n               % Object 1 at byte offset 15
0000000074 00000 n               % Object 2 at byte offset 74
0000000192 00000 n               % Object 3 at byte offset 192
0000000291 00000 n               % Object 4 at byte offset 291
0000000409 00000 n               % Object 5 at byte offset 409

每個交叉引用條目由正好 20 個位元組組成:一個 10 位數字的位元組偏移量(帶前導零)、一個 5 位數字的生成編號,以及一個字元(對於正常物件,使用 "n";對於空物件,使用 "f"),後面是必有的空格。這種固定長度的格式可以實現對交叉引用表本身的隨機訪問。

第一個條目(物件 0)始終是一個特殊條目,指向空物件列表的頭部,其生成編號為 65535。這種機制允許 PDF 在增量更新期間重用物件編號,從而在刪除物件時可以重用。

預告片:重要的後設資料和檔案導航。

預告片部分提供了至關重要的資訊,用於 PDF 處理程序導航文件結構。

1
2
3
4
5
6
7
8
trailer
<<
/Root 5 0 R                      % Document catalog reference
/Size 6                          % Number of xref entries
>>
startxref
459                              % Byte offset of xref table
%%EOF                            % End-of-file marker

預告片以 trailer 關鍵字開頭,後跟包含重要導航資訊的預告片字典。 /Size 條目指定交叉引用表中條目的總數,而 /Root 指向文件目錄,它是物件圖的根元素。

好的。 startxref 關鍵字前面是一個數字,表示交叉引用表開始的位元組偏移量。最後, %%EOF 標記 PDF 檔案的結束。PDF 閱讀器通過找到此檔案結束標記開始處理,然後反向查詢預告片和交叉引用表,然後按需載入物件。

詞彙約定:PDF語法的基石。

PDF檔案是遵循特定詞彙規則的8位位元組序列,用於解析成token。理解這些約定對於PDF處理至關重要:

字元分類

PDF 識別三種類型的字元:

  • 普通字元 – 所有非空格和分隔符的字元。
  • 空格字元 – 用於token分隔。
  • 分隔符 – 特殊字元: ( ) < > [ ] { } / %

PDF 中的空白字元包括:

Character Code Meaning
0 Null
9 Tab
10 Line feed
12 Form feed
13 Carriage return
32 Space

PDF 檔案可以使用 <CR>、<LF> 或 <CR><LF> 序列來結束行。但是,批次更改行尾符很可能會損壞檔案,因為它會影響壓縮二進位制資料部分中的行尾符序列。

PDF 物件型別:完整的分類

PDF 支援八種基本物件型別,這些型別是所有文件內容的構建塊。這些型別分為基本物件、複合物件和連結機制:

基本物件

整數和實數

數字是 PDF 數字系統的基礎。

1
2
3
4
5
% Integer examples
0 +1 -1 63
 
% Real number examples  
0.0 0. .0 -0.004 65.4

整數由十進位制數字(0-9)組成,可以選擇在前面加上正號或負號。 浮點數遵循類似的規則,但可以包含一個小數點,該小數點可以出現在數字的開頭、中間或結尾。 值得注意的是,指數表示法(如 4.5e-6)在 PDF 中是不允許的。

數字的範圍和精度取決於 PDF 的實現,而不是規範。 一些實現會將整數轉換為浮點數,當整數超出可用範圍時。

字串:兩種表示方法

PDF 提供了兩種不同的字串格式,適用於不同的用例。

文本字串

文本字串出現在括號內,並支援轉義序列:

1
2
3
4
5
6
7
8
% Simple string
(Hello, World!)
 
% String with escaped characters
(Some \\ escaped \(characters\))
 
% String with balanced parentheses (no escaping needed)
(Red (Rouge))

文本字串中的轉義序列包括:

Sequence Meaning
\n Line feed
\r Carriage return
\t Horizontal tab
\b Backspace
\f Form feed
\ddd Character code in three octal digits
十六進位制字串

十六進位制字串提供了一種替代表示方法,尤其適用於二進位制資料:

1
2
<4F6Eff00>                       % Bytes 0x4F, 0x6E, 0xFF, 0x00
<48656C6C6F>                     % "Hello" in ASCII hex

每兩個十六進位制數字代表一個位元組。當出現奇數個數字時,假設最後一個數字後面跟著一個 0。這種格式使二進位制資料易於人類閱讀,同時保持與字面字串的功能等效性。

名稱:PDF 的識別符號系統

名稱在 PDF 中用作識別符號,充當字典鍵和符號常量:

1
2
3
4
/French                         % Simple name
/                               % Valid name (just the slash)
/Websafe#20Dark#20Green         % Name with encoded spaces (#20 = space)
/A#42                           % Name with encoded character (#42 = 'B')

名稱以正斜槓開頭,不允許直接包含空格或分隔符字元。特殊字元使用雜湊編碼,編碼方式為兩個十六進位制數字。名稱區分大小寫,因此 /French/french 代表不同的識別符號。

布林值和空值

PDF 支援標準的布林值和空物件。

1
2
3
true                             % Boolean true
false                            % Boolean false
null                             % Null object

這些用作字典條目的標誌和物件結構中的佔位符值。

複合物件

陣列:有序集合

陣列包含任何 PDF 物件的有序序列,包括其他陣列。

1
2
3
[0 0 400 500]                   % Four integers (typical rectangle)
[/Green /Blue [/Red /Yellow]]   % Mixed types with nested array
[1 0 R 2 0 R 3 0 R]             % Array of indirect references

陣列不需要型別一致性,元素可以是數字、字串、名稱、其他陣列或任何 PDF 物件型別。

字典:鍵值對映

字典表示無序的鍵值對集合,其中鍵始終是名稱。

1
2
3
4
5
6
7
8
<</One 1 /Two 2 /Three 3>>      % Simple mappings
<<                               % Multi-line dictionary
  /Type /Page
  /Parent 1 0 R
  /Resources 3 0 R
  /MediaBox [0 0 612 792]
  /Contents [4 0 R]
>>

字典是 PDF 結構化資料的核心,包含從頁面定義到字型規範的所有內容。 它們可以任意巢狀,形成複雜的層次結構。

流:二進位制資料容器

流將字典與二進位制資料結合,對於影像、字型和壓縮內容至關重要。

1
2
3
4
5
6
7
8
9
10
11
12
4 0 obj
<<
/Length 65                       % Stream length in bytes
/Filter /FlateDecode            % Optional compression filter
>>
stream
1. 0. 0. 1. 50. 700. cm BT      % Binary or text data
  /F0 36. Tf
  (Hello, World!) Tj
ET
endstream
endobj

流由一個字典(至少包含一個條目),一個關鍵詞,一個換行符,資料位元組,另一個換行符,以及另一個關鍵片語成。 所有流都必須是間接物件,並且通常使用壓縮來提高效率。 /Length entry stream 關鍵詞 endstream 關鍵詞

間接引用:物件連結

間接引用在物件之間建立連結,從而形成圖結構,這使得 PDF 具有高效性。

1
2
6 0 R                            % Reference to object 6, generation 0
<</Resources 10 0 R /Contents [4 0 R]>>  % Dictionary using references

該格式由物件編號、生成編號以及關鍵字組成。 R 這種機制允許物件相互引用,而無需嵌入完整的定義,從而實現共享和隨機訪問。

流和過濾器:高階資料處理。

流是 PDF 中儲存二進位制資料的關鍵機制。 大部分 PDF 內容,包括頁面圖形和嵌入的字型,都儲存在流中,通常進行壓縮以提高空間效率。

綜合過濾器型別。

PDF 支援多種壓縮和編碼過濾器,每種過濾器都針對特定資料型別進行了最佳化。

Filter Name Description and Use Cases
/ASCIIHexDecode Converts hexadecimal digit pairs to bytes. ‘>’ indicates end of data. Primarily for 7-bit data transmission compatibility.
/ASCII85Decode More efficient 7-bit encoding using printable characters ‘!’ through ‘u’ and ‘z’. Sequence ‘~>’ marks end of data.
/LZWDecode Lempel-Ziv-Welch compression, identical to TIFF implementation. Good general-purpose compression.
/FlateDecode Deflate compression (RFC 1950), used by zlib. Most common PDF compression method. Supports predictors for enhanced compression.
/RunLengthDecode Simple run-length encoding for data with repeated byte sequences.
/CCITTFaxDecode Group 3/4 fax compression. Excellent for monochrome (1-bit) images, poor for general data.
/JBIG2Decode Advanced compression for monochrome, grayscale, and color images. Superior to CCITT methods.
/DCTDecode JPEG lossy compression. Complete JPEG files with headers can be embedded directly.
/JPXDecode JPEG2000 compression supporting both lossy and lossless modes. Limited to JPX baseline feature set.

多重過濾器鏈。

過濾器可以連結使用,以滿足複雜的處理需求。

1
2
/Filter [/ASCII85Decode /DCTDecode]    % JPEG data then ASCII85 encoded
/Filter [/ASCIIHexDecode /FlateDecode] % Deflate compression then hex encoding

在解碼過程中,過濾器按相反的順序應用,陣列中的最後一個過濾器在讀取資料時首先應用。

高階 PDF 架構

增量更新:非破壞性修改

增量更新允許通過追加更改而不是重寫整個檔案來修改 PDF。這個關鍵特性提供了以下幾個好處:

  • 效能 – 僅寫入新的/已更改的物件。
  • 數字簽名 – 原始的已簽名內容保持不變。
  • 版本歷史 – 可以恢復之前的文件狀態。
  • 大型檔案效率。 – 針對大型文件,採用儘可能少的寫入操作。

在增量更新過程中,新的物件和新的交叉引用部分會追加到檔案末尾。新的尾部資訊包含一個。 /Prev 一個條目,指向前一個交叉引用表的位元組偏移量,從而建立一個文件版本的連結串列。

物件和交叉引用流 (PDF 1.5 及以上版本)

現代PDF版本引入了物件流和交叉引用流,以實現更好的壓縮比。

  • 物件流 (Object Streams) – 多個物件壓縮在一起形成單個流。
  • 交叉引用流 (Cross-Reference Streams) – 交叉引用資料以壓縮流的形式儲存。
  • 分組策略 (Grouping Strategy) – 物件按使用模式進行分組(例如,將所有第 1 頁的物件放在一起)。

這種方法在顯著減小檔案大小的同時,保持了隨機訪問能力,尤其適用於包含許多小物件的文件。

線性化 PDF:Web 最佳化結構。

線性化 PDF(在 PDF 1.2 中引入)重新組織檔案結構,以實現最佳的網路瀏覽體驗。

1
2
3
4
5
6
7
8
9
10
11
12
%PDF-1.4
%âãÏÓ
4 0 obj                          % Linearization dictionary
<< /E 200967                     % End of first page
   /H [ 667 140 ]               % Hint stream location and length
   /L 201431                    % File length
   /Linearized 1                % Linearization flag
   /N 1                         % Number of pages
   /O 7                         % First page object number
   /T 201230                    % Traditional xref table offset
>>
endobj

線性化檔案可以實現:

  • 快速顯示第一頁 – 檔案中首先出現第 1 頁的物件
  • 漸進式載入 – 在下載過程中,內容會逐步顯示
  • 高效導航。 – 提示表最佳化頁面訪問
  • 向後相容 檔案仍然可以被非線性閱讀器讀取。

PDF 檔案處理:技術實現。

讀取演算法:從位元組到物件。

PDF 閱讀器採用複雜的解析策略。

  1. 頭部驗證。 驗證 PDF 簽名並提取版本資訊。
  2. 預告片位置。 從檔案末尾向後搜尋以查詢 %%EOF 標記。
  3. 交叉引用解析. – 從交叉引用表中構建物件位置對映.
  4. 預告詞典處理. – 提取文件目錄和後設資料.
  5. 物件載入策略. – 採用按需載入或預載入關鍵物件的方式.
  6. 內容樹構建. – 從物件圖中構建邏輯文件結構.

此過程處理包括加密、線性化、物件流和增量更新等複雜情況。

寫入演算法:從物件到位元組。

PDF 生成遵循一個更直接的過程:

  1. 頭部生成。 – 輸出 PDF 版本和二進位制標記。
  2. 物件圖分析。 – 移除未引用的物件以減小檔案大小。
  3. 物件重編號。 – 逐個分配從 1 到 n 的序列號。
  4. 物件序列化。 – 在記錄位元組偏移量的同時寫入物件。
  5. 交叉引用生成。 – 從記錄的偏移量建立交叉引用表。
  6. 尾部資訊建立。 – 生成尾部字典和檔案結束標記。

資料結構表示。

一個完整的 PDF 物件可以使用這種遞迴資料結構來表示。

1
2
3
4
5
6
7
8
9
10
pdfobject ::= Null
            | Boolean of bool
            | Integer of int  
            | Real of real
            | String of string
            | Name of string
            | Array of pdfobject array
            | Dictionary of (string, pdfobject) array
            | Stream of (pdfobject, bytes)
            | Indirect of int

例如,字典物件 << /Kids [2 0 R] /Count 1 /Type /Pages >> 將被表示為:

1
2
3
4
5
Dictionary [
  ("Kids", Array [Indirect 2]);
  ("Count", Integer 1);
  ("Type", Name "Pages")
]

實用工具和專業工作流程

幾個命令列工具可以幫助進行 PDF 分析和操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
% Linearize PDF for web optimization
pdfopt input.pdf output.pdf
 
% Decompress streams for manual inspection  
pdftk input.pdf output decompressed.pdf uncompress
 
% Extract and analyze PDF structure
pdf-parser --stats document.pdf
 
% Repair corrupted PDF files
pdftk broken.pdf output repaired.pdf
 
% Extract specific pages
pdftk document.pdf cat 1-3 output pages1-3.pdf
 
% Get comprehensive PDF information
pdfinfo -meta -struct document.pdf
 
% Convert PDF to PostScript for analysis
pdftops document.pdf document.ps

安全性和完整性注意事項

理解 PDF 結構對於安全分析至關重要。

  • 嵌入內容檢測 識別隱藏的流和物件。
  • 惡意程式碼分析。 檢查 JavaScript 和表單操作。
  • 後設資料提取。 恢復文件歷史和作者資訊。
  • 數字簽名驗證。 驗證增量更新的完整性。

結論:掌握 PDF 架構。

理解 PDF 檔案的結構是進行高階文件處理、法醫分析和應用程式開發的基礎。這種格式優雅的設計——由四個主要部分協同工作——創造了一個既易於人類閱讀(在未壓縮狀態下)又非常適合複雜文件的系統。

從簡單的“Hello, World”示例,到包含數千頁且具有複雜互動功能的企業級文件,相同的基本原則適用。這種一致性使得 PDF 既具有可擴充套件性,又在各種用例中都具有可靠性。

從 PDF 1.0 發展到現在的版本,體現了對向後相容的謹慎考慮,同時引入了強大的功能,例如物件流、高階壓縮和網頁最佳化。理解這些架構決策有助於更有效地進行 PDF 處理和故障排除。

⚠️ 實施注意事項

雖然本指南涵蓋了 PDF 結構的基本概念,但完整的規範包含數百頁的內容,詳細描述了各種特殊情況、可選功能以及相容性要求。對於生產環境的應用,請使用成熟的 PDF 庫(例如)。 HotPDF 元件。Delphi PDF 庫) 而不是從頭開始實現解析器。這些庫可以處理許多複雜情況和可選功能,而這些內容在本入門指南中沒有涵蓋。