PDFium Component 會驗證 ISO 19005-1 Annex C 的實作限制——127 位元組的名稱權杖、8191 個陣列元素、4095 個字典項目,以及 28 層的容器巢狀——並回報帶有 /Encoding 項目的符號式 TrueType 字型。這兩項檢查都在位元組掃描路徑上執行,所以 Delphi 或 Lazarus 應用程式完全不需要載入 PDFium DLL,就能取得判定
這些是最讓人困惑的失敗,因為文件看起來沒問題。它能算繪、能列印、每套字型都已內嵌、輸出意向也在。然後一個驗證器因為一個有 4096 個項目的字典而拒絕它,而可見文件裡沒有東西能解釋為什麼
Annex C 限制實際上在保護什麼?
與那些早於你的產生器的實作之間的互通性。Annex C 把 PDF Reference 的實作限制帶進每一個 PDF/A 部分,而這些數字不是任意定的——它們描述的是一份符合規範的閱讀器在歷史上被要求必須處理的範圍。一份超過它們的檔案,也許能在現代閱讀器裡完美開啟,卻在一個十五年前標準化的紀錄系統所用的封存閱讀器裡失敗,而這正是 PDF/A 存在所要防止的情境
這四個限制是含邊界的。一個恰好 127 位元組的名稱權杖通過驗證;128 則否。一個恰好 8191 個元素的陣列通過;8192 則否。PDFium Component 因此在其測試套件中釘住每一條邊界的兩側,因為限制檢查裡的差一錯誤,會產生最糟糕的一種驗證器:拒絕符合規範檔案、卻仍然被相信的那一種
uses FPdfPdfa;
var
Src: TFileStream;
Res: TPdfAValidationResult;
begin
Src := TFileStream.Create('archive.pdf', fmOpenRead or fmShareDenyWrite);
try
Res := ValidatePdfACompliance(Src);
if pvaiArrayOverLimit in Res.Issues then
Memo1.Lines.Add('An array carries more than 8191 elements');
if pvaiDictOverLimit in Res.Issues then
Memo1.Lines.Add('A dictionary carries more than 4095 entries');
if pvaiNestingOverLimit in Res.Issues then
Memo1.Lines.Add('Containers nest deeper than 28 levels');
if pvaiNameOverLimit in Res.Issues then
Memo1.Lines.Add('A name token is longer than 127 bytes');
finally
Src.Free;
end;
end;
哪些產生器實際上會碰到這些限制?
那些以程式建構結構的,也就是大多數的業務線輸出。一份帶有數千個欄位的表單,會產生一個超過 8191 的 /Annots 陣列或 AcroForm /Fields 陣列。一個其資源字典為每個產生的圖片或字型實例累積一個項目的頁面,會跨過 4095。深度產生的結構樹——一份透過對巢狀資料模型遞迴而建立的標記文件——會在沒有人注意的情況下走過 28 層,因為沒有人看巢狀深度
過長的名稱則來自另一種習慣:把資料編進名稱權杖裡。一個由客戶識別碼建構的色料名稱、一個以完整檔案路徑命名的選用內容群組、一個把六層階層串接起來的表單欄位完整名稱。名稱產生起來很廉價、也很容易變長,而一旦牽涉到 UTF-8 編碼的標籤,127 位元組消失得比你想像的快
每一種情況的修正都是結構性的。拆分陣列、拆分字典、壓平巢狀、縮短名稱——針對每個問題的預檢建議會點出具體的限制,而不是告訴你檔案無效。標記注入在這裡幫不上忙:這些不是中繼資料宣告,而是物件圖的形狀
為什麼符號式 TrueType 字型不能帶 /Encoding
因為 ISO 19005-1 §6.3.7 對符號式 TrueType 字型只承認字型內建的 cmap,而一個 /Encoding 項目會與之矛盾。一套符號式字型按自己的規則把代碼對應到字形——這正是符號式所代表的意義。加上一張編碼表,「位元組 0x41 選的是哪個字形」這個問題就有了兩個答案,而檔案裡沒有任何規則說明哪一個勝出。不同的閱讀器以不同方式解決它,於是一份在某個閱讀器裡算繪成文字的文件,在另一個裡算繪成裝飾符號
PDFium Component 會從 /FontDescriptor 讀取符號旗標,無論該描述子是內嵌寫在字型字典裡,還是以間接方式參照。非符號式的 TrueType 字型會保留它所需的 /WinAnsiEncoding 或 /MacRomanEncoding 而不被標記,因為對非符號式字型而言,編碼正是標準所要求的東西。檢查是在矛盾上觸發,而不是在編碼的存在上觸發
if pvaiSymbolicTrueTypeEncoding in Res.Issues then
Memo1.Lines.Add(
'A symbolic TrueType font carries /Encoding; PDF/A admits only its ' +
'built-in cmap (ISO 19005-1 6.3.7)');
這項缺陷的實際來源,是某個產生器把每套 TrueType 字型一視同仁地做子集化。Symbol、Wingdings、條碼字型與圖示字型是常見的帶原者——正是商業文件用來呈現核取方塊、標誌與條碼的字型,也正是文件因為「字型」而驗證失敗時,沒有人重新檢視的那些
這些問題如何進入預檢報告
四個容器限制歸類在結構之下;符號式 TrueType 編碼問題歸類在內容之下。當一份報告要送給兩個不同的人時,這條分野很重要:結構發現通常屬於撰寫產生器的人,而內容發現通常屬於提供素材的人
每個問題都帶著一條以具體用語點出補救方式的建議——把名稱權杖縮短到 127 位元組或更少、拆分陣列使沒有任何一個承載超過 8191 個元素、從符號式 TrueType 字型移除 /Encoding。一份說「不符合 PDF/A」的報告會啟動一項調查。一份說明超過了哪個限制、超出多少的報告,則會結束一項調查
不載入 DLL 進行驗證,以及為何在此很重要
上述所有檢查都是針對檔案位元組執行的,所以它們能在未部署 PDFium 二進位檔的服務裡、在一次建置步驟中、或在一部載入原生 DLL 是政策問題的機器上運作。這是 PDFium Component 中一條刻意的設計界線:能從結構回答的檢查,就從結構回答,而 DLL 則保留給那些真正需要算繪引擎的檢查
關於周邊工作流程——對一個資料夾執行驗證、產生報告,以及決定如何處置發現——請見 Delphi 中的 PDF/A 預檢驗證以及批次預檢報告 CLI的逐步解說。至於位於這一切檢查之上的封存設定檔選擇,PDF/A 封存合規的筆記涵蓋了在開始修復發現之前,應該把目標設在哪個部分與哪個等級
PDFium Component 為 Delphi、C++Builder 與 Lazarus 包裹 PDFium 引擎,提供一套高階 VCL API,以及一組可隨 DLL 有無執行的一致性驗證器——支援的標準與平台請見 PDFium Component 產品頁