Trei rasterizatoare pot citi același PDF și pot fi în dezacord cu privire la ce spune. Motorul integrat din PDF Library for Delphi este cel care vine fără fișiere suplimentare și randează totul competent, motiv pentru care ocupă poziția implicită. Cairo aduce un pipeline diferit de transparență și anti-aliasing și tinde să fie cel la care oamenii apelează atunci când măștile soft (soft masks) sau modurile de blend ies greșit în altă parte. PDFium poartă codul de randare al Chrome, așa că o pagină care arată corect într-un browser arată de obicei corect și sub PDFium, cu prețul unui DLL de dimensiuni considerabile și al unei arhitecturi (bitness) pe care insistă să o respecte. Niciunul dintre cele trei nu este corect în abstract. Corectitudinea este per document, iar singura modalitate onestă de a afla care motor gestionează bine un anumit corpus este să rulezi acel corpus prin fiecare dintre ele
Acesta este argumentul pentru a trata motorul ca pe o alegere la runtime, nu la build. PDF Library for Delphi, biblioteca PDF pentru Delphi și C++Builder de la losLab, pune toate cele trei în spatele unei singure suprafețe de randare, astfel încât decizia costă un întreg, nu o ramificație de cod. Restul acestui articol se rezumă la selectarea în siguranță între ele, confirmarea motoarelor pe care le poartă efectiv un binar implementat și evitarea situației în care starea de randare otrăvește pe tăcute următoarea lucrare
Trei rasterizatoare în spatele unei singure suprafețe de apel
Biblioteca își numerotează motoarele. Motorul 1 este randorul integrat, cel implicit, cu opțiuni de netezire GDI+ pe Windows. Motorul 2 este Cairo, iar motorul 3 este PDFium, ambele selectate la runtime prin SelectRenderer. Cele două motoare externe se încarcă din DLL-uri ale căror căi le furnizezi cu SetCairoFileName și SetPDFiumFileName înainte de a le selecta. Indiferent care motor este activ, lucrul trece prin aceleași apeluri: RenderPageToFile, RenderPageToStream, RenderDocumentToFile. Schimbarea motorului mută un singur număr; restul codului tău de randare nu observă nimic
Modelul de destinație depășește cu mult bitmapurile. Clasa de randare țintește de asemenea metafișiere (WMF, EMF, EMF+), EPS, contexte de dispozitiv directe, imprimante și HTML5, Cairo și PDFium apărând ca destinații suplimentare doar atunci când au fost compilate. Ieșirea raster este locul unde cele trei motoare diverg cel mai vizibil, așa că aceasta este ceea ce folosesc exemplele de aici
Nu presupune niciodată că un motor există: investighează la pornire
Cairo și PDFium sunt caracteristici de compilare condiționată, ceea ce înseamnă că un binar poate fi construit complet fără ele. Când se întâmplă asta, cererea motorului 2 sau 3 nu generează nimic. SelectRenderer pur și simplu returnează o altă valoare decât ID-ul cerut, iar codul care ignoră valoarea returnată continuă să randeze cu orice motor era deja activ. Apărarea este o investigare la pornire care cere fiecărui motor să se identifice și înregistrează răspunsul:
function ProbeEngines(PDF: TPDFlib): string;
begin
Result := 'built-in'; // motorul 1 este întotdeauna prezent
if (PDF.SetCairoFileName('cairo.dll') = 1) and (PDF.SelectRenderer(2) = 2) then
Result := Result + ', cairo';
if (PDF.SetPDFiumFileName('pdfium.dll') = 1) and (PDF.SelectRenderer(3) = 3) then
Result := Result + ', pdfium';
PDF.SelectRenderer(1); // restaurează implicitul înainte de lucrul real
end;
Rulează acea investigare o dată la pornire și scrie rezultatul ei în jurnal alături de fiecare lucrare de randare. Cea mai frecventă întrebare atunci când un client raportează o diferență de randare este ce motoare are efectiv instalarea sa, iar un răspuns de o singură linie aflat în jurnal o rezolvă fără o sesiune de acces la distanță (remote-desktop). Un efect secundar util: dacă SetPDFiumFileName însuși returnează 0, știi deja că DLL-ul este problema (cale greșită, arhitectură greșită, o dependență lipsă), nu un binar compilat fără suport pentru PDFium, pentru că apelul de cale nu a rezolvat nimic înainte ca SelectRenderer să ruleze vreodată
Zece formate de ieșire în spatele unui singur întreg Options
Parametrul Options al apelurilor de randare selectează codificarea de ieșire: 0 este BMP, 1 JPEG, 2 WMF, 3 EMF, 4 EPS, 5 PNG, 6 GIF, 7 TIFF, 8 EMF+ și 9 HTML5. PNG (5) este alegerea implicită rezonabilă pentru previzualizări și imagini de pagină pentru arhivare. JPEG (1), combinat cu SetJPEGQuality, este alegerea mai bună pentru scanări fotografice unde dimensiunea fișierului contează mai mult decât marginile clare
Un format ascunde o cerință privind fluxul țintă. Calea BMP scrie mai întâi datele imaginii, apoi caută înapoi la offsetul 0x26 pentru a corecta câmpurile de rezoluție din antet. Îndreaptă asta către un flux doar-înainte, un wrapper de compresie sau un socket de rețea, iar apelul eșuează într-un mod care pare o eroare de motor, dar nu este. Când o țintă nesecvențiabilă (non-seekable) este inevitabilă, randează în schimb PNG, sau eșalonează BMP-ul printr-un flux de memorie și copiază-l mai departe odată ce este complet
DPI-ul pe care îl transmiți nu este DPI-ul pe care îl obții
Fiecare apel de randare ia un argument DPI, dar rezoluția pe care o obții de fapt este acea valoare înmulțită cu scara globală de randare. SetRenderScale pornește de la 1.0, iar odată ce îl modifici, noul factor se aplică pe tăcute la fiecare randare ulterioară pe acea instanță:
PDF.SetRenderScale(2.0); // fiecare randare ulterioară este dublată
PDF.RenderPageToFile(150, 1, 5, 'p1.png'); // practic 300 DPI
PDF.SetRenderScale(1.0); // resetează, altfel miniaturile tale ajung uriașe
Aceeași persistență se aplică lui SetRenderCropType și setării calității JPEG. Într-un serviciu care produce miniaturi, previzualizări și imagini la rezoluție de tipar dintr-o singură instanță partajată, aceste setări rămase în urmă sunt ceea ce se află de fapt în spatele ocazionalului tichet „miniaturile au ajuns brusc la 40 MB”. Două căi curate de ieșire din asta: resetează starea relevantă la începutul fiecărei operații, sau dedică o instanță separată fiecărui profil de ieșire, astfel încât nimic să nu se scurgă între ele
Reglarea motorului implicit înainte de a apela la altul
O proporție surprinzătoare din cererile de tipul „avem nevoie de un alt motor” se dovedesc a fi probleme de setări deghizate. Randorul integrat își expune comportamentul de netezire prin SetGDIPlusOptions și familia mai largă SetRenderOptions, iar SetGDIPlusFileName îți permite să-l îndrepți către un runtime GDI+ specific atunci când un mediu de implementare vine cu unul neobișnuit. Linii zimțate la DPI scăzut, text neclar în miniaturi, benzi (banding) în gradiente: toate acestea răspund la aceste butoane, iar activarea lor nu costă nimic în instalator. Adăugarea Cairo sau PDFium, dimpotrivă, înseamnă livrarea mai multor DLL-uri, urmărirea unei a doua sau a treia variante de arhitectură (bitness) și asumarea obligației de a le actualiza
Așadar, o reclamație privind calitatea are o ordine naturală a operațiilor. Reproduce-o mai întâi la DPI-ul și scara exacte ale clientului, deoarece în jumătate din cazuri diferența dispare odată ce acestea se potrivesc. Încearcă apoi opțiunile de netezire ale motorului integrat. Abia atunci pune pagina alăturat, comparată între motoare, cu orice altă variabilă menținută constantă: randeaz-o în PNG prin motoarele 1, 2 și 3 la DPI identic și atașează toate cele trei. De obicei, două dintre cele trei sunt de acord, iar acea majoritate îți spune dacă valoarea aberantă este documentul interpretat diferit sau propria ta așteptare de referință care este greșită. Trei imagini concrete rezolvă o dispută de tipul „se randează greșit” mult mai repede decât un paragraf de adjective
Un lanț de rezervă (fallback) care se explică singur
Odată ce investigarea și disciplina stării sunt puse la punct, lanțul de rezervă propriu-zis este scurt. Detectarea unui eșec se bazează pe LastRenderError, care păstrează textul mesajului propriu al motorului pentru cea mai recentă randare și este gol atunci când randarea a reușit:
procedure RenderPageWithFallback(PDF: TPDFlib; Page: Integer; const OutFile: string);
begin
PDF.SelectRenderer(1); // integrat mai întâi
PDF.RenderPageToFile(200, Page, 5, OutFile); // 5 = PNG
if PDF.LastRenderError = '' then Exit;
LogEngineFailure('built-in', Page, PDF.LastRenderError);
if PDF.SelectRenderer(3) = 3 then // PDFium ca rezervă puternică (heavy fallback)
begin
PDF.RenderPageToFile(200, Page, 5, OutFile);
if PDF.LastRenderError = '' then Exit;
LogEngineFailure('pdfium', Page, PDF.LastRenderError);
end;
raise Exception.CreateFmt('Page %d failed on all available engines', [Page]);
end;
Două aspecte de design contează aici. Lanțul înregistrează de ce s-a produs fiecare comutare, deoarece o linie de jurnal care spune „această pagină a trecut pe PDFium din versiunea 3.7” este un semnal de regresie pe care vrei să-l urmărești în monitorizare, nu să-l pierzi. Ordinea de rezervă în sine este o politică ce merită aleasă per sarcină de lucru. Motorul integrat se implementează fără DLL-uri suplimentare, ceea ce îl face prima încercare corectă în majoritatea instalărilor, în timp ce documentele încărcate cu grupuri de transparență sau umbrire neobișnuită sunt motivul obișnuit pentru care o echipă conectează deloc un motor alternativ. Niciun motor nu este cel mai rapid în general, ceea ce este întregul rost al alegerii per apel: testează-le pe fiecare în raport cu un eșantion din documentele tale reale, la DPI-ul tău real, și revizuiește acea măsurătoare de fiecare dată când DLL-urile motorului sau amestecul de documente se schimbă. Corpusul câștigă argumentul de fiecare dată
Dincolo de paginile individuale: loturi TIFF și contexte de dispozitiv live
Doi vecini ai apelurilor per pagină completează setul de instrumente. RenderAsMultipageTIFFToFile randează o expresie de interval de pagini direct într-un TIFF multi-pagină, forma naturală pentru predările de arhivare către sisteme de management al documentelor anterioare PDF-ului. RenderPageToDC pictează direct pe un context de dispozitiv Windows pentru controale de previzualizare, guvernat de propriul trio de setări persistente (SetRenderDCOffset, SetRenderDCErasePage, plus tipul de decupare), care necesită aceeași disciplină de resetare ca factorul de scară. Randarea de previzualizare pe ecran și cea pe calea de tipărire poartă suficiente capcane proprii pentru a merita un articol dedicat, legat mai jos
Unde continui de aici
Un obicei care merită dus mai departe: deoarece SelectRenderer are efect pentru fiecare apel ulterior pe instanță, o singură pagină încăpățânată poate fi reîncercată pe un alt motor, în timp ce restul documentului rămâne pe cel implicit. Pentru pictarea previzualizării, selecția imprimantei și gestionarea DevMode, continuă cu articolul despre previzualizarea tipăririi și contextul de dispozitiv. Când randările alimentează un pipeline de volum mare pentru fișiere foarte mari, abordarea bazată pe handle din ghidul de acces direct se combină natural cu randarea per pagină prin DARenderPageToFile
Ambalarea motoarelor, formatele acceptate și build-urile de probă sunt detaliate pe pagina produsului PDF Library for Delphi