技術記事

HotPDFのCMYKオーバープリントプルーフとレンダーデバイス

シアンのボックスが黄色の背景に載っていて、それを塗るグラフィックス状態が/op trueと/OPM 1を運んでいるとします。画面上では重なりはフラットなシアンです。刷版では、同じ重なりが緑になって出てきます。RIPが黄色の版をそのまま残し、その上にシアンを載せたからです。どちらの結果も、ファイルに忠実です。画面のレンダリングは単純に、そのフラグをイメージングデバイスがどう扱うかを尋ねていないだけであり、画面上での承認は、紙には決して届かない色を覆い隠しているのです

ページがプレビューと違って印刷される理由

オーバープリントは、版についての出力デバイスへの指示であり、RGBの画面には版がありません。ISO 32000-1 §8.6.7は、ExtGStateのOP(ストローク)やop(塗り)エントリがtrueのときに何が起こるかを定義しています。現在のペイントが名指さない色材は、ゼロへ消し去られる代わりに、版に既にあったものを保持する、とです。そのように塗られたシアンのオブジェクトは、黄色の版から何も取り除きません。RGBで合成するビューアには黄色を置いておく場所がないため、シアンを上から塗って先へ進みます

どちらの出力も壊れていません。食い違いは2つのイメージングモデルに組み込まれており、誰かが刷り校正をモニターの横に持ってくるまで見えません。黒テキストを誤ってノックアウトに設定したデザイナーや、白ロゴをオーバープリントのままにして表示を期待した人にとって、それは仕事の遅い段階です

HotPDF での CMYK オーバープリント比較。素のビューアはシアンの下の黄色をノックアウトし、オーバープリントプルーフは両プロセスインクをプルーフして、誤ってオーバープリントされた白テキストを暴く
素のビューアは上のインクだけを塗りますが、オーバープリントプルーフは両インクを重ね、誤ってオーバープリントされた白テキストのような罠を暴きます

RenderOverprintPreviewでプルーフを有効にする

HotPDFは、プルーフをドキュメント上の1つのBoolean、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:ペイントが名指すのは黒の版だけなので、下のシアン、マゼンタ、黄色は生き延びます。CMYの背景にオーバープリントで設定された白のDeviceGrayロゴは、したがって消えます
  • SeparationとDeviceN:Cyan、Magenta、Yellow、Blackと名付けられた色材はそのプロセス版を直接置き換え、/Noneはまったく塗らず、/Allはすべてのプロセス版へ書き込みます
  • スポット色材:その他の名前はスポットインクとして扱われ、ティント変換が代替色を与え、それが下のプロセス版の上に掛け合わされます。スポットは置き換えるのではなく、覆ったものを暗くするのです

プルーフは、塗りとストロークのパス、テキストに適用されます。ストロークレンダリングモードのテキストを含みます。ここではopではなくストロークフラグのOPが決めます。画像、インライン画像、shシェーディングは、フルのラスターカラーパイプラインを通し続け、通常どおりの不透明度で塗ります。実務では、これはプレフライトレポートが最も旗を立てるもの、つまりオーバープリントの規則、キーライン、文字、フラットティントを覆います

プルーフがオンでも、透明度は活動を続けます。ノックアウトに設定された透明度グループはやはりノックアウトし、グループはオーバープリント規則と同じパスで合成されるため、オーバープリントする黒背景の上に透明なアートワークを置いた印刷準備済みファイルは、両方の効果を一緒にプルーフします。片方だけ正しくてもう片方が間違っていれば、別の間違ったプルーフが生まれるだけです

この設定は、キャッシュされたレンダリングの同一性にも参加します。RenderLoadedPageToBitmapCachedは、エントリの鍵をページ、DPI、そしてRenderOverprintPreviewを含むレンダリング設定から組み立てたバリアントへ掛け、ディスク層も同じバリアントを使います。通常プレビューとプルーフプレビューを切り替えるビューアは、2つの別のキャッシュエントリを得て、トグルが変わっただけでInvalidateRenderedPageCacheを呼ぶ必要がありません

DeviceCMYKファイルの/OPM 0の罠

人をバグ探しに走らせるのは、このケースです。ファイルがDeviceCMYKのシアン塗りに/op trueを設定しながら、OPMをデフォルトの0のままにしていると、プルーフはそれでもシアンが黄色をノックアウトする様子を示します。プルーフは正しいのです。オーバープリントモード0の下では、§8.6.7により、DeviceCMYKのペイントは4つのプロセス色材すべてを設定し、ゼロを含みます。したがってゼロの黄色コンポーネントは、ノックアウトがするのとまったく同じように、黄色の版を置き換えます。オーバープリントモード0が守るのは、ペイントが名指さない版だけです。4コンポーネントのCMYK色にとって、それはスポットの版であって、他に何もありません

コンポジットプルーフが示すプロセス版の上では、モード0と素のノックアウトは同じピクセルを与えるため、HotPDFはそれらのDeviceCMYKオブジェクトを普通のノックアウトとして塗ります。CMYKオブジェクトに他のプロセスインクをオーバープリントさせるには、/op trueを運ぶExtGStateが、さらに/OPM 1を必要とします。プルーフとデザイナーの意図がDeviceCMYKオブジェクトで食い違ったら、何かを疑う前に、そのExtGStateを開いてOPMエントリを探してください

逆の驚きも存在します。/OPM 1の下では、CMYKの白は4つのゼロであり、オーバープリントの下の4つのゼロは、すべての版をそのままにします。オーバープリントに設定された白文字は刷り紙の上で消え、プルーフはその消滅を示します。これこそ、プルーフを走らせる理由のすべてです

RenderLoadedPageToDeviceの背後の3つのデバイス

THotPDF.RenderLoadedPageToDevice(PageIndex, DPI, Device)は単一のエントリポイントであり、ターゲットは渡したデバイスオブジェクトが決めます。nilのデバイスには例外を上げ、1未満のDPIは150として扱い、デバイスのExecuteメソッドが報告するものを返します。THPDFRenderDeviceは2つの仮想メンバーを持つ抽象基底です。rdkBitmap、rdkDeviceContext、rdkEnhancedMetafileを返すKindと、Execute(Document, PageIndex, DPI)です

同梱の3つのデバイスは、それぞれ違う仕方で出力を所有します:

  • THPDFBitmapRenderDeviceはBitmapプロパティにTBitmapを保持し、TakeBitmapが先にあなたへ渡していなければ、デストラクタで解放します
  • THPDFDeviceContextRenderDevice.Create(ADC, AWidth, AHeight)は、既に持っているHDCへ描き、何も所有しません。ゼロのDCや非正のサイズにはコンストラクタが例外を上げ、背景は一切塗らないため、画面のDCは自分で白塗りが必要です
  • THPDFMetafileRenderDeviceは、TakeMetafileが引き渡すまでTMetafileを保持します。テキストとベクトルパスは拡張メタファイルの中でスケーラブルなままであり、画像XObjectはラスタライズされて届きます
HotPDF のレンダーアーキテクチャ。RenderLoadedPageToDevice の 1 呼び出しが、Kind 値と所有権規則を備えたビットマップ、プリンター デバイス コンテキスト、拡張メタファイルの各レンダーデバイスへ扇状に分岐する様子
1つのレンダリング呼び出しが、Kind でターゲットを報告し、Take メソッドで所有権を引き渡すビットマップ、デバイスコンテキスト、メタファイルの各デバイスへ扇状に分岐します

所有権は、素のDelphiの規則に従います。オブジェクトがデバイスと同じ寿命でよいなら、BitmapやMetafileプロパティを読みます。デバイスより長生きしなければならないなら、Takeメソッドを呼びます。ビットマップを取り、デバイスを解放し、ビットマップの解放を忘れると、漏れはページ数とともに育ちます。5ページのサンプルでは見えず、500ページのバッチでは明白です。各オブジェクトに自分のtry/finallyを与えれば、誰が何を解放するかの問いは自分で答えます

アプリケーションコードでは、is THPDFBitmapRenderDeviceのテストではなく、Kindでディスパッチしてください。ExecuteとKindはpublic仮想メソッドなので、自分のデバイスクラスを書けますし、種別を検査するコードは、同梱デバイスの代役に自分のデバイスが立っても動き続けます

プリンターレンダリングが隅に落ちる理由

デバイスコンテキスト経路は、ページのサイズを、渡された幅と高さではなくDPI引数から決めます。レンダラーはPDF単位をDPI / 72でスケールし、高さはページの原点を面の下端へ固定するためにしか使いません。300をハードコードしてジョブを600dpiのプリンターへ送ると、ページは半分のサイズで、用紙の左下へ押し込まれて出てきます。プリンター解像度はドライバーの性質ですから、ドライバーに尋ねてください:

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ほどです。上の引き伸ばし矩形は印刷可能領域を満たします。ページと用紙の形が違うなら、ページのアスペクト比をそこへ保ってください

プルーフがプレフライトの隣に収まる場所

プルーフが答えるのは1つの問いです。オブジェクトが重なる場所で、どのインクが生き残るか。他のレンダリングと同じ24ビットRGBビットマップの上で合成されるため、その色は、測定された刷り紙の色ではなく、インクの組み合わせを示します。プレス条件はカラーマネジメントの問いであり、ドキュメントはそれをConfigureRenderICCWorkflowで扱います。プルーフプロファイル、出力プロファイル、ブラックポイント補正フラグを受け取り、外すためのClearRenderICCWorkflowもあります

プルーフレンダリングが最も生産的な居場所は、既に走らせている検査の隣です。プレフライトパスは、黒テキストがオーバープリントに設定されていると報告し、プルーフレンダリングはオペレーターに、それがページ上で何をするかを見せ、両者は同じレポートに着地します。パッケージングの仕事はオーバープリントとスポットインクを混ぜがちで、SeparationとDeviceNのスポットカラーレンダリングの解説が、RenderLoadedPageSeparationPlateによる単一版ビューを含め、同じページの色材の側を扱います。PDFページのビットマップへのレンダリングとTPrinterを通した読み込み済みPDFの印刷の記事が、2つのデバイスターゲットの素の形を扱います

これらすべては、実行ファイルの隣に同梱するレンダリングDLLなしで、ネイティブなDelphiと C++Builder コードの中で走ります。HotPDF Delphiコンポーネントページに、レンダリングとプリプレスの機能一覧があり、自分の印刷ファイルに対してこれらの例を走らせるトライアルビルドがあります