PDF koordinate su izražene u točkama (points), koordinate pisača u jedinicama uređaja (device units), a te dvije vrste koordinata nemaju nikakve veze jedna s drugom sve dok ih namjerno ne pretvorite. Taj nesklad je uzrok većine loših ispisa u Delphi aplikacijama: kod šalje ispravnu datoteku, ali stranica ispadne odrezana, razvučena ili prazna. PDFium VCL čisto obrađuje stranu iscrtavanja, dok je sustav ispisa standardni VCL. To dvoje se povezuje uz umjerenu količinu koda nakon što shvatite što svaka strana očekuje.
Kako funkcionira cjevovod iscrtavanja i ispisa
PDFium VCL ne komunicira izravno s pisačima. Predložak rada je sljedeći: iscrtajte stranicu u TBitmap u željenoj razlučivosti, a zatim prenesite tu bitmapu na platno (canvas) pisača pomoću StretchDIBits. Metoda TPdf.RenderPage vraća bitmapu u vlasništvu pozivatelja, što znači da sami kontrolirate dimenzije u pikselima. Proslijedite [rePrinting] u skupu opcija i PDFium će prebaciti svoj put iscrtavanja na onaj koji izostavlja efekte namijenjene isključivo zaslonu, poput LCD subpikselnog renderiranja (hinting), te ispravno obrađuje MediaBox stranice za ispis. Ako izostavite rePrinting, pisaču šaljete iscrtavanje prilagođeno zaslonu, što izgleda dobro na monitoru, ali obično rezultira mekšim ispisom na pisačima visoke razlučivosti (high-DPI) jer odluke o prilagodbi donesene za zaslone od 96 DPI ne odgovaraju ispisu na 300 ili 600 DPI.
Svojstvo TPdf.Active je jedina provjera koju morate izvršiti prije pristupa bilo kojem svojstvu stranice. Komponenta tiho zanemaruje pogreške pri učitavanju: postavljanje Active := True na oštećenu datoteku ili datoteku zaštićenu lozinkom ne podiže iznimku, već jednostavno ostavlja Active na False. Uvijek provjerite to svojstvo nakon dodjele. Čitanje PageCount or PageWidth na neaktivnom dokumentu vraća nulu, što dovodi do tihih neizvršavanja operacija koje je vrlo teško dijagnosticirati nakon što stignu u red čekanja za ispis (spooler).
Minimalna petlja ispisa
Najjednostavniji funkcionalni primjer učitava datoteku, otvara posao ispisa, prolazi kroz stranice i zatvara posao. Jedini složeni detalj je taj da se Printer.NewPage ne smije pozvati prije prve stranice, zbog čega koristimo zastavicu FirstPage. Prijenos putem StretchDIBits koristi funkcije GetDIBSizes i GetDIB za preuzimanje bitova neovisnih o uređaju (DIB) iz ručke bitmape, a zatim ih iscrtava na platno pisač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;
Prosljeđivanje Printer.PageWidth i Printer.PageHeight kao dimenzija bitmape znači da iscrtavate u izvornoj veličini piksela pisača, koja već uzima u obzir DPI uređaja. Poziv StretchDIBits zatim mapira te piksele 1:1 na stranicu. To osigurava najbolju moguću vjernost prikaza bez ikakve eksplicitne DPI aritmetike, no to radi isključivo kada su stranica PDF-a i fizički papir iste veličine. Kada se razlikuju, potrebno vam je eksplicitno skaliranje.
Skaliranje kada se veličine stranice i papira razlikuju
PDF stranica u uspravnom (portrait) A4 formatu ne odgovara automatski pisaču s US Letter formatom, a vodoravna (landscape) stranica poslana na pisač postavljen uspravno bit će odrezana. Standardni pristup je izračunavanje jedinstvenog faktora skaliranja iz omjera piksela pisača i PDF točaka, a zatim njegova primjena na obje dimenzije kako bi se očuvao omjer širine i visine. Svojstva Pdf.PageWidth i Pdf.PageHeight daju trenutne dimenzije stranice u točkama, pri čemu je jedna točka jednaka 1/72 inča. Množenjem s ciljanim DPI-jem i dijeljenjem sa 72 dobiva se broj piksela u toj razlučivosti. Uzmite Min iz X i Y omjera kako biste dobili najveće mjerilo koje još uvijek stane unutar ispisnog područja:
// 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 pri Dpi = 300 odgovara većini uredskih pisača. Pri 600 DPI, bitmapa za jednu A4 stranicu doseže otprilike 34 megapiksela, što je oko 100 MB kao 32-bitna bitmapa. Poboljšanje kvalitete za obične tekstualne dokumente je minimalno, a memorijski trošak po stranici je značajan. Zadržite 600 DPI za tiskare ili tehničke crteže s puno vektorskih elemenata gdje je to doista važno.
Zastavica reAnnotations u drugom bloku koda neovisna je o rePrinting. Uključite je kada korisnik očekuje da se pečati, isticanja i okviri s komentarima pojave na papiru. Izostavite je za ispis isključivo tekstualnog i grafičkog sadržaja stranice. Uz to, obje zastavice mogu se slobodno kombinirati.
Rotacija stranice
PDFium pohranjuje rotaciju stranice u PDF-u kao stavku /Rotate, kojoj se pristupa putem Pdf.PageRotation, što vraća vrijednost TRotation (ro0, ro90, ro180, ro270). Koordinatni sustav pisača invertira rotacije od 90 i 270 stupnjeva u odnosu na zaslon. Ako izravno proslijedite izvornu vrijednost PageRotation metodi RenderPage bez ikakvih prilagodbi, vodoravne stranice umetnute u uspravni dokument ispisat će se naopako na većini Windows upravljačkih programa za pisače. Rješenje je jednostavna zamjena prije poziva iscrtavanja: mapirajte ro90 u ro270 i ro270 natrag u ro90, ostavljajući ro0 i ro180 nepromijenjenima.
Provjerite ovo ponašanje na svom specifičnom ciljnom pisaču prije isporuke softvera. Ponašanje upravljačkih programa u vezi s rotacijom nije ujednačeno među proizvođačima, a neki driveri primjenjuju vlastitu korekciju rotacije na razini GDI-ja. Ako primijetite dvostruku rotaciju, uklonite zamjenu; ako ne vidite nikakvu korekciju, dodajte je. Dokument s miješanim usmjerenjima stranica (izmjenične uspravne i vodoravne stranice) najbrži je način za uočavanje bilo kojeg od ovih problema tijekom testiranja.
Upravljanje memorijom tijekom dugotrajnih poslova ispisa
Svaki poziv metode RenderPage dodjeljuje novi TBitmap koji je u vlasništvu pozivatelja i mora se osloboditi. U gore prikazanoj petlji, blok try/finally Bitmap.Free ispravno rješava ovo za svaku pojedinu stranicu. Nemojte nakupljati bitmap datoteke kroz više stranica: iscrtavanje dokumenta od 200 stranica pri 300 DPI potrošilo bi gigabajte memorije prije nego što prva stranica uopće stigne do sustava za ispis. Oslobodite svaku bitmapu prije prelaska na sljedeću stranicu.
Par AllocMem / FreeMem unutar bloka za prijenos slijedi isto pravilo. GetDIBSizes vam govori koliko je memorije potrebno za DIB zaglavlje i slikovne podatke; vi ih dodjeljujete, popunjavate, iscrtavate i oslobađate, sve u okviru jedne stranice. Dopuštanje curenja u bilo kojem od ovih blokova dovest će do toga da posao ispisa iscrpi memoriju procesa (heap) na dokumentima koji imaju više od nekoliko desetaka stranica.
Ako trebate pokrenuti poslove ispisa u pozadinskoj dretvi (background thread), držite TPdf i sve VCL pozive pisača unutar iste dretve. Sama komponenta TPdf nije sigurna za rad s više dretvi (thread-safe) preko instanci koje dijele globalno stanje PDFium DLL-a; najsigurniji model je jedna TPdf komponenta po dretvi, pri čemu svaka učitava vlastitu kopiju datoteke.
API za iscrtavanje i dokumente prikazan ovdje dio je komponente PDFium VCL Component za Delphi i C++Builder.