PDF koordinate su u tačkama (points), koordinate štampača su u jedinicama uređaja, i to dvoje nemaju ništa zajedničko dok ih namerno ne konvertujete. To neslaganje je koren većine loših rezultata štampanja u Delphi aplikacijama: kod šalje ispravnu datoteku, ali stranica izlazi skraćena, rastegnuta ili prazna. PDFium VCL čisto rukuje stranom iscrtavanja; vodovod štampača je standardni VCL. Ovo dvoje se uklapa sa skromnom količinom koda kada razumete šta svaka strana očekuje.
Kako funkcioniše cevovod iscrtaj-pa-štampaj
PDFium VCL ne razgovara direktno sa štampačima. Šablon je: iscrtajte stranicu u TBitmap u rezoluciji koju želite, a zatim prenesite tu bitmapu na platno štampača pomoću StretchDIBits. TPdf.RenderPage vraća bitmapu koju poseduje pozivalac, tako da vi kontrolišete dimenzije piksela. Prenesite [rePrinting] u skup opcija i PDFium prebacuje svoju putanju iscrtavanja na onu koja izostavlja efekte samo za ekran kao što je LCD subpixel hinting, i ispravno rukuje MediaBox-om stranice za izlaz štampanja. Ostavite rePrinting po strani i ono što šaljete štampaču je iscrtavanje ekrana, što izgleda u redu na monitoru, ali ima tendenciju da proizvede mekši izlaz na štampačima visoke rezolucije jer odluke o hintingu donete za ekrane od 96 DPI ne odgovaraju štampanju od 300 ili 600 DPI.
TPdf.Active je jedina kapija koju treba proveriti pre dodirivanja bilo kog svojstva stranice. Komponenta tiho guta greške učitavanja: postavljanje Active := True na oštećenu datoteku ili datoteku zaštićenu lozinkom ne podiže izuzetak; to jednostavno ostavlja Active kao False. Uvek ga proverite nakon dodele. Čitanje PageCount ili PageWidth na neaktivnom dokumentu vraća nulu, što proizvodi tihe neuspehe koje je veoma teško dijagnostikovati kada stignu do spooler-a.
Minimalna petlja štampanja
Najjednostavniji radni slučaj učitava datoteku, otvara posao štampanja, prolazi kroz stranice i zatvara ga. Jedini lukav detalj jeste da se Printer.NewPage ne sme pozvati pre prve stranice, otuda i zastavica FirstPage. Prenos StretchDIBits ide kroz GetDIBSizes i GetDIB da bi se izvukli bitovi nezavisni od uređaja iz ručke bitmape, a zatim ih crta na platnu štampača u punoj veličini stranice:
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;
Prenošenje Printer.PageWidth i Printer.PageHeight kao dimenzija bitmape znači da iscrtavate u izvornoj veličini piksela štampača, što već uzima u obzir DPI uređaja. Poziv StretchDIBits zatim mapira te piksele 1:1 na stranicu. Ovo vam daje najbolju moguću vernost bez ikakve eksplicitne DPI aritmetike, ali radi samo kada su PDF stranica i fizički papir slučajno iste veličine. Kada se razlikuju, potrebno vam je eksplicitno skaliranje.
Skaliranje kada se veličine stranice i papira razlikuju
PDF stranica na A4 uspravno ne odgovara automatski US Letter štampaču, a položena stranica prosleđena štampaču u uspravnoj orijentaciji biće odsečena. Standardni pristup je izračunavanje jedinstvenog faktora skaliranja iz odnosa piksela štampača i PDF tačaka, a zatim ga primeniti na obe dimenzije kako bi se sačuvao odnos širine i visine. Pdf.PageWidth i Pdf.PageHeight izlažu trenutne dimenzije stranice u tačkama, gde je jedna tačka 1/72 inča. Množenje sa ciljnim DPI i deljenje sa 72 pretvara u piksele u toj rezoluciji. Uzmite Min od X i Y odnosa da biste dobili najveće skaliranje koje se i dalje uklapa u oblast za štampanje:
// 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;
Iscrtavanje na Dpi = 300 odgovara većini kancelarijskih štampača. Na 600 DPI, bitmapa za jednu A4 stranicu iznosi otprilike 34 megapiksela, što je oko 100 MB kao 32-bitna bitmapa; dobitak u kvalitetu za obične tekstualne dokumente je minimalan, a trošak memorije po stranici je značajan. Zadržite 600 DPI za štamparije ili vektorski intenzivne tehničke crteže gde to zaista ima značaja.
Zastavica reAnnotations u drugom bloku koda je nezavisna od rePrinting. Uključite je kada korisnik očekuje da se pečati, isticanja i okviri sa komentarima pojave na papiru. Izostavite je za izlaz koji sadrži samo osnovni sadržaj. Obe zastavice se mogu slobodno kombinovati.
Rotacija stranice
PDFium čuva rotaciju stranice u PDF-u kao unos /Rotate, dostupan preko Pdf.PageRotation, koji vraća vrednost TRotation (ro0, ro90, ro180, ro270). Koordinatni sistem štampača invertuje rotacije od 90 i 270 stepeni u odnosu na ekran. Ako prenesete sirovu vrednost PageRotation direktno u RenderPage bez ikakvog prilagođavanja, položene stranice ugrađene u uspravan dokument odštampaće se naopako na većini upravljačkih programa za štampače u Windows-u. Rešenje je jednostavna zamena pre poziva iscrtavanja: mapirajte ro90 u ro270 i ro270 nazad u ro90, ostavljajući ro0 i ro180 nepromenjenim.
Potvrdite ovo ponašanje na vašem specifičnom štampaču pre isporuke. Ponašanje drajvera oko rotacije nije jedinstveno među proizvođačima, a neki drajveri primenjuju sopstvenu korekciju rotacije na GDI nivou. Ako vidite dvostruku rotaciju, uklonite zamenu; ako ne vidite nikakvu korekciju, dodajte je. Dokument sa mešovitom orijentacijom sa naizmeničnim uspravnim i položenim stranicama jeste najbrži način da uhvatite bilo koji od neuspeha tokom testiranja.
Upravljanje memorijom kroz dug posao štampanja
Svaki poziv RenderPage dodeljuje novu TBitmap koju pozivalac poseduje i mora je osloboditi. U gornjoj petlji, blok try/finally Bitmap.Free ispravno rukuje ovim za jednu po jednu stranicu. Nemojte akumulirati bitmape kroz stranice: iscrtavanje od 300 DPI za dokument od 200 stranica potrošilo bi gigabajte pre nego što prva stranica stigne do spooler-a. Oslobodite svaku bitmapu pre prelaska na sledeću stranicu.
Par AllocMem / FreeMem unutar bloka prenosa prati isto pravilo. GetDIBSizes vam govori koliko je memorije potrebno za zaglavlje DIB-a i podatke o pikselima; vi alocirate, popunjavate, crtate i oslobađate, sve u okviru opsega jedne stranice. Puštanje bilo kog bloka da procuri prouzrokovaće da posao štampanja iscrpi gomilu procesa (heap) na dokumentima dužim od nekoliko desetina stranica.
Ako treba da pokrećete poslove štampanja na pozadinskoj niti, držite TPdf i sve VCL pozive štampača na istoj niti. TPdf sam po sebi nije bezbedan za niti (thread-safe) kroz instance koje dele globalno stanje PDFium DLL-a; najsigurniji model je jedan TPdf po niti, pri čemu svaka učitava sopstvenu kopiju datoteke.
API za iscrtavanje i dokumente prikazan ovde deo je PDFium VCL komponente za Delphi i C++Builder.