기술 문서

HotPDF에서 CMYK 오버프린트 교정과 렌더 디바이스

노란 배경 위에 시안 상자가 앉아 있고, 그것을 그리는 그래픽 상태는 /op true와 /OPM 1을 지니고 있습니다. 화면에서 겹침은 평평한 시안입니다. 인쇄판에서는 같은 겹침이 초록으로 나옵니다. RIP가 노란 판을 그대로 두고 그 위에 시안을 얹었기 때문입니다. 두 결과 모두 파일을 글자 그대로 따른 것이고, 화면 렌더는 그저 그 플래그를 이미징 디바이스가 어떻게 다루는지 물어본 적이 없을 뿐입니다. 화면에서 내린 승인은 종이에 닿지 않을 색을 덮는 셈입니다

페이지가 미리보기와 다르게 인쇄되는 이유는?

오버프린트는 출력 디바이스에 대한 판에 관한 지시이고, RGB 화면에는 판이 없습니다. ISO 32000-1 §8.6.7은 ExtGState의 OP(stroke)나 op(fill) 항목이 true일 때 일어나는 일을 정의합니다. 현재 페인트가 이름을 부르지 않는 색소는 0으로 지워지는 대신 이미 자기 판에 있던 것을 유지합니다. 그렇게 그려진 시안 객체는 노란 판에서 아무것도 걷어내지 않습니다. RGB로 합성하는 뷰어는 노란색을 간직할 곳이 없으므로 시안을 위에 그리고 지나갑니다

어느 출력도 망가진 것이 아닙니다. 불일치는 두 이미징 모델에 내장되어 있고, 누군가 인쇄 교정쇠를 모니터 옆에 들고 올 때까지 보이지 않습니다. 검은 텍스트를 실수로 녹아웃으로 설정했거나, 흰색 로고를 오버프린트로 두고 나오기를 기대한 디자이너에게는 그것이 작업의 늦은 시점입니다

HotPDF의 CMYK 오버프린트 비교. 평범한 뷰어는 시안 아래 노란색을 녹아웃하지만, 오버프린트 미리보기는 두 프로세스 잉크를 교정하고 실수로 오버프린트된 흰 텍스트를 드러냄
평범한 뷰어는 맨 위 잉크만 그리지만 오버프린트 교정은 두 잉크를 겹쳐 그려서 실수로 오버프린트된 흰 텍스트 같은 함정을 드러냅니다

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: 0인 컴포넌트는 자기 판을 그대로 두고, 0이 아닌 컴포넌트는 그것을 대체합니다. 노란 배경 위의 Cyan 100 / Yellow 0은 노란색을 유지한 채 초록이 됩니다
  • DeviceGray: 페인트는 검은 판만 이름 부르므로 아래의 시안, 마젠타, 노랑이 살아남습니다. CMY 배경 위에 오버프린트로 설정된 흰색 DeviceGray 로고가 사라지는 이유입니다
  • Separation과 DeviceN: Cyan, Magenta, Yellow, Black이라는 이름의 색소는 그 프로세스 판을 곧장 대체하고, /None은 전혀 그리지 않으며, /All은 모든 프로세스 판을 기록합니다
  • 스팟 색소: 그 외의 이름은 스팟 잉크로 취급됩니다. 틴트 변환이 대체 색을 주고, 그것이 아래의 프로세스 판들 위에 곱해지므로, 스팟은 대체하는 대신 자기가 덮는 것을 어둡게 합니다

교정은 채워진 경로와 stroke 경로, 그리고 텍스트에 적용됩니다. stroke 렌더 모드의 텍스트 포함이며, 그 경우 op 대신 stroke 플래그 OP가 결정합니다. 이미지, 인라인 이미지, sh 셰이딩은 자신의 전체 래스터 색 파이프라인을 그대로 거치며 평범한 불투명도로 그립니다. 실전에서는 프리플라이트 리포트가 가장 많이 지적하는 것, 즉 오버프린트 규칙, 키라인, 텍스트, 플랫 틴트를 커버합니다

교정이 켜져 있어도 투명도는 살아 있습니다. knockout이 설정된 투명도 그룹은 여전히 녹아웃하고, 그룹은 오버프린트 규칙과 같은 패스에서 합성되므로, 오버프린트하는 검은 배경 위의 투명 아트웍을 지닌 인쇄 준비 파일은 두 효과를 함께 교정합니다. 하나만 맞추면 그저 다른 잘못된 교정쇠가 나올 뿐입니다

이 설정은 캐시된 렌더의 식별에도 관여합니다. RenderLoadedPageToBitmapCached는 페이지, DPI, 그리고 RenderOverprintPreview를 포함한 렌더 설정에서 만든 variant로 항목의 키를 정하고, 디스크 계층도 같은 variant를 씁니다. 일반 미리보기와 교정 미리보기를 오가는 뷰어는 별개의 캐시 항목 두 개를 얻고, 토글이 바뀌었다는 이유만으로 InvalidateRenderedPageCache를 호출할 필요가 없습니다

DeviceCMYK 파일의 /OPM 0 함정

사람들이 버그를 찾게 만드는 사례가 이것입니다. 파일이 DeviceCMYK 시안 채우기에 /op true를 설정하면서 OPM을 기본값 0으로 두면, 교정은 여전히 시안이 노랑을 녹아웃한다고 보여 줍니다. 교정은 맞습니다. 오버프린트 모드 0 아래에서 §8.6.7은 DeviceCMYK 페인트가 0을 포함한 네 프로세스 색소 전부를 설정한다고 말합니다. 그래서 0인 노란 컴포넌트는 녹아웃이 하듯 정확히 노란 판을 대체합니다. 오버프린트 모드 0이 지키는 것은 페인트가 이름 부르지 않는 판뿐이고, 네 컴포넌트 CMYK 색에 대해 그것은 스팟 판뿐입니다

합성 교정이 보여 주는 프로세스 판들에서는 모드 0과 평범한 녹아웃이 같은 픽셀을 내므로, HotPDF는 그런 DeviceCMYK 객체를 평범한 녹아웃으로 그립니다. CMYK 객체가 다른 프로세스 잉크 위에 오버프린트하려면 /op true를 지닌 ExtGState가 /OPM 1도 필요합니다. 교정과 디자이너의 의도가 DeviceCMYK 객체에서 어긋난다면 다른 것을 의심하기 전에 그 ExtGState를 열어 OPM 항목을 찾아 보십시오

거꾸로 된 놀라움도 존재합니다. /OPM 1 아래에서 CMYK 흰색은 네 개의 0이고, 오버프린트 아래의 네 0은 모든 판을 그대로 둡니다. 오버프린트로 설정된 흰 텍스트는 인쇄쇠에서 사라지고 교정은 그 사라짐을 보여 줍니다. 교정을 돌리는 이유 전부입니다

RenderLoadedPageToDevice 뒤의 세 디바이스

THotPDF.RenderLoadedPageToDevice(PageIndex, DPI, Device)는 단일 진입점이며 대상은 여러분이 넘기는 디바이스 객체가 결정합니다. nil 디바이스에는 예외를 일으키고, 1 미만의 DPI는 150으로 취급하며, 디바이스의 Execute 메서드가 보고하는 것을 돌려줍니다. THPDFRenderDevice는 두 가상 멤버를 지닌 추상 기반입니다. rdkBitmap, rdkDeviceContext, rdkEnhancedMetafile을 돌려주는 Kind, 그리고 Execute(Document, PageIndex, DPI)입니다

함께 실려 나오는 세 디바이스는 각자 다른 방식으로 출력을 소유합니다:

  • THPDFBitmapRenderDevice는 Bitmap 프로퍼티에 TBitmap을 지니고, TakeBitmap이 먼저 넘겨주지 않았다면 소멸자에서 해제합니다
  • THPDFDeviceContextRenderDevice.Create(ADC, AWidth, AHeight)는 이미 가지고 있는 HDC에 그리고 아무것도 소유하지 않습니다. 생성자는 0 DC나 0 이하 크기에 예외를 일으키고, 배경을 칠하지 않으므로 화면 DC에는 흰 채우기가 필요합니다
  • THPDFMetafileRenderDevice는 TakeMetafile이 넘겨줄 때까지 TMetafile을 지닙니다. 텍스트와 벡터 경로는 확장 메타파일 안에서 확장 가능하게 유지되고, 이미지 XObject는 래스터화되어 도착합니다
HotPDF 렌더 아키텍처. RenderLoadedPageToDevice 호출 하나가 Kind 값과 소유 규칙을 지닌 비트맵, 프린터 디바이스 컨텍스트, 확장 메타파일 렌더 디바이스로 뻗어 나감
렌더링 호출 하나가 비트맵, 디바이스 컨텍스트, 메타파일 디바이스로 갈라지며, 각자의 Kind가 대상을 보고하고 Take 메서드가 소유권을 넘겨 줍니다

소유는 평범한 Delphi 규칙을 따릅니다. 객체가 디바이스와 같은 수명으로 사는 것으로 충분하면 Bitmap이나 Metafile 프로퍼티를 읽고, 그보다 오래 살아야 하면 Take 메서드를 호출하십시오. 비트맵을 가져오고, 디바이스를 해제하고, 비트맵 해제를 잊으면 누수는 페이지 수와 함께 자랍니다. 5페이지 샘플에서는 보이지 않고 500페이지 배치에서는 명백합니다. 객체마다 자기 try/finally를 주면 누가 무엇을 해제하는지 질문은 스스로 답합니다

애플리케이션 코드에서는 is THPDFBitmapRenderDevice 검사 대신 Kind로 디스패치하십시오. Execute와 Kind는 공개 가상 메서드이므로 여러분 자신의 디바이스 클래스를 쓸 수 있고, 종류를 검사하는 코드는 여러분의 것이 함께 실린 디바이스를 대신하더라도 계속 동작합니다

프린터 렌더는 왜 구석에 박히나?

디바이스 컨텍스트 경로는 페이지 크기를 여러분이 넘기는 폭과 높이가 아니라 DPI 인수에서 정합니다. 렌더러는 PDF 단위를 DPI / 72로 스케일하고 높이는 페이지 원점을 표면 바닥에 고정하는 데만 씁니다. 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 인쇄하기 글들은 두 디바이스 대상을 맨 형태로 다룹니다

이 모든 것은 실행 파일 곁에 나를 렌더링 DLL 없이 네이티브 Delphi와 C++Builder 코드에서 돌아갑니다. HotPDF Delphi 컴포넌트 페이지에 렌더링과 인쇄 전 기능이 정리되어 있고, 여러분의 인쇄 파일로 이 예제들을 돌려 볼 수 있는 평가판 빌드가 있습니다