Súradnice PDF sú v bodoch, súradnice tlačiarne v jednotkách zariadenia a tieto dva systémy nemajú nič spoločného, pokiaľ ich zámerne neprepočítate. Tento nesúlad je príčinou väčšiny nekvalitných tlačových výstupov v aplikáciách Delphi: kód odošle správny súbor, ale stránka vyjde orezaná, roztiahnutá alebo prázdna. Komponent PDFium VCL rieši vykresľovaciu časť čisto, zatiaľ čo prepojenie s tlačiarňou prebieha cez štandardné VCL. Obe časti sa dajú spojiť pomocou malého množstva kódu, ak pochopíte, čo ktorá strana očakáva.
Como funguje proces vykresľovania a následnej tlače
Komponent PDFium VCL nekomunikuje s tlačiarňami priamo. Postup je nasledovný: vykreslíte stránku do objektu TBitmap v požadovanom rozlíšení a potom túto bitmapu prenesiete na plátno tlačiarne pomocou StretchDIBits. Metóda TPdf.RenderPage vracia bitmapu vlastnenú volajúcim, takže máte plnú kontrolu nad pixelovými rozmermi. Odovzdaním príznaku [rePrinting] v sade možností prepne PDFium svoju vykresľovaciu cestu na takú, ktorá vynecháva efekty určené iba pre obrazovku (napríklad LCD subpixel hinting) a správne spracuje MediaBox stránky pre tlačový výstup. Ak príznak rePrinting vynecháte, odošlete do tlačiarne vykreslenie určené pre obrazovku. To síce vyzerá dobre na monitore, ale na tlačiarňach s vysokým rozlíšením (DPI) má tendenciu vytvárať neostrý výstup, pretože rozhodnutia o hintingu navrhnuté pre 96 DPI obrazovky nevyhovujú tlači pri 300 alebo 600 DPI.
Vlastnosť TPdf.Active je jedinou kontrolnou bránou pred prístupom k akejkoľvek vlastnosti stránky. Komponent potichu ignoruje chyby načítania: nastavenie Active := True na poškodenom súbore alebo súbore chránenom heslom nevyvolá výnimku, ale iba ponechá vlastnosť Active na hodnote False. Po priradení ju preto vždy skontrolujte. Čítanie hodnôt PageCount alebo PageWidth na neaktívnom dokumente vracia nulu, čo vedie k tichým zlyhaniam operácií, ktoré sa po odoslení do tlačového zaradenia (spooleru) diagnostikujú veľmi ťažko.
Minimálny tlačový cyklus
Najjednoduchší funkčný prípad načíta súbor, otvorí tlačovú úlohu, prechádza stránky a zatvorí ju. Jediným zložitým detailom je, že Printer.NewPage sa nesmie volať pred prvou stránkou, odtiaľ pochádza príznak FirstPage. Prenos cez StretchDIBits využíva funkcie GetDIBSizes a GetDIB na získanie dát nezávislých od zariadenia z handle bitmapy a následne ich vykreslí na plátno tlačiarne v plnej veľkosti 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;
Odovzdanie hodnôt Printer.PageWidth a Printer.PageHeight ako rozmerov bitmapy znamená, že vykresľujete v natívnom rozlíšení tlačiarne v pixeloch, ktoré už zohľadňuje DPI zariadenia. Volanie StretchDIBits následne namapuje tieto pixely na stránku v pomere 1:1. Získate tak najlepšiu možnú vernosť bez akejkoľvek explicitnej aritmetiky DPI, ale funguje to iba vtedy, ak majú stránka PDF a fyzický papier rovnakú veľkosť. Keď sa líšia, potrebujete explicitnú zmenu mierky (scaling).
Zmena mierky pri rozdielnych veľkostiach stránky a papiera
Stránka PDF vo formáte A4 na výšku sa automaticky neprispôsobí tlačiarni s formátom US Letter a stránka na šírku poslaná na tlačiareň orientovanú na výšku sa oreže. Štandardným prístupom je výpočet jednotného faktora mierky z pomeru pixelov tlačiarne k bodom PDF a jeho následné aplikovanie na oba rozmery, čím sa zachová pomer strán. Vlastnosti Pdf.PageWidth a Pdf.PageHeight sprístupňujú aktuálne rozmery stránky v bodoch, pričom jeden bod predstavuje 1/72 palca. Vynásobením cieľovým DPI a vydelením číslom 72 získate pixely v danom rozlíšení. Použitím funkcie Min na pomery X a Y získate najväčšiu mierku, ktorá sa ešte zmestí do tlačovej 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;
Vykresľovanie pri rozlíšení Dpi = 300 vyhovuje väčšine kancelárskych tlačiarní. Pri 600 DPI dosahuje bitmapa pre jednu stránku formátu A4 približne 34 megapixelov, čo predstavuje asi 100 MB ako 32-bitová bitmapa. Nárast kvality pri bežných textových dokumentoch je minimálny, pričom pamäťová náročnosť na stránku je značná. Rozlíšenie 600 DPI si ponechajte pre tlačiarne v profesionálnych prevádzkach alebo pre technické výkresy s vysokým podielom vektorov, kde na tom skutočne záleží.
Príznak reAnnotations v druhom bloku kódu je nezávislý od rePrinting. Zahrňte ho, ak používateľ očakáva, že sa na papieri objavia pečiatky, zvýraznenia a komentáre. Pre výstup obsahujúci iba samotný text ho vynechajte. Oba príznaky je možné ľubovoľne kombinovať.
Otočenie stránky
PDFium ukladá otočenie stránky v PDF ako položku /Rotate dostupnú prostredníctvom Pdf.PageRotation, ktorá vracia hodnotu TRotation (ro0, ro90, ro180, ro270). Súradnicový systém tlačiarne invertuje otočenia o 90 a 270 stupňov vzhľadom na obrazovku. Ak odovzdáte pôvodnú hodnotu PageRotation priamo metóde RenderPage bez akejkoľvek úpravy, stránky orientované na šírku vložené do dokumentu orientovaného na výšku sa na väčšine tlačových ovládačov pre Windows vytlačia hore nohami. Riešením je jednoduchá výmena pred volaním vykresľovania: namapujte ro90 na ro270 and ro270 späť na ro90, pričom ro0 a ro180 ponecháte bezo zmeny.
Pred vydaním aplikácie overte toto správanie na vašej konkrétnej cieľovej tlačiarni. Správanie ovládačov pri otáčaní nie je u všetkých výrobcov rovnaké a niektoré ovládače aplikujú vlastnú korekciu otočenia na úrovni GDI. Ak vidíte dvojité otočenie, výmenu odstráňte. Ak nevidíte žiadnu korekciu, pridajte ju. Dokument s kombinovanou orientáciou striedajúci stránky na výšku a na šírku je najrýchlejším spôsobom, ako zachytiť oba chybové stavy počas testovania.
Správa pamäte pri dlhej tlačovej úlohe
Každé volanie metódy RenderPage alokuje nový objekt TBitmap, ktorý volajúci vlastní a musí ho uvoľniť. Vo vyššie uvedenom cykle to blok try/finally Bitmap.Free rieši správne pre každú stránku samostatne. Nezhromažďujte bitmapy pre viacero stránok naraz: vykreslenie 200-stranového dokumentu pri 300 DPI by spotrebovalo gigabajty pamäte ešte pred tým, ako sa prvá stránka dostane do tlačového frontu. Pred prechodom na ďalšiu stránku každú bitmapu uvoľnite.
Dvojica AllocMem / FreeMem vo vnútri prenosového bloku sa riadi rovnakým pravidlom. Funkcia GetDIBSizes vám povie, koľko pamäte vyžaduje hlavička DIB a pixelové dáta. Všetko alokujete, vyplníte, vykreslíte a uvoľníte v rámci jednej stránky. Ak necháte ktorýkoľvek blok unikať, tlačová úloha pri dokumentoch dlhších ako niekoľko desiatok stránok vyčerpá pamäť procesu.
Ak potrebujete spúšťať tlačové úlohy na vlákne na pozadí, ponechajte komponent TPdf a všetky volania tlačiarne VCL na rovnakom vlákne. Samotný komponent TPdf nie je bezpečný pre vlákna (thread-safe) naprieč inštanciami zdieľajúcimi globálny stav knižnice PDFium DLL. Najbezpečnejším modelom je jeden komponent TPdf na vlákno, pričom každý načíta vlastnú kópiu súboru.
API pre vykresľovanie a správu dokumentov zobrazené v tomto článku je súčasťou komponentu PDFium VCL Component pre Delphi a C++Builder.