PDF 與 Word 或 RTF 的文件格式不同;那些格式儲存的是算繪器在顯示時解譯的內容序列,因此輸出取決於剛好存在的任何字型和排版引擎;PDF 則儲存了該過程的結果:精確的算繪指令、字型程式、壓縮的影像串流,以及將它們綁定在一起以完整描述每個頁面的物件圖;檔案攜帶了足夠的資訊,可以在任何合規的算繪器上完全相同地重現每個頁面,這既是其主要的設計目標,也是您在嘗試以程式設計方式產生、解析或修改它時所遇到的大多數複雜性的根源
物件模型
每個 PDF 都是已編號物件的集合;物件可以是布林值、整數、實數、名稱、字串、陣列、字典、串流或 null;幾乎所有有趣的內容都是字典,這是一組鍵值對,其中鍵是名稱,值是任何其他物件類型,包括透過編號和世代計數對其他物件的引用;串流是一個字典,後面緊跟著一個通常經過壓縮的位元組序列
目錄字典是根;它指向頁面樹,頁面樹以平衡樹結構而不是扁平清單來組織頁面字典,因此瀏覽 10,000 頁文件的第 5,000 頁不需要走訪每個先前的頁面描述元;每個頁面字典引用其內容串流(一個或多個頁面描述運算子序列)、其資源字典(反過來引用字型描述元、色彩空間和影像 XObject),以及其媒體框(頁面所在的座標空間);座標原點在左下角,正 Y 向上運行,單位為 1/72 英吋
在檔案的末尾坐落著交叉引用表,它將每個物件編號對應到其在檔案中的位元組偏移量;這正是隨機存取的依據:檢視器先讀取交叉引用表,然後直接尋找它需要的任何物件;PDF 1.5 引入了交叉引用串流,將表壓縮為一個串流物件,並將相關物件封裝到物件串流中,對於包含許多小物件的文件,這顯著減了檔案大小
內容串流與繪圖模型
頁面的視覺內容存在於一個或多個內容串流中;每個串流都是一個 PDF 運算子序列,其中夾雜著它們的運算元;文字運算子 BT 開始一個文字物件,Tf 從資源字典中選擇字型和大小,Td 定位文字游標,Tj 或 TJ 繪製字串,而 ET 關閉文字物件;向量圖形遵循類似的模式:m 設定路徑起點,l 新增直線線段,c 新增貝茲曲線,而 f 或 S 填滿或繪製路徑
繪圖狀態決定了運算子之間發生的一切:目前轉換矩陣、線條寬度、色彩空間、填滿色彩、筆劃色彩和裁剪路徑;像 q 和 Q 這樣的運算子將繪圖狀態推入堆疊或從中彈出,這就是 PDF 實作局部座標轉換和暫時狀態覆寫,而不影響周圍上下文的方式;表單 XObject 對此進行了推廣:這是一個獨立的內容串流,擁有自己的資源字典,可以使用單個 Do 運算子在任意位置和比例繪製到頁面上
字型嵌入與文字擷取
PDF 可以按名稱引用字型,並依賴檢視器來替代某些內容,但在實踐中,您打算共享的任何文件都必須嵌入字型資料;嵌入在 PDF 中的 Type 1 或 TrueType/OpenType 字型攜帶一個指向字型檔案串流的字型描述元字典;對於 TrueType 字型,該串流包含二進位字型程式,對於 Type 1,它是 PFB 資料;子集化是每個嚴謹的 PDF 產生器都會做的事,它去除了文件中未引用的字形,即使對於大型 Unicode 字型也能使檔案大小保持在可控範圍內
文字擷取是字型嵌入反噬的地方;字元的視覺表示是由嵌入字型程式中的字形決定的;該字元的 Unicode 值是由附加到字型字典的 ToUnicode CMap 串流決定的;當 ToUnicode CMap 缺失或錯誤時,PDF 檢視器可以清晰地算繪文字,但無法將其擷取為有意義的 Unicode,這就是從某些 PDF 複製貼上會產生亂碼的原因;標籤化 PDF(ISO 32000 §14.8)增加了第二層:一個邏輯結構樹,將頁面內容對應到文件語義角色,如段落、標題和表格儲存格;螢幕閱讀器和重新排版引擎使用結構樹,而不是原始的內容串流順序,這解釋了為什麼一個視覺上配置良好的 PDF,如果缺少標籤,可能仍然是無法存取的
增量更新與數位簽章
當您儲存對現有 PDF 的變更而不用從頭開始重寫時,新物件會隨同新的交叉引用區段和新的 trailer 字典一起附加在原始檔案主體之後;更新後的 trailer 指向新的交叉引用資料,而被取代的物件仍保留在檔案中,但根本沒有被新的交叉引用鏈引用;這就是增量更新,它有兩個重大後果
首先,檔案隨每次儲存循環而增長;反覆編輯和儲存的文件會累積多層過時的物件;像 QPDF 這樣的工具可以線性化或壓縮重寫檔案以回收該空間,但預設是累積;其次,數位簽章在完整性模型上依賴增量更新;ISO 32000 簽章覆蓋了檔案的位元組範圍,通常是除簽章值本身預留位置之外的所有內容;簽章後發生的、以附加增量更新形式出現的任何變更,對於驗證閱讀器來說都是簽章後所做的修改,這正是您想要的稽核軌跡;然而,這也意味著某些修改(例如新增核准簽章或填寫表單欄位)是標準明確允許的,且不會使原始簽章失效,前提是變更符合文件的權限設定(ISO 32000-2 §12.7.6);超出這些權限的修改會被標記為未授權;當您產生的文件將在後續進行聯署時,正確處理此區別非常重要
合規層級與 ISO 32000 血統
PDF 於 1993 年作為 Adobe 專有格式問世,吸收了 PostScript 的成像模型,並在十五個版本中累積了許多功能:1.1 中的加密、1.2 中的互動式表單、1.3 中的數位簽章和邏輯結構、1.4 中的透明度、1.5 中的物件串流、1.6 中的 AES 加密;Adobe 於 2007 年將 PDF 1.7 提交給 ISO,從而誕生了 ISO 32000-1:2008;ISO 32000-2:2020 涵蓋了 PDF 2.0,它收緊了幾個未明確定義的領域,修改了 AES-256 金鑰衍生演算法(第 6 版取代第 5 版),並增加了對關聯檔案和多媒體的明確支援
子標準源自於相同的基礎;PDF/A(ISO 19005)為了長期封存的穩定性而折衷了功能:無加密、無外部內容依賴、所有字型皆嵌入、色彩空間與裝置無關,且需要 XMP 中介資料;PDF/A-1 基於 PDF 1.4,PDF/A-2 基於 PDF 1.7,PDF/A-3 則允許嵌入任何格式的檔案;PDF/X(ISO 15930)是印刷生產子集:輸出意圖、出血和裁切框,在較舊的合規層級中沒有透明度;PDF/UA(ISO 14289)強制要求標籤化結構、Unicode 映射以及語言中介資料以實現無障礙存取;這些並不是競爭格式,它們是核心 PDF 之上的附加限制,只要限制不衝突,單一檔案可以同時符合多個標準
對於編寫產生或處理 PDF 的程式碼的人來說,實用的基準是 ISO 32000-2,並需要仔細注意涵蓋以下內容的章節:交叉引用模型 (§7.5)、繪圖狀態 (§8.4)、文字狀態運算子 (§9.3)、字型描述元和 ToUnicode (§9.6 和 §9.10)、互動式表單 (§12.7) 以及數位簽章 (§12.8);該標準很長,但大多數程式化 PDF 工作只會反覆觸及它的一小部分;理解物件模型和交叉引用機制是起點,其他一切都是從這裡開始的專業分工