技術文章

Delphi 中的 PDF/A、PDF/X 和 PDF/UA 輸出:HotPDF 指南

PDF/A、PDF/X 和 PDF/UA 是解決三個不同問題的三個不同標準:長期封存、列印交換和無障礙功能。它們不是一張合規表單上的三個核取方塊,而最常見的錯誤就是把它們當作是。一個檔案可以是完美無瑕的 PDF/A,但對印刷廠來說毫無用處;一個完美的列印母版可能讓螢幕閱讀器無法讀取。更糟的是,這三者都是對檔案內部結構的限制,而不是對其外觀的限制。一份在您擁有的每個檢視器中都能乾淨開啟的檔案,在第一次嘗試時仍然可能無法通過驗證,而且通常都不會通過

HotPDF 是 losLab 的原生 VCL PDF 函式庫,它將合規性視為您在第一頁存在之前就宣告的東西。您設定一個合規屬性,附加標準所需的結構,而函式庫會在儲存時拒絕與設定檔相牴觸的配置。這是一個比產生檔案並希望後處理器能對其進行改造更好的模式,因為這些標準要求的大部分內容是無法在事後添加的

三個 ISO 標準,三個不同的承諾

PDF/A (ISO 19005) 關乎時間。它承諾一個檔案在幾十年後仍然能呈現相同的渲染效果,因此它要求完全的自給自足:每個字型都必須嵌入,每個顏色都必須透過輸出意圖(OutputIntent)賦予與裝置無關的意義,完整的 XMP 中繼資料,並禁止任何行為依賴環境的東西。加密和 JavaScript 出局了,因為沒有人能保證解密器或腳本引擎在 2050 年還會存在

PDF/X (ISO 15930) 關乎紙張上的顏色。它的存在是為了讓設計師可以將檔案交給印刷廠,而雙方都不需要討論,這意味著特性化的列印條件、強制的 /Trapped 鍵、定義好的裁切(trim)和出血(bleed)幾何形狀,而且在 X-1a 版本中,沒有讓 RIP 去猜測的即時透明度(live transparency)。PDF/UA (ISO 14289) 關乎誰能讀取結果。輔助技術需要完整的標籤樹、合理的閱讀順序、宣告的檔案語言,以及任何非文字內容的替代文字

由於這三者拉向不同的方向,請根據每個輸出管道挑選主導的標準,而不是追求一個滿足所有標準的檔案。一個只有 CMYK 的列印母版,對一個從未見過顏色的螢幕閱讀器使用者來說,恰好是錯誤的東西;而封存設定檔對動態行為的鎖定,會與任何互動式的東西發生衝突。從相同的來源資料按管道分別產生,您就能避開整個衝突

PDF/A:OutputIntent 是每個人都會忘記的部分

如果 PDF/A 檔案未通過驗證,首先要檢查的就是 OutputIntent。它是產生器最常跳過的結構,正是因為沒有任何可見的東西依賴它。ISO 19005 要求有一個:一個嵌入的 ICC 設定檔,它確定了檔案裝置顏色的實際意義。HotPDF 使該設定檔成為一個明確的輸入,而不是事後的想法:

var
  Pdf: THotPDF;
  ICC: TFileStream;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.FileName := 'invoice-archival.pdf';
    Pdf.PDFACompliance := 'B';            // level B: visual fidelity
    Pdf.Lang := 'en-US';
    Pdf.StandardFontEmulation := False;   // embed real fonts, no Base-14 emulation
    ICC := TFileStream.Create('sRGB.icc', fmOpenRead);
    try
      Pdf.AddPDFAOutputIntent('sRGB IEC61966-2.1', '', ICC, 3, 'DeviceRGB');
    finally
      ICC.Free;
    end;
    Pdf.BeginDoc;
    Pdf.CurrentPage.SetFont('Arial', [], 11);
    Pdf.CurrentPage.TextOut(50, 760, 0, 'Archival invoice body');
    Pdf.EndDoc;
  finally
    Pdf.Free;
  end;
end;

這裡有幾個細節決定了通過或失敗。StandardFontEmulation 必須關閉:模擬的 Base-14 字型不會被嵌入,而在 ISO 19005 標準下,嵌入是沒有商量餘地的。加密必須保持停用狀態,因此永遠不要將 PDFAComplianceActivateProtection 結合使用;加密的封存檔案是一個矛盾,驗證器會立即抓出它。AddPDFAOutputIntent 中的組成元素數量(component count)必須符合該設定檔,像 sRGB IEC61966-2.1 這樣的 RGB 設定檔為 3,CMYK 為 4。HotPDF 會在寫入時根據宣告的意圖追蹤 DeviceRGB 和 DeviceCMYK 的使用情況,因此 RGB 意圖檔案中的一個遊蕩的 CMYK 填滿,會變成一個被回報的問題,而不是一個無聲的錯誤

關於 ICC 設定檔有一點值得一提:將其視為有版本的部署產物,而不是某人曾經丟在建置伺服器上的一個檔案。它的位元組被嵌入到您產生的每一份檔案中,因此一個被截斷或損毀的設定檔會悄悄地毒害整批檔案,而您直到驗證時才會發現。將它與您的安裝程式一起發布,在執行日誌中記錄其檢查碼(checksum),並透過上面顯示的 TFileStream 模式載入它,這樣遺失的檔案在產生期間就會大聲地宣告失敗,而不是在封存的門口默默地失敗

用於列印的 PDF/X:Trapped、CMYK 和印刷機設定檔

列印母版顛倒了顏色故事。印刷機需要特性化的 CMYK,而標準讓您陳述是否已套用補漏白(trapping),即使誠實的答案是您根本不知道。無論如何,/Trapped 鍵都是強制性的:

Pdf.PDFXCompliance := 'X-1a';
Pdf.Trapped := 'Unknown';        // mandatory key under ISO 15930
ICC := TFileStream.Create('FOGRA39.icc', fmOpenRead);
try
  Pdf.AddPDFXOutputIntent('FOGRA39 (ISO 12647-2:2004)', '', ICC, 4, 'DeviceCMYK');
finally
  ICC.Free;
end;
Pdf.BeginDoc;
// draw with CMYK-safe colors, no transparency, no encryption
Pdf.EndDoc;

CMYK 印刷機設定檔的組成元素數量現在是 4。X-1a 也禁止即時透明度,因此請稽核任何將半透明元素分層的繪圖程式碼;檢視器在螢幕上合成的任何東西,正是 RIP 會拒絕直譯的東西。當您的印刷廠發送不同的特性化資料時,請交換設定檔位元組和識別碼字串,但不要動周圍的結構

PDF/UA:結構是產生的,絕非事後改造的

無障礙功能是團隊最常試圖在最後才加上去的標準,而它懲罰這種做法的力道比另外兩者都要大。標籤樹必須反映內容被邏輯建立的順序,而一旦檔案寫入後,您就根本沒有這些資訊了。設定 PDFUACompliance 會開啟帶有標籤的輸出,而結構 API 會在您進行的過程中,將每個繪圖呼叫綁定到其語意角色(semantic role):

Pdf.PDFUACompliance := True;     // auto-enables tagged PDF
Pdf.Lang := 'en-US';             // set explicitly; empty falls back to 'en'
Pdf.BeginDoc;

Root := Pdf.AddStructureElement(sstDocument, nil);
H1 := Pdf.EmitTaggedHeading(1, Root, 50, 700, 'Quarterly Report');
Para := Pdf.BeginTaggedContent('P', Root);
Pdf.CurrentPage.TextOut(50, 650, 0, 'Revenue grew in all regions.');
Pdf.EndTaggedContent;

Pdf.EndDoc;

要注意的是文字是否被繪製在任何 BeginTaggedContent/EndTaggedContent 配對之外。它渲染得非常完美,且對螢幕閱讀器保持隱形,因此沒有任何明眼的測試人員會抓住它;這個錯誤就這樣發布了,直到一個真正的輔助技術使用者撞上這個缺口時才會浮出水面。當您的範本帶有自訂的結構角色名稱時,請使用 AddStructRoleMap('MyHead', 'H1') 將它們對應到標準集合上,以便符合標準的讀取器知道它們是什麼意思。ISO 14289 也要求宣告語言。當 Lang 為空時,HotPDF 會退回到 'en',但這是一個安全網,不是讓真實檔案語言保持未設定的理由

驗證:信任驗證器,而不是檢視器

一個能開啟您檔案的檢視器,不能證明任何合規性,因此驗證屬於發布路徑,應使用檢查結構而非渲染的工具。對於 PDF/A 和 PDF/UA,veraPDF 是參考等級的開放式驗證器;它會根據 ISO 條款報告失敗,這些條款會直接對應回上面的配置。對於 PDF/X,Adobe Acrobat 的 Preflight 設定檔仍然是實用的檢查,因為印刷合規性不僅僅關乎語法,也關乎色彩意圖

產生器本身也完成了它該做的部分。在儲存時,HotPDF 會根據設定的 PDF 版本來協調功能旗標,悄悄地降級該版本無法表達的內容,例如在 PDF 1.7 之下將 AES-256 降至 AES-128。EndDoc 中的合規性閘門走得更遠,並且會在嚴重的矛盾上直接引發例外,就像在要求 PDFACompliance 的同時要求加密一樣。這兩者都不能取代外部驗證器。它們只是阻止不可能的配置到達那裡

有一個習慣會一再獲得回報:將整個合規設定作為一個單位進行版本控制。HotPDF 的版本、範本修訂、ICC 設定檔檢查碼、簽核的驗證器建置。當其中任何一個在其他項目下發生變化時,合規性就會漂移,而最難看的稽核是那些沒有人能重建是哪種組合產生了一份五年歷史的封存檔案的稽核。每個批次一份單一的配置記錄就能永遠解決這個問題

最後,在真實的生產輸出上執行驗證器,絕對不要在整齊的手工製作樣本上執行。會咬人的失敗來自沒有人預料到的資料:當意圖說是 RGB 時,客戶的標誌卻是 CMYK;一個滑入未嵌入字型的範本調整;一個在標籤樹外繪製文字的新程式碼路徑。從每一次過去的事件中保留一個已知的不良檔案作為迴歸輸入,合規性閘門就會隨著時間推移保持誠實。關於這些管道的渲染端,請參閱 我們關於使用 HotPDF 進行報表輸出、字型和圖片的文章;對於將驗證器連接到建置中,這裡有一篇 關於自動化 PDF 預檢檢查的姊妹篇

這些範例中使用的合規屬性、輸出意圖和標記 API,隨附於 Delphi 和 C++Builder 適用的 HotPDF Component 中;產品頁面連結了這裡顯示的每個呼叫的完整參考