Artykuł techniczny

Drukowanie załadowanego pliku PDF w Delphi za pomocą HotPDF i TPrinter

Komponent HotPDF pozwala na drukowanie załadowanego pliku PDF na fizycznej drukarce, w dowolnym kontekście urządzenia Windows (device context) lub kopiowanie do schowka za pomocą trzech wywołań: PrintLoadedDocument steruje komponentem VCL TPrinter, RenderLoadedPageToDC rysuje stronę bezpośrednio w dowolnym kontekście HDC, a RenderLoadedPageToClipboard kopiuje stronę jako mapę bitową lub wektorowy plik metadanych (metafile). HotPDF to natywny komponent VCL PDF dla środowisk Delphi i C++Builder. Ta bezpośrednia ścieżka drukowania została wprowadzona w wersji v2.331.0 i udoskonalona w wersji v2.348.0, dzięki czemu nie musisz już samodzielnie pisać pętli obsługi TPrinter wokół renderera map bitowych. Wszystkie trzy wywołania rysują wektorową zawartość strony bezpośrednio na docelowej powierzchni, co sprawia, że wydruk pozostaje tak ostry, jak pozwala na to rozdzielczość drukarki

Dlaczego warto rysować stronę PDF bezpośrednio w drukarce zamiast generować mapę bitową?

Strona PDF to zestaw wektorowych operatorów rysowania — ścieżek, tekstów i obrazów wykonywanych zgodnie z modelem graficznym ISO 32000-1 — a nie siatka pikseli. Oczywistym sposobem na jej wydrukowanie jest rasteryzacja do mapy bitowej i przeniesienie (blitowanie) tej mapy na płótno (canvas) drukarki. To rozwiązanie działa, ale pozbawia nas głównej zalety drukarki. Współczesna drukarka laserowa oferuje rozdzielczość 600 lub 1200 DPI. Mapa bitowa zrasteryzowana w rozdzielczości 150 or 300 DPI, a następnie rozciągnięta do dopasowania na wydruku, będzie nieostra, z poszarpanymi krawędziami tekstu i widoczną interpolacją. Metoda RenderLoadedPageToDC wybiera lepszą drogę: odtwarza strumień zawartości strony jako wywołania GDI kierowane bezpośrednio do docelowego kontekstu urządzenia. Dzięki temu tekst wektorowy i grafika liniowa trafiają do drukarki jako wektory i są rasteryzowane tylko raz — przez samą drukarkę, w jej natywnej rozdzielczości

Jak wydrukować załadowany plik PDF na fizycznej drukarce w Delphi?

HotPDF drukuje załadowany dokument za pomocą metody PrintLoadedDocument(Printer, Options), która przyjmuje przypisany obiekt VCL TPrinter oraz rekord parametrów THPDFPrintOptions, drukuje wybrane strony i zwraca liczbę wysłanych stron. Najpierw ustaw właściwość Printer.PrinterIndex, aby wybrać urządzenie, a następnie uzupełnij rekord opcji — funkcja klasy THPDFPrintOptions.Default dostarcza optymalne wartości domyślne. Biblioteka otwiera zadanie drukowania przez Printer.BeginDoc, przechodzi przez zakres stron generując Printer.NewPage między nimi i zamyka zadanie poprzez Printer.EndDoc, dzięki czemu nie musisz ręcznie zarządzać cyklem życia zadania drukowania

uses Printers;

var
  Pdf: THotPDF;
  Opts: THPDFPrintOptions;
begin
  Pdf := THotPDF.Create(nil);
  try
    if Pdf.LoadFromFile('invoice.pdf') > 0 then
    begin
      Printer.PrinterIndex := -1;         // -1 selects the default printer
      Opts := THPDFPrintOptions.Default;  // all pages, 300 DPI, FitPage, colour
      Opts.PageRange := '1,3-5';          // one-based; empty prints every page
      Opts.Copies := 2;
      Pdf.PrintLoadedDocument(Printer, Opts);
    end;
  finally
    Pdf.Free;
  end;
end;

Ciąg znaków PageRange korzysta z indeksowania od 1 i jest interpretowany tak samo, jak w oknie dialogowym drukowania: zapis '1,3-5' drukuje stronę 1 oraz strony od 3 do 5, a pusty ciąg drukuje cały dokument. Właściwość Copies odpowiada bezpośrednio za Printer.Copies. Warto jednak pamiętać o kwestii układania stron (Collate): ponieważ biblioteka VCL obsługuje układanie w różny sposób w zależności od wersji Delphi, HotPDF ustawia liczbę kopii i pozostawia układanie domyślnym ustawieniom sterownika drukarki. Jeśli potrzebujesz gwarantowanej kolejności układania stron, skonfiguruj ją bezpośrednio w sterowniku poprzez TPrinterSetupDialog, zamiast polegać na flagach w kodzie

W jaki sposób parametry DPI i FitPage określają skalę wydruku?

Interakcja między FitPage a DPI jest warta zrozumienia przed wdrożeniem kodu. Przy wartości FitPage ustawionej na True (domyślnie) HotPDF odczytuje obszar MediaBox każdej strony, skaluje go do wymiarów Printer.PageWidth i Printer.PageHeight z zachowaniem proporcji stron oraz renderuje w skali, która pozwala na wypełnienie arkusza papieru. W tym trybie wartość Options.DPI jest nadpisywana przez skalę dopasowania, ponieważ wynik musi odpowiadać fizycznym wymiarom strony drukarki. Przy FitPage ustawionym na False, HotPDF renderuje każdą stronę w skali 1:1 dokładnie według Options.DPI, mapując 72 jednostki PDF na piksele urządzenia DPI. Jest to pożądane zachowanie, gdy strona źródłowa i papier mają ten sam rozmiar i zależy Ci na dokładnej rozdzielczości. Wybierz FitPage, gdy rozmiar strony źródłowej i papieru w podajniku się różnią; wybierz 1:1, gdy są zgodne i zależy Ci na stałym skalowaniu

Dwie opcje dodane w wersji v2.348.0 rozwiązują problem wydruków, które wyglądają „prawie” dobrze. Każda fizyczna drukarka posiada margines sprzętowy (hard margin) — nieprzeznaczony do druku obszar na krawędzi papieru. Domyślnie punkt początkowy strony plasuje się wewnątrz tego marginesu, co przy druku pełnoekranowym (full-bleed) powoduje ucięcie krawędzi. Ustawienie właściwości IgnoreHardMargin na True sprawia, że HotPDF odczytuje wartości PHYSICALOFFSETX i PHYSICALOFFSETY drukarki, a następnie przesuwa punkt początkowy urządzenia tak, aby zneutralizować to przesunięcie. Wtedy punkt początkowy strony pokrywa się z rzeczywistym rogiem arkusza. Druga opcja, AutoRotate, jest domyślnie włączona: odczytuje ona MediaBox każdej strony i jeśli strona jest szersza niż wyższa, przełącza orientację na poziomą (landscape), dzięki czemu szeroka strona nie zostanie przycięta do pionu. Opcja AutoRotate ustępuje miejsca w momencie, gdy zdefiniujesz Orientation jawnie w kodzie

Jak wyenderować stronę PDF w dowolnym kontekście urządzenia?

Metoda RenderLoadedPageToDC(PageIndex, DC, DPI, W, H) to podstawowa funkcja leżąca u podstaw pętli drukowania i jest bardzo użyteczna sama w sobie. Metoda PrintLoadedDocument wywołuje ją raz dla każdej strony na płótnie drukarki, ale celem może być dowolny kontekst HDC systemu Windows: kontekst pamięci poza ekranem (off-screen memory DC), płótno kontrolki we własnej przeglądarce czy powierzchnia podglądu wydruku. Parametr PageIndex korzysta z indeksowania od 0, a W i H to wymiary powierzchni docelowej w jednostkach urządzenia. Renderer przechodzi przez sekcję /Contents strony i wysyła wywołania GDI do Twojego kontekstu. Warto pamiętać o jednej rzeczy: funkcja ta nie rysuje tła. To wywołujący zarządza powierzchnią, więc jeśli potrzebujesz białego papieru pod stroną, musisz samodzielnie wypełnić kontekst przed wywołaniem renderowania, co pozwala na nakładanie strony na istniejące grafiki

// Draw page 1 into an off-screen 24-bit surface at 150 DPI
var
  Surface: TBitmap;
begin
  Surface := TBitmap.Create;
  try
    Surface.PixelFormat := pf24bit;
    Surface.SetSize(1275, 1650);           // 8.5 x 11 in at 150 DPI
    Surface.Canvas.Brush.Color := clWhite;
    Surface.Canvas.FillRect(Surface.Canvas.ClipRect);  // caller paints the background
    Pdf.RenderLoadedPageToDC(0, Surface.Canvas.Handle, 150, Surface.Width, Surface.Height);
    // Surface now holds the composited page, ready to blit or save
  finally
    Surface.Free;
  end;
end;

Gdy potrzebujesz gotowego obiektu TBitmap — miniatury, wyeksportowanego obrazu czy podglądu do zapamiętania w pamięci podręcznej — skorzystaj z metody RenderLoadedPageToBitmap opisanej w powiązanym artykule o renderowaniu stron PDF do mapy bitowej w Delphi. Różnica polega na tym, kto zarządza powierzchnią: wywołanie bitmapy alokuje i zwraca bufor pikseli, podczas gdy RenderLoadedPageToDC rysuje bezpośrednio w posiadanym już kontekście urządzenia (który udostępnia np. płótno drukarki). Obie metody współdzielą ten sam interpreter strumienia zawartości i silnik osadzonych glifów, więc tekst w obu przypadkach zachowuje oryginalne kontury

Jak wkleić stronę PDF do programu Word lub edytora graficznego?

Metoda RenderLoadedPageToClipboard(PageIndex, DPI, AsMetafile) umieszcza wybraną stronę w schowku systemu Windows, skąd można ją wkleić do programów Word, PowerPoint, wiadomości e-mail czy edytora graficznego. Flaga AsMetafile wybiera format zapisu i ta decyzja ma spore znaczenie. Przekazanie wartości False sprawia, że HotPDF umieszcza w schowku 24-bitową mapę bitową (CF_BITMAP) wyrenderowaną w określonej rozdzielczości DPI — jest to stała siatka pikseli, którą można wkleić wszędzie, ale ulegnie ona rozmyciu przy skalowaniu. Przekazanie wartości True powoduje umieszczenie w schowku rozszerzonego pliku metadanych (CF_ENHMETAFILE), w którym tekst i grafika wektorowa są przesyłane jako polecenia rysowania GDI. Dzięki temu wklejony element pozostaje ostry przy dowolnym powiększeniu i dopasowuje się do wymiarów docelowego dokumentu

// Copy page 2 as a scalable vector metafile for pasting into Word
Pdf.RenderLoadedPageToClipboard(1, 300, True);

// Copy the same page as a fixed 300-DPI bitmap instead
Pdf.RenderLoadedPageToClipboard(1, 300, False);

Gdzie kończą się możliwości ścieżki drukowania

Należy jasno zaznaczyć, że wszystkie opisane tutaj funkcje są dostępne wyłącznie w systemie Windows. Metody PrintLoadedDocument, RenderLoadedPageToDC oraz RenderLoadedPageToClipboard bazują na komponentach TPrinter, modelu kontekstu urządzenia GDI, schowku Windows oraz rozszerzonych plikach metadanych (EMF) — czyli na architekturze Win32 i Win64. Na innych platformach pozostała część komponentu HotPDF pozwala na tworzenie, edycję i odczyt plików PDF, ale funkcje oparte na GDI nie są dostępne. Renderer obsługuje typowe elementy dokumentu, takie jak tekst, ścieżki i obrazy, dlatego projekty z zaawansowanymi gradientami i zarządzaniem kolorami ICC zostaną wydrukowane jako wierny podgląd, a nie wydruk kalibrowany kolorystycznie

W przypadku drukowania wsadowego wielu plików należy wczytywać i drukować każdy dokument po kolei, kontrolując zużycie pamięci: metoda PrintLoadedDocument renderuje jedną stronę naraz, ale struktura załadowanego dokumentu pozostaje w pamięci przez cały czas trwania zadania. Jeśli obsługujesz kolejkę drukowania składającą się z setek dużych plików, dobre rezultaty daje połączenie ścieżki drukowania ze strumieniowym interfejsem Direct File API do pracy z dużymi plikami PDF — możesz najpierw podzielić, połączyć lub wybrać potrzebne strony na dysku, a następnie załadować do druku tylko odchudzony plik wynikowy. W ten sposób metody PrintLoadedDocument, RenderLoadedPageToDC oraz RenderLoadedPageToClipboard są dostarczane jako część komponentu HotPDF Component dla Delphi i C++Builder — natywnej biblioteki VCL bez zewnętrznych zależności DLL, która obsługuje tworzenie, edycję, ekstrakcję tekstu, renderowanie, a teraz także drukowanie bezpośrednio do TPrinter, dowolnego kontekstu urządzenia lub schowka