Tri rasterizatora mogu čitati isti PDF dokument i ne slagati se oko toga šta na njemu piše. Ugrađeni motor u PDFlibPas-u je onaj koji se isporučuje bez dodatnih datoteka i kompetentno renderuje sve, zbog čega mu i pripada podrazumevano mesto. Cairo donosi drugačiji sistem za obradu providnosti i antialijasinga (anti-aliasing) i obično je rešenje za kojim ljudi posežu kada se meke maske (soft masks) ili režimi mešanja boja (blend modes) ne prikažu kako treba na drugim mestima. PDFium nosi Chrome-ov kod za renderovanje, pa stranica koja izgleda ispravno u pregledaču obično izgleda ispravno i pod PDFium-om, ali po cenu prilično velike DLL datoteke i zahteva za usklađivanjem bitnosti procesa (bitness). Nijedan od ova tri motora nije ispravan sam po sebi. Ispravnost se procenjuje po dokumentu, a jedini iskren način da saznate koji motor najbolje rukuje određenim skupom dokumenata jeste da taj skup propustite kroz svaki od njih.
To je razlog zašto motor treba tretirati kao izbor u vreme izvršavanja (runtime choice), a ne u vreme prevođenja koda (build-time). PDFlibPas, biblioteka za PDF u Delphi-ju i C++Builder-u kompanije losLab, postavlja sva tri motora iza jedne površine za renderovanje, tako da ova odluka košta samo jedan ceo broj umesto grananja koda. Ostatak ovog procesa svodi se na bezbedan izbor između njih, potvrdu koje motore isporučeni binarni fajl zapravo sadrži i sprečavanje da stanje renderovanja tiho pokvari sledeći zadatak.
Tri rasterizatora iza jedne površine za pozive
Biblioteka označava svoje motore brojevima. Motor 1 je ugrađeni renderer, podrazumevani, sa opcijama GDI+ glađenja na Windows-u. Motor 2 je Cairo, a motor 3 je PDFium, i oba se biraju u vreme izvršavanja pomoću funkcije SelectRenderer. Dva eksterna motora se učitavaju iz DLL-ova čije putanje dostavljate preko SetCairoFileName i SetPDFiumFileName pre nego što ih izaberete. Koji god motor da je aktivan, posao se obavlja kroz iste pozive: RenderPageToFile, RenderPageToStream, RenderDocumentToFile. Promena motora pomera samo jedan broj; ostatak vašeg koda za renderovanje to uopšte ne primećuje.
Model odredišta prevazilazi obične bitmape. Klasa renderera takođe podržava metafajlove (WMF, EMF, EMF+), EPS, direktne kontekste uređaja (device contexts), štampače i HTML5, pri čemu se Cairo i PDFium pojavljuju kao dodatna odredišta samo kada su uključeni prilikom prevođenja koda. Rasterizovani izlaz je tačka u kojoj se ova tri motora najuočljivije razlikuju, pa se zato u ovim primerima koristi upravo taj izlaz.
Nikada ne pretpostavljajte da motor postoji: proverite na startup-u
Cairo i PDFium su funkcije uslovnog prevođenja (conditional compilation), što znači da binarni fajl može biti izgrađen u potpunosti bez njih. Kada se to dogodi, zahtevanje motora 2 ili 3 ne podiže nikakvu grešku. Funkcija SelectRenderer jednostavno vraća vrednost koja se razlikuje od identifikatora koji ste tražili, a kod koji ignoriše povratnu vrednost nastavlja da renderuje pomoću onog motora koji je već bio aktivan. Odbrana od ovoga je početna provera (startup probe) koja traži od svakog motora da se identifikuje i beleži taj odgovor:
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;
Pokrenite ovu proveru jednom pri pokretanju sistema i upišite njen rezultat u dnevnik rada pored svakog posla renderovanja. Najčešće pitanje kada korisnik prijavi razliku u renderovanju jeste koje motore njegova instalacija zapravo poseduje, a odgovor od jedne linije u dnevniku to rešava bez potrebe za sesijom udaljene radne površine (remote desktop). Koristan nusefekat: ako sama funkcija SetPDFiumFileName vrati 0, već znate da je problem u DLL-u (pogrešna putanja, pogrešna bitnost, nedostajuća zavisnost), a ne u binarnom fajlu prevedenom bez podrške za PDFium, jer je poziv za putanju otkazao pre nego što se SelectRenderer uopšte pokrenuo.
Deset izlaznih formata iza jednog celog broja Options
Parametar Options u pozivima za renderovanje bira kodiranje izlaza: 0 je BMP, 1 je JPEG, 2 je WMF, 3 je EMF, 4 je EPS, 5 je PNG, 6 je GIF, 7 je TIFF, 8 je EMF+, i 9 je HTML5. PNG (5) je razumljiva podrazumevana opcija za preglede i arhivske slike stranica. JPEG (1), u kombinaciji sa SetJPEGQuality, bolji je izbor za fotografske skenove gde je veličina datoteke važnija od oštrih ivica.
Jedan format krije poseban zahtev u vezi sa ciljnim tokom (target stream). BMP putanja prvo upisuje podatke o slici, a zatim se vraća (seeks back) na ofset 0x26 kako bi korigovala polja rezolucije u zaglavlju. Ako to usmerite na tok koji podržava samo upis unapred (forward-only stream), na primer, kompresioni omotač ili mrežni soket, poziv otkazuje na način koji izgleda kao greška motora, ali to zapravo nije. Kada je nemoguće izbeći cilj koji ne podržava pozicioniranje (non-seekable), renderujte PNG ili sprovedite BMP kroz memorijski tok (memory stream) i kopirajte ga dalje tek kada se proces završi.
DPI koji prosledite nije DPI koji dobijete
Svaki poziv za renderovanje prima DPI argument, ali rezolucija koju zapravo dobijate jeste ta vrednost pomnožena sa globalnom razmerom renderovanja (render scale). SetRenderScale počinje sa 1.0, a nakon što je promenite, novi faktor se tiho primenjuje na svako kasnije renderovanje u toj instanci:
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
Ista perzistentnost važi i za SetRenderCropType i podešavanje JPEG kvaliteta. U servisu koji proizvodi sličice (thumbnails), preglede i slike visoke rezolucije za štampu iz jedne zajedničke instance, ova preostala podešavanja su stvarni uzrok povremenih problema tipa „sličice su odjednom velike 40 MB”. Dva čista izlaza su: resetovanje relevantnog stanja na početku svake operacije, ili dodeljivanje posebne instance za svaki izlazni profil kako ništa ne bi procurelo između njih.
Podešavanje podrazumevanog motora pre prelaska na drugi
Iznenađujuće veliki udeo zahteva za „drugim motorom” ispostavi se kao problem sa podešavanjima pod maskom. Ugrađeni renderer izlaže svoje ponašanje glađenja kroz SetGDIPlusOptions i širu porodicu SetRenderOptions, a SetGDIPlusFileName vam omogućava da ga usmerite na specifično GDI+ okruženje kada okruženje za primenu isporučuje neko nestandardno. Nazubljene linije na niskom DPI-ju, zamućen tekst na sličicama, prelazi na gradijentima: sve ovo reaguje na ta podešavanja, a njihovo menjanje ne košta ništa u instalacionom paketu. Nasuprot tome, dodavanje Cairo-a ili PDFium-a znači isporuku dodatnih DLL datoteka, praćenje druge ili treće varijante bitnosti i preuzimanje obaveze njihovog ažuriranja.
Zato žalba na kvalitet ima prirodan redosled koraka. Prvo je reprodukujte na tačnom DPI-ju i razmeri korisnika, jer se u polovini slučajeva razlika gubi kada se ti parametri usklade. Zatim isprobajte opcije glađenja ugrađenog motora. Tek nakon toga postavite stranicu jednu pored druge na različitim motorima, držeći sve ostale varijable konstantnim: renderujte je u PNG pomoću motora 1, 2 i 3 na identičnom DPI-ju i priložite sve tri slike. Obično se dve od tri slike slažu, a ta većina vam govori da li je odstupanje posledica drugačijeg tumačenja dokumenta ili vašeg pogrešnog osnovnog očekivanja. Tri konkretne slike rešavaju spor oko „lošeg renderovanja” znatno brže nego pasus pun prideva.
Fallback lanac koji objašnjava sam sebe
Kada se uspostave provera i disciplina upravljanja stanjem, sam fallback lanac je prilično kratak. Detekcija neuspeha se oslanja na LastRenderError, koji čuva poruku o grešci samog motora za poslednje renderovanje i prazan je kada je renderovanje uspešno obavljeno:
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;
Dve dizajnerske tačke ovde imaju veliku važnost. Lanac beleži zašto je došlo do svake promene, jer linija dnevnika koja glasi „ova stranica je prebačena na PDFium od izdanja 3.7” predstavlja signal regresije koji želite da pratite kroz monitoring, umesto da bude izgubljen. Sam redosled fallback-a je pravilo koje vredi izabrati po tipu posla. Ugrađeni motor se primenjuje bez dodatnih DLL-ova, što ga čini ispravnim prvim pokušajem u većini instalacija, dok su dokumenti sa složenim grupama providnosti ili neobičnim senčenjem uobičajeni razlog zašto tim uopšte uvodi alternativni motor. Nijedan motor nije najbrži u opštem slučaju, što je i poenta izbora po pozivu: testirajte performanse svakog motora na uzorku vaših stvarnih dokumenata na vašem stvarnom DPI-ju i ponovo procenite te rezultate kad god se DLL-ovi motora ili sastav dokumenata promene. Skup dokumenata (corpus) svaki put pobeđuje u diskusiji.
Iza pojedinačnih stranica: grupni TIFF i živi konteksti uređaja
Dva srodna poziva dopunjuju ovaj alat. RenderAsMultipageTIFFToFile renderuje izraz opsega stranica direktno u višestranični TIFF, što je prirodan oblik za arhivske predaje sistemima za upravljanje dokumentima koji su stariji od PDF formata. RenderPageToDC iscrtava direktno na Windows kontekst uređaja (device context) radi kontrole pregleda, a njime upravlja sopstveni trio perzistentnih podešavanja (SetRenderDCOffset, SetRenderDCErasePage, plus tip isecanja) koji zahtevaju istu disciplinu resetovanja kao i faktor razmere. Pregled na ekranu i renderovanje putanje za štampanje nose dovoljno sopstvenih zamki da zaslužuju poseban članak, linkovan u nastavku.
Kuda dalje
Jedna navika koju vredi zadržati: pošto SelectRenderer stupa na snagu za svaki kasniji poziv na toj instanci, renderovanje jedne problematične stranice može se ponovo pokušati na drugom motoru dok ostatak dokumenta ostaje na podrazumevanom. Za iscrtavanje pregleda, izbor štampača i rukovanje DevMode-om, nastavite sa člankom o pregledu štampe i kontekstu uređaja. Kada renderovanje snabdeva procese visoke frekvencije nad veoma velikim datotekama, pristup zasnovan na opisnicima iz vodiča za direktan pristup prirodno se povezuje sa renderovanjem pojedinačnih stranica pomoću funkcije DARenderPageToFile.
Pakovanje motora, podržani formati i probne verzije detaljno su opisani na PDFlibPas stranici proizvoda.