PDFlibPas 從 Delphi 產出 PDF/X 列印檔只需一次呼叫:TPDFlib.SetPDFXMode,用它選擇 PDF/X-1a:2003、PDF/X-3:2003 或 PDF/X-4。這次呼叫會寫入 XMP 的 pdfxid 識別資訊、加入 GTS_PDFX 輸出意圖、宣告 /Trapped /False、釘死 PDF 版本,從此以後就拒絕所選 Part 禁止的繪圖呼叫。再用 CheckFileCompliance 加測試 3 對存好的檔案做稽核
這是簡答。長答案之所以重要,是因為印刷廠拒收檔案不是因為它醜,而是在 preflight 拒收,通常在週五,通常是為了產生端程式碼本來就能攔下的東西:X-1a 工作裡的一個 RGB logo、一個只被參照而沒有內嵌的字型、一個沒有裁切尺寸的頁面。PDF/X 的存在就是為了讓這些失敗變成結構性的而不是視覺性的,而函式庫這一側的職責,就是讓您一開始就產不出這種檔案
Delphi 應用程式該以哪個 PDF/X Part 為目標?
接收端流程沒意見就選 PDF/X-4,有意見就照它說的選 PDF/X-1a。三個 Part 的差異恰好沿著三個軸:色彩、透明度與基底 PDF 版本,而每一個軸都對應到 PDFlibPas 內部的一道關卡:
- PDF/X-1a:2003(ISO 15930-4):PDF 1.4 語法,僅限 CMYK、灰階與特別色,不含 ICC 色彩,不含透明度。檔案已經分色成印刷色,印刷端完全不必做色彩轉換
- PDF/X-3:2003(ISO 15930-6):仍是 PDF 1.4,仍無透明度,但允許色彩管理的色彩空間;當輸出意圖本身描述的是 RGB 條件時,設備 RGB 也被接受
- PDF/X-4(ISO 15930-7):PDF 1.6 語法,支援活的透明度與 Optional Content(圖層)。現代 RIP 就是為這個 Part 打造的,因為在輸出途中把透明度平面化,正是老 X-1a 痛苦的一大來源
在廣告交件與老式印刷流程裡,X-1a 仍然無所不在,那些流程要的是一個想都不用想就能輸出的檔案。只要印刷廠收,X-4 就是更好的工程選擇:透明度原封不動保留下來,色彩決策在 RIP 上用真正的印刷設定檔完成,而不是在您的程式碼裡。模式編號分別是 1、2、3,GetInformation(203) 會回報目前的模式。PDFlibPas 也為 PDF 2.0 上的 PDF/X-6 家族定義了更高的模式編號,那些遵循不同的版本與輸出意圖規則,不在本文範圍內
SetPDFXMode 會寫進檔案的東西
TPDFlib.SetPDFXMode 處理的是 PDF/X 讀取器最先檢查的識別資訊與樣板內容,應用程式碼只需要提供真正的印刷設定檔與內容。具體來說,呼叫成功會做四件事:
- 把版本字串以
pdfxid:GTS_PDFXVersion寫入 XMP(PDF/X-1a:2003、PDF/X-3:2003或PDF/X-4)。2003 版的字串本身就能完整識別所屬 Part,所以不另外寫合規屬性 - 文件沒有輸出意圖就加一個,並把其子型別設為
/S /GTS_PDFX,這是 PDF/X 讀取器要找的子型別(ISO 32000-1 §14.11.5 定義了輸出意圖字典) - 在文件資訊字典裡設定
/Trapped /False,因為產生端從不做 trapping,而 PDF/X 要求 trapping 狀態必須被宣告,不能留白不明 - 釘死版本基底:X-1a 與 X-3 為 1.4,X-4 為 1.6
存檔時還有兩項自動修補。每個沒有 TrimBox 的頁面都會得到一個等於自己 MediaBox 的 TrimBox,因為 PDF/X 要求每頁宣告成品尺寸(ISO 32000-1 §14.11.2 講的就是頁面邊界);註解也會被補上標準要求的列印旗標。TrimBox 這個後備值是安全網,不是版面決策。做出血的工作需要真正的裁切框放在更大的媒體框之內,PDFlibPas 的 TrimBox、BleedBox 與 CropBox 預設值指南說明了怎麼設這些框,以及繼承機制會怎麼讓您吃驚
uses
PDFlibrary;
procedure BuildX1aAd(const OutFile, PressProfile: string);
var
Pdf: TPDFlib;
begin
Pdf := TPDFlib.Create;
try
if Pdf.SetPDFXMode(1) <> 1 then // 1 = PDF/X-1a:2003
raise Exception.CreateFmt('PDF/X mode refused (error %d)',
[Pdf.LastErrorCode]);
// 把預設設定檔換成實際的印刷條件
if Pdf.LoadOutputIntentProfile(PressProfile, 'DeviceCMYK') <> 1 then
raise Exception.Create('Could not load the CMYK output profile');
// Embed = 1 會內嵌完整字型程式;PDF/X 不接受 0
if Pdf.AddTrueTypeFont('Arial {1252}', 1) = 0 then
raise Exception.Create('Font could not be loaded for embedding');
Pdf.SetTextSize(24);
Pdf.SetFillColorCMYK(0, 1, 1, 0); // 印刷色紅,不含 RGB
Pdf.DrawText(72, 720, 'Spring catalog, page 1');
Pdf.SaveToFile(OutFile);
finally
Pdf.Free;
end;
end;
上面草圖的順序是有講究的。LoadOutputIntentProfile 取代的是已存在輸出意圖的設定檔,所以必須在 SetPDFXMode 建出輸出意圖之後執行。這個呼叫也無法憑空變出輸出意圖:文件裡沒有輸出意圖時,它就直接回傳 0
為什麼預設的輸出意圖需要替換?
SetPDFXMode 為模式 1 到 3 加入的輸出意圖宣告的是 DeviceCMYK,但它背後的 ICC 位元組,其實是函式庫內建的 sRGB 設定檔,只是把色彩分量數改寫成 4。這個預設值是為了向後相容而存在,也足以讓檔案在結構上成為 PDF/X,但內嵌的設定檔並不描述任何印刷機。檢查設定檔標頭的 preflight 工具會看到一個貼著 CMYK 標籤的 RGB 設定檔,而靠輸出意圖做打樣的印刷廠,拿到的東西毫無意義
所以真正的印刷工作,請把 LoadOutputIntentProfile(FileName, 'DeviceCMYK') 當成必要步驟,傳入印刷服務商為該紙張與印刷機指定的 ICC 設定檔。TPDFlib.LoadOutputIntentProfile 會替換 DestOutputProfile 串流並更新 /N,意圖字典的其餘部分維持原樣。特別是,預設意圖的輸出條件識別碼與描述字串仍是 sRGB 風格的值,如果您配合的印刷廠是拿識別碼、而不是拿內嵌設定檔來比對,出貨前先跟他們確認到底檢查什麼。這是 2003 年代預設值的真實缺口,不是您的程式碼做錯了什麼
陷阱一:您沒選過的那個版本
新的 PDFlibPas 文件從產生端預設的 PDF 1.5 起步,高於 PDF/X-1a 與 PDF/X-3 要求的 1.4 基底。常見情況下,TPDFlib.SetPDFXMode 會悄悄解掉這件事:存檔版本若未被鎖定,它就把文件版本降到該 Part 的基底然後繼續。第一版實作是直接拒絕,於是每個新文件上的 SetPDFXMode(1) 都回傳 0——這是一條紙上正確、實務上毫無用處的規則的好例子
有一種情況它確實該拒絕,而那種情況的拒絕仍在。如果先前有人用高於基底的版本呼叫過 LockSaveVersion,SetPDFXMode 會回傳 0 並把 LastErrorCode 設為 601,因為呼叫者是故意釘死版本的,函式庫不會越權推翻。在共用的文件設定程式碼裡,某個輔助函式基於不相干的理由鎖了版本,X-1a 工作就是死在這裡:
Pdf := TPDFlib.Create;
try
Pdf.LockSaveVersion('1.7'); // 由通用的「公司預設」輔助函式設定
if Pdf.SetPDFXMode(1) = 0 then
// LastErrorCode = 601:1.7 已被鎖定,PDF/X-1a 需要 1.4
// 列印工作請跳過鎖定。PDF/X-4 也逃不掉:
// 它的 1.6 基底同樣低於 1.7 鎖定,會以同樣方式失敗
LogError('PDF/X refused, error %d', [Pdf.LastErrorCode]);
finally
Pdf.Free;
end;
同一道關卡也解釋了一個相關限制。PDF/X 模式是文件建立期的開關:SetPDFXMode 對新文件有效,對從檔案載入的文件回傳 0,因為函式庫無法為不是它寫的內容背書。把既有 PDF 轉成 PDF/X 是另一種工作,需要檢查與修復,不是一個旗標
陷阱二:悄悄拒絕的關卡
PDF/X 模式一旦啟用,PDFlibPas 會在您呼叫的當下,拒絕所選 Part 禁止的每一個內容呼叫,而不是放著檔案之後在 preflight 死掉。這些拒絕涵蓋:設備 RGB 的填色與描邊(X-1a 全部禁止;X-3 與 X-4 除非輸出意圖是 RGB)、X-1a 的 ICC 色彩、X-1a 與 X-3 的透明度與混合模式、X-1a 與 X-3 的 Optional Content、JavaScript 動作、內嵌檔案附件、LZW 影像濾鏡、會淪為未內嵌的 Standard 14 字型,以及公開金鑰加密。透過 Encrypt 的密碼加密在建立期不受關卡限制,但驗證器對任何加密檔案都會標記,所以列印工作請保持不加密
這些拒絕不共用的東西是錯誤碼,而這正是陷阱。SetFillColor、SetLineColor 裡的色彩空間關卡、ICC 色彩呼叫與 EncryptPublicKey 會把 LastErrorCode 設為 601,也就是 PDF/A 關卡也在用的那個函式庫合規錯誤碼。透明度、JavaScript、附件、LZW 與 AddStandardFont 的關卡則只是回傳 0,LastErrorCode 原封不動,也就是說它可能還留著更早呼叫的值。一整批繪圖呼叫之後只檢查 LastErrorCode 的程式碼,會漏掉一半的拒絕,所以每個回傳值都要檢查:
procedure Require(Ok: Integer; const What: string);
begin
if Ok = 0 then
raise EPrintConformance.CreateFmt('%s refused under PDF/X', [What]);
end;
Require(Pdf.SetFillColorCMYK(0, 0, 0, 1), 'CMYK fill');
Require(Pdf.SetTransparency(40), 'transparency'); // 在 X-1a / X-3 下為 0
Require(Pdf.AddLinkToJavaScript(72, 72, 120, 20,
'app.alert(1)', 0), 'JavaScript link'); // 在每個 Part 下都為 0
字型值得單獨警告一次。AddStandardFont 在 PDF/X 下回傳 0,因為函式庫不附帶 Standard 14 的外框資料,所以無法內嵌它們。AddTrueTypeFont 不一樣:PDF/A 模式下它會替您把 Embed = 0 升級成完整內嵌,PDF/X 模式下不會,於是被參照的系統字型就這樣進了檔案,只有驗證器抓得到。請自己傳 1(完整)或 2(子集),或在設定階段呼叫一次 SetEmbedAllFonts(1),讓漏網的 0 無處可溜
送到印刷廠之前如何驗證 PDF/X 檔案?
對存好的檔案執行 CheckFileCompliance,ComplianceTest = 3。沒有記錄到任何發現時,TPDFlib.CheckFileCompliance 回傳 0;否則回傳一個字串清單 handle,項目是帶 PDFX- 前綴的行。PDF/X 稽核與 PDFlibPas 的 PDF/A 與 PDF/UA preflight 用同一套串流讀取器讀檔,檢查的是印刷 preflight 最先查的那些規則:
- 檔案未加密,XMP 帶有已知的
GTS_PDFXVersion,PDF 版本不超過該 Part 的基底 - 輸出意圖恰好一個,子型別為
GTS_PDFX,且帶有目標設定檔與輸出條件識別碼 - 資訊字典有宣告
/Trapped - 每頁都有位於 MediaBox 之內的 TrimBox,BleedBox 若存在,則包住 TrimBox
- 頁面上用到的每個字型都有內嵌的字型程式,Type0 字型透過其子字型檢查
- 沒有被禁止的註解型別(RichMedia、Screen、3D、Movie、Sound、FileAttachment),沒有 JavaScript 開啟動作或文件層級 JavaScript,沒有內嵌檔案,物件圖裡任何地方都沒有 LZWDecode 濾鏡
function PreflightPDFX(Pdf: TPDFlib; const FileName: string;
Report: TStrings): Boolean;
var
ListId, I: Integer;
begin
ListId := Pdf.CheckFileCompliance(FileName, '', 3, 0); // 3 = PDF/X
Result := ListId = 0;
if Result then
Exit;
try
for I := 1 to Pdf.GetStringListCount(ListId) do // 項目從 1 起算
Report.Add(Pdf.GetStringListItem(ListId, I));
finally
Pdf.ReleaseStringList(ListId);
end;
end;
請搞清楚這個通過是什麼意思。PDFlibPas 驗證器稽核的是上面列的結構性規則,而那些正是能抓到產生端失誤的規則。它不是色彩精確的 preflight:不檢查總墨量、影像解析度,也不檢查內嵌設定檔是否吻合印刷廠實際跑的條件。把它當成建置或匯出管線裡的關卡,印刷機專屬的檢查留給印刷廠的 preflight 設定檔——反正他們無論如何都會跑
FAQ
PDFlibPas 支援 PDF/X-4 透明度嗎?
支援。在 SetPDFXMode(3) 下,PDFlibPas 允許透明度、混合模式與 Optional Content 群組,並寫出 PDF 1.6 檔案。同樣的呼叫在 PDF/X-1a 與 PDF/X-3 下回傳 0
PDFlibPas 能把既有 PDF 轉成 PDF/X 嗎?
不能。SetPDFXMode 只適用於目前在 TPDFlib 工作階段中建立的文件,對載入的檔案回傳 0。既有 PDF 仍然可以用 CheckFileCompliance 加測試 3 來稽核
被拒絕的 PDF/X 呼叫回傳什麼錯誤碼?
色彩空間與公開金鑰加密的拒絕會把 LastErrorCode 設為 601。其他關卡,例如透明度、JavaScript 與 Standard 14 字型,只回傳 0,所以務必檢查函式結果
PDF/X 輸出、PDF/X 驗證器與其餘印刷及封存工具集,都是 PDFlibPas——losLab 給 Delphi 用的原生 Object Pascal PDF 函式庫——的一部分。版本、平台與完整 API 參考,請見 PDFlibPas Delphi PDF Library 產品頁