U PDFium Component-u za Delphi, uključivanje BrotliEnabled-a ili IsolatePerDocument-a u TPdfLibraryConfiguration prebacivalo je uključeni Skia build na AGG renderer bez ijedne greške, jer obe opcije podižu FPDF_LIBRARY_CONFIG na verziju na kojoj PDFium čita m_RendererType doslovno. Od v3.123.0 podrazumevani renderer ostaje sopstveni podrazumevani DLL-a, a od v3.125.0 Skia ili Fontations zahtev koji DLL ne može da ispuni podiže uhvatljiv EPdfError umesto da obara proces
Nijedan bag se nije najavio. Prvi je proizvodio stranice koje su izgledale dobro, samo renderovane drugim rasterizatorom, s mrvicu drugačijim glatkanjem ivica i tekstom od builda koji ste isporučili i testirali. Drugi se najavio, glomazno, obaranjem procesa domaćina iz unutrašnjosti nativne inicijalizacije. Oba dolaze s istog mesta: verzionisana C struktura čija se polja računaju tek kad broj verzije kaže da se računaju, i čije nulte vrednosti nisu „nepostavljeno“ nego prave odluke
Kako FPDF_LIBRARY_CONFIG odlučuje koji renderer PDFium koristi?
FPDF_InitLibraryWithConfig pita m_RendererType samo kad je polje Version strukture 4 ili više, i od te verzije koristi vrednost tačno onakvom kakva je upisana. Ispod verzije 4 PDFium ignoriše polje i bira podrazumevani build-a, što je Skia u buildovima kompajliranim s PDF_USE_SKIA i AGG svuda drugde
Svako kasnije polje prati isti obrazac. Struktura je rasla po jednoj sposobnosti, i svaka sposobnost je stigla zajedno s novim brojem verzije. PDFium Component gradi nativnu strukturu u LoadLibrary-ju iz vašeg TPdfLibraryConfiguration-a i podiže verziju samo toliko koliko opcije koje ste postavili traže
| Verzija strukture | Polje koje dodaje | Postavlja se |
|---|---|---|
| 2 | m_pIsolate, m_v8EmbedderSlot | Uvek se piše; 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 poslednja dva reda. Verzije su kumulativne: struktura verzije 6 je i struktura verzije 4 i verzije 5, pa PDFium čita m_RendererType i m_FontLibraryType iako ste tražili samo Brotli. Šta god stoji u ta dva polja u tom trenutku postaje renderer i font bekend, da li ste ih nameravali birati ili ne
Zašto je uključenje Brotli-ja zamenilo renderer s AGG-om?
Pre v3.123.0 PDFium Component je upisivao FPDF_RENDERERTYPE_AGG u m_RendererType za prpDefault, pa je svaka konfiguracija koja je gurala strukturu na verziju 6 ili 7 nameće AGG na Skia buildu. Runtimei pdfium.dll i pdfium.v8.dll koji se isporučuju s komponentom su Skia buildovi, pa je ovo pogodilo podrazumevano raspoređivanje, ne neki egzotičan slučaj
Preslikavanje je delovalo bezopasno kad je napisano. Na verziji 2 ili 3 polje se nikad ne čita, pa je prpDefault zaista značio „šta god DLL radi“. Trenutak kad je BrotliEnabled (verzija 6) ili IsolatePerDocument (verzija 7) ušao u sliku, isti kod pretvorio je „bez preferencije“ u eksplicitan AGG zahtev. Ništa nije palo. PDFium se inicijalizovao normalno, renderovao svaku stranicu i vratio se bez koda greške, jer je iz njegove tačke gledišta pozivaoc tražio AGG i dobio AGG
Heš piksela čini zamenu vidljivom tamo gde snimci ekrana ne vidi. Renderovanje prve stranice istog oglednog dokumenta pod tri konfiguracije dalo je:
- Podrazumevana konfiguracija: heš
502D77C3711B4ACF BrotliEnabled= True uzRendererostavljen naprpDefault: hešF75B5EB4728ADE87- Eksplicitan
prpAgg: hešF75B5EB4728ADE87, identičan Brotli pokretanju
Popravka u v3.123.0 jeste javna funkcija PdfNativeRendererType, koja razrešava TPdfRendererPreference u vrednost upisanu u m_RendererType. prpAgg i prpSkia se preslikavaju jedan u jedan. prpDefault se sada preslikava u Skia kad učitani DLL izvozi FPDF_RenderPageSkia i u AGG inače. Taj izvoz se kompajlira pod istim uslovom PDF_USE_SKIA kao i sama Skia podrazumevana, što ga čini jedinim svojstvom builda koje možete posmatrati spolja iz DLL-a. Posle popravke Brotli konfiguracija daje isti heš kao podrazumevana
Font bekend nikad nije imao isti problem. m_FontLibraryType se čita od verzije 5, i njegova nulta vrednost, FPDF_FONTBACKENDTYPE_FREETYPE, jeste i PDFium-ov podrazumevani kad se polje uopšte ne čita. Pisanje FreeType-a za pfbpDefault zato reprodukuje nativnu podrazumevanu tačno. Nulte vrednosti nisu uvek pogrešne, samo nikad nisu automatski ispravne
S v3.123.0 ili kasnijom, startni kod koji biste prirodno napisali sada radi ono što kaže:
uses
PDFium;
procedure ConfigurePdfiumAtStartup;
var
Config: TPdfLibraryConfiguration;
begin
// Mora da radi pre nego što bilo šta učita nativnu biblioteku
Config := TPdfLibraryConfiguration.Default;
Config.BrotliEnabled := True; // podiže FPDF_LIBRARY_CONFIG na verziju 6
// Renderer ostaje prpDefault: razrešuje se u Skia na buildovima koji izvoze
// FPDF_RenderPageSkia i u AGG na samo-AGG buildovima
SetLength(Config.UserFontPaths, 1);
Config.UserFontPaths[0] := 'C:\ProgramData\MyApp\Fonts';
ConfigurePdfLibrary(Config);
end;
Zapamtite da BrotliEnabled čini PDF 2.0 /BrotliDecode tokove dekodirljivim samo kad je i sam DLL buildovan s PDF_ENABLE_BROTLI-om. Flag je zahtev, i na buildu bez Brotli podrške nema efekta. TPdfLibraryConfiguration.Hardened je isto što i Default osim što je AllowMachineTime False, što sprečava JavaScript dokumenta da čita pravi sat; razumna je početna tačka za serversku obradu nepoverljivih fajlova
Šta se dešava kad zahtevate bekend koji DLL ne sadrži?
PDFium ne vraća grešku za renderer ili font bekend kojeg nema u buildu: FPDF_InitLibraryWithConfig ne uspeva nativni CHECK, što se na Windowsu pokazuje kao breakpoint izuzetak i, bez strukturiranog handlera izuzetaka oko poziva, prekida proces. Zaglavlje to i kaže, uz upozorenje da nepodržana vrednost „će slično pasti s trenutnim crash-om“
Dva konkretna slučaja su samo-AGG build koji primi FPDF_RENDERERTYPE_SKIA, i build bez Fontations-a koji primi FPDF_FONTBACKENDTYPE_FONTATIONS. Uključeni Skia runtime je u drugoj grupi: renderuje s Skiom ali koristi FreeType za fontove. Traženje prpSkia uz pfbpFontations nad njim dalo je External exception 80000003 na Delphi strani. Kad to slučajno uhvati debugger ili handler izuzetaka, situacija je i dalje neopoziva:
- PDFium je ostavljen poinicijalizovan
- Konfiguracija za ceo proces je već zalepljena, pa
ConfigurePdfLibraryodbija ispravljenu konfiguraciju - Ponavljanje s drugačijom konfiguracijom u istom procesu više nije moguće
Ovo je suprotni neuspeh od Brotli baga. Tamo je polje nosilo vrednost koju niko nije izabrao i PDFium ju je tiho prihvatio. Ovde polje nosi vrednost koju je pozivaoc svesno izabrao i PDFium o njoj ne prima nikakvu raspravu. Oba su problemi koje omotač mora rešiti pre nativnog poziva, jer posle njega nema više ničega što se može uhvatiti
Kako PDFium Component prethodno proverava Skiu i Fontations
Od v3.125.0 LoadLibrary validira konfiguraciju posle vezivanja izvoza DLL-a i pre poziva FPDF_InitLibraryWithConfig, i pretvara nepodržan renderer ili font bekend u EPdfError s porukom koja imenuje pogrešnu postavku i alternative. DLL se istovaruje i konfiguracija se odlepljuje, pa pozivaoc može birati druge postavke i učitati ponovo
Odluka sama živi u čistoj funkciji PdfLibraryConfigurationSupportError, koja prima konfiguraciju plus dva Booleana koji opisuju build i vraća prazan string kad je kombinacija bezbedna. Pošto ne dira nativno stanje, možete je zvati iz sopstvenih testova s bilo kojom kombinacijom sposobnosti. Unutar LoadLibrary-ja dva Booleana dolaze iz različitih vrsta dokaza, i zaslužuju različite nivoe poverenja:
- Skia se otkriva po prisustvu izvoza
FPDF_RenderPageSkia, istog signala kojim se služiPdfNativeRendererType. Izvoz i Skia renderer kompajliraju se pod jednim uslovom, pa je provera tačna - Fontations nema izvoz za sebe. Jedini trag koji ostavlja jesu Rust font crates koje povlači u binarni fajl, pa PDFium Component pregleda učitanu bibliotečku datoteku za imenima crates-a
skrifairead-fonts(takođeread_fonts). Pregled radi samo kad se tražipfbpFontations, i fajl koji se ne može pročitati računa se kao „bez Fontations-a“
Fontations provera je heuristika, i može pogrešiti u jednom smeru: Fontations build ogoljen od svih tih stringova bio bi odbijen iako je mogao da radi. Taj kompromis je sklopljen namerno. Lažno odbijanje košta vas izuzetak koji možete uhvatiti i pad na FreeType. Lažno prihvatanje košta vas proces
Odlepljivanje znači koliko i provera. LoadLibrary zalepljuje konfiguraciju na samom početku učitavanja, pa bez resetovanja bi odbijanje sposobnosti ostavilo ConfigurePdfLibrary da na svako ponavljanje odgovara s EPdfError „PDFium bibliotečka konfiguracija je već zalepljena“. Put odbijanja prvo zove UnloadLibrary; njegov poziv FPDF_DestroyLibrary je bezbedan u tom trenutku jer PDFium još nije inicijalizovan i vraća se odmah. Drugi neuspesi učitavanja, poput nedostajućeg DLL-a ili nepoklapanja arhitekture, zadržavaju zaptivač, pa petlja ponavljanja mora da ih razlikuje:
uses
SysUtils, PDFium;
function StartPdfiumPreferringSkia: TPdfRendererPreference;
var
Config: TPdfLibraryConfiguration;
begin
Config := TPdfLibraryConfiguration.Default;
Config.Renderer := prpSkia;
ConfigurePdfLibrary(Config);
try
PDFium.LoadLibrary; // kvalifikovano jedinicom: Windows.LoadLibrary nosi isto ime
Result := prpSkia;
except
on E: EPdfError do
begin
// Odbijanje sposobnosti istovaruje DLL i odlepljuje konfiguraciju.
// DLL koji uopšte nije učitan ostaje zalepljen: ponavljanje ne pomaže
if PdfLibraryConfigurationSealed then
raise;
Config.Renderer := prpAgg;
ConfigurePdfLibrary(Config);
PDFium.LoadLibrary;
Result := prpAgg;
end;
end;
end;
Primetite eksplicitno PDFium.LoadLibrary. U jedinici koja koristi i Windows ili Winapi.Windows, nekvalifikovano LoadLibrary razrešuje se u jedinicu koja stoji poslednja u uses klauzi; kad je to Win32 funkcija, poziv bez argumenata ne prolazi kompilaciju s greškom o broju argumenata koja ništa ne kaže o PDFium-u
Validacija koja se dešava još ranije
ConfigurePdfLibrary odbija neke kombinacije pre nego što se bilo koji DLL uopšte pojavi, sve s EPdfError-om. Eksplicitan FontBackend, uključujući pfbpFreeType, traži Renderer = prpSkia, jer PDFium pita font bekend samo za Skia renderer. IsolatePerDocument traži da je V8Isolate nil, pošto PDFium sam pravi isolate po dokumentu i ne uspeva nativni CHECK ako mu i vi date jednog. Prazni stringovi u UserFontPaths se odbijaju. I svaki poziv posle prvog pokušaja učitavanja pada s „PDFium bibliotečka konfiguracija je već zalepljena“
Poslednje pravilo ima praktičnu posledicu: ne možete prvo sondirati DLL pa ga konfigurisati posle. GetSkiaRenderCapabilities, V8FeaturesAvailable, otvaranje dokumenta i većina ostalih ulaza zovu LoadLibrary interno, što zalepljuje konfiguraciju na licu mesta. Poziv UnloadLibrary-ja kasnije je ni ne otvara. Konfigurišite prvo, pa učitajte, pa postavljajte pitanja, što je tačno red kojem bi dijagnostička rutina trebalo da sledi:
uses
SysUtils, PDFium, FPdfView;
function DescribePdfiumState: string;
var
Config: TPdfLibraryConfiguration;
Renderer: string;
begin
Config := GetPdfLibraryConfiguration; // kopija, bezbedno 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;
// Isto razrešenje koje je LoadLibrary primenio 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;
Logovanje te linije jednom pri startu je jeftino, i to je prvo što želite u support tiketu koji kaže „tekst na serveru izgleda drugačije“. PDFium.Loaded je kvalifikovan iz istog razloga kao LoadLibrary: unutar metode forme ili komponente, goli Loaded se veže za TComponent.Loaded
Dva načina da verzionisana C konfiguracijska struktura krene naopako
Svaka verzionisana konfiguracijska struktura, bila to FPDF_LIBRARY_CONFIG, Win32 cbSize zapis ili plugin ABI, pada na dva simetrična načina, i omotač mora da se čuva od obojice. Prvo je popunjavanje polja uz ostavljanje verzije preniskom; drugo je podizanje verzije uz ostavljanje polja na nultoj vrednosti koju biblioteka čita kao namernu odluku
- Polje postavljeno, verzija preniska. Upis
m_BrotliEnabled= 1 u strukturu verzije 2 i PDFium nikad ne pogleda. Poziv uspeva i Brotli tokovi ostaju nedekodirljivi. Odbrana je izvođenje verzije iz polja koja zaista koristite, štoLoadLibraryradi, umesto da se čvrsto ukucava jedno - Verzija dovoljno visoka, nulta vrednost polja nešto znači. Podignite verziju na 6 i svako polje do verzije 6 je sada živo.
FillCharnuliram_RendererTypeuFPDF_RENDERERTYPE_AGG, što je pravi renderer, ne „nepostavljeno“. Odbrana je pisanje svakog polja koje izabrana verzija pokriva s namernom vrednošću, i razrešavanje „podrazumevanog“ protiv stvarnog builda umesto pretpostavke
Treće pravilo sledi za vrednosti koje mogu oboriti pozvanog: validirajte ih protiv onoga što binarni fajl ume pre poziva, s najjačim dokazom koji je dostupan, i budite iskreni u kodu i dokumentaciji kad je taj dokaz heuristika. Izvezeni simbol je dokaz. Ime crate-a u tabeli stringova je dobra pretpostavka
Brzi podsetnik: konfiguracija biblioteke PDFium Component
- Zovite
ConfigurePdfLibraryjednom, pre nego što bilo šta učita DLL; svaki upit sposobnosti ili učitavanje dokumenta ga zalepljuje - Nadogradite na v3.123.0 ili kasniju ako postavljate
BrotliEnablediliIsolatePerDocumenti očekujete Skia izlaz od uključenih runtime-a - Ostavite
RenderernaprpDefaultosim ako vam treba konkretan rasterizator; sada se razrešuje u podrazumevani build-a na svakoj verziji strukture - Koristite
PdfNativeRendererTypesGetSkiaRenderCapabilities.PageRenderda zabeležite koji je renderer zaista aktivan - Očekujte
EPdfError, ne crash, zaprpSkiana samo-AGG DLL-u ilipfbpFontationsna ne-Fontations DLL-u u v3.125.0 ili kasnijoj - Posle odbijanja sposobnosti,
PdfLibraryConfigurationSealedje False i smete se prekonfigurisati; posle neuspelog učitavanja DLL-a ostaje True - Tretirajte Fontations detekciju kao heuristiku i držite FreeType rezervu
- Pišite
PDFium.LoadLibraryiPDFium.Loadeds imenom jedinice da izbegnete Win32 iTComponentsudare imena
Ako DLL padne pre nego što konfiguracija uopšte počne da znači, krenite od dijagnostikovanja neuspeha učitavanja PDFium DLL-a u Delphiju, a o tome kako komponenta nalazi pravi binarni fajl na svakoj platformi pogledajte učitavanje PDFium nativne biblioteke na bilo kom cilju. Kad se renderer sredi, render keš i taktike gladkog zumiranja pokriva kako da renderovanje stranica ostane brzo u pregledaču
PDFium Component omotava PDFium engine za Delphi i C++Builder s proverama konfiguracije poput ovih, pa nativna inicijalizacija pada kao Pascal izuzetak koji možete uhvatiti umesto izlaska procesa. Detalji proizvoda i preuzimanja su na stranici proizvoda PDFium Component for Delphi