U PDFium Componentu za Delphi, uključivanje BrotliEnabled ili IsolatePerDocument u TPdfLibraryConfiguration prebacivalo je priloženi Skia build na AGG renderer bez ikakve greške, jer obje opcije podižu FPDF_LIBRARY_CONFIG na verziju na kojoj PDFium m_RendererType čita doslovno. Od v3.123.0 zadani renderer ostaje vlastiti zadani DLL-a, a od v3.125.0 Skia ili Fontations zahtjev koji DLL ne može ispuniti podiže uhvatljiv EPdfError umjesto da ubije proces
Nijedan bug nije najavio sebe. Prvi je proizvodio stranice koje su izgledale u redu, samo renderirane drugim rasterizatorom, s malo drugačijim anti-aliasingom i rubovima teksta od builda koji ste isporučili i testirali. Drugi se jest najavio, glasno, obaranjem host procesa iznutra iz nativne inicijalizacije. Oba dolaze s istog mjesta: verzionirana C struktura čija polja računaju se tek kad broj verzije kaže da se računaju, i čije su nulte vrijednosti ne "nepostavljeno" nego pravi izbori
Kako FPDF_LIBRARY_CONFIG odlučuje koji renderer PDFium koristi?
FPDF_InitLibraryWithConfig konzultira m_RendererType samo kad je polje Version strukture 4 ili više, i od te verzije koristi vrijednost točno onakvom kakva je zapisana. Ispod verzije 4 PDFium polje ignorira i bira zadani build, koji je Skia u buildovima kompajliranim s PDF_USE_SKIA i AGG svugdje drugdje
Svako kasnije polje slijedi isti obrazac. Struktura je rasla jednom sposobnošću odjednom, i svaka je sposobnost stigla zajedno s novim brojem verzije. PDFium Component gradi nativnu strukturu u LoadLibrary iz vašeg TPdfLibraryConfiguration i podiže verziju samo toliko koliko opcije koje ste postavili zahtijevaju
| Verzija strukture | Polje koje dodaje | Postavlja se kad |
|---|---|---|
| 2 | m_pIsolate, m_v8EmbedderSlot | Uvijek zapisano; V8Isolate, V8EmbedderSlot |
| 3 | m_pPlatform | V8Platform nije nil |
| 4 | m_RendererType | Renderer različit od prpDefault |
| 5 | m_FontLibraryType | FontBackend različit od pfbpDefault |
| 6 | m_BrotliEnabled | BrotliEnabled = True |
| 7 | m_IsolatePerDocument | IsolatePerDocument = True |
Zamka je u zadnja dva retka. Verzije su kumulativne: struktura verzije 6 ujedno je struktura verzije 4 i verzije 5, pa PDFium čita m_RendererType i m_FontLibraryType iako ste tražili samo Brotli. Što god se u tom trenutku nalazilo u ta dva polja postaje renderer i font backend, bilo da ste ih namjeravali odabrati ili ne
Zašto je uključivanje Brotlija prebacilo renderer na AGG?
Prije v3.123.0 PDFium Component pisao je FPDF_RENDERERTYPE_AGG u m_RendererType za prpDefault, pa je svaka konfiguracija koja je strukturu gurnula na verziju 6 ili 7 forsirala AGG na Skia buildu. pdfium.dll i pdfium.v8.dll runtimei koji idu uz komponentu Skia su buildovi, pa je ovo pogodilo zadano uvođenje, ne neki egzotični
Mapiranje je izgledalo bezopasno kad je napisano. Na verziji 2 ili 3 polje se nikad ne čita, pa je prpDefault stvarno značilo "što god DLL radi". U trenutku kad su BrotliEnabled (verzija 6) ili IsolatePerDocument (verzija 7) ušli u sliku, isti je kod "bez preferencije" pretvorio u izričit AGG zahtjev. Ništa nije palo. PDFium se normalno inicijalizirao, renderirao svaku stranicu i vratio bez koda greške, jer je iz njegove točke gledišta pozivatelj tražio AGG i dobio AGG
Pikselni hash čini zamjenu vidljivom tamo gdje slike zaslona ne. Renderiranje prve stranice istog uzorčnog dokumenta pod tri konfiguracije dalo je:
- Zadana konfiguracija: hash
502D77C3711B4ACF BrotliEnabled= True uzRendererostavljen naprpDefault: hashF75B5EB4728ADE87- Izričit
prpAgg: hashF75B5EB4728ADE87, identičan Brotli pokretanju
Popravak u v3.123.0 jest javna funkcija PdfNativeRendererType, koja razrješuje TPdfRendererPreference u vrijednost zapisanu u m_RendererType. prpAgg i prpSkia mapiraju se jedan-na-jedan. prpDefault sada mapira u Skiu kad učitani DLL izvozi FPDF_RenderPageSkia i u AGG inače. Taj se izvoz kompajlira pod istim PDF_USE_SKIA uvjetom kao i sama Skia zadana postavka, što ga čini jedinim svojstvom builda koje možete promatrati izvana iz DLL-a. Nakon popravka Brotli konfiguracija proizvodi isti hash kao zadana
Font backend nikad nije imao isti problem. m_FontLibraryType čita se od verzije 5, a njegova nulta vrijednost, FPDF_FONTBACKENDTYPE_FREETYPE, istodobno je PDFiumov zadani kad se polje uopće ne čita. Pisanje FreeTypea za pfbpDefault zato reproducira nativni zadani točno. Nulte vrijednosti nisu uvijek krive, samo nikad nisu automatski točne
S v3.123.0 ili novijim, startup kod koji biste prirodno napisali sada radi ono što kaže:
uses
PDFium;
procedure ConfigurePdfiumAtStartup;
var
Config: TPdfLibraryConfiguration;
begin
// Mora se pokrenuti prije nego bilo što učita nativnu biblioteku
Config := TPdfLibraryConfiguration.Default;
Config.BrotliEnabled := True; // podiže FPDF_LIBRARY_CONFIG na verziju 6
// Renderer ostaje prpDefault: razrješuje se u Skiu na buildovima koji izvoze
// FPDF_RenderPageSkia i u AGG na AGG-only buildovima
SetLength(Config.UserFontPaths, 1);
Config.UserFontPaths[0] := 'C:\ProgramData\MyApp\Fonts';
ConfigurePdfLibrary(Config);
end;
Upamtite da BrotliEnabled tokove /BrotliDecode PDF 2.0 čini dekodabilnima samo kad je i sam DLL građen s PDF_ENABLE_BROTLI. Zastavica je zahtjev, i na buildu bez Brotli podrške nema učinka. TPdfLibraryConfiguration.Hardened isto je što i Default, osim što je AllowMachineTime False, što JavaScriptu dokumenta blokira čitanje pravog sata; razumna je polazna točka za obradu nepovjerljivih datoteka na strani servera
Što se dogodi kad zatražite backend koji DLL ne sadrži?
PDFium ne vraća grešku za renderer ili font backend kojeg nema u buildu: FPDF_InitLibraryWithConfig provali nativni CHECK, koji se na Windowsu pokazuje kao iznimka breakpointa i, bez structured exception handlera oko poziva, terminira proces. Header to i kaže, upozoravajući da nepodržana vrijednost "slično pada s trenutačnim padom"
Dva konkretna slučaja jesu AGG-only build koji dobije FPDF_RENDERERTYPE_SKIA, i build bez Fontationsa koji dobije FPDF_FONTBACKENDTYPE_FONTATIONS. Priloženi Skia runtime u je drugoj grupi: renderira s Skiom, ali za fontove koristi FreeType. Traženje prpSkia zajedno s pfbpFontations protiv njega proizvelo je External exception 80000003 na Delphi strani. Kad to slučajno uhvati debugger ili exception handler, situacija je i dalje nepopravljiva:
- PDFium ostaje napola inicijaliziran
- Konfiguracija na razini procesa već je zaključana, pa
ConfigurePdfLibraryodbija ispravljenu konfiguraciju - Ponavljanje s drugom konfiguracijom u istom procesu više nije moguće
Ovo je suprotni pad od Brotli buga. Tamo je polje držalo vrijednost koju nitko nije odabrao i PDFium ju je tiho prihvatio. Ovdje polje drži vrijednost koju je pozivatelj svjesno odabrao i PDFium o njoj uopće ne prihvaća raspravu. Oba su problemi koje wrapper mora riješiti prije nativnog poziva, jer poslije njega nema više ničega za uhvatiti
Kako PDFium Component provjerava unaprijed Skiu i Fontations
Od v3.125.0 LoadLibrary validira konfiguraciju nakon vezanja DLL izvoza i prije poziva FPDF_InitLibraryWithConfig, i nepodržani renderer ili font backend pretvara u EPdfError s porukom koja imenuje problematičnu postavku i alternative. DLL se istovaruje i konfiguracija se otključava, pa pozivatelj može odabrati druge postavke i učitati ponovno
Odluka sama živi u čistoj funkciji PdfLibraryConfigurationSupportError, koja prima konfiguraciju plus dva Booleana koji opisuju build i vraća prazan string kad je kombinacija sigurna. Budući da ne dira nikakvo nativno stanje, možete je zvati iz vlastitih testova s bilo kojom kombinacijom sposobnosti. Unutar LoadLibrary dva Booleana dolaze iz raznih vrsta dokaza, i zaslužuju razne razine povjerenja:
- Skia se otkriva iz prisutnosti izvoza
FPDF_RenderPageSkia, istog signala kojeg koristiPdfNativeRendererType. Izvoz i Skia renderer kompajliraju se pod jednim uvjetom, pa je provjera točna - Fontations nema vlastiti izvoz. Jedini trag koji ostavlja jesu Rust font crateovi koje vuče u binarnu datoteku, pa PDFium Component pretražuje učitanu bibliotečku datoteku za imenima crateova
skrifairead-fonts(takođerread_fonts). Pretraga radi samo kad se zatražipfbpFontations, a datoteka koja se ne može čitati računa se kao "nema Fontationsa"
Fontations provjera jest heuristika i može pogriješiti u jednom smjeru: Fontations build ogoljen od svakog od tih stringova bio bi odbijen iako je mogao raditi. Taj kompromis namjerno je skrojen. Lažna odbijenica košta vas iznimku koju možete uhvatiti i fallback na FreeType. Lažno prihvaćanje košta vas proces
Otključavanje bitno je kao i provjera. LoadLibrary zaključava konfiguraciju na samom početku učitavanja, pa bez resetiranja bi odbijenica sposobnosti ostavila ConfigurePdfLibrary da na svako ponavljanje odgovara s EPdfError "PDFium library configuration is already sealed". Put odbijenice prvo zove UnloadLibrary; njegov poziv FPDF_DestroyLibrary u tom je trenutku siguran jer PDFium još nije inicijaliziran i vraća se odmah. Ostali neuspjesi učitavanja, poput nedostajućeg DLL-a ili nepoklapanja arhitekture, zadržavaju zaključanost, pa retry petlja mora razlikovati dvoje:
uses
SysUtils, PDFium;
function StartPdfiumPreferringSkia: TPdfRendererPreference;
var
Config: TPdfLibraryConfiguration;
begin
Config := TPdfLibraryConfiguration.Default;
Config.Renderer := prpSkia;
ConfigurePdfLibrary(Config);
try
PDFium.LoadLibrary; // kvalificirano jedinicom: Windows.LoadLibrary isto se zove
Result := prpSkia;
except
on E: EPdfError do
begin
// Odbijenica sposobnosti istovaruje DLL i otključava konfiguraciju.
// DLL koji uopće nije učitan ostaje zaključan: ponavljanje ne pomaže
if PdfLibraryConfigurationSealed then
raise;
Config.Renderer := prpAgg;
ConfigurePdfLibrary(Config);
PDFium.LoadLibrary;
Result := prpAgg;
end;
end;
end;
Obratite pozornost na izričit PDFium.LoadLibrary. U jedinici koja također koristi Windows ili Winapi.Windows, nekvalificirani LoadLibrary razrješuje se na jedinicu koja stoji zadnja u uses klauzuli; kad je to Win32 funkcija, poziv bez parametara ne prolazi kompajliranje s greškom broja argumenata koja o PDFiumu ne govori ništa
Validacija koja se događa još ranije
ConfigurePdfLibrary odbija neke kombinacije prije nego bilo koji DLL uopće sudjeluje, sve s EPdfError. Izričit FontBackend, uključujući pfbpFreeType, zahtijeva Renderer = prpSkia, jer PDFium font backend konzultira samo za Skia renderer. IsolatePerDocument zahtijeva da V8Isolate bude nil, jer PDFium stvara vlastiti isolate po dokumentu i provali nativni CHECK ako mu i sami uručite jednog. Prazni stringovi u UserFontPaths odbijaju se. I svaki poziv nakon prvog pokušaja učitavanja pada s "PDFium library configuration is already sealed"
To zadnje pravilo ima praktičnu posljedicu: ne možete prvo sondirati DLL, a konfigurirati ga poslije. GetSkiaRenderCapabilities, V8FeaturesAvailable, otvaranje dokumenta i većina drugih ulaznih točaka interno zove LoadLibrary, koji konfiguraciju zaključava na licu mjesta. Kasnije zvanje UnloadLibrary je također ne otvara. Prvo konfigurirajte, zatim učitajte, pa postavljajte pitanja, što je točno red kojega bi se dijagnostička rutina trebala držati:
uses
SysUtils, PDFium, FPdfView;
function DescribePdfiumState: string;
var
Config: TPdfLibraryConfiguration;
Renderer: string;
begin
Config := GetPdfLibraryConfiguration; // kopija, sigurno za pregled
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;
// Ista razlučivost koju je LoadLibrary primijenio kad je gradio FPDF_LIBRARY_CONFIG
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;
Logirati taj redak jednom pri pokretanju jeftino je, i to je prvo što želite u support tiketu koji kaže "tekst izgleda drugačije na serveru". PDFium.Loaded kvalificiran je iz istog razloga kao LoadLibrary: unutar metode forme ili komponente, goli Loaded veže se na TComponent.Loaded
Dva načina na koja verzionirana C konfiguracijska struktura krene krivo
Svaka verzionirana konfiguracijska struktura, bilo da je FPDF_LIBRARY_CONFIG, Win32 cbSize zapis ili plugin ABI, pada na dva simetrična načina, i wrapper se mora čuvati oba. Prvo je punjenje polja uz ostavljanje verzije preniskom; drugo je podizanje verzije uz ostavljanje polja na nultoj vrijednosti koju biblioteka čita kao svjesni izbor
- Polje postavljeno, verzija preniska. Upiredite
m_BrotliEnabled= 1 u strukturu verzije 2 i PDFium nikad na njega ne pogleda. Poziv uspije i Brotli tokovi ostaju nedekodabilni. Obrana je izvoditi verziju iz polja koja su stvarno u upotrebi, što radiLoadLibrary, umjesto da se hard-coda - Verzija dovoljno visoka, nulto polje nešto znači. Podignite verziju na 6 i svako polje do verzije 6 sada je živo.
FillCharnuliram_RendererTypeuFPDF_RENDERERTYPE_AGG, što je pravi renderer, ne "nepostavljeno". Obrana je upisati svako polje koje odabrana verzija pokriva namjernom vrijednošću, i razrješiti "zadano" protiv stvarnog builda umjesto pretpostaviti ga
Treće pravilo slijedi za vrijednosti koje mogu srušiti pozvanoga: validirajte ih protiv onoga što binarna datoteka može prije poziva, koristeći najjači dostupni dokaz, i budite iskreni u kodu i dokumentaciji kad je taj dokaz heuristika. Izvezeni simbol dokaz je. Ime cratea u string tablici dobra je pretpostavka
Brza referenca: konfiguracija biblioteke PDFium Componenta
- Zovite
ConfigurePdfLibraryjednom, prije nego bilo što učita DLL; svaki upit o sposobnostima ili učitavanje dokumenta zaključava je - Nadogradite na v3.123.0 ili novije ako postavljate
BrotliEnablediliIsolatePerDocumenti od priloženih runtimea očekujete Skia izlaz - Ostavite
RenderernaprpDefaultosim ako trebate konkretan rasterizator; sada se na svakoj verziji strukture razrješuje u zadani build - Koristite
PdfNativeRendererTypesGetSkiaRenderCapabilities.PageRenderda logirate koji je renderer stvarno aktivan - Očekujte
EPdfError, ne pad, zaprpSkiana AGG-only DLL-u ilipfbpFontationsna DLL-u bez Fontationsa u v3.125.0 ili novijem - Nakon odbijenice sposobnosti,
PdfLibraryConfigurationSealedje False i smijete rekonfigurirati; nakon neuspjelog učitavanja DLL-a ostaje True - Detekciju Fontationsa tretirajte kao heuristiku i držite FreeType fallback
- Pišite
PDFium.LoadLibraryiPDFium.Loadeds imenom jedinice da izbjegnete sudare imena s Win32 iTComponent
Ako DLL padne prije nego konfiguracija uopće postane bitna, počnite s dijagnostikom neuspjeha učitavanja PDFium DLL-a u Delphiju, a za to kako komponenta na svakoj platformi nalazi pravu binarnu datoteku pogledajte učitavanje PDFium nativne biblioteke na bilo kojem cilju. Kad se renderer namjesti, taktike render cachea i glatkog zuma pokriva kako u pregledniku zadržati brzo renderiranje stranica
PDFium Component omotava PDFium motor za Delphi i C++Builder provjerama konfiguracije poput ovih, pa nativna inicijalizacija pada kao Pascal iznimka koju možete uhvatiti umjesto izlaza iz procesa. Detalji o proizvodu i preuzimanja na PDFium Component for Delphi stranici proizvoda