Tri rastrovacie moduly môžu načítať rovnaký súbor PDF a nezhodnúť sa na jeho zobrazení. Integrovaný modul v PDFlibPas je ten, ktorý sa dodáva bez akýchkoľvek dodatočných súborov a vykresľuje všetko kompetentne, vďaka čomu získava predvolenú pozíciu. Cairo prináša odlišnú pipeline pre priehľadnosť a vyhladzovanie (anti-aliasing) a býva voľbou, po ktorej vývojári siahnu, keď sa inde nesprávne zobrazujú mäkké masky (soft masks) alebo režimy prelínania. PDFium obsahuje vykresľovací kód z prehliadača Chrome, takže stránka, ktorá vyzerá správne v prehliadači, zvyčajne vyzerá správne aj pod PDFium, avšak za cenu veľkej knižnice DLL a nutnosti zladiť bitovú architektúru. Žiadny z týchto troch modulov nie je univerzálne správny. Správnosť závisí od konkrétneho dokumentu a jediný spoľahlivý spôsob, ako zistiť, ktorý modul spracuje danú sadu súborov najlepšie, je otestovať ju v každom z nich.
To je dôvod, prečo by sa mal vykresľovací modul vyberať počas behu aplikácie (runtime) a nie pri kompilácii (build-time). Knižnica PDFlibPas, určená pre Delphi a C++Builder od losLab, zastrešuje všetky tri moduly pod jednotným rozhraním, takže výber konkrétneho modulu stojí iba jedno celé číslo namiesto vetvenia kódu. Zvyšok tohto článku sa zaoberá bezpečným výberom medzi nimi, overením, ktoré moduly nasadený binárny súbor skutočne obsahuje, a zabránením tomu, aby stav vykresľovania potichu ovplyvnil ďalšiu úlohu.
Tri rastrovacie moduly za jedným volacím rozhraním
Knižnica čísluje svoje moduly. Modul 1 je integrovaný vykresľovač, predvolený, s možnosťami vyhladzovania GDI+ v systéme Windows. Modul 2 je Cairo a modul 3 je PDFium, pričom oba sa vyberajú počas behu programu pomocou SelectRenderer. Tieto dva externé moduly sa načítavajú z knižníc DLL, ktorých cesty zadávate pomocou SetCairoFileName and SetPDFiumFileName pred ich výberom. Bez ohľadu na to, ktorý modul je aktívny, práca prebieha cez rovnaké volania: RenderPageToFile, RenderPageToStream, RenderDocumentToFile. Prepnutie modulov zmení iba jedno číslo; zvyšok vášho kódu pre vykresľovanie si zmenu ani nevšimne.
Model cieľových formátov presahuje rámec bitmapových obrázkov. Trieda vykresľovača podporuje aj súbory metafile (WMF, EMF, EMF+), EPS, priame kontexty zariadení (device context), tlačiarne a HTML5, pričom Cairo a PDFium sa zobrazujú ako dodatočné ciele iba vtedy, keď boli skompilované. Rastrový výstup je oblasť, kde sa tieto tri moduly odlišujú najvýraznejšie, preto ho používame v nasledujúcich príkladoch.
Nikdy nepredpokladajte, že modul existuje: otestujte ho pri štarte
Cairo a PDFium sú funkcie podmienečnej kompilácie, čo znamená, že binárny súbor môže byť zostavený úplne bez nich. Keď k tomu dôjde, vyžiadanie modulu 2 alebo 3 nevyvolá žiadnu výnimku. SelectRenderer jednoducho vráti inú hodnotu než požadované ID a kód, ktorý ignoruje návratovú hodnotu, pokračuje vo vykresľovaní pomocou predtým aktívneho modulu. Obranou je test pri štarte, ktorý požiada každý modul o identifikáciu a zaznamená odpoveď:
function ProbeEngines(PDF: TPDFlib): string;
begin
Result := 'built-in'; // engine 1 is always present
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); // restore the default before real work
end;
Spusťte tento test raz pri štarte a zapíšte jeho výsledok do logu ku každej úlohe vykresľovania. Najčastejšou otázkou pri hlásení rozdielov vo vykresľovaní zákazníkom je, ktoré moduly ich inštalácia reálne obsahuje. Jednoriadková odpoveď v logu to vyrieši bez potreby vzdialeného pripojenia. Užitočný vedľajší efekt: ak samotná funkcia SetPDFiumFileName vráti hodnotu 0, hneď viete, že problémom je samotná knižnica DLL (nesprávna cesta, zlá bitová architektúra, chýbajúca závislosť) a nie binárny súbor skompilovaný bez podpory PDFium, keďže k nastaveniu cesty došlo ešte pred samotným spustením SelectRenderer.
Desať výstupných formátov za jedným celým číslom Options
Parameter Options vo volaniach vykresľovania určuje kódovanie výstupu: 0 je BMP, 1 JPEG, 2 WMF, 3 EMF, 4 EPS, 5 PNG, 6 GIF, 7 TIFF, 8 EMF+ a 9 HTML5. PNG (5) je rozumnou predvolenou voľbou pre náhľady a archívne obrázky stránok. JPEG (1) v kombinácii s SetJPEGQuality je lepšou voľbou pre fotografické skeny, kde na veľkosti súboru záleží viac než na ostrých hranách.
Jeden z formátov skrýva špecifickú požiadavku na cieľový prúd dát (stream). Zápis BMP najprv zapíše obrazové dáta a potom sa vráti (seek) na pozíciu 0x26, aby upravil polia s rozlíšením v hlavičke. Ak toto volanie nasmerujete na jednosmerný prúd (forward-only stream), napríklad kompresný wrapper alebo sieťový socket, zlyhá spôsobom, ktorý vyzerá ako chyba modulu, hoci ňou nie je. Ak sa nemôžete vyhnúť cieľu bez podpory náhodného prístupu (non-seekable), vykreslite radšej PNG alebo BMP spracujte v pamäťovom prúde (memory stream) a po dokončení ho skopírujte ďalej.
Zadané DPI nie je výsledné DPI
Každé volanie vykresľovania prijíma argument DPI, ale reálne získané rozlíšenie je táto hodnota vynásobená globálnou mierkou vykresľovania. Funkcia SetRenderScale začína na hodnote 1.0 a po jej zmene sa nový koeficient ticho uplatňuje na každé ďalšie vykresľovanie v danej inštancii:
PDF.SetRenderScale(2.0); // every later render is doubled
PDF.RenderPageToFile(150, 1, 5, 'p1.png'); // effectively 300 DPI
PDF.SetRenderScale(1.0); // reset, or your thumbnails arrive huge
Rovnaká pretrvávajúca platnosť (stickiness) platí pre SetRenderCropType a nastavenie kvality JPEG. V službe, ktorá vytvára miniatúry, náhľady a obrázky v tlačovej kvalite z jednej zdieľanej inštancie, sú tieto zabudnuté nastavenia skutočným dôvodom občasných hlásení typu „miniatúry majú zrazu 40 MB“. Existujú dve čisté riešenia: resetovať príslušný stav na začiatku každej operácie alebo vyhradiť samostatnú inštanciu pre každý profil výstupu, aby sa predišlo akémukoľvek prenosu nastavení.
Ladenie predvoleného modulu pred siahnutím po inom
Prekvapivo veľká časť požiadaviek typu „potrebujeme iný modul“ sa ukáže byť iba skrytým problémom s nastaveniami. Integrovaný vykresľovač sprístupňuje svoje správanie pri vyhladzovaní prostredníctvom SetGDIPlusOptions a širšej rodiny funkcií SetRenderOptions. Funkcia SetGDIPlusFileName navyše umožňuje nasmerovať ho na konkrétne runtime prostredie GDI+, ak klientske prostredie obsahuje neštandardnú verziu. Zubaté čiary pri nízkom DPI, rozmazaný text v miniatúrach či pásmo v prechodoch farieb (gradient banding): na to všetko pomáhajú spomínané nastavenia, ktorých úprava nič nestojí. Naopak, pridanie modulu Cairo alebo PDFium znamená distribúciu ďalších knižníc DLL, sledovanie bitovej verzie a povinnosť ich pravidelne aktualizovať.
Preto má riešenie sťažností na kvalitu svoj prirodzený postup. Najprv problém reprodukujte pri presnom DPI a mierke zákazníka, pretože v polovici prípadov sa rozdiel po zladení týchto parametrov stratí. Následne vyskúšajte možnosti vyhladzovania integrovaného modulu. Až potom porovnajte stránku vedľa seba v rôznych moduloch pri zachovaní všetkých ostatných premenných: vykreslite ju do PNG pomocou modulov 1, 2 a 3 pri rovnakom DPI a priložte všetky tri výsledky. Zvyčajne sa dva z troch zhodujú, a táto väčšina vám napovie, či ide o odlišnú interpretáciu dokumentu modulom, alebo o nesprávne očakávanie. Tri konkrétne obrázky vyriešia spor o chybnom vykresľovaní oveľa rýchlejšie ako odsek plný prívlastkov.
Samo-vysvetľujúci reťazec záložných modulov (fallback chain)
Po zavedení testovania a dôslednej správe stavu je samotný záložný reťazec krátky. Detekcia zlyhania sa opiera o vlastnosť LastRenderError, ktorá obsahuje text chybovej správy z posledného vykresľovania a je prázdna, ak prebehlo úspešne:
procedure RenderPageWithFallback(PDF: TPDFlib; Page: Integer; const OutFile: string);
begin
PDF.SelectRenderer(1); // built-in first
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 as the 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;
Dva konštrukčné body tu majú veľkú váhu. Reťazec zaznamenáva dôvod každého prepnutia, pretože riadok v logu typu „táto stránka prešla na PDFium od verzie 3.7“ je dôležitým signálom o regresii, ktorý chcete sledovať v monitoringu a nie ho stratiť. Samotné poradie zálohy je politika, ktorú je vhodné zvoliť podľa zaťaženia. Integrovaný modul sa nasadzuje bez dodatočných DLL, vďaka čomu je správnou prvou voľbou vo väčšine inštalácií, zatiaľ čo dokumenty s komplikovanými skupinami priehľadnosti alebo neštandardným tieňovaním sú zvyčajne dôvodom, prečo tímy integrujú alternatívne moduly. Vo všeobecnosti žiaden modul nie je najrýchlejší, čo je presne dôvodom voľby pre každé volanie zvlášť: otestujte každý z nich na vzorke vašich reálnych dokumentov pri vašom reálnom DPI a toto meranie zopakujte vždy, keď sa zmenia DLL moduly alebo štruktúra dokumentov. Súbor testovacích dát má v diskusii vždy posledné slovo.
Okrem jednotlivých stránok: dávky TIFF a živé kontexty zariadení
Nástroje dopĺňajú dve príbuzné funkcie k volaniam pre jednotlivé stránky. RenderAsMultipageTIFFToFile vykreslí rozsah stránok priamo do viacstránkového súboru TIFF, čo je ideálny formát pre archiváciu a odovzdávanie starším systémom správy dokumentov. RenderPageToDC kreslí priamo na kontext zariadenia (device context) Windows pre ovládacie prvky náhľadu. Riadi sa vlastnou trojicou pretrvávajúcich nastavení (SetRenderDCOffset, SetRenderDCErasePage a typ orezania), ktoré vyžadujú rovnakú disciplínu resetovania stavu ako koeficient mierky. Náhľad na obrazovke a vykresľovanie pre tlač však nesú dostatok špecifických úskalí na to, aby si zaslúžili samostatný článok, na ktorý odkazujeme nižšie.
Kam pokračovať ďalej
Jeden zvyk, ktorý stojí za to si osvojiť: keďže SelectRenderer ovplyvňuje každé ďalšie volanie na danej inštancii, jedinú problematickú stránku môžete skúsiť vykresliť v inom module, zatiaľ čo zvyšok dokumentu spracujete v predvolenom module. Pre kreslenie náhľadu, výber tlačiarne a správu DevMode pokračujte článkom o náhľade tlače a kontexte zariadenia. Ak vykresľovanie zásobuje vysoko vyťaženú pipeline nad veľkými súbormi, prístup založený na popisovačoch popísaný v príručke pre priamy prístup sa prirodzene dopĺňa s vykresľovaním po stránkach cez DARenderPageToFile.
Bližšie informácie o distribúcii modulov, podporovaných formátoch a testovacích zostavách nájdete na produktovej stránke PDFlibPas.