一個青色方塊疊在黃色背景上,畫它的圖形狀態帶著 /op true 與 /OPM 1。螢幕上重疊處是一片純青色。印刷紙上同樣的重疊變成綠色,因為 RIP 沒動黃版、把青色疊在它上面。兩種結果都嚴格遵循檔案;螢幕轉譯只是從沒問過成像裝置會怎麼處理那個旗標,所以螢幕上做的核可,蓋住的是一個永遠到不了紙上的顏色
為什麼頁面印出來跟預覽不一樣?
疊印是給輸出裝置的指示,講的是色版的事,而 RGB 螢幕沒有色版。ISO 32000-1 §8.6.7 定義了 ExtGState 的 OP(描邊)或 op(填色)項為 true 時會發生什麼:目前繪料沒點名的色料,保留其色版上既有的內容,而不是被擦成零。這樣畫出的青色物件不從黃版上拿走任何東西。以 RGB 合成的檢視器沒地方放黃色,於是把青色畫在上層、然後繼續
兩種輸出都沒有壞掉。分歧內建在兩種成像模型裡,直到有人把印刷打樣拿到螢幕旁對照,它一直隱形。對那個不小心把黑字設成挖空、或把白 logo 設成疊印還期待它顯示的設計師來說,發現的時機都太晚
用 RenderOverprintPreview 開啟打樣
HotPDF 把打樣暴露成文件上的一個布林值 THotPDF.RenderOverprintPreview,預設 False。設了之後,點陣圖轉譯會追蹤每個疊印繪料底下的製程色版,並在寫像素之前套用 §8.6.7 規則,所以重疊處顯示的是印刷機會產生的油墨組合。一般檢視保持預設關閉是對的,辦公室 PDF 的讀者期待它長得跟平常的檢視器一樣。在輸出要交給印前操作員的那個步驟打開它
var
Pdf: THotPDF;
Device: THPDFBitmapRenderDevice;
Proof: TBitmap;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.LoadFromFile('cover-cmyk.pdf');
Pdf.RenderOverprintPreview := True; // 打樣色版,預設為 False
Device := THPDFBitmapRenderDevice.Create;
try
if Pdf.RenderLoadedPageToDevice(0, 150, Device) then
begin
Proof := Device.TakeBitmap; // 從這裡起 Proof 歸呼叫端所有
try
Image1.Picture.Assign(Proof);
finally
Proof.Free;
end;
end;
finally
Device.Free;
end;
finally
Pdf.Free;
end;
end;
色版規則取決於疊印繪料的色彩空間,值得了解,因為每一條各自解釋打樣上一種不同的意外:
/OPM 1下的 DeviceCMYK:分量為零的成分不動其色版,非零分量取而代之。黃底上青 100/黃 0 保留黃色、變成綠色- DeviceGray:繪料只點名黑版,底下的青、洋紅與黃因此存活。設成疊印的白色 DeviceGray logo 疊在 CMY 底上於是消失
- Separation 與 DeviceN:名為
Cyan、Magenta、Yellow或Black的色料直接取代那個製程色版,/None什麼都不畫,/All寫進每一個製程色版 - 特別色色料:其他任何名稱都當特別色印墨處理;其色調轉換給出一個替代色,與底下製程色版相乘,所以特別色讓它覆蓋之處變暗,而不是取代它們
打樣適用於填色與描邊路徑,也適用於文字,包括描邊渲染模式的文字——那裡由描邊旗標 OP 決定而非 op。影像、行內影像與 sh 著色繼續走它們完整的點陣色彩管線、以平常的不透明度繪製。實務上這涵蓋了預檢報告點名最多的東西:疊印規則、關鍵線、文字與平網
打樣開著時透明度照樣作用。設了 knock out 的透明度群組照樣挖空,而且該群組與疊印規則在同一趟裡合成,所以疊印黑底上有透明美工的印刷就緒檔案,兩種效果一起打樣。一種對另一種錯,只會產出另一種錯誤的打樣
這個設定也參與快取轉譯的身分識別。RenderLoadedPageToBitmapCached 以頁面、DPI 與一個由含 RenderOverprintPreview 在內的轉譯設定構成的變體作為條目鍵,磁碟層用同一個變體。在普通與打樣預覽之間切換的檢視器拿到兩個分開的快取條目,不必僅因為切換了開關就去呼叫 InvalidateRenderedPageCache
DeviceCMYK 檔案裡的 /OPM 0 陷阱
這就是讓人去找 bug 的那個案例。檔案在 DeviceCMYK 青色填色上設了 /op true、卻讓 OPM 停在預設值 0,然後打樣仍然顯示青色挖空黃色。打樣是對的。疊印模式 0 之下,§8.6.7 說 DeviceCMYK 繪料設定全部四個製程色料、零也照設,所以零的黃色分量取代黃版,與挖空毫無分別。疊印模式 0 只保護繪料沒點名的色版,對四分量 CMYK 顏色來說,那意味著特別色版、僅此而已
在合成打樣顯示的製程色版上,模式 0 與普通挖空給出相同的像素,所以 HotPDF 把那些 DeviceCMYK 物件畫成普通挖空。CMYK 物件要疊印其他製程印墨,帶 /op true 的 ExtGState 還需要 /OPM 1。當打樣與設計師的意圖在一個 DeviceCMYK 物件上不一致,先打開那個 ExtGState 找 OPM 項,再懷疑其他任何東西
反向的意外也存在。/OPM 1 之下,CMYK 白是四個零,四個零在疊印下讓每個色版保持原樣。設成疊印的白字在紙上消失,打樣也顯示它消失——這正是要跑一次打樣的全部理由
RenderLoadedPageToDevice 背後的三個裝置
THotPDF.RenderLoadedPageToDevice(PageIndex, DPI, Device) 是單一入口,目標由你傳入的裝置物件決定。nil 裝置拋出例外,低於 1 的 DPI 按 150 處理,回傳裝置 Execute 方法回報的東西。THPDFRenderDevice 是抽象基底,有兩個虛擬成員:Kind——回傳 rdkBitmap、rdkDeviceContext 或 rdkEnhancedMetafile——以及 Execute(Document, PageIndex, DPI)
三個內建裝置以不同方式擁有自己的輸出:
THPDFBitmapRenderDevice把TBitmap放在Bitmap屬性裡,並在解構函式釋放它——除非TakeBitmap已先把它交給你THPDFDeviceContextRenderDevice.Create(ADC, AWidth, AHeight)畫進你已有的HDC、不擁有任何東西;建構函式在 DC 為零或尺寸非正時拋出例外,且不畫背景,所以螢幕 DC 需要自己補白色填滿THPDFMetafileRenderDevice持有TMetafile直到TakeMetafile移交。文字與向量路徑在加強型中繼檔裡保持可縮放,影像 XObject 則以點陣形式抵達
所有權遵循樸素的 Delphi 規則。物件只需要活得跟裝置一樣久,就讀 Bitmap 或 Metafile 屬性;必須活得比裝置久,就呼叫 Take 方法。拿了點陣圖、釋放裝置、忘了點陣圖,洩漏隨頁數增長:五頁的樣品上不可見,五百頁的批次上顯而易見。每個物件給自己的 try/finally,誰釋放什麼的問題就自己回答了自己
應用程式碼按 Kind 分派,不要測 is THPDFBitmapRenderDevice。Execute 與 Kind 是公開虛擬方法,你可以寫自己的裝置類別,而檢查 kind 的程式碼在你的裝置頂替內建裝置時照樣工作
為什麼印表機轉譯落在角落?
裝置內容路徑以 DPI 引數決定頁面大小,不是以你傳的寬高。轉譯器以 DPI / 72 縮放 PDF 單位,高度只用來把頁面原點錨在表面底部。寫死 300、把工作送到 600 dpi 印表機,頁面以一半尺寸出現、被推到紙張左下角。印表機解析度是驅動程式的屬性,所以要問驅動程式:
procedure PrintPage(Pdf: THotPDF; PageIndex: Integer);
var
Device: THPDFDeviceContextRenderDevice;
PrinterDPI, W, H, Tmp: Integer;
L, B, R, T: Single;
begin
if not Pdf.GetLoadedPageVisibleBox(PageIndex, L, B, R, T) then
Exit;
Printer.BeginDoc;
try
// 問驅動程式;300、600、1200 dpi 都很常見
PrinterDPI := GetDeviceCaps(Printer.Handle, LOGPIXELSX);
W := Round((R - L) * PrinterDPI / 72);
H := Round((T - B) * PrinterDPI / 72);
if (Pdf.GetLoadedPageRotation(PageIndex) mod 180) = 90 then
begin
Tmp := W; W := H; H := Tmp; // 90 / 270 度頁面要對調
end;
Device := THPDFDeviceContextRenderDevice.Create(
Printer.Canvas.Handle, W, H);
try
Pdf.RenderLoadedPageToDevice(PageIndex, PrinterDPI, Device);
finally
Device.Free;
end;
finally
Printer.EndDoc;
end;
end;
寬與高來自頁面的可見框——被 MediaBox 裁過的 CropBox——以印表機自己的解析度計算,呼叫端在建立裝置之前為旋轉頁面對調兩者。這正符合轉譯器錨定原點時的期待
疊印打樣不走這條路。裝置內容與中繼檔裝置直接把 GDI 呼叫發進目標,而色版規則需要每個繪料底下既有的像素——只有點陣圖轉譯有。要紙本打樣,先把打樣點陣圖轉譯出來、再拉伸到印表機畫布上。一個小小的自訂裝置能把這一切留在同一個入口背後:
type
// 你自己的裝置:先打樣點陣圖,再拉伸到印表機上
TPrinterProofDevice = class(THPDFRenderDevice)
public
function Kind: THPDFRenderDeviceKind; override;
function Execute(Document: THotPDF; PageIndex, DPI: Integer): boolean;
override;
end;
function TPrinterProofDevice.Kind: THPDFRenderDeviceKind;
begin
Result := rdkDeviceContext;
end;
function TPrinterProofDevice.Execute(Document: THotPDF;
PageIndex, DPI: Integer): boolean;
var
Proof: TBitmap;
OldProof: Boolean;
begin
OldProof := Document.RenderOverprintPreview;
Document.RenderOverprintPreview := True;
try
Proof := Document.RenderLoadedPageToBitmap(PageIndex, DPI);
finally
Document.RenderOverprintPreview := OldProof; // 讓呼叫端的設定保持原樣
end;
Result := Proof <> nil;
if Result then
try
Printer.Canvas.StretchDraw(
Rect(0, 0, Printer.PageWidth, Printer.PageHeight), Proof);
finally
Proof.Free;
end;
end;
在 Printer.BeginDoc 與 Printer.EndDoc 之間呼叫它,DPI 取一個能讓點陣圖為紙張備足像素的值,信紙尺寸的打樣大約 200 到 300。上面的拉伸矩形填滿可列印區域;頁面與紙張形狀不同時,記得在矩形裡保持頁面長寬比
打樣在預檢旁邊的位置
打樣回答一個問題:物件重疊處哪些印墨存活。它與任何其他轉譯一樣合成在同一張 24 位元 RGB 點陣圖上,所以它的顏色顯示的是油墨組合,不是實測的紙張顏色。印刷條件是色彩管理的問題,文件透過 ConfigureRenderICCWorkflow 處理它——吃一個打樣描述檔、一個輸出描述檔與一個黑點補償旗標——ClearRenderICCWorkflow 再把它們丟掉
打樣轉譯最有生產力的位置,是在你已經在跑的檢查旁邊。預檢趟次回報黑字被設成疊印;打樣轉譯讓操作員看見它在頁面上造成的後果;兩者落進同一份報告。包裝工作常把疊印與特別色混在一起,Separation 與 DeviceN 特別色轉譯 的逐步解說涵蓋同一頁面的色料那一側,包括透過 RenderLoadedPageSeparationPlate 的單色版檢視。把 PDF 頁面轉譯成點陣圖 與 透過 TPrinter 列印已載入的 PDF 兩文以素樸形式涵蓋兩個裝置目標
這一切都在原生 Delphi 與 C++Builder 程式碼裡跑,不必在執行檔旁邊出貨任何轉譯 DLL。HotPDF Delphi 元件頁 列出轉譯與印前功能,並有試用版可對你自己的印刷檔案跑這些範例