Tekninen artikkeli

Monimoottorinen PDF-renderöinti Delphissä: PDF Library for Delphi

Kolme rasteroijaa voivat lukea saman PDF:n ja olla eri mieltä siitä, mitä se sanoo. Sisäänrakennettu moottori PDFlibPasissa on se, joka toimitetaan ilman ylimääräisiä tiedostoja ja renderöi kaiken kelvollisesti, mistä syystä se ansaitsee oletuspaikan. Cairo tuo erilaisen läpinäkyvyys- ja antialiasointiputken, ja on yleensä se, johon tartutaan kun pehmeät maskit tai blend-moodit tulevat väärin muualla. PDFium kantaa Chromen renderöintikoodin, joten sivu joka näyttää oikealta selaimessa näyttää yleensä oikealta myös PDFiumin alla, hinnalla kohtuullisen kokoinen DLL ja bittisyys jota se vaatii vastaavaksi. Mikään kolmesta ei ole oikea abstraktisti. Oikeellisuus on per dokumentti, ja ainoa rehellinen tapa oppia, mikä moottori käsittelee tietyn joukon, on ajaa tuo joukko kunkin läpi

Siitä on kyse kun moottoria pidetään ajonaikaisena valintana ennemmin kuin build-aikaisena. PDF Library for Delphi, losLabin Delphi- ja C++Builder-PDF-kirjasto, laittaa kaikki kolme yhden renderöintipinnan taakse, joten päätös maksaa yhden kokonaisluvun eikä koodihaaran. Loput tulee turvallisen valitsemiseen niiden välillä, sen vahvistamiseen mitä moottoreita käyttöönotettu binääri oikeasti kantaa, ja renderöintitilan pitämisen hiljaa myrkyttämästä seuraavaa työtä

Kolme rasteroijaa yhden kutsupinnan takana

Kirjasto numeroi moottorinsa. Moottori 1 on sisäänrakennettu renderöijä, oletus, ja GDI+ pehmennysvaihtoehdot Windowsissa. Moottori 2 on Cairo ja moottori 3 on PDFium, molemmat valittuna ajonaikaisesti SelectRenderer:n kautta. Kaksi ulkoista moottoria latautuvat DLL:istä, joiden polut syötät SetCairoFileName:lla ja SetPDFiumFileName:lla ennen kuin valitset ne. Mikä moottori on aktiivinen, työ menee samoisten kutsujen läpi: RenderPageToFile, RenderPageToStream, RenderDocumentToFile. Moottorien vaihto siirtää yhtä numeroa; loput renderöintikoodistasi ei huomaa mitään

Kohdemalli ulottuu kauas bitmapien ohi. Renderöijäluokka kohdistaa myös metafileihin (WMF, EMF, EMF+), EPS, suoriin device contexteihin, tulostimiin ja HTML5:een, Cairo ja PDFium näkyen lisäkohteina vain kun ne on käännetty mukaan. Rasterituloste on siellä, missä kolme moottoria eroavat näkyvimmmin, joten se on mitä esimerkit tässä käyttävät

Kolme PDF-renderointimoottoria yhden kutsupinnan takana: SelectRenderer vaihtaa sisäänrakennetun moottorin, Cairon ja PDFiumin välillä, kun sovelluskoodi kutsuu edelleen samoja renderointifunktioita
SelectRenderer vaihtaa yhden kokonaisluvun työnsiirtämiseen sisäänrakennetun, Cairon ja PDFium-moottorin välillä. Sovelluskoodi jatkaa RenderPageToFile-kutsuja kumppaneineen riippumatta siitä, kumpi moottori tuotti pikselit

Älä koskaan oleta moottorin olemassaoloa: tutki käynnistyksessä

Cairo ja PDFium ovat ehdollisen käännöksen ominaisuuksia, mikä tarkoittaa että binääri voidaan rakentaa kokonaan ilman niitä. Kun näin käy, moottorin 2 tai 3 pyytäminen ei nosta mitään. SelectRenderer palauttaa arvon, joka on jokin muu kuin pyytämäsi ID, ja koodi, joka ohittaa paluuarvon, jatkaa renderöintiä moottorilla, joka oli jo aktiivinen. Puolustus on käynnistystutka, joka pyytää kutakin moottoria tunnistamaan itsensä ja tallentaa vastauksen:

function ProbeEngines(PDF: TPDFlib): string;
begin
  Result := 'built-in';                        // moottori 1 on aina läsnä
  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);                       // palauta oletus ennen varsinaista työtä
end;

Aja tuo tutka kerran käynnistyksessä ja kirjoita sen tulos lokiin jokaisen renderöintityön viereen. Yleisin kysymys kun asiakas raportoi renderöintieron, on mitä moottoreita hänen asennuksensa oikeasti on, ja yhden rivin vastaus lokissa ratkaisee sen ilman remote-desktop-istuntoa. Hyödyllinen sivuvaikutus: jos SetPDFiumFileName itse palauttaa 0, tiedät jo DLL:n olevan ongelma (väärä polku, väärä bittisyys, puuttuva riippuvuus) ennemmin kuin binääri joka on käännetty ilman PDFium-tukea, koska polkukutsu ei ratkaissut mitään ennen kuin SelectRenderer koskaan ajettiin

Kymmenen tulosteformaattia yhden Options-kokonaisluvun takana

Options-parametri renderöintikutsuilla valitsee tulosteen koodauksen: 0 on BMP, 1 JPEG, 2 WMF, 3 EMF, 4 EPS, 5 PNG, 6 GIF, 7 TIFF, 8 EMF+, ja 9 HTML5. PNG (5) on järkevä oletus esikatseluille ja arkistosivukuville. JPEG (1), paritettuna SetJPEGQuality:n kanssa, on parempi valinta valokuvaskannauksille, missä tiedostokoko merkitsee enemmän kuin terävät reunat

Yksi formaatti piilottaa vaatimuksen kohdevirran suhteen. BMP-polku kirjoittaa kuvadatan ensin, sitten etsiytyy takaisin offsetiin 0x26 paikatakseen resoluutiokentät otsikossa. Suuntaa se vain-eteenpäin-virtaan, pakkauskääreeseen tai verkkopistokkeeseen, ja kutsu epäonnistuu tavalla, joka luetaan moottorivikana mutta ei ole. Kun ei-etsittävä kohde on välttämätön, renderöi PNG:itä sen sijaan, tai välitä BMP muistivirran kautta ja kopioi se eteenpäin kun se on valmis

DPI, jonka välität, ei ole DPI, jonka saat

Jokainen renderöintikutsu ottaa DPI-argumentin, mutta resoluutio, jonka oikeasti saat, on tuo arvo kerrottuna globaalilla renderöintiskaalalla. SetRenderScale alkaa 1.0:sta, ja kerran muutettuasi uusi kerroin soveltuu hiljaa jokaiseen myöhempään renderöintiin tuossa instanssissa:

PDF.SetRenderScale(2.0);                    // jokainen myöhempi renderöinti kaksinkertaistuu
PDF.RenderPageToFile(150, 1, 5, 'p1.png');  // käytännössä 300 DPI
PDF.SetRenderScale(1.0);                    // nollaa, tai pikkukuvistasi tulee valtavia

Sama tarttuvuus koskee SetRenderCropType:ä ja JPEG-laatuasetusta. Palvelussa, joka tuottaa thumbnailia, esikatseluja ja tulostusresoluution kuvia yhdestä jaetusta instanssista, nämä jäännösasetukset ovat todella satunnaisten "thumbnailit ovat yhtäkkiä 40 MB" -lippujen takana. Kaksi puhdasta ulospääsyä: nollaa olennainen tila jokaisen operaation yläosassa, tai omistaa erillinen instanssi kullekin tulosteprofiilille jottei mikään vuoda niiden välillä

PDF Library for Delphi: käynnistyksen moottorikyselyn vuokaavio: jokainen renderoija vahvistaa DLL-polkunsa ja SelectRenderer-vastauksensa, ennen kuin saatavuusyhteenveto kirjataan lokiin jokaisen renderointityön yhteyteen
Epäonnistunut polkukutsu osoittaa syylliseksi DLL:n, kun taas SelectRenderer:in ristiriitainen tulos merkitsee, ettei binääriin ole koskaan käännetty moottoria sisään. Koe ajetaan kerran, ja sen yksirivinen yhteenveto ratkaisee useimmat asiakkaiden renderöintikysymykset

Oletusmoottorin virittäminen ennen toisen tavoittelua

Yllättävä osa "tarvitsemme eri moottorin" -pyynnöistä paljastuu asetusongelmiksi naamioituneina. Sisäänrakennettu renderöijä paljastaa pehmennyskäyttäytymisensä SetGDIPlusOptions:n ja laajemman SetRenderOptions-perheen kautta, ja SetGDIPlusFileName antaa sinun tähdätä sitä tiettyyn GDI+-ajonaikaan kun käyttöönottoympäristö toimittaa epätavallisen. Rogainen viivataide matalalla DPI:llä, sumea teksti thumbnailissa, banding gradienttien yli: kaikki nämä vastaavat noihin nuppeihin, ja niiden kääntäminen ei maksa mitään asennusohjelmassa. Cairon tai PDFiumin lisääminen taas tarkoittaa useampien DLL:ien toimittamista, toisen tai kolmannen bittisyysvariantin seuraamista ja velvollisuuden omistamista päivittää ne

Joten laatuvalituksella on luonnollinen operaatioiden järjestys. Reprodukoi se ensin asiakkaan tarkalla DPI:llä ja skaalalla, koska puolet ajasta ero haihtuu kun nuo täsmäävät. Kokeile seuraavaksi sisäänrakennetun moottorin pehmennysvaihtoehtoja. Vasta sitten aseta sivu rinnakkain moottoreiden yli kaikilla muilla muuttujilla pidettynä vakiona: renderöi se PNG:ksi moottoreiden 1, 2 ja 3 läpi identtisellä DPI:llä ja liitä kaikki kolme. Yleensä kaksi kolmesta on samaa mieltä, ja tuo enemmistö kertoo, onko outlier dokumentti, jota tulkitaan eri tavalla, vai oma peruskatsauksesi odotus, joka on väärässä. Kolme konkreettista kuvaa ratkaisevat "renderöityy väärin" -kiistan paljon nopeammin kuin adjectivejä kappale

Fallback-ketju, joka selittää itsensä

Kun tutkimus ja tilakuri ovat paikoillaan, itse fallback-ketju on lyhyt. Epäonnistumisen havaitseminen nojaa LastRenderError:iin, joka kantaa moottorin omaa viestitekstiä viimeisimmälle renderöinnille ja on tyhjä kun renderöinti onnistui:

procedure RenderPageWithFallback(PDF: TPDFlib; Page: Integer; const OutFile: string);
begin
  PDF.SelectRenderer(1);                            // sisäänrakennettu ensin
  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 raskaana varajärjestelmänä
  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;

Kaksi suunnittelukohtaa kantavat täällä painoa. Ketju tallentaa, miksi kukin vaihto tapahtui, koska lokirivi joka lukee "tämä sivu siirtyi PDFiumiin release 3.7:stä lähtien" on regressiosignaali, jonka haluat trendaavan monitoroinnissa ennemmin kuin hukkuvan. Itse fallback-järjestys on käytäntö, jonka valitseminen per työkuorma kannattaa. Sisäänrakennettu moottori otetaan käyttöön ilman ylimääräisiä DLL:itä, mikä tekee siitä oikean ensimmäisen yrityksen useimmissa asennuksissa, kun taas dokumentit jotka ovat raskaita läpinäkyvyysryhmien tai epätavallisen varjostuksen kanssa ovat yleinen syy, miksi tiimi ylipäätään kytkkee vaihtoehtoisen moottorin. Mikään moottori ei ole nopein yleisesti, mikä on koko pointti valita per kutsu: benchmarkaa kukin otoksella oikeista dokumenteistasi oikealla DPI:lläsi, ja käy läpi tuo mittaus aina kun moottori-DLL:t tai dokumenttisekoitus muuttuvat. Joukko voittaa väittelyn joka kerta

PDF-renderoinnin varajono: sisäänrakennettu moottori yrittää ensin, virheet kirjataan lokiin, PDFium yrittää uudelleen, ja poikkeus raportoi, kun kaikki käytettävissä olevat moottorit epäonnistuvat sivulla
Jokainen yritys tarkistaa LastRenderError:n ja kirjaa syyn lokiin ennen moottorin vaihtoa. Vasta kun jokainen asennettu moottori on epäonnistunut, ketju nostaa poikkeuksen, ja kerätyt syyt ovat jo valmiina lokissa

Yksittäisten sivujen ohi: TIFF-erät ja live-device contextit

Kaksi per-sivu-kutsujen naapuria täydentävät työkalupakin. RenderAsMultipageTIFFToFile renderöi sivuvälilausekkeen suoraan monisivuiseen TIFF:ään, luonnollinen muoto arkistointikättelyille dokumentinhallintajärjestelmille, jotka edeltävät PDF:ää. RenderPageToDC maalaa suoraan Windowsin device contextiin esikatselukontrolloille, jota ohjaa sen oma kolmoinen tarttuvien asetusten joukko (SetRenderDCOffset, SetRenderDCErasePage, plus rajauksen tyyppi), jotka tarvitsevat saman nollauskurin kuin skaalakerroin. Näyttöesikatselu ja tulostuspolun renderöinti kantavat tarpeeksi omia ansojaan ansaitakseen oman artikkelin, linkitetty alla

Minne seuraavaksi

Yksi tapa, jonka kannattaa viedä eteenpäin: koska SelectRenderer astuu voimaan jokaisessa myöhemmässä kutsussa instanssissa, yksittäinen itsepäinen sivu voidaan yrittää uudelleen toisella moottorilla kun loput dokumentista pysyy oletuksella. Esikatselumaalaukseen, tulostimen valintaan ja DevMode-käsittelyyn jatka tulostuksen esikatselu- ja device-context-artikkelista. Kun renderöinnit syöttävät suurivolyymistä putkea hyvin suurten tiedostojen yli, kahva-pohjainen lähestymistapa suoran pääsyn opastuksessa parittuu luonnollisesti per-sivu-renderöintiin DARenderPageToFile:n kautta

Moottorien paketointi, tuetut formaatit ja kokeilubuildit on yksityiskohtaisesti PDF Library for Delphi-tuotesivulla