Souřadnice PDF jsou v bodech, souřadnice tiskárny jsou v jednotkách zařízení a obě spolu nemají nic společného, dokud je záměrně nepřevedete. Tento nesoulad je kořenem většiny špatných tiskových výstupů v aplikacích Delphi: kód odešle správný soubor, ale stránka vyjde oříznutá, roztažená nebo prázdná. Komponenta PDFium (PDFium Component) čistě zpracovává stranu vykreslování; tisková instalace je standardní VCL. Obě části do sebe zapadají s nevelkým množstvím kódu, jakmile pochopíte, co každá strana očekává
Jak funguje proces render-then-print (vykresli a vytiskni)
Komponenta PDFium nekomunikuje přímo s tiskárnami. Vzor je: vykreslete stránku do TBitmap v požadovaném rozlišení, poté přeneste tuto bitmapu na plátno tiskárny (canvas) pomocí StretchDIBits. TPdf.RenderPage vrací bitmapu vlastněnou volajícím, takže řídíte rozměry v pixelech. Předejte [rePrinting] v sadě voleb a PDFium přepne svou cestu vykreslování na tu, která vynechá efekty určené pouze pro obrazovku, jako je subpixelové vyhlazování LCD, a správně zpracuje MediaBox stránky pro tiskový výstup. Pokud rePrinting vynecháte, pošlete na tiskárnu render obrazovky, který vypadá na monitoru dobře, ale na tiskárnách s vysokým DPI má tendenci produkovat měkčí výstup, protože rozhodnutí o vyhlazování vytvořená pro 96 DPI obrazovky nevyhovují tisku v 300 nebo 600 DPI
TPdf.Active je jedinou bránou ke kontrole před jakoukoliv úpravou vlastností stránky. Komponenta tiše pohlcuje chyby při načítání: nastavení Active := True u poškozeného souboru nebo souboru chráněného heslem nevyvolá výjimku; jednoduše ponechá Active jako False. Vždy to po přiřazení zkontrolujte. Čtení PageCount nebo PageWidth u neaktivního dokumentu vrací nulu, což vede k tichým prázdným operacím (no-ops), které se velmi obtížně diagnostikují, jakmile dorazí do zařazovače tisku (spooler)
Minimální tisková smyčka
Nejjednodušší funkční případ načte soubor, otevře tiskovou úlohu, iteruje stránky a úlohu uzavře. Jediným záludným detailem je, že Printer.NewPage nesmí být voláno před první stránkou, odtud příznak FirstPage. Přenos StretchDIBits probíhá přes GetDIBSizes a GetDIB, aby se získaly na zařízení nezávislé bity z handle bitmapy, a poté je namaluje na plátno tiskárny (canvas) v plné velikosti stránky:
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;
Předání Printer.PageWidth a Printer.PageHeight jako rozměrů bitmapy znamená, že vykreslíte v nativní velikosti pixelů tiskárny, která již zohledňuje DPI zařízení. Volání StretchDIBits pak mapuje tyto pixely 1:1 na stránku. To vám dává nejlepší dosažitelnou věrnost bez jakékoli explicitní aritmetiky DPI, ale funguje to pouze tehdy, když se stránka PDF a fyzický papír náhodou shodují ve velikosti. Když se liší, potřebujete explicitní měřítko
Škálování při rozdílné velikosti stránky a papíru
PDF stránka ve formátu A4 na výšku se automaticky nevejde na tiskárnu formátu US Letter a stránka na šířku poslaná na tiskárnu orientovanou na výšku se ořízne. Standardním přístupem je vypočítat jednotný faktor měřítka z poměru pixelů tiskárny k bodům PDF (points), pak jej aplikovat na oba rozměry, aby byl zachován poměr stran. Pdf.PageWidth a Pdf.PageHeight odhalují aktuální rozměry stránky v bodech (points), kde jeden bod je 1/72 palce. Násobení cílovým DPI a dělení 72 je převede na pixely v daném rozlišení. Pomocí funkce Min z poměrů X a Y získejte největší měřítko, které se stále vejde do tisknutelné oblasti:
// 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;
Vykreslování při Dpi = 300 vyhovuje většině kancelářských tiskáren. Při 600 DPI má bitmapa jedné stránky A4 zhruba 34 megapixelů, což je asi 100 MB jako 32bitová bitmapa; nárůst kvality u běžných textových dokumentů je minimální a paměťové nároky na stránku jsou značné. 600 DPI si ponechte pro tiskárny a obchody s tiskem nebo pro vektorově náročné technické výkresy, kde na tom skutečně záleží
Příznak reAnnotations ve druhém bloku kódu je nezávislý na rePrinting. Zahrňte jej, když uživatel očekává, že se razítka, zvýraznění a pole s komentáři objeví na papíře. Pro výstup samotného obsahu jej vynechte. Oba příznaky lze libovolně kombinovat
Rotace stránky
PDFium ukládá rotaci stránky v PDF jako záznam /Rotate, přístupný přes Pdf.PageRotation, který vrací hodnotu TRotation (ro0, ro90, ro180, ro270). Souřadnicový systém tiskárny invertuje otočení o 90 a 270 stupňů vzhledem k obrazovce. Pokud předáte neupravenou hodnotu PageRotation přímo do RenderPage bez jakéhokoli přizpůsobení, stránky na šířku vložené do dokumentu na výšku se vytisknou vzhůru nohama na většině ovladačů tiskáren pro Windows. Opravou je jednoduché prohození před voláním vykreslení: mapujte ro90 na ro270 a ro270 zpět na ro90, přičemž ro0 a ro180 nechejte beze změny
Před vydáním (shipping) ověřte toto chování na vaší konkrétní cílové tiskárně. Chování ovladačů ohledně rotace není mezi výrobci jednotné a některé ovladače aplikují svou vlastní korekci rotace na úrovni GDI. Pokud vidíte dvojitou rotaci, odstraňte prohození; pokud nevidíte žádnou korekci, přidejte jej. Dokument se smíšenou orientací se střídajícími se stránkami na výšku a na šířku je nejrychlejší způsob, jak zachytit oba režimy selhání během testování
Správa paměti v rámci dlouhé tiskové úlohy
Každé volání RenderPage alokuje novou TBitmap, kterou vlastní volající a musí ji uvolnit. Ve smyčce výše, blok try/finally Bitmap.Free to zpracovává správně pro každou stránku zvlášť. Nehromažďujte bitmapy z různých stránek: 300-DPI vykreslování 200stránkového dokumentu by spotřebovalo gigabajty předtím, než se první stránka dostane do zařazovače tisku (spooler). Uvolněte každou bitmapu, než přejdete na další stránku
Dvojice AllocMem / FreeMem uvnitř bloku přenosu se řídí stejným pravidlem. GetDIBSizes vám řekne, kolik paměti potřebuje hlavička DIB a pixelová data; alokujete, naplníte, namalujete a uvolníte vše v rámci jedné stránky. Pokud necháte některý z bloků unikat (memory leak), způsobí to, že tisková úloha vyčerpá procesní haldu (heap) u dokumentů delších než několik desítek stránek
Pokud potřebujete spustit tiskové úlohy na vláknu na pozadí (background thread), udržujte TPdf a všechna volání tiskárny VCL na stejném vlákně. Samotné TPdf není thread-safe napříč instancemi sdílejícími globální stav knihovny PDFium DLL; nejbezpečnějším modelem je jedno TPdf na vlákno, z nichž každé načítá svou vlastní kopii souboru
Zde zobrazené renderovací a dokumentové API je součástí komponenty PDFium pro Delphi a C++Builder