Tehnični članak

Upodabljanje PDF z več pogoni v Delphiju

Trije rasterizatorji lahko preberejo isti PDF in se ne strinjajo o tem, kaj v njem piše. Vgrajeni pogon v PDF Library for Delphi je tisti, ki pride brez dodatnih datotek in vse upodobi spodobno, in prav zato si prisluži privzeto mesto. Cairo prinaša drugačen cevovod za prosojnost in glajenje robov ter je običajno tisti, po katerem ljudje sežejo, kadar mehke maske ali načini zlivanja drugod pridejo ven narobe. PDFium nosi upodabljalno kodo Chroma, zato je stran, ki je videti prav v brskalniku, običajno videti prav tudi pod PDFium, za ceno obsežne knjižnice DLL in bitnosti, na ujemanju katere vztraja. Nobeden od treh ni pravilen na splošno. Pravilnost je stvar posameznega dokumenta, edini pošten način, da ugotovite, kateri pogon obvlada določen korpus, pa je, da ta korpus poženete skozi vsakega od njih

To je argument za to, da pogon obravnavate kot izbiro med izvajanjem in ne kot izbiro ob prevajanju. PDF Library for Delphi, knjižnica losLab za PDF v Delphiju in C++Builderju, vse tri postavi za eno samo upodabljalno površino, tako da odločitev stane eno celo število namesto veje v kodi. Preostanek se zvede na varno izbiranje med njimi, na potrjevanje, katere pogone nameščena binarna datoteka dejansko nosi, in na preprečevanje, da bi upodabljalno stanje tiho zastrupilo naslednje opravilo

Trije rasterizatorji za eno klicno površino

Knjižnica svoje pogone oštevilči. Pogon 1 je vgrajeni upodabljalnik, privzeti, z možnostmi glajenja GDI+ v sistemu Windows. Pogon 2 je Cairo, pogon 3 pa PDFium, oba izbrana med izvajanjem prek SelectRenderer. Zunanja pogona se naložita iz knjižnic DLL, katerih poti podate s SetCairoFileName in SetPDFiumFileName, preden ju izberete. Kateri koli pogon je dejaven, delo teče skozi iste klice: RenderPageToFile, RenderPageToStream, RenderDocumentToFile. Menjava pogona premakne eno število; preostanek vaše upodabljalne kode tega nikoli ne opazi

Model ciljev sega precej dlje od bitnih slik. Upodabljalni razred cilja tudi metadatoteke (WMF, EMF, EMF+), EPS, neposredne kontekste naprav, tiskalnike in HTML5, pri čemer se Cairo in PDFium pojavita kot dodatna cilja le, kadar sta bila vključena ob prevajanju. Rastrski izhod je tam, kjer se trije pogoni najbolj vidno razidejo, zato ga uporabljajo tudi primeri tukaj

Trije pogoni za upodabljanje PDF za eno klicno površino: SelectRenderer preklaplja med vgrajenim pogonom, Cairom in PDFium, medtem ko koda aplikacije še naprej kliče iste upodabljalne funkcije
SelectRenderer zamenja eno celo število in delo preseli med vgrajenim pogonom, Cairom in PDFium. Koda aplikacije še naprej kliče RenderPageToFile in sorodnike, ne glede na to, kateri pogon je izdelal slikovne pike

Nikoli ne predpostavljajte, da pogon obstaja: sondirajte ob zagonu

Cairo in PDFium sta zmožnosti pogojnega prevajanja, kar pomeni, da je binarno datoteko mogoče zgraditi povsem brez njiju. Kadar se to zgodi, zahteva po pogonu 2 ali 3 ne sproži ničesar. SelectRenderer preprosto vrne vrednost, ki ni zahtevani ID, koda, ki vrnjeno vrednost prezre, pa upodablja naprej s tistim pogonom, ki je bil že dejaven. Obramba je zagonska sonda, ki vsak pogon prosi, naj se identificira, in odgovor zabeleži:

PDF Library for Delphi: diagram poteka zagonske sonde pogonov, kjer vsak upodabljalnik potrdi svojo pot DLL in svoj odgovor SelectRenderer, preden se ob vsako upodabljalno opravilo zabeleži povzetek razpoložljivosti
Neuspel klic poti obtoži DLL, neujemajoč se izid SelectRenderer pa pomeni, da pogon ob prevajanju sploh ni bil vključen v binarno datoteko. Sonda teče enkrat, njen enovrstični povzetek pa reši večino strankinih vprašanj o upodabljanju
function ProbeEngines(PDF: TPDFlib): string;
begin
  Result := 'built-in';                        // pogon 1 je vedno prisoten
  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);                       // pred pravim delom obnovi privzetega
end;

To sondo poženite enkrat ob zagonu in njen izid zapišite v dnevnik ob vsakem upodabljalnem opravilu. Najpogostejše vprašanje, kadar stranka poroča o razliki pri upodabljanju, je, katere pogone njena namestitev sploh ima, enovrstični odgovor v dnevniku pa to reši brez seje z oddaljenim namizjem. Koristen stranski učinek: če SetPDFiumFileName sam vrne 0, že veste, da je težava DLL (napačna pot, napačna bitnost, manjkajoča odvisnost) in ne binarna datoteka, prevedena brez podpore za PDFium, saj klic poti ni razrešil ničesar, še preden je SelectRenderer sploh stekel

Deset izhodnih zapisov za enim celim številom Options

Parameter Options pri upodabljalnih klicih izbere izhodno kodiranje: 0 je BMP, 1 JPEG, 2 WMF, 3 EMF, 4 EPS, 5 PNG, 6 GIF, 7 TIFF, 8 EMF+ in 9 HTML5. PNG (5) je smiselna privzeta izbira za predoglede in arhivske slike strani. JPEG (1), v paru s SetJPEGQuality, je boljša izbira za fotografske skene, kjer velikost datoteke šteje bolj kot ostri robovi

En zapis skriva zahtevo glede ciljnega toka. Pot za BMP najprej zapiše slikovne podatke, nato pa se vrne na odmik 0x26, da popravi polji ločljivosti v glavi. Če to usmerite v tok, ki gre samo naprej, v ovoj za stiskanje ali v omrežno vtičnico, klic odpove na način, ki se bere kot okvara pogona, a to ni. Kadar se cilju brez iskanja ni mogoče izogniti, upodobite raje PNG ali pa BMP pripravite prek pomnilniškega toka in ga prekopirajte naprej, ko je dokončan

DPI, ki ga podate, ni DPI, ki ga dobite

Vsak upodabljalni klic vzame argument DPI, ločljivost, ki jo dejansko dobite, pa je ta vrednost, pomnožena z globalnim upodabljalnim merilom. SetRenderScale se začne pri 1,0, in ko ga spremenite, novi faktor tiho velja za vsako poznejše upodabljanje na tem primerku:

PDF.SetRenderScale(2.0);                    // vsako poznejše upodabljanje je podvojeno
PDF.RenderPageToFile(150, 1, 5, 'p1.png');  // dejansko 300 DPI
PDF.SetRenderScale(1.0);                    // ponastavite, sicer bodo sličice ogromne

Enaka lepljivost velja za SetRenderCropType in za nastavitev kakovosti JPEG. V storitvi, ki iz enega skupnega primerka izdeluje sličice, predoglede in slike v tiskarski ločljivosti, so te preostale nastavitve tisto, kar v resnici stoji za občasno prijavo "sličice so nenadoma velike 40 MB". Dve čisti poti ven: ustrezno stanje ponastavite na začetku vsake operacije ali pa vsakemu izhodnemu profilu namenite ločen primerek, da nič ne pušča med njimi

Nastavljanje privzetega pogona, preden sežete po drugem

Presenetljiv delež zahtev "potrebujemo drug pogon" se izkaže za težave z nastavitvami v preobleki. Vgrajeni upodabljalnik svoje vedenje pri glajenju razkriva prek SetGDIPlusOptions in širše družine SetRenderOptions, SetGDIPlusFileName pa vam omogoča, da ga usmerite na določeno izvajalno okolje GDI+, kadar namestitveno okolje prinese nenavadnega. Nazobčane črtne risbe pri nizkem DPI, zabrisano besedilo v sličicah, pasovi po prelivih: vse to se odziva na te gumbe, njihovo obračanje pa v namestitvenem programu ne stane nič. Dodajanje Caira ali PDFium nasprotno pomeni odpremo dodatnih knjižnic DLL, sledenje drugi ali tretji različici glede na bitnost in prevzem obveznosti, da ju posodabljate

Pritožba glede kakovosti ima torej naraven vrstni red korakov. Najprej jo ponovite pri strankinem točnem DPI in merilu, saj razlika v polovici primerov izpuhti, brž ko se ta dva ujemata. Nato preizkusite možnosti glajenja vgrajenega pogona. Šele zatem stran postavite drugo ob drugo po pogonih, pri čemer je vsaka druga spremenljivka nespremenjena: upodobite jo v PNG skozi pogone 1, 2 in 3 pri enakem DPI ter priložite vse tri. Običajno se dva od treh ujemata, ta večina pa vam pove, ali je izstopajoči primer dokument, ki se razlaga drugače, ali vaše lastno izhodiščno pričakovanje, ki je zgrešeno. Tri konkretne slike spor o "napačnem upodabljanju" rešijo veliko hitreje kot odstavek pridevnikov

Veriga zasilnih poti, ki pojasni samo sebe

Ko sta sondiranje in disciplina glede stanja vzpostavljena, je veriga zasilnih poti sama kratka. Zaznavanje odpovedi se opira na LastRenderError, ki drži pogonovo lastno besedilo sporočila za zadnje upodabljanje in je prazno, kadar je upodabljanje uspelo:

Veriga zasilnih poti pri upodabljanju PDF: najprej poskusi vgrajeni pogon, odpovedi se zabeležijo, PDFium poskusi znova, sprožena izjema pa javi, kadar stran odpove na vseh razpoložljivih pogonih
Vsak poskus preveri LastRenderError in razlog zabeleži, preden zamenja pogon. Šele ko odpovejo vsi nameščeni pogoni, veriga sproži izjemo, zbrani vzroki pa že ležijo v dnevniku
procedure RenderPageWithFallback(PDF: TPDFlib; Page: Integer; const OutFile: string);
begin
  PDF.SelectRenderer(1);                            // najprej vgrajeni
  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 kot težka zasilna pot
  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 načrtovalski točki tu nosita težo. Veriga zabeleži, zakaj je do vsake menjave prišlo, saj je vrstica v dnevniku, ki se glasi "ta stran je od izdaje 3.7 padla na PDFium", regresijski signal, ki ga želite spremljati v nadzoru in ne izgubiti. Vrstni red zasilnih poti je sam po sebi politika, ki jo je vredno izbrati za vsako delovno breme posebej. Vgrajeni pogon se namesti brez dodatnih knjižnic DLL, kar ga v večini namestitev postavi za pravi prvi poskus, dokumenti, obloženi s skupinami prosojnosti ali nenavadnimi senčenji, pa so običajni razlog, da ekipa sploh vgradi nadomestni pogon. Noben pogon ni na splošno najhitrejši, kar je celoten smisel izbiranja pri vsakem klicu: vsakega izmerite na vzorcu svojih resničnih dokumentov pri svojem resničnem DPI in to meritev ponovite vsakič, ko se knjižnice DLL pogonov ali mešanica dokumentov spremenijo. Razpravo vsakič dobi korpus

Onkraj posameznih strani: paketi TIFF in živi konteksti naprav

Nabor orodij zaokrožita dva soseda klicev za posamezno stran. RenderAsMultipageTIFFToFile izraz z obsegom strani upodobi naravnost v večstranski TIFF, kar je naravna oblika za arhivske predaje sistemom za upravljanje dokumentov, ki so starejši od PDF. RenderPageToDC slika neposredno na kontekst naprave Windows za kontrolnike predogleda in jo vodi lastna trojica lepljivih nastavitev (SetRenderDCOffset, SetRenderDCErasePage ter vrsta obrezovanja), ki potrebujejo enako disciplino ponastavljanja kot faktor merila. Predogled na zaslonu in upodabljanje po tiskalniški poti nosita dovolj lastnih pasti, da si zaslužita svoj članek, povezan spodaj

Kam naprej

Ena navada je vredna, da jo odnesete s seboj: ker SelectRenderer velja za vsak poznejši klic na primerku, je mogoče eno trmasto stran znova poskusiti na drugem pogonu, medtem ko preostanek dokumenta ostane na privzetem. Za slikanje predogleda, izbiro tiskalnika in ravnanje z DevMode nadaljujte s člankom o predogledu tiskanja in kontekstu naprave. Kadar upodabljanja napajajo obsežen cevovod nad zelo velikimi datotekami, se pristop na osnovi ročic iz vodnika po neposrednem dostopu naravno poveže z upodabljanjem po straneh prek DARenderPageToFile

Pakiranje pogonov, podprti zapisi in preizkusne gradnje so podrobno opisani na strani izdelka PDF Library for Delphi