技術文章

以 HotPDF 在 Delphi 保持 NChannel 特別色一致

印前廠把工作退了回來:同一個特別色被分色到兩塊印版上。HotPDF 在編寫時就防止這種事:以 ISO 32000-2 的五元素 DeviceN 形式寫出 NChannel,並在整份文件中為每個特別色名維護一份唯一的標準替代空間與濃度轉換,拒絕第二份相互衝突的定義,而不是把它發出去

NChannel 不是色彩空間家族名

第一個要忘掉的是名字本身。NChannel 不是像 SeparationDeviceN 那樣的色彩空間家族。ISO 32000-2 §8.6.6.5 把它描述為 DeviceN 的子型別,所以一個合規的 NChannel 空間要寫成五元素陣列 [/DeviceN names alternateSpace tintTransform attributes],而屬性字典攜帶 /Subtype /NChannel。規格中不存在 [/NChannel ...] 陣列。如果您曾經手工打造過一個、然後看著 RIP 對它聳肩,原因就是這個

HotPDF 把 NChannel 空間寫成五元素 DeviceN 陣列,其屬性字典攜帶 Subtype NChannel 以及 Process、Colorants 與 MixingHints 項目,因為規格中根本不存在 NChannel 家族名陣列
合規的 NChannel 空間是帶屬性字典的 DeviceN 陣列,所以寫出端只發出這種形式,而讀取端仍接受舊式語彙

HotPDF 曾經在這裡做錯,後來修正了——這一點值得明說,因為它塑造了元件今天的行為。較舊的 HotPDF 版本發出的是家族名形式。THotPDF.RegisterNChannelColorSpace 現在只發出標準的 DeviceN 加屬性形式,而且因為 NChannel 子型別在 PDF 1.6 才出現,生產端入口以 RequirePDFVersion(pdf16, ...) 把關,對更舊的目標直接婉拒。算繪端刻意比寫出端寬容:HPDFResolveColorSpace 仍接受舊式 /NChannel 語彙作為 DeviceN 家族,所以舊寫出器產生的檔案能繼續算繪,但 HotPDF 寫出的任何東西都用標準編碼。輸入從寬、輸出從嚴在這裡是正確的不對稱,因為您的讀取端必須應付不是它產生的檔案,而您的寫出端沒有這種藉口

為什麼一個特別色名會落到兩塊印版上?

因為特別色著色劑名是全文件範圍的印版身分,不是局部引數。兩次呼叫都指名 Orange,卻交出不同的替代空間,或同一個替代空間配不同的濃度轉換,描述的是兩種恰好共用一個標籤的不同油墨。建構分色的 RIP 沒有辦法調和這一點,所以它做唯一誠實的事:給您兩塊印版。因此 HotPDF 為每個著色劑名維護一份文件級的標準簽章。RegisterSpotColorantDefinition 從替代色彩空間與濃度函式的形狀組成該簽章,而每個 RegisterSeparationRegisterSeparationFuncRegisterSeparationLUT 與 NChannel 特別色定義都經過它。當第二份定義不一致時,呼叫會拋出例外,而不是靜靜註冊第二個變體,而且訊息刻意具體說明失敗模式——因為替代方案是三週後在印版打樣上發現它

每個 HotPDF 特別色註冊呼叫都匯入 RegisterSpotColorantDefinition,它為文件中的每個著色劑名記錄一份標準的替代空間與濃度簽章,並在同名的第二份不一致定義抵達時拋出例外
一個名字、一份簽章:特別色的第二份衝突定義在編寫時就被拒絕,而不是在印版打樣上才被發現
// Orange 先前已對 DeviceCMYK 註冊,滿濃度時的
// 濃度轉換為 0 / 0.55 / 1 / 0。
Conflicting := Pdf.RegisterExponentialFunction(
  Domain1, NoInk, OtherOrangeCMYK, 1, []);
try
  Pdf.RegisterSeparationFunc('Orange', 'DeviceCMYK', Conflicting);
except
  on E: Exception do
    // 'Spot colourant "Orange" has inconsistent alternate colour
    //  space or tint definition in this document'
    LogPrepressWarning(E.Message);
end;

把主名清單拆成印刷色與特別色

一個完整的 NChannel 必須把它的每個主著色劑名恰好交代一次:不是印刷分量,就是特別色著色劑。進階的 RegisterNChannelColorSpace 多載接受主 ColorantNamesProcessColorantNames、替代空間、整體濃度轉換、THPDFNChannelSpotColorant 記錄陣列,以及選配的印刷順序。每筆特別色記錄攜帶自己的名字、自己的單輸入 Separation 濃度轉換、選配的實地率,以及選配的網點擴大函式。整體濃度轉換必須把 N 個輸入對應到替代空間的分量數;每個特別色濃度必須把一個輸入對應到同一個分量數

const
  Colorants: array[0..4] of AnsiString =
    ('Cyan', 'Magenta', 'Yellow', 'Black', 'Orange');
  ProcessNames: array[0..3] of AnsiString =
    ('Cyan', 'Magenta', 'Yellow', 'Black');
  Order: array[0..4] of AnsiString =
    ('Yellow', 'Magenta', 'Cyan', 'Orange', 'Black');
  Domain5: array[0..9] of Single = (0, 1, 0, 1, 0, 1, 0, 1, 0, 1);
  Range4: array[0..7] of Single = (0, 1, 0, 1, 0, 1, 0, 1);
  Domain1: array[0..1] of Single = (0, 1);
  NoInk: array[0..3] of Single = (0, 0, 0, 0);
  OrangeCMYK: array[0..3] of Single = (0, 0.55, 1, 0);
  GainC0: array[0..0] of Single = (0);
  GainC1: array[0..0] of Single = (1);
var
  Pdf: THotPDF;
  Spots: array[0..0] of THPDFNChannelSpotColorant;
  CSName: AnsiString;
begin
  Pdf.Version := pdf20;
  Pdf.BeginDoc;
  Spots[0].Name := 'Orange';
  Spots[0].TintTransform := Pdf.RegisterExponentialFunction(
    Domain1, NoInk, OrangeCMYK, 1, []);
  Spots[0].HasSolidity := True;
  Spots[0].Solidity := 0.82;
  Spots[0].DotGainFunction := Pdf.RegisterExponentialFunction(
    Domain1, GainC0, GainC1, 1, []);
  CSName := Pdf.RegisterNChannelColorSpace(Colorants, ProcessNames,
    'DeviceCMYK',
    Pdf.RegisterPostScriptFunction(Domain5, Range4,
      '{ pop pop pop pop pop 0 0 0 0 }'),
    Spots, Order);

HotPDF 從那次呼叫發出規格要求的屬性字典:/Subtype /NChannel;一個 /Process 字典,其 /ColorSpace 是印刷空間、/Components 按該空間分量順序列出印刷名;一個 /Colorants 字典,為每個特別色持有一個真正的 [/Separation name alternate tintfn] 陣列;以及在您提供了對應資料時攜帶 /Solidities/PrintingOrder/DotGain/MixingHints 字典。回傳的資源名交給 SetFillColorSpaceSetStrokeColorSpace 的方式,與Separation 與 DeviceN 特別色算繪一文中較簡單的空間完全相同

HotPDF 把 NChannel 空間的主著色劑名拆成裝四個 CMYK 分量的 Process 字典與每特別色一個 Separation 陣列的 Colorants 字典,所有元數在寫出前檢查
每個主名恰好被認領一次,而整體濃度轉換、逐特別色濃度與印刷順序都在任何物件發出前驗證

一致性檢查實際拒絕什麼?

它拒絕空間內部的結構性不連貫,而且發生在任何物件被寫出之前。著色劑名必須唯一,且不得為空、AllNone。印刷與特別色定義合起來必須恰好涵蓋主名清單,沒有著色劑身兼兩職,也沒有誰未被定義。當替代空間是 DeviceCMYK 時,印刷分量必須依序是 CyanMagentaYellowBlack,且印刷名數量必須與替代分量數相符。每個濃度轉換與網點擴大函式必須是輸入輸出元數正確的間接函式物件。實地率必須是 0..1 內的有限值。印刷順序必須是空的、或是主名清單的完整排列,絕不能是局部清單。它不做的是評判色彩:這裡沒有任何東西檢查您的 Orange 濃度轉換是否真的像油墨罐裡的那支墨、其 CMYK 組成是否是合理的代理,或您提供的實地率是否與承印材料上的量測行為相符。那些是印刷機與量測的問題,元件沒有立場回答。較簡單的純印刷色多載設計上更嚴格:它產生純印刷色的 NChannel,並刻意拒絕接受特別色名,因為把特別色寫進名稱陣列卻沒有對應的 /Colorants 項目會產生結構上不合規的檔案,而捏造一份預設定義比直接失敗更糟

PDF/X-6n 輸出意圖必須涵蓋每個已註冊特別色

一份 N 著色劑的 PDF/X-6n 檔案把它的著色劑宣告兩次,而兩份宣告必須一致。AddPDFX6ExternalOutputIntent 寫出外部 ICC 描述檔參照及其 ColorantTable,而在那之前,ValidateRegisteredSpotOutputColorants 走訪文件已註冊的每個特別色,若有任何一個不在表中便拋出例外。檢查是雙向的:一旦某個輸出意圖發佈了它的著色劑清單,之後對清單外名字的特別色註冊也會被拒絕。AddPDFX6ExternalOutputIntentSpotData 在其上加入逐油墨的中繼資料,並強制自己的規則,尤其是一個著色劑只能帶實地率值或 CxF/X-4 光譜資料其中之一,絕不能兩者兼具。這與PDF/A、PDF/X 與 PDF/UA 驗證一文討論的是同一個合規介面

Spectral := TMemoryStream.Create;
try
  LoadCxFForInk('Orange', Spectral);   // ISO 17972-4 負載
  Pdf.AddPDFX6ExternalOutputIntentSpotData(
    'ECG-5', 'Five-colour output condition',
    'https://profiles.example.com/ecg-5.icc', '5CLR',
    Colorants,
    '00112233445566778899AABBCCDDEEFF', #4#3#0#0,
    ['Cyan'], [0.70],                  // 一個著色劑的實地率
    Order,
    ['Orange'], [Spectral]);           // 另一個的光譜資料
finally
  Spectral.Free;
end;

有兩個操作細節容易漏看。支撐這一切的註冊表是文件級的,並在文件邊界清除,所以把新檔案載入同一個 THotPDF 實例不會繼承上一份文件的特別色身分;這種隔離正是重點,因為洩漏的簽章會拒絕下一份工作中完全合法的內容。而描述檔那一側也有自己的硬性上限:外部輸出意圖要求 PDF 2.0 目標、絕對的 HTTP 或 HTTPS 描述檔 URL、四位元組的 ICC 色彩空間簽章,以及 PDF/X-6n 要求 2 到 15 個著色劑與相符的 2CLR 到 FCLR 簽章

檢查在哪裡停下

CxF/X-4 處理是要誠實交代的部分。HotPDF 對光譜串流施加上限明確的結構安全檢查,並確認其中的油墨身分與您指名的著色劑相符。它為負載大小封頂、拒絕內嵌的 null 位元組、拒絕任何含 DOCTYPEENTITY 宣告的串流、要求可辨識的 CxF 根元素,並要求恰好一個 SpotInkCharacterisation 元素攜帶恰好一個等於您著色劑名的 SpotInkName。那是抵擋畸形與惡意輸入的閘門,不是結構描述驗證器。它不是完整的 ISO 17972-4 實作,它不驗證您的光譜量測,也不反向稽核任意既有的第三方物件圖或內嵌 ICC 描述檔內部的著色劑表。如果您的工作流程依賴完整的 CxF 合規,請在交給元件之前用專用工具驗證檔案

有一個相鄰的限制會咬到從未預期會遇上它的人。明度柔邊遮罩不能以 SeparationDeviceN 或 NChannel 作為其透明群組的 /CS;群組混色空間必須是裝置或 CIE 為基礎的空間,所以群組內的特別色油墨會在計算明度之前先經由其濃度轉換解析進替代空間。RegisterLuminositySoftMaskState 建構 DeviceGray 群組,正是為了讓這件事不可能意外出錯。實務後果是:以這種方式遮罩的特別色設計是透過其 CMYK 代理而非其油墨被評估的,當您對照分色打樣時這一點很重要,見疊印打樣與算繪裝置的說明

這一切都不會取代印版打樣的必要,但它把一整類印前退件從印刷車間搬回了建置環節。本文描述的 NChannel、Separation 與 PDF/X-6n 輸出意圖 API 隨標準版 HotPDF Delphi Component 提供,適用於 Delphi 與 C++Builder,其參考文件收錄了完整的記錄配置,以及每個呼叫封閉式失敗的確切條件