Współrzędne PDF są w punktach, współrzędne drukarki w jednostkach urządzenia, a te dwie rzeczy nie mają ze sobą nic wspólnego, dopóki celowo ich nie przekonwertujesz. To niedopasowanie jest źródłem większości problemów ze złym wydrukiem w aplikacjach Delphi: kod wysyła właściwy plik, ale strona wychodzi przycięta, rozciągnięta lub pusta. Komponent PDFium czysto obsługuje stronę renderowania; mechanizm drukarki to standardowe VCL. Te dwie części pasują do siebie za pomocą niewielkiej ilości kodu, gdy tylko zrozumiesz, czego oczekuje każda ze stron
Jak działa potok renderowania a następnie drukowania (render-then-print)
Komponent PDFium nie komunikuje się bezpośrednio z drukarkami. Wzorzec jest następujący: wyrenderuj stronę do TBitmap w pożądanej rozdzielczości, a następnie przenieś tę mapę bitową na płótno drukarki (canvas) za pomocą StretchDIBits. TPdf.RenderPage zwraca mapę bitową należącą do wywołującego, więc masz kontrolę nad wymiarami w pikselach. Przekaż [rePrinting] w zestawie opcji, a PDFium przełączy ścieżkę renderowania na taką, która pomija efekty dostępne tylko na ekranie, takie jak subpikselowe wygładzanie LCD, i poprawnie obsługuje MediaBox strony dla wydruku. Pominięcie rePrinting sprawi, że to, co wyślesz do drukarki, będzie renderowaniem ekranowym, które wygląda dobrze na monitorze, ale ma tendencję do tworzenia bardziej miękkiego wydruku na drukarkach o wysokim DPI, ponieważ decyzje o wygładzaniu podjęte dla ekranów o rozdzielczości 96 DPI nie pasują do drukowania przy 300 lub 600 DPI
TPdf.Active to jedyna brama do sprawdzenia przed dotknięciem jakiejkolwiek właściwości strony. Komponent bezgłośnie pochłania błędy ładowania: ustawienie Active := True na uszkodzonym lub chronionym hasłem pliku nie powoduje błędu (exception); po prostu pozostawia Active jako False. Zawsze sprawdzaj tę wartość po przypisaniu. Odczytywanie PageCount lub PageWidth na nieaktywnym dokumencie zwraca zero, co powoduje ciche operacje puste (no-ops), które są bardzo trudne do zdiagnozowania, gdy trafią do bufora wydruku
Minimalna pętla drukowania
Najprostszy działający przypadek ładuje plik, otwiera zadanie drukowania, iteruje strony i zamyka. Jedynym trudnym szczegółem jest to, że Printer.NewPage nie może być wywołane przed pierwszą stroną, stąd flaga FirstPage. Transfer StretchDIBits przechodzi przez GetDIBSizes i GetDIB, aby pobrać niezależne od urządzenia bity (device-independent bits) z uchwytu mapy bitowej, a następnie maluje je na płótnie drukarki na pełnym rozmiarze strony:
procedure PrintPdfFile(const FileName: string);
var
Pdf: TPdf;
I: Integer;
Bitmap: TBitmap;
InfoHeaderSize, ImageSize: DWORD;
InfoHeader: PBitmapInfo;
Image: Pointer;
FirstPage: Boolean;
begin
Pdf := TPdf.Create(nil);
try
Pdf.FileName := FileName;
Pdf.Active := True;
if not Pdf.Active then
Exit; // load failed silently; bail out
Printer.Title := Pdf.Title;
Printer.BeginDoc;
try
FirstPage := True;
for I := 1 to Pdf.PageCount do
begin
if FirstPage then
FirstPage := False
else
Printer.NewPage;
Pdf.PageNumber := I;
// Render at printer resolution; rePrinting adjusts the render path
Bitmap := Pdf.RenderPage(
0, 0,
Printer.PageWidth,
Printer.PageHeight,
ro0,
[rePrinting]
);
try
GetDIBSizes(Bitmap.Handle, InfoHeaderSize, ImageSize);
InfoHeader := AllocMem(InfoHeaderSize);
try
Image := AllocMem(ImageSize);
try
GetDIB(Bitmap.Handle, 0, InfoHeader^, Image^);
StretchDIBits(
Printer.Canvas.Handle,
0, 0, Printer.PageWidth, Printer.PageHeight,
0, 0, Bitmap.Width, Bitmap.Height,
Image, InfoHeader^, DIB_RGB_COLORS, SRCCOPY
);
finally
FreeMem(Image);
end;
finally
FreeMem(InfoHeader);
end;
finally
Bitmap.Free;
end;
end;
finally
Printer.EndDoc;
end;
finally
Pdf.Active := False;
Pdf.Free;
end;
end;
Przekazanie Printer.PageWidth i Printer.PageHeight jako wymiarów mapy bitowej oznacza renderowanie w natywnym rozmiarze piksela drukarki, co już uwzględnia DPI urządzenia. Wywołanie StretchDIBits mapuje następnie te piksele w skali 1:1 na stronę. Daje to najlepszą osiągalną wierność bez wyraźnej arytmetyki DPI, ale działa tylko wtedy, gdy strona PDF i fizyczny papier mają przypadkiem ten sam rozmiar. Jeśli się różnią, potrzebujesz jawnego skalowania
Skalowanie, gdy rozmiary strony i papieru się różnią
Strona PDF w formacie A4 w orientacji pionowej nie dopasowuje się automatycznie do drukarki US Letter, a strona w orientacji poziomej przesłana do drukarki zorientowanej pionowo zostanie przycięta. Standardowym podejściem jest obliczenie jednolitego współczynnika skali ze stosunku pikseli drukarki do punktów PDF, a następnie zastosowanie go do obu wymiarów, aby zachować proporcje obrazu. Pdf.PageWidth i Pdf.PageHeight uwidaczniają bieżące wymiary strony w punktach, gdzie jeden punkt to 1/72 cala. Pomnożenie przez docelowe DPI i podzielenie przez 72 daje w wyniku piksele w tej rozdzielczości. Weź Min ze stosunków X i Y, aby uzyskać największą skalę, która nadal mieści się w obszarze zadruku:
// Fit PDF page to printable area, preserving aspect ratio
var
ScaleX, ScaleY, Scale: Double;
DestWidth, DestHeight: Integer;
Dpi: Integer;
begin
Dpi := 300; // target render resolution
Pdf.PageNumber := PageIndex;
ScaleX := Printer.PageWidth / (Pdf.PageWidth * Dpi / 72);
ScaleY := Printer.PageHeight / (Pdf.PageHeight * Dpi / 72);
Scale := Min(ScaleX, ScaleY);
// Clamp to 1.0 for shrink-to-fit only (no enlargement)
if Scale > 1.0 then Scale := 1.0;
DestWidth := Round(Pdf.PageWidth * Dpi / 72 * Scale);
DestHeight := Round(Pdf.PageHeight * Dpi / 72 * Scale);
Bitmap := Pdf.RenderPage(0, 0, DestWidth, DestHeight, ro0,
[rePrinting, reAnnotations]);
// ... transfer with StretchDIBits as above
end;
Renderowanie z Dpi = 300 odpowiada większości drukarek biurowych. Przy 600 DPI, mapa bitowa pojedynczej strony A4 zajmuje około 34 megapiksele, co oznacza około 100 MB jako 32-bitowa mapa bitowa; przyrost jakości dla zwykłych dokumentów tekstowych jest minimalny, a koszt pamięci na stronę jest znaczny. Zachowaj 600 DPI dla drukarni lub ciężkich rysunków technicznych wektorowych, gdzie ma to rzeczywiste znaczenie
Flaga reAnnotations w drugim bloku kodu jest niezależna od rePrinting. Dołącz ją, gdy użytkownik oczekuje, że na papierze pojawią się pieczątki, zakreślenia i pola komentarzy. Pomiń ją w przypadku danych wyjściowych zawierających tylko treść (content-only). Obie flagi można dowolnie łączyć
Obracanie stron
PDFium przechowuje rotację strony w pliku PDF jako wpis /Rotate, dostępny przez Pdf.PageRotation, który zwraca wartość TRotation (ro0, ro90, ro180, ro270). System współrzędnych drukarki odwraca obroty o 90 i 270 stopni w stosunku do ekranu. Jeśli przekażesz surową wartość PageRotation bezpośrednio do RenderPage bez żadnego dopasowania, poziome strony osadzone w pionowym dokumencie zostaną wydrukowane do góry nogami na większości sterowników drukarek systemu Windows. Rozwiązaniem jest prosta zamiana przed wywołaniem renderowania: zmapuj ro90 na ro270 i ro270 z powrotem na ro90, pozostawiając ro0 i ro180 bez zmian
Przed wdrożeniem zweryfikuj to zachowanie na konkretnej drukarce docelowej. Działanie sterowników w kwestii obracania nie jest jednolite u wszystkich dostawców, a niektóre sterowniki stosują własną korektę obrotu na poziomie GDI. Jeśli widzisz podwójne obracanie, usuń zamianę; jeśli nie widzisz żadnej korekty, dodaj ją. Dokument o mieszanej orientacji, na przemian z pionowymi i poziomymi stronami, jest najszybszym sposobem na wychwycenie dowolnego z błędów podczas testów
Zarządzanie pamięcią podczas długiego zadania drukowania
Każde wywołanie RenderPage alokuje nowy TBitmap, którego właścicielem jest wywołujący i którego należy zwolnić. W powyższej pętli blok try/finally Bitmap.Free obsługuje to poprawnie dla jednej strony na raz. Nie kumuluj map bitowych między stronami: renderowanie w rozdzielczości 300 DPI 200-stronicowego dokumentu zużyłoby gigabajty zanim pierwsza strona trafiłaby do bufora wydruku. Zwolnij każdą mapę bitową przed przejściem do następnej strony
Para AllocMem / FreeMem wewnątrz bloku transferu podlega tej samej regule. GetDIBSizes informuje, ile pamięci potrzebuje nagłówek DIB i dane pikseli; alokujesz, wypełniasz, malujesz i zwalniasz wszystko w zakresie jednej strony. Jeśli któryś z bloków spowoduje wyciek (leak), doprowadzi to do wyczerpania sterty procesu przez zadanie drukowania w przypadku dokumentów dłuższych niż kilkadziesiąt stron
Jeśli musisz uruchamiać zadania drukowania w wątku w tle, zachowaj TPdf i wszystkie wywołania drukarki VCL w tym samym wątku. Sam TPdf nie jest bezpieczny dla wątków (thread-safe) pomiędzy instancjami współdzielącymi globalny stan biblioteki DLL PDFium; najbezpieczniejszym modelem jest jeden TPdf na wątek, a każdy z nich ładuje swoją własną kopię pliku
Pokazane tutaj API renderowania i dokumentów jest częścią komponentu PDFium Component dla Delphi i C++Builder