PDF koordinate su u točkama (points), koordinate pisača (printer) su u jedinicama uređaja (device units), a to dvoje nema nikakve veze jedno s drugim dok ih namjerno ne pretvorite. To nepodudaranje korijen je većine loših ispisa u Delphi aplikacijama: kod šalje pravu datoteku, ali stranica izlazi izrezana, rastegnuta ili prazna. PDFium Component čisto rješava stranu iscrtavanja (rendering); ožičenje pisača (printer plumbing) je standardni VCL. To dvoje se uklapa sa skromnom količinom koda jednom kada shvatite što koja strana očekuje
Kako radi cjevovod (pipeline) "iscrtaj-pa-ispisuj"
PDFium Component ne razgovara izravno s pisačima. Obrazac je sljedeći: iscrtajte (render) stranicu u TBitmap u rezoluciji koju želite, a zatim prenesite tu bitmapu na platno pisača (printer's canvas) pomoću StretchDIBits. TPdf.RenderPage vraća bitmapu u vlasništvu pozivatelja (caller-owned), tako da vi kontrolirate dimenzije piksela. Proslijedite [rePrinting] u skupu opcija i PDFium prebacuje svoju putanju iscrtavanja na onu koja izostavlja efekte samo za zaslon kao što je LCD subpixel hinting i ispravno rukuje s MediaBoxom stranice za ispis. Izostavite li rePrinting, ono što šaljete na pisač je prikaz na zaslonu (screen render), koji izgleda u redu na monitoru, ali obično proizvodi mekši izlaz na pisačima visoke razlučivosti (high-DPI) jer odluke o hintingu (hinting decisions) donesene za zaslone od 96 DPI ne odgovaraju ispisu na 300 ili 600 DPI
TPdf.Active jedina je prepreka koju treba provjeriti prije dodirivanja bilo kojeg svojstva stranice. Komponenta tiho guta pogreške pri učitavanju (load errors): postavljanje Active := True na oštećenoj datoteci ili datoteci zaštićenoj lozinkom ne pokreće iznimku (exception); jednostavno ostavlja Active kao False. Uvijek ga provjerite nakon dodjele. Čitanje PageCount ili PageWidth na neaktivnom dokumentu vraća nulu, što proizvodi tihe "no-ops" koje je vrlo teško dijagnosticirati kada jednom stignu u red čekanja na ispis (spooler)
Minimalna petlja ispisa
Najjednostavniji radni slučaj učitava datoteku, otvara posao ispisa (print job), prolazi kroz stranice i zatvara se. Jedini nezgodan detalj je da Printer.NewPage ne smije biti pozvan prije prve stranice, otuda i oznaka FirstPage. Prijenos StretchDIBits ide kroz GetDIBSizes i GetDIB kako bi povukao neovisne o uređaju bitove (device-independent bits) iz ručke bitmape (bitmap handle), a zatim ih slika 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 vam daje najbolju moguću vjernost (fidelity) bez ikakve eksplicitne DPI aritmetike, ali funkcionira samo kada su PDF stranica i fizički papir slučajno iste veličine. Kad se razlikuju, potrebno vam je eksplicitno skaliranje (scaling)
Skaliranje kada se razlikuju veličine stranice i papira
PDF stranica u A4 portretnom (portrait) formatu ne uklapa se automatski na US Letter pisač, a pejzažna (landscape) stranica uložena u portretno orijentirani pisač bit će odrezana (clip). Standardni pristup je izračunati jedinstveni faktor skaliranja iz omjera piksela pisača i PDF točaka, a zatim ga primijeniti na obje dimenzije tako da se sačuva omjer širine i visine (aspect ratio). Pdf.PageWidth i Pdf.PageHeight izlažu trenutne dimenzije stranice u točkama, gdje je jedna točka 1/72 inča. Množenjem ciljnim DPI i dijeljenjem sa 72 pretvara se u piksele na toj rezoluciji. Uzmite Min X i Y omjera kako biste dobili najveće mjerilo koje još uvijek stane unutar područja za ispis (printable area):
// 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 (Rendering) na Dpi = 300 odgovara većini uredskih pisača. Na 600 DPI, bitmapa za jednu A4 stranicu doseže otprilike 34 megapiksela, što je oko 100 MB kao 32-bitna bitmapa; dobitak u kvaliteti za obične tekstualne dokumente je minimalan, a cijena memorije po stranici je značajna. Zadržite 600 DPI za tiskare (print shops) ili tehničke crteže s puno vektora gdje je to zaista važno
Zastavica (flag) reAnnotations u drugom bloku koda neovisna je o rePrinting. Uključite je kada korisnik očekuje da će se pečati, isticanja (highlights) i okviri za komentare pojaviti na papiru. Izostavite je za ispis samo sadržaja. Obje zastavice mogu se slobodno kombinirati
Rotacija stranice
PDFium pohranjuje rotaciju stranice u PDF-u kao unos /Rotate, dostupan putem Pdf.PageRotation, koji vraća vrijednost TRotation (ro0, ro90, ro180, ro270). Koordinatni sustav pisača obrnuto rotira za 90 i 270 stupnjeva u odnosu na zaslon. Ako izravno proslijedite sirovu vrijednost PageRotation funkcije RenderPage bez ikakvog podešavanja, pejzažne stranice ugrađene u portretni dokument ispisivat će se naopako na većini upravljačkih programa (drivers) za Windows pisače. Rješenje je jednostavna zamjena prije poziva za iscrtavanje: preslikajte ro90 u ro270 i ro270 natrag u ro90, ostavljajući ro0 i ro180 nepromijenjenim
Prije isporuke (shipping), provjerite ovo ponašanje na vašem specifičnom ciljnom pisaču. Ponašanje upravljačkog programa u vezi s rotacijom nije ujednačeno među proizvođačima (vendors), a neki upravljački programi primjenjuju vlastitu korekciju rotacije na razini GDI-a. Ako vidite dvostruku rotaciju, uklonite zamjenu; ako uopće ne vidite korekciju, dodajte je. Dokument mješovite orijentacije s izmjeničnim portretnim i pejzažnim stranicama najbrži je način da uhvatite bilo koji način kvara tijekom testiranja
Upravljanje memorijom tijekom dugog ispisa
Svaki poziv na RenderPage dodjeljuje (allocates) novi TBitmap koji posjeduje pozivatelj i mora ga osloboditi (free). U gornjoj petlji, blok try/finally Bitmap.Free ispravno to rješava za jednu po jednu stranicu. Nemojte akumulirati bitmape preko stranica: iscrtavanje dokumenta od 200 stranica s 300-DPI potrošilo bi gigabajte prije nego što prva stranica stigne u red čekanja (spooler). Oslobodite svaku bitmapu prije prijelaza na sljedeću stranicu
Par AllocMem / FreeMem unutar bloka prijenosa slijedi isto pravilo. GetDIBSizes govori vam koliko memorije trebaju DIB zaglavlje (header) i podaci o pikselima; vi dodjeljujete (allocate), popunjavate (fill), slikate (paint) i oslobađate (free) sve unutar dosega jedne stranice. Ako dopustite curenje (leak) u bilo kojem bloku, to će uzrokovati da posao ispisa iscrpi hrpu (heap) procesa na dokumentima dužim od nekoliko desetaka stranica
Ako trebate pokretati poslove ispisa u pozadinskoj niti (background thread), držite TPdf i sve pozive VCL pisača u istoj niti. Sam TPdf nije siguran za niti (thread-safe) među instancama koje dijele globalno stanje PDFium DLL-a; najsigurniji model je jedan TPdf po niti, pri čemu svaka učitava svoju kopiju datoteke
API za iscrtavanje i dokument prikazan ovdje dio je PDFium Component za Delphi i C++Builder