Artykuł techniczny

Proofing overprintu CMYK i urządzenia renderujące w HotPDF

Cyjanowy prostokąt siedzi na żółtym tle, a stan graficzny, który go maluje, niesie /op true i /OPM 1. Na ekranie nałożenie to płaski cyjan. Na arkuszu drukarskim te same nałożenie wychodzi zielone, bo RIP zostawił płytę żółtą w spokoju i położył cyjan na jej wierzchu. Oba wyniki trzymają się pliku co do litery; render ekranowy po prostu nigdy nie zapytał, co urządzenie odtwarzające robi z tą flagą — więc akceptacja zrobiona na ekranie przykrywa kolor, który nigdy nie dotrze do papieru

Dlaczego strona drukuje się inaczej, niż podgląda?

Overprint to instrukcja dla urządzenia wyjściowego o płytach, a ekran RGB nie ma płyt. ISO 32000-1 §8.6.7 definiuje, co się dzieje, gdy wpis OP (kreskowanie) albo op (wypełnianie) ExtGState jest prawdziwy: coloranty, których bieżąca farba nie nazywa, zachowują to, co już leżało na ich płycie, zamiast być wymazywane do zera. Obiekt cyjanowy malowany tak nie zdejmuje niczego z płyty żółtej. Przeglądarka składająca w RGB nie ma gdzie trzymać żółtego, więc maluje cyjan na wierzchu i jedzie dalej

Żadno z wyjść nie jest zepsute. Rozjazd siedzi wbudowany w dwa modele odtwarzania i pozostaje niewidoczny, dopóki ktoś nie przyłoży drukowanej próbki do monitora. To późno w pracy dla projektanta, który ustawił czarny tekst na knockout przez pomyłkę albo zostawił białe logo na overprinccie i liczył, że się pokaże

Porównanie overprint CMYK w HotPDF, gdzie zwykła przeglądarka wygryza żółty spod cyjanu, podczas gdy podgląd overprintu zestawia obie farby procesowe i ujawnia przypadkowo nadrukowany biały tekst
Zwykła przeglądarka maluje samą górną farbę, podczas gdy proof overprint nakłada obie farby, obnażając pułapki takie jak przypadkowo nadrukowany biały tekst

Włączanie proofu przez RenderOverprintPreview

HotPDF wystawia proof jako jedną wartość Boolean na dokumencie, THotPDF.RenderOverprintPreview, domyślnie False. Po ustawieniu render bitmapowy śledzi płyty procesowe pod każdą nakładającą farbą i nakłada reguły §8.6.7 przed zapisem piksela, więc nałożenia pokazują kombinację farb, którą wyprodukowałaby maszyna. Zostawienie domyślnego wyłączenia jest właściwe do zwykłego oglądania, bo czytelnicy biurowego PDF oczekują, że będzie wyglądał tak, jak pokazuje go ich zwykła przeglądarka. Włącz go w kroku, którego wyjście idzie do operatora druku przygotowawczego

var
  Pdf: THotPDF;
  Device: THPDFBitmapRenderDevice;
  Proof: TBitmap;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.LoadFromFile('cover-cmyk.pdf');
    Pdf.RenderOverprintPreview := True;    // proofuj farby, domyślnie False
    Device := THPDFBitmapRenderDevice.Create;
    try
      if Pdf.RenderLoadedPageToDevice(0, 150, Device) then
      begin
        Proof := Device.TakeBitmap;        // wywołujący posiada Proof od tego momentu
        try
          Image1.Picture.Assign(Proof);
        finally
          Proof.Free;
        end;
      end;
    finally
      Device.Free;
    end;
  finally
    Pdf.Free;
  end;
end;

Reguły płyt zależą od przestrzeni kolorów nakładającej farby i warto je znać, bo każda wyjaśnia inne zaskoczenie na próbce:

  • DeviceCMYK pod /OPM 1: składnik zerowy zostawia swoją płytę nietkniętą, składnik niezerowy ją zastępuje. Cyjan 100 / żółty 0 na żółtym tle zachowuje żółty i robi zielony
  • DeviceGray: farba nazywa wyłącznie płytę czarną, więc cyjan, magenta i żółty pod spodem przeżywają. Białe logo DeviceGray ustawione na overprint na tle CMY dlatego znika
  • Separation i DeviceN: colorant nazwany Cyan, Magenta, Yellow albo Black zastępuje płytę procesową wprost, /None nie maluje niczego, a /All zapisuje każdą płytę procesową
  • Coloranty spot: każda inna nazwa jest traktowana jako farba spot; jej transformacja tintu daje kolor alternatywny, który jest mnożony przez płyty procesowe pod spodem, więc spot zaciemnia to, co pokrywa, zamiast tego zastępować

Proof obejmuje wypełniane i kreskowane ścieżki oraz tekst, włącznie z tekstem w trybie renderowania kreskowania, gdzie decyduje flaga kreskowania OP, a nie op. Obrazy, obrazy inline i cieniowania sh przechodzą dalej przez swoje pełne rastrowe potoki kolorów i malują ze zwykłą nieprzezroczystością. W praktyce pokrywa to to, co raporty preflight flagują najczęściej: reguły overprintu, keyline'y, tekst i płaskie tinty

Przezroczystość zostaje żywa, póki proof jest włączony. Grupa przezroczystości z ustawionym knockout nadal wycina, a grupa jest składana w tym samym przebiegu co reguły overprintu, więc plik gotowy do druku z przezroczystą grafiką na nakładającym się czarnym tle proofuje oba efekty razem. Trafienie w jedno i pudło w drugie wyprodukowałoby po prostu inny zły proof

Ustawienie bierze też udział w tożsamości renderów w cache. RenderLoadedPageToBitmapCached kluczuje swoje wpisy na stronie, DPI i wariancie zbudowanym z ustawień renderowania, do których należy RenderOverprintPreview, a warstwa dyskowa używa tego samego wariantu. Przeglądarka przełączająca się między podglądem zwykłym a proofem dostaje dwa osobne wpisy w cache i nigdy nie potrzebuje wywołania InvalidateRenderedPageCache tylko dlatego, że przełącznik się zmienił

Pułapka /OPM 0 w plikach DeviceCMYK

Oto przypadek, który wysyła ludzi szukać błędu. Plik ustawia /op true na cyjanowym wypełnieniu DeviceCMYK, ale zostawia OPM na domyślnym 0 — proof nadal pokazuje cyjan wycinający żółty. Proof ma rację. Pod trybem overprintu 0 §8.6.7 mówi, że farba DeviceCMYK ustawia wszystkie cztery coloranty procesowe, zera włącznie, więc zerowy składnik żółty zastępuje płytę żółtą dokładnie tak, jak zrobiłby to knockout. Tryb overprintu 0 chroni tylko płyty, których farba nie nazywa — co dla koloru czteroskładnikowego CMYK znaczy płyty spot i nic więcej

Na płytach procesowych, które pokazuje proof składany, tryb 0 i zwykły knockout dają te same piksele, więc HotPDF maluje te obiekty DeviceCMYK jako zwykłe knockouti. Żeby obiekt CMYK nakładał się na inne farby procesowe, ExtGState niosący /op true potrzebuje też /OPM 1. Gdy proof i zamiar projektanta rozmijają się na obiekcie DeviceCMYK, otwórz ten ExtGState i poszukaj wpisu OPM, zanim podejrzysz cokolwiek innego

Odwrócone zaskoczenie też istnieje. Pod /OPM 1 biel CMYK to cztery zera, a cztery zery pod overprintem zostawiają każdą płytę taką, jaka była. Biały tekst ustawiony na overprint znika z arkusza, a proof pokazuje jego znikanie — i to jest cały powód, żeby go odpalać

Trzy urządzenia za RenderLoadedPageToDevice

THotPDF.RenderLoadedPageToDevice(PageIndex, DPI, Device) to pojedynczy punkt wejścia, którego cel rozstrzyga obiekt urządzenia, który podasz. Podnosi wyjątek dla urządzenia nil, traktuje DPI poniżej 1 jako 150 i zwraca to, co urządzenie raportuje z metody Execute. THPDFRenderDevice to abstrakcyjna baza z dwoma wirtualnymi składnikami: Kind, zwracającym rdkBitmap, rdkDeviceContext albo rdkEnhancedMetafile, oraz Execute(Document, PageIndex, DPI)

Trzy dostarczone urządzenia posiadają swoje wyjścia po różnemu:

  • THPDFBitmapRenderDevice trzyma TBitmap we właściwości Bitmap i zwalnia go w destruktorze, chyba że TakeBitmap oddał go ci najpierw
  • THPDFDeviceContextRenderDevice.Create(ADC, AWidth, AHeight) rysuje do HDC, który już masz, i nie posiada niczego; konstruktor podnosi wyjątek na zerowym DC albo niedodatnim rozmiarze, a tło nie jest malowane, więc ekranowy DC potrzebuje własnego białego wypełnienia
  • THPDFMetafileRenderDevice trzyma TMetafile, aż TakeMetafile go przekaże. Tekst i wektorowe ścieżki zostają skalowalne w rozszerzonym metapliku, a image XObject przychodzą zrastrowane
Architektura renderowania HotPDF rozdzielająca jedno wywołanie RenderLoadedPageToDevice na urządzenia renderujące bitmapę, kontekst urządzenia drukarki i metaplik rozszerzony z ich wartościami Kind i regułami własności
Jedno wywołanie renderujące rozchodzi się na urządzenia bitmapy, kontekstu urządzenia i metafile, których Kind raportuje cel, a których metody Take przekazują własność

Własność podąża za zwykłymi regułami Delphi. Czytaj właściwość Bitmap albo Metafile, gdy obiekt musi żyć tylko tak długo jak urządzenie; wołaj metodę Take, gdy ma je przeżyć. Weź bitmapę, zwolnij urządzenie, zapomnij o bitmapie — a wyciek rośnie z liczbą stron: niewidoczny na próbce pięciostronicowej, oczywisty na partii pięciuset stron. Daj każdemu obiektowi własne try/finally, a pytanie kto co zwalnia odpowie się samo

Dyspatchuj po Kind w kodzie aplikacji zamiast testować is THPDFBitmapRenderDevice. Skoro Execute i Kind są publicznymi metodami wirtualnymi, możesz napisać własną klasę urządzenia, a kod sprawdzający kind dalej działa, gdy któraś z twoich zastępuje dostarczone urządzenie

Dlaczego render drukarkowy ląduje w rogu?

Ścieżka kontekstu urządzenia wymiaruje stronę z argumentu DPI, nie z szerokości i wysokości, które podajesz. Renderer skaluje jednostki PDF przez DPI / 72 i używa wysokości wyłącznie do zakotwiczenia początku strony na dole powierzchni. Zahardkoduj 300 i wyślij zadanie do drukarki 600 dpi, a strona wyjdzie w połowie rozmiaru, wepchnięta w lewy dolny róg arkusza. Rozdzielczość drukarki to własność sterownika, więc zapytaj o nią sterownika:

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
    // zapytaj sterownik; 300, 600 i 1200 dpi to wszystko częste wartości
    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;            // zamiana dla stron 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;

Szerokość i wysokość pochodzą z widocznego pola strony — CropBox przyciętego do MediaBox — w własnej rozdzielczości drukarki, a wywołujący zamienia je miejscami dla obróconych stron przed utworzeniem urządzenia. To odpowiada temu, czego renderer oczekuje przy kotwiczeniu początku

Proofing overprintu nie jedzie tą drogą. Urządzenia kontekstu urządzenia i metapliku emitują wywołania GDI wprost do celu, a reguły płyt potrzebują pikseli już leżących pod każdą farbą — co ma tylko render bitmapowy. Dla próbki na papierze wyrenderuj proof bitmapowy i rozciągnij go na kanwę drukarki. Małe własne urządzenie trzyma to za tym samym punktem wejścia:

type
  // urządzenie własne: proof bitmapowy, potem rozciągnięty na drukarkę
  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;  // zostaw ustawienie wywołujące takie, jakie było
  end;
  Result := Proof <> nil;
  if Result then
  try
    Printer.Canvas.StretchDraw(
      Rect(0, 0, Printer.PageWidth, Printer.PageHeight), Proof);
  finally
    Proof.Free;
  end;
end;

Wołaj go między Printer.BeginDoc a Printer.EndDoc z DPI dającym bitmapie dość pikseli na arkusz — około 200 do 300 dla próbki wielkości listu. Prostokąt rozciągania wyżej wypełnia obszar drukowalny; zachowaj w nim proporcje strony, jeśli strona i papier różnią się kształtem

Gdzie proofing pasuje obok preflight

Proof odpowiada na jedno pytanie: które farby przeżywają tam, gdzie obiekty się nakładają. Jest składany na tej samej bitmapie RGB 24-bitowej co każdy inny render, więc jego kolory pokazują kombinację farb, a nie zmierzoną barwę arkusza. Warunki maszynowe to pytanie o zarządzanie kolorem i dokument załatwia je przez ConfigureRenderICCWorkflow, które bierze profil proofu, profil wyjściowy i flagę kompensacji czerni, oraz ClearRenderICCWorkflow, żeby je z powrotem zrzucić

Najbardziej produktywnym miejscem dla renderu proofu jest sąsiedztwo kontroli, które już odpalasz. Przebieg preflight raportuje, że czarny tekst jest ustawiony na overprint; render proofu pokazuje operatorowi, co to robi na stronie; oba lądują w tym samym raporcie. Praca opakowaniowa miesza overprint z farbami spot, a przejście przez artykuł renderowanie farb spot Separation i DeviceN pokrywa stronę colorantów tej samej karty, włącznie z widokami pojedynczej płyty przez RenderLoadedPageSeparationPlate. Artykuły o renderowaniu strony PDF do bitmapy i drukowaniu wczytanego PDF przez TPrinter pokrywają oba cele urządzeń w ich gołej formie

Całość biegnie w natywnym kodzie Delphi i C++Builder bez żadnej DLL renderującej do wysyłki obok pliku wykonywalnego. Strona komponentu HotPDF Delphi wypisuje cechy renderowania i przygotowania do druku oraz ma build próbny, na którym odpalisz te przykłady na własnych plikach drukowych