Coordonatele PDF sunt în puncte, coordonatele imprimantei sunt în unități de dispozitiv, iar cele două nu au nimic în comun până când nu le convertiți în mod deliberat. Această nepotrivire este rădăcina majorității rezultatelor proaste de tipărire în aplicațiile Delphi: codul trimite fișierul corect, dar pagina iese decupată, întinsă sau goală. PDFium Component gestionează curat partea de randare; instalația imprimantei este VCL standard. Cele două se îmbină cu o cantitate modestă de cod, odată ce înțelegeți ce așteaptă fiecare parte
Cum funcționează pipeline-ul randare-apoi-tipărire
PDFium Component nu comunică direct cu imprimantele. Modelul este: randați o pagină într-un TBitmap la rezoluția dorită, apoi transferați acel bitmap pe canvas-ul imprimantei cu StretchDIBits. TPdf.RenderPage returnează un bitmap deținut de apelant, așa că dvs. controlați dimensiunile în pixeli. Transmiteți [rePrinting] în setul de opțiuni, iar PDFium comută calea sa de randare la una care omite efectele exclusive de ecran, precum hinting-ul de subpixel LCD, și gestionează corect MediaBox-ul paginii pentru rezultatul tipărit. Omiteți rePrinting, iar ceea ce trimiteți către imprimantă este o randare de ecran, care arată bine pe un monitor, dar tinde să producă un rezultat mai neclar pe imprimantele cu DPI ridicat, deoarece deciziile de hinting luate pentru ecrane de 96 DPI nu se potrivesc tipăririi la 300 sau 600 DPI
TPdf.Active este singura poartă de verificat înainte de a atinge orice proprietate de pagină. Componenta înghite erorile de încărcare silențios: setarea Active := True pe un fișier deteriorat sau protejat cu parolă nu ridică o excepție; pur și simplu lasă Active pe False. Verificați-o întotdeauna după atribuire. Citirea PageCount sau PageWidth pe un document inactiv returnează zero, ceea ce produce operații fără efect silențioase, foarte greu de diagnosticat odată ce ajung la spooler
O buclă de tipărire minimală
Cel mai simplu caz funcțional încarcă un fișier, deschide o sarcină de tipărire, iterează paginile și închide. Singurul detaliu dificil este că Printer.NewPage nu trebuie apelat înainte de prima pagină, de unde indicatorul FirstPage. Transferul StretchDIBits trece prin GetDIBSizes și GetDIB pentru a extrage biți independenți de dispozitiv din handle-ul bitmap-ului, apoi îi desenează pe canvas-ul imprimantei la dimensiunea completă a paginii:
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; // încărcarea a eșuat silențios; ieșire
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;
// Randează la rezoluția imprimantei; rePrinting ajustează calea de randare
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;
Transmiterea lui Printer.PageWidth și Printer.PageHeight ca dimensiuni ale bitmap-ului înseamnă că randați la dimensiunea nativă în pixeli a imprimantei, care ține deja cont de DPI-ul dispozitivului. Apelul StretchDIBits mapează apoi acei pixeli 1:1 pe pagină. Asta vă oferă cea mai bună fidelitate realizabilă fără nicio aritmetică DPI explicită, dar funcționează doar atunci când pagina PDF și hârtia fizică se întâmplă să aibă aceeași dimensiune. Când diferă, aveți nevoie de scalare explicită
Scalarea atunci când dimensiunile paginii și ale hârtiei diferă
O pagină PDF A4 portret nu se încadrează automat pe o imprimantă US Letter, iar o pagină de tip landscape alimentată către o imprimantă orientată portret se va decupa. Abordarea standard este să calculați un factor de scalare uniform din raportul dintre pixelii imprimantei și punctele PDF, apoi să îl aplicați ambelor dimensiuni, astfel încât raportul de aspect să fie păstrat. Pdf.PageWidth și Pdf.PageHeight expun dimensiunile paginii curente în puncte, unde un punct este 1/72 dintr-un inch. Înmulțirea cu un DPI țintă și împărțirea la 72 convertește în pixeli la acea rezoluție. Luați Min-ul raporturilor X și Y pentru a obține cea mai mare scală care încă încape în aria tipăribilă:
// Încadrează pagina PDF în aria tipăribilă, păstrând raportul de aspect
var
ScaleX, ScaleY, Scale: Double;
DestWidth, DestHeight: Integer;
Dpi: Integer;
begin
Dpi := 300; // rezoluția țintă de randare
Pdf.PageNumber := PageIndex;
ScaleX := Printer.PageWidth / (Pdf.PageWidth * Dpi / 72);
ScaleY := Printer.PageHeight / (Pdf.PageHeight * Dpi / 72);
Scale := Min(ScaleX, ScaleY);
// Plafonează la 1.0 doar pentru micșorare la încadrare (fără mărire)
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 cu StretchDIBits ca mai sus
end;
Randarea la Dpi = 300 se potrivește majorității imprimantelor de birou. La 600 DPI, bitmap-ul pentru o singură pagină A4 ajunge la aproximativ 34 de megapixeli, adică aproximativ 100 MB ca bitmap pe 32 de biți; câștigul de calitate pentru documentele text obișnuite este minim, iar costul de memorie per pagină este semnificativ. Păstrați 600 DPI pentru tipografii sau desene tehnice bogate în conținut vectorial, unde chiar contează
Indicatorul reAnnotations din al doilea bloc de cod este independent de rePrinting. Includeți-l atunci când utilizatorul se așteaptă ca ștampilele, evidențierile și căsuțele de comentarii să apară pe hârtie. Omiteți-l pentru un rezultat doar cu conținut. Ambii indicatori pot fi combinați liber
Rotația paginii
PDFium stochează rotația paginii în PDF ca o intrare /Rotate, accesibilă prin Pdf.PageRotation, care returnează o valoare TRotation (ro0, ro90, ro180, ro270). Sistemul de coordonate al imprimantei inversează rotațiile de 90 și 270 de grade relativ la ecran. Dacă transmiteți valoarea brută PageRotation direct către RenderPage fără nicio ajustare, paginile landscape încorporate într-un document portret se vor tipări cu susul în jos pe majoritatea driverelor de imprimantă Windows. Remediul este o simplă interschimbare înainte de apelul de randare: mapați ro90 la ro270 și ro270 înapoi la ro90, lăsând ro0 și ro180 neschimbate
Verificați acest comportament pe imprimanta dvs. țintă specifică înainte de lansare. Comportamentul driverelor legat de rotație nu este uniform între furnizori, iar unele drivere aplică propria corecție de rotație la nivelul GDI. Dacă vedeți o rotație dublă, eliminați interschimbarea; dacă nu vedeți deloc corecție, adăugați-o. Un document cu orientare mixtă, cu pagini portret și landscape alternante, este cel mai rapid mod de a prinde oricare dintre cele două moduri de eșec în timpul testării
Gestionarea memoriei pe parcursul unei sarcini lungi de tipărire
Fiecare apel al RenderPage alocă un nou TBitmap pe care apelantul îl deține și trebuie să îl elibereze. În bucla de mai sus, blocul try/finally Bitmap.Free gestionează corect acest lucru câte o pagină pe rând. Nu acumulați bitmap-uri de-a lungul paginilor: o randare la 300 DPI a unui document de 200 de pagini ar consuma gigabytes înainte ca prima pagină să ajungă la spooler. Eliberați fiecare bitmap înainte de a avansa la pagina următoare
Perechea AllocMem / FreeMem din interiorul blocului de transfer urmează aceeași regulă. GetDIBSizes vă spune câtă memorie necesită antetul DIB și datele de pixeli; alocați, umpleți, desenați și eliberați totul în interiorul scopului unei singure pagini. Dacă lăsați oricare dintre blocuri să scape de gestionare, sarcina de tipărire va epuiza heap-ul procesului pe documente mai lungi de câteva zeci de pagini
Dacă trebuie să rulați sarcini de tipărire pe un thread de fundal, păstrați TPdf și toate apelurile de imprimantă VCL pe același thread. TPdf în sine nu este thread-safe între instanțe care partajează starea globală a DLL-ului PDFium; modelul cel mai sigur este câte un TPdf per thread, fiecare încărcând propria copie a fișierului
API-ul de randare și de document prezentat aici face parte din PDFium Component pentru Delphi și C++Builder