PDFium Component for Delphissä kohteen BrotliEnabled tai IsolatePerDocument kytkeminen päälle kohteessa TPdfLibraryConfiguration vaihtoi mukana toimitetun Skia-koontiversion AGG-renderöijään ilman mitään virhettä, koska molemmat asetukset nostavat kohteen FPDF_LIBRARY_CONFIG versioon, jossa PDFium lukee kohteen m_RendererType kirjaimellisesti. Versiosta v3.123.0 alkaen oletusrenderöijä pysyy DLL:n omana oletuksena, ja versiosta v3.125.0 alkaen Skia- tai Fontations-pyyntö, jota DLL ei voi kunnioittaa, nostaa siepattavan EPdfErrorin prosessin tappamisen sijaan
Kumpikaan vika ei ilmoittanut itsestään. Ensimmäinen tuotti sivuja, jotka näyttivät hyviltä, vain eri rasteroijan renderöiminä, hieman erilaisilla antialiasoinnin ja tekstin reunoilla kuin koontiversiossa, jonka toimitit ja testasit. Toinen ilmoitti itsestään, kovaäänisesti, viemällä isäntäprosessin alas natiivin alustuksen sisältä. Molemmat tulevat samasta paikasta: versioidusta C-rakenteesta, jonka kentät alkavat laskea vasta, kun versionumero sanoo niin, ja jonka nolla-arvot eivät ole "asettamattomia" vaan aitoja valintoja
Miten FPDF_LIBRARY_CONFIG päättää, mitä renderöijää PDFium käyttää?
FPDF_InitLibraryWithConfig kysyy kohteen m_RendererType arvoa vain, kun rakenteen Version-kenttä on 4 tai korkeampi, ja kyseisestä versiosta alkaen se käyttää arvoa täsmälleen kirjoitettuna. Version 4 alapuolella PDFium ohittaa kentän ja poimii koontiversion oletuksen, joka on Skia koontiversioissa, jotka on käännetty PDF_USE_SKIAlla, ja AGG kaikkialla muualla
Jokainen myöhempi kenttä noudattaa samaa kuviota. Rakenne kasvoi yhden ominaisuuden kerrallaan, ja kukin ominaisuus saapui yhdessä uuden versionumeron kanssa. PDFium Component rakentaa natiivin rakenteen funktiossa LoadLibrary kohteestasi TPdfLibraryConfiguration ja nostaa version vain niin pitkälle, kuin asettamasi asetukset vaativat
| Rakenteen versio | Lisäämä kenttä | Asettaja |
|---|---|---|
| 2 | m_pIsolate, m_v8EmbedderSlot | Aina kirjoitettu; V8Isolate, V8EmbedderSlot |
| 3 | m_pPlatform | V8Platform ei nil |
| 4 | m_RendererType | Renderer muu kuin prpDefault |
| 5 | m_FontLibraryType | FontBackend muu kuin pfbpDefault |
| 6 | m_BrotliEnabled | BrotliEnabled = True |
| 7 | m_IsolatePerDocument | IsolatePerDocument = True |
Ansa on kahdessa viimeisessä rivissä. Versiot ovat kumulatiivisia: version 6 rakenne on myös version 4 ja version 5 rakenne, joten PDFium lukee kohteet m_RendererType ja m_FontLibraryType, vaikka pyysit vain Brotlia. Se, mikä istuu kyseisissä kentissä sillä hetkellä, muuttuu renderöijäksi ja fonttibackendiksi, halusitko valita ne vai et
Miksi Brotlin kytkeminen päälle vaihtoi renderöijän AGG:hen?
Ennen versiota v3.123.0 PDFium Component kirjoitti arvon FPDF_RENDERERTYPE_AGG kohteeseen m_RendererType asetukselle prpDefault, joten mikä tahansa konfiguraatio, joka työnsi rakenteen versioon 6 tai 7, pakotti AGG:n Skia-koontiversiolle. Komponentin mukana toimitettavat ajonaikaiset pdfium.dll ja pdfium.v8.dll ovat Skia-koontiversioita, joten tämä osui oletusasennukseen, ei johonkin eksoottiseen
Kuvaus näytti harmittomalta kirjoitushetkellä. Version 2 tai 3 kohdalla kenttää ei koskaan lueta, joten prpDefault oikeasti tarkoitti "sitä, mitä DLL tekee". Heti kun BrotliEnabled (versio 6) tai IsolatePerDocument (versio 7) astui kuvaan, sama koodi muutti "ei preferenssiä" eksplisiittiseksi AGG-pyynnöksi. Mikään ei epäonnistunut. PDFium alustui normaalisti, renderöi jokaisen sivun eikä palauttanut virhekoodia, koska sen näkökulmasta kutsuja oli pyytänyt AGG:tä ja saanut AGG:n
Pikselihashi tekee vaihdoksen näkyväksi siellä, missä kuvakaappaukset eivät. Saman malliasiakirjan ensimmäisen sivun renderöinti kolmella konfiguraatiolla antoi:
- Oletuskonfiguraatio: hash
502D77C3711B4ACF BrotliEnabled= True, kunRendererjätettiin arvoonprpDefault: hashF75B5EB4728ADE87- Eksplisiittinen
prpAgg: hashF75B5EB4728ADE87, identtinen Brotli-ajon kanssa
Korjaus versiossa v3.123.0 on julkinen funktio PdfNativeRendererType, joka ratkaisee kohteen TPdfRendererPreference arvoksi, joka kirjoitetaan kohteeseen m_RendererType. prpAgg ja prpSkia kuvautuvat yksi yhteen. prpDefault kuvautuu nykyään Skiaksi, kun ladattu DLL vie FPDF_RenderPageSkian, ja muuten AGG:hen. Kyseinen vienti käännetään saman PDF_USE_SKIA-ehdon alla kuin Skia-oletus itsekin, mikä tekee siitä ainoan koontiversion ominaisuuden, jonka voi havaita DLL:n ulkopuolelta. Korjauksen jälkeen Brotli-konfiguraatio tuottaa saman hashin kuin oletus
Fonttibackendillä ei koskaan ollut samaa ongelmaa. m_FontLibraryType luetaan versiosta 5 alkaen, ja sen nolla-arvo, FPDF_FONTBACKENDTYPE_FREETYPE, on myös PDFiumin oletus, kun kenttää ei lueta lainkaan. FreeTypen kirjoittaminen asetukselle pfbpDefault toistaa siksi natiivin oletuksen täsmälleen. Nolla-arvot eivät aina ole vääriä, ne vain eivät ole koskaan automaattisesti oikein
Versiolla v3.123.0 tai uudemmalla luonnollisesti kirjoittamasi käynnistyskoodi tekee nykyään sen, mitä se sanoo:
uses
PDFium;
procedure ConfigurePdfiumAtStartup;
var
Config: TPdfLibraryConfiguration;
begin
// On ajettava ennen kuin mikään lataa natiivikirjaston
Config := TPdfLibraryConfiguration.Default;
Config.BrotliEnabled := True; // nostaa FPDF_LIBRARY_CONFIGin versioon 6
// Renderer pysyy arvossa prpDefault: ratkeaa Skiaksi koontiversioissa, jotka vievät
// FPDF_RenderPageSkian ja AGG:hen vain AGG-koontiversioissa
SetLength(Config.UserFontPaths, 1);
Config.UserFontPaths[0] := 'C:\ProgramData\MyApp\Fonts';
ConfigurePdfLibrary(Config);
end;
Muista, että BrotliEnabled tekee PDF 2.0:n /BrotliDecode-streamit dekoodattaviksi vain, kun itse DLL on käännetty PDF_ENABLE_BROTLIlla. Flagi on pyyntö, ja koontiversiolla ilman Brotli-tukea sillä ei ole vaikutusta. TPdfLibraryConfiguration.Hardened on sama kuin Default, paitsi että AllowMachineTime on False, mikä estää asiakirjan JavaScriptiä lukemasta oikeaa kelloa; se on järkevä lähtökohta luottamattomien tiedostojen palvelinpuolen käsittelyyn
Mitä tapahtuu, kun pyydät backendia, jota DLL ei sisällä?
PDFium ei palauta virhettä renderöijälle tai fonttibackendille, joka puuttuu koontiversiosta: FPDF_InitLibraryWithConfig kaataa natiivin CHECKin, joka Windowsissa ilmenee breakpoint-poikkeuksena ja ilman jäsenneltyä poikkeuskäsittelijää kutsun ympärillä lopettaa prosessin. Otsikkotiedosto sanoo asian niin ikään varoittaen, että ei-tuettu arvo "epäonnistuu vastaavasti välittömällä kaatumisella"
Kaksi konkreettista tapausta ovat vain AGG -koontiversio, joka vastaanottaa arvon FPDF_RENDERERTYPE_SKIA, ja koontiversio ilman Fontationsia, joka vastaanottaa arvon FPDF_FONTBACKENDTYPE_FONTATIONS. Mukana toimitettu Skia-ajonaikainen on toisessa ryhmässä: se renderöi Skialla mutta käyttää FreeTypeä fonteille. prpSkian pyytäminen yhdessä pfbpFontationsin kanssa sitä vastaan tuotti External exception 80000003 -virheen Delphi-puolella. Kun debuggaaja tai poikkeuskäsittelijä sattuu sieppaamaan sen, tilanne on silti palautumaton:
- PDFium jää puoli-alustuneeksi
- Prosessinlaajuinen konfiguraatio on jo sinetöity, joten
ConfigurePdfLibrarytorjuu korjatun konfiguraation - Uudelleenyritys eri konfiguraatiolla samassa prosessissa ei ole enää mahdollista
Kyseessä on Brotli-vikan vastakohta. Siellä kenttä kantoi arvoa, jota kukaan ei valinnut, ja PDFium hyväksyi sen hiljaa. Täällä kenttä kantaa arvoa, jonka kutsuja valitsi tahallaan, eikä PDFium hyväksy siitä mitään keskustelua lainkaan. Molemmat ovat ongelmia, jotka kääreen on ratkaistava ennen natiivia kutsua, koska sen jälkeen ei ole enää mitään, mitä siepata
Miten PDFium Component esitarkistaa Skian ja Fontationsin
Versiosta v3.125.0 alkaen LoadLibrary validoi konfiguraation DLL-vientien sidonnan jälkeen ja ennen funktion FPDF_InitLibraryWithConfig kutsumista, ja muuttaa ei-tuetun renderöijän tai fonttibackendin EPdfErroriksi viestillä, joka nimeää syyllisen asetuksen ja vaihtoehdot. DLL puretaan ja konfiguraatio avataan, joten kutsuja voi poimia muita asetuksia ja ladata uudelleen
Itse päätös asuu puhtaassa funktiossa PdfLibraryConfigurationSupportError, joka ottaa konfiguraation plus kaksi totuusarvoa, jotka kuvaavat koontiversiota, ja palauttaa tyhjän merkkijonon, kun yhdistelmä on turvallinen. Koska se ei kosketa natiivia tilaa, voit kutsua sitä omista testeistäsi millä tahansa ominaisuusyhdistelmällä. Funktion LoadLibrary sisällä kaksi totuusarvoa tulevat erilaisista todisteista, ja ne ansaitsevat eri luottamustasot:
- Skia havaitaan
FPDF_RenderPageSkia-viennin läsnäolosta, sama signaali, jotaPdfNativeRendererTypekäyttää. Vienti ja Skia-renderöijä käännetään yhden ehdon alla, joten tarkistus on tarkka - Fontationsilla ei ole omaa vientiä. Ainoa jälki, jonka se jättää, on Rustin fontticrate, jotka se vetää binääriin, joten PDFium Component skannaa ladatun kirjastotiedoston cratenimien
skrifajaread-fonts(myösread_fonts) varalta. Skannaus ajaa vain, kunpfbpFontationspyydetään, ja tiedosto, jota ei voi lukea, lasketaan muodossa "ei Fontationsia"
Fontations-tarkistus on heuristiikka, ja se voi olla väärin yhteen suuntaan: Fontations-koontiversio, josta jokainen kyseinen merkkijono on karsittu, torjuttaisiin, vaikka se olisi voinut toimia. Kyseinen kompromissi tehtiin tahallaan. Väärä torjunta maksaa sinulle poikkeuksen, jonka voit siepata, ja paluun FreeTypeen. Väärä hyväksyntä maksaa sinulle prosessin
Sinetin avaaminen merkitsee yhtä paljon kuin tarkistus. LoadLibrary sinetöi konfiguraation latauksen ihan alussa, joten ilman nollausta ominaisuustorjunta jättäisi kohteen ConfigurePdfLibrary vastaamaan jokaista uudelleenyritystä muodossa EPdfError "PDFium library configuration is already sealed". Torjuntapolku kutsuu funktiota UnloadLibrary ensin; sen FPDF_DestroyLibrary-kutsu on turvallinen tuolla hetkellä, koska PDFiumia ei ole vielä alustettu ja se palautuu välittömästi. Muut latausviat, kuten puuttuva DLL tai arkkitehtuurierot, pitävät sinetin, joten uudelleenyrityssilmukan on erotettava kaksi toisistaan:
uses
SysUtils, PDFium;
function StartPdfiumPreferringSkia: TPdfRendererPreference;
var
Config: TPdfLibraryConfiguration;
begin
Config := TPdfLibraryConfiguration.Default;
Config.Renderer := prpSkia;
ConfigurePdfLibrary(Config);
try
PDFium.LoadLibrary; // yksikkötarkennettu: Windows.LoadLibrarylla on sama nimi
Result := prpSkia;
except
on E: EPdfError do
begin
// Ominaisuustorjunta purkaa DLL:n ja avaa konfiguraation sinetin.
// DLL, joka ei ladannut lainkaan, pysyy sinetoituna: uudelleenyritys ei auta
if PdfLibraryConfigurationSealed then
raise;
Config.Renderer := prpAgg;
ConfigurePdfLibrary(Config);
PDFium.LoadLibrary;
Result := prpAgg;
end;
end;
end;
Huomaa eksplisiittinen PDFium.LoadLibrary. Yksikössä, joka käyttää myös kohteita Windows tai Winapi.Windows, tarkentamaton LoadLibrary ratkaisee siihen yksikköön, joka ilmestyy viimeisenä uses-lauseessa; kun se on Win32-funktio, parametriton kutsu ei käänny argumenttimäärävirheellä, joka ei sano mitään PDFiumista
Validointi, joka tapahtuu vielä aiemmin
ConfigurePdfLibrary torjuu jotkin yhdistelmät ennen kuin mikään DLL on kuvassa, kaikki muodossa EPdfError. Eksplisiittinen FontBackend, mukaan lukien pfbpFreeType, vaatii Rendererin = prpSkian, koska PDFium kysyy fonttibackendia vain Skia-renderöijälle. IsolatePerDocument vaatii kohteen V8Isolate olevan nil, sillä PDFium luo oman isolenssinsä per asiakirja ja kaataa natiivin CHECKin, jos myös sinä ojennat sille sellaisen. Tyhjät merkkijonot kohteessa UserFontPaths torjutaan. Ja mikä tahansa kutsu ensimmäisen latausyrityksen jälkeen epäonnistuu muodossa "PDFium library configuration is already sealed"
Kyseisellä viimeisellä säännöllä on käytännön seuraus: et voi tutkia DLL:ää ensin ja konfiguroida sitä jälkeenpäin. GetSkiaRenderCapabilities, V8FeaturesAvailable, asiakirjan avaaminen ja useimmat muut sisääntulopisteet kutsuvat funktiota LoadLibrary sisäisesti, mikä sinetöi konfiguraation paikallaan. Funktion UnloadLibrary kutsuminen myöhemmin ei avaa sitä uudelleen. Konfiguroi ensin, sitten lataa, sitten kysy kysymyksiä, mikä on täsmälleen se järjestys, jota diagnostiikkarutiinin pitää noudattaa:
uses
SysUtils, PDFium, FPdfView;
function DescribePdfiumState: string;
var
Config: TPdfLibraryConfiguration;
Renderer: string;
begin
Config := GetPdfLibraryConfiguration; // kopio, turvallinen tarkastella
if not PDFium.Loaded then
begin
if PdfLibraryConfigurationSealed then
Exit('PDFium failed to load; configuration is sealed');
Exit('PDFium not loaded; configuration can still change');
end;
// Sama ratkaisu, jonka LoadLibrary sovelsi rakentaessaan FPDF_LIBRARY_CONFIGin
if PdfNativeRendererType(Config.Renderer,
GetSkiaRenderCapabilities.PageRender) = FPDF_RENDERERTYPE_SKIA then
Renderer := 'Skia'
else
Renderer := 'AGG';
Result := Format('Renderer=%s Brotli=%s IsolatePerDocument=%s',
[Renderer, BoolToStr(Config.BrotliEnabled, True),
BoolToStr(Config.IsolatePerDocument, True)]);
end;
Kyseisen rivin lokittaminen kerran käynnistyksessä on halpaa, ja se on ensimmäinen asia, jonka haluat tukipyyntöön, joka sanoo "teksti näyttää erilaiselta palvelimella". PDFium.Loaded on tarkennettu samasta syystä kuin LoadLibrary: lomakkeen tai komponentin metodin sisällä paljas Loaded sidotaan kohteeseen TComponent.Loaded
Kaksi tapaa, joilla versioitu C-konfiguraatiorakenne menee pieleen
Jokainen versioitu konfiguraatiorakenne, olkoon se FPDF_LIBRARY_CONFIG, Win32-cbSize-tietue tai pluginin ABI, epäonnistuu kahdella symmetrisellä tavalla, ja kääreen on varjeltava molempia vastaan. Ensimmäinen on kentän täyttäminen jättäen version liian matalaksi; toinen on version nostaminen jättäen kenttä nolla-arvoon, jonka kirjasto lukee tahallisena valintana
- Kenttä asetettu, versio liian matala. Kirjoita
m_BrotliEnabled= 1 version 2 rakenteeseen, eikä PDFium koskaan katso sitä. Kutsu onnistuu ja Brotli-streamit pysyvät dekoodaamattomina. Puolustus on johtaa version kentistä, jotka oikeasti ovat käytössä, mikä on se, mitäLoadLibrarytekee, kovakoodatun sijaan - Versio tarpeeksi korkea, nollakenttä tarkoittaa jotain. Nosta versio arvoon 6, ja jokainen kenttä versioon 6 asti on nyt elossa.
FillCharnollaa kohteenm_RendererTypemuotoonFPDF_RENDERERTYPE_AGG, joka on aito renderöijä, ei "asettamaton". Puolustus on kirjoittaa jokainen valitun version kattama kenttä tahallisella arvolla ja ratkaista "oletus" todellista koontiversiota vasten olettamisen sijaan
Kolmas sääntö seuraa arvoille, jotka voivat kaataa kutsuttavan: validoi ne sitä vasten, mitä binääri osaa tehdä ennen kutsua, käyttäen vahvinta saatavilla olevaa todistetta, ja ole rehellinen koodissa ja dokumentaatiossa, kun kyseinen todiste on heuristiikka. Vietty symboli on todiste. Cratenimi merkkijonotaulukossa on hyvä arvaus
Pikaopas: PDFium Componentin kirjastokonfiguraatio
- Kutsu funktiota
ConfigurePdfLibrarykerran, ennen kuin mikään lataa DLL:n; mikä tahansa ominaisuuskysely tai asiakirjan lataus sinetöi sen - Päivitys versioon v3.123.0 tai uudempaan, jos asetat
BrotliEnabledin taiIsolatePerDocumentin ja odotat Skia-tuotosta mukana toimitetuilta ajonaikaisilta - Jätä
RendererarvoonprpDefault, ellet tarvitse tiettyä rasteroijaa; se ratkeaa nykyään koontiversion oletukseen jokaisella rakenteen versiolla - Käytä funktiota
PdfNativeRendererTypekohteenGetSkiaRenderCapabilities.PageRenderkanssa lokittaaksesi, mikä renderöijä on oikeasti aktiivinen - Odota
EPdfErroria, ei kaatumista, muodolleprpSkiavain AGG -DLL:llä tai muodollepfbpFontationsei-Fontations-DLL:llä versiolla v3.125.0 tai uudemmalla - Ominaisuustorjunnan jälkeen
PdfLibraryConfigurationSealedon False ja voit konfiguroida uudelleen; epäonnistuneen DLL-latauksen jälkeen se pysyy Totena - Kohtele Fontationsin tunnistusta heuristiikkana ja pidä FreeType-varapolku
- Kirjoita
PDFium.LoadLibraryjaPDFium.Loadedyksikön nimellä välttääksesi Win32- jaTComponent-nimiristiriidat
Jos DLL epäonnistuu ennen kuin konfiguraatiollakaan on merkitystä, aloita artikkelista PDFium DLL -latausvikojen diagnosointi Delphissä, ja siitä, miten komponentti löytää oikean binäärin kullakin alustalla, kerrotaan artikkelissa PDFium-natiivikirjaston lataaminen millä tahansa kohteella. Kun renderöijä on selvillä, artikkeli render-välimuisti ja sujuvan zoomauksen taktiikat kattaa, miten sivujen renderöinti pidetään nopeana katselimessa
PDFium Component käärii PDFium-moottorin Delphille ja C++Builderille tällaisilla konfiguraatiotarkistuksilla, joten natiivi alustus epäonnistuu Pascal-poikkeuksena, jonka voit käsitellä, prosessin lopetuksen sijaan. Tuoteyksityiskohdat ja lataukset ovat PDFium Component for Delphi -tuotesivulla