I PDFium Component til Delphi brugte det at slå BrotliEnabled eller IsolatePerDocument til i TPdfLibraryConfiguration til at bytte det medfølgende Skia-build ud med AGG-rendereren uden nogen fejl, fordi begge options hæver FPDF_LIBRARY_CONFIG til en version, hvor PDFium læser m_RendererType bogstaveligt. Siden v3.123.0 forbliver default-rendereren DLL'ens egen default, og siden v3.125.0 udløser en Skia- eller Fontations-forespørgsel, DLL'en ikke kan imødekomme, en EPdfError, du kan fange, i stedet for at dræbe processen
Ingen af de to bugs annoncerede sig selv. Den første producerede sider, der så fint ud, bare renderet af en anden rasterizer, med en anelse anderledes anti-aliasing og tekstkanter end det build, du skibede og testede. Den anden annoncerede sig selv, højtlydt, ved at tage værtsprocessen ned indefra nativ initialisering. Begge kommer fra samme sted: en versioneret C-struktur, hvis felter først tæller, når versionsnummeret siger det, og hvis nulværdier ikke er "usat", men reelle valg
Hvordan beslutter FPDF_LIBRARY_CONFIG, hvilken renderer PDFium bruger?
FPDF_InitLibraryWithConfig konsulterer m_RendererType kun, når strukturens Version-felt er 4 eller højere, og fra den version af bruger den værdien præcis, som den er skrevet. Under version 4 ignorerer PDFium feltet og vælger buildets default, som er Skia i builds kompileret med PDF_USE_SKIA og AGG alle andre steder
Hvert senere felt følger samme mønster. Strukturen voksede én capability ad gangen, og hver capability ankom sammen med et nyt versionsnummer. PDFium Component bygger den native struktur i LoadLibrary ud fra din TPdfLibraryConfiguration og hæver versionen kun så langt, som de options, du har sat, kræver
| Strukturversion | Felt, den tilføjer | Sat af |
|---|---|---|
| 2 | m_pIsolate, m_v8EmbedderSlot | Altid skrevet; V8Isolate, V8EmbedderSlot |
| 3 | m_pPlatform | V8Platform ikke nil |
| 4 | m_RendererType | Renderer andet end prpDefault |
| 5 | m_FontLibraryType | FontBackend andet end pfbpDefault |
| 6 | m_BrotliEnabled | BrotliEnabled = True |
| 7 | m_IsolatePerDocument | IsolatePerDocument = True |
Fælden sidder i de sidste to rækker. Versioner er kumulative: en version 6-struktur er også en version 4- og en version 5-struktur, så PDFium læser m_RendererType og m_FontLibraryType, selv om du kun bad om Brotli. Hvad end der sidder i de to felter i det øjeblik, bliver rendereren og font-backend'en, uanset om du havde tænkt dig at vælge dem eller ej
Hvorfor byttede aktivering af Brotli rendereren ud med AGG?
Før v3.123.0 skrev PDFium Component FPDF_RENDERERTYPE_AGG ind i m_RendererType for prpDefault, så enhver konfiguration, der skubbede strukturen til version 6 eller 7, tvang AGG på et Skia-build. De pdfium.dll- og pdfium.v8.dll-runtimes, der skiber med komponenten, er Skia-builds, så dette ramte default-deploymentet, ikke et eksotisk
Mappingen så harmløs ud, da den blev skrevet. Ved version 2 eller 3 læses feltet aldrig, så prpDefault betød virkelig "whatever DLL'en gør". I det øjeblik BrotliEnabled (version 6) eller IsolatePerDocument (version 7) kom ind i billedet, forvandlede samme kode "ingen præference" til en eksplicit AGG-forespørgsel. Intet fejlede. PDFium initialiserede normalt, renderede hver side og returnerede ingen fejlkode, for set fra dens synsvinkel havde kalderen bedt om AGG og fået AGG
En pixel-hash gør byttet synligt dér, hvor skærmbilleder ikke gør. Rendering af første side af det samme eksempeldokument under tre konfigurationer gav:
- Default-konfiguration: hash
502D77C3711B4ACF BrotliEnabled= True medRendererefterladt påprpDefault: hashF75B5EB4728ADE87- Eksplicit
prpAgg: hashF75B5EB4728ADE87, identisk med Brotli-kørslen
Fixet i v3.123.0 er den offentlige funktion PdfNativeRendererType, som opløser en TPdfRendererPreference til værdien, der skrives ind i m_RendererType. prpAgg og prpSkia mapper én til én. prpDefault mapper nu til Skia, når den indlæste DLL eksporterer FPDF_RenderPageSkia, og til AGG ellers. Den export kompileres under samme PDF_USE_SKIA-betingelse som Skia-defaulten selv, hvilket gør den til den eneste build-egenskab, du kan observere udefra DLL'en. Efter fixet producerer Brotli-konfigurationen den samme hash som default'en
Font-backend'en havde aldrig det samme problem. m_FontLibraryType læses fra version 5, og dens nulværdi, FPDF_FONTBACKENDTYPE_FREETYPE, er også PDFiums default, når feltet slet ikke læses. At skrive FreeType for pfbpDefault gengiver derfor det native default præcist. Nulværdier er ikke altid forkerte, de er bare aldrig automatisk rigtige
Med v3.123.0 eller senere gør den startup-kode, du naturligt ville skrive, nu, hvad den siger:
uses
PDFium;
procedure ConfigurePdfiumAtStartup;
var
Config: TPdfLibraryConfiguration;
begin
// Skal køre, før noget indlæser det native bibliotek
Config := TPdfLibraryConfiguration.Default;
Config.BrotliEnabled := True; // hæver FPDF_LIBRARY_CONFIG til version 6
// Renderer forbliver prpDefault: opløses til Skia på builds, der eksporterer
// FPDF_RenderPageSkia, og til AGG på AGG-only builds
SetLength(Config.UserFontPaths, 1);
Config.UserFontPaths[0] := 'C:\ProgramData\MyApp\Fonts';
ConfigurePdfLibrary(Config);
end;
Husk, at BrotliEnabled kun gør PDF 2.0 /BrotliDecode-streams dekodbar, når DLL'en selv er bygget med PDF_ENABLE_BROTLI. Flaget er en forespørgsel, og på et build uden Brotli-understøttelse har det ingen effekt. TPdfLibraryConfiguration.Hardened er det samme som Default, bortset fra at AllowMachineTime er False, hvilket blokerer dokument-JavaScript fra at læse det rigtige ur; det er et fornuftigt udgangspunkt til server-side-behandling af utroværdige filer
Hvad sker der, når du beder om en backend, DLL'en ikke indeholder?
PDFium returnerer ingen fejl for en renderer- eller font-backend, der mangler i buildet: FPDF_InitLibraryWithConfig fejler en nativ CHECK, som på Windows viser sig som en breakpoint-exception og uden en structured exception handler omkring kaldet terminerer processen. Headeren siger det selv og advarer om, at en ikke-understøttet værdi "will similarly fail with an immediate crash"
De to konkrete tilfælde er et AGG-only-build, der modtager FPDF_RENDERERTYPE_SKIA, og et build uden Fontations, der modtager FPDF_FONTBACKENDTYPE_FONTATIONS. Den medfølgende Skia-runtime er i den anden gruppe: den renderer med Skia, men bruger FreeType til fonts. At bede om prpSkia sammen med pfbpFontations mod den gav External exception 80000003 på Delphi-siden. Når debuggere eller en exception-handler tilfældigt fanger det, er situationen stadig uoprettelig:
- PDFium efterlades halvt initialiseret
- Den procesvide konfiguration er allerede sealeret, så
ConfigurePdfLibrarynægter en korrigeret konfiguration - At prøve igen med en anden konfiguration i samme proces er ikke længere muligt
Dette er den modsatte fejl af Brotli-buggen. Dér holdt feltet en værdi, ingen havde valgt, og PDFium accepterede den lydløst. Her holder feltet en værdi, kalderen bevidst har valgt, og PDFium accepterer ingen diskussion om den overhovedet. Begge er problemer, en wrapper må løse før det native kald, for bagefter er der intet tilbage at fange
Hvordan PDFium Component prechecker Skia og Fontations
Siden v3.125.0 validerer LoadLibrary konfigurationen efter binding af DLL-exporterne og før kaldet af FPDF_InitLibraryWithConfig og forvandler en ikke-understøttet renderer eller font-backend til en EPdfError med en besked, der nævner den skyldige indstilling og alternativerne. DLL'en aflæsses, og konfigurationen unseales, så kalderen kan vælge andre indstillinger og indlæse igen
Beslutningen bor selv i den rene funktion PdfLibraryConfigurationSupportError, som tager konfigurationen plus to Booleans, der beskriver buildet, og returnerer en tom streng, når kombinationen er sikker. Eftersom den ikke rører nativ tilstand, kan du kalde den fra dine egne tests med enhver capability-kombination. Inde i LoadLibrary kommer de to Booleans fra forskellige slags beviser, og de fortjener forskellige grader af tillid:
- Skia detekteres ud fra tilstedeværelsen af
FPDF_RenderPageSkia-exporten, samme signal somPdfNativeRendererTypebruger. Exporten og Skia-rendereren kompileres under én betingelse, så tjekket er eksakt - Fontations har ingen export af egen art. Det eneste spor, den efterlader, er de Rust-font-crates, den trækker ind i binærfilen, så PDFium Component scanner den indlæste biblioteksfil for cratenavnene
skrifaogread-fonts(ogsåread_fonts). Scanningen kører kun, nårpfbpFontationsforespørges, og en fil, der ikke kan læses, tæller som "ingen Fontations"
Fontations-tjekket er en heuristik, og det kan tage fejl i én retning: et Fontations-build strippet for hver eneste af de strenge ville blive afvist, selv om det kunne have virket. Det trade blev bevidst. En falsk afvisning koster dig en exception, du kan fange, og en fallback til FreeType. En falsk accept koster dig processen
At unseale tæller lige så meget som tjekket. LoadLibrary sealer konfigurationen helt i starten af indlæsningen, så uden nulstillingen ville en capability-afvisning efterlade ConfigurePdfLibrary med at svare ethvert retry med EPdfError "PDFium library configuration is already sealed". Afvisningsvejen kalder UnloadLibrary først; dets FPDF_DestroyLibrary-kald er sikkert på det punkt, fordi PDFium endnu ikke er initialiseret og returnerer straks. Andre indlæsningsfejl, som en manglende DLL eller et arkitektur-mismatch, beholder seallet, så en retry-løkke må skelne de to:
uses
SysUtils, PDFium;
function StartPdfiumPreferringSkia: TPdfRendererPreference;
var
Config: TPdfLibraryConfiguration;
begin
Config := TPdfLibraryConfiguration.Default;
Config.Renderer := prpSkia;
ConfigurePdfLibrary(Config);
try
PDFium.LoadLibrary; // unit-kvalificeret: Windows.LoadLibrary hedder det samme
Result := prpSkia;
except
on E: EPdfError do
begin
// En capability-afvisning aflæsser DLL'en og unsealer konfigurationen.
// En DLL, der slet ikke kunne indlæses, forbliver sealeret: retry hjælper ikke
if PdfLibraryConfigurationSealed then
raise;
Config.Renderer := prpAgg;
ConfigurePdfLibrary(Config);
PDFium.LoadLibrary;
Result := prpAgg;
end;
end;
end;
Bemærk den eksplicitte PDFium.LoadLibrary. I en unit, der også bruger Windows eller Winapi.Windows, opløser et ukvalificeret LoadLibrary til den unit, der optræder sidst i uses-klausulen; er det Win32-funktionen, fejler det parameterløse kald i kompileringen med en argumentantal-fejl, der siger ingenting om PDFium
Validering, der sker endnu tidligere
ConfigurePdfLibrary afviser visse kombinationer, før nogen DLL er involveret, alle med EPdfError. En eksplicit FontBackend, inklusive pfbpFreeType, kræver Renderer = prpSkia, for PDFium konsulterer kun font-backend'en for Skia-rendereren. IsolatePerDocument kræver, at V8Isolate er nil, da PDFium opretter sin egen isolate pr. dokument og fejler en nativ CHECK, hvis du også rækker den én. Tomme strenge i UserFontPaths afvises. Og ethvert kald efter det første indlæsningsforsøg fejler med "PDFium library configuration is already sealed"
Den sidste regel har en praktisk konsekvens: du kan ikke probe DLL'en først og konfigurere den bagefter. GetSkiaRenderCapabilities, V8FeaturesAvailable, at åbne et dokument og de fleste andre indgangspunkter kalder LoadLibrary internt, hvilket sealer konfigurationen på stedet. At kalde UnloadLibrary senere genåbner den heller ikke. Konfigurér først, indlæs så, og stil så spørgsmål, hvilket er præcis den orden, en diagnostisk rutine bør følge:
uses
SysUtils, PDFium, FPdfView;
function DescribePdfiumState: string;
var
Config: TPdfLibraryConfiguration;
Renderer: string;
begin
Config := GetPdfLibraryConfiguration; // en kopi, sikker at inspicere
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;
// Samme opløsning LoadLibrary anvendte, da den byggede 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;
At logge den linje én gang ved startup er billigt, og det er det første, du vil have i en support-sag, der lyder "teksten ser anderledes ud på serveren". PDFium.Loaded er kvalificeret af samme grund som LoadLibrary: inde i en form- eller komponentmetode binder en bar Loaded til TComponent.Loaded
To måder en versioneret C-konfigurationsstruktur tager fejl på
Enhver versioneret konfigurationsstruktur, om det er FPDF_LIBRARY_CONFIG, en Win32 cbSize-record eller et plugin-ABI, fejler på to symmetriske måder, og en wrapper må gardere sig mod begge. Den første er at udfylde et felt, mens versionen efterlades for lav; den anden er at hæve versionen, mens et felt efterlades på en nulværdi, som biblioteket læser som et bevidst valg
- Felt sat, version for lav. Skriv
m_BrotliEnabled= 1 ind i en version 2-struktur, og PDFium kigger aldrig på den. Kaldet lykkes, og Brotli-streams forbliver udekoderbare. Forsvaret er at udlede versionen af de felter, der faktisk er i brug, hvilketLoadLibrarygør, frem for at hardkode én - Version høj nok, nulfelt betyder noget. Hæv versionen til 6, og hvert felt op til version 6 er nu levende.
FillCharnulstillerm_RendererTypetilFPDF_RENDERERTYPE_AGG, hvilket er en reel renderer, ikke "usat". Forsvaret er at skrive hvert felt, den valgte version dækker, med en bevidst værdi, og at opløse "default" mod det faktiske build i stedet for at antage det
En tredje regel følger for værdier, der kan crashe den kaldte: valider dem mod, hvad binærfilen kan, før kaldet, med det stærkeste tilgængelige bevis, og vær ærlig i kode og dokumentation, når det bevis er en heuristik. Et eksporteret symbol er bevis. Et cratenavn i en strengtabel er et godt gæt
Hurtig reference: PDFium Component bibliotekskonfiguration
- Kald
ConfigurePdfLibraryén gang, før noget indlæser DLL'en; enhver capability-forespørgsel eller dokumentindlæsning sealer den - Opgradér til v3.123.0 eller senere, hvis du sætter
BrotliEnabledellerIsolatePerDocumentog forventer Skia-output fra de medfølgende runtimes - Lad
Rendererstå påprpDefault, medmindre du behøver en specifik rasterizer; den opløses nu til buildets default ved hver strukturversion - Brug
PdfNativeRendererTypemedGetSkiaRenderCapabilities.PageRendertil at logge, hvilken renderer der faktisk er aktiv - Forvent
EPdfError, ikke et crash, forprpSkiapå en AGG-only DLL ellerpfbpFontationspå en ikke-Fontations DLL i v3.125.0 eller senere - Efter en capability-afvisning er
PdfLibraryConfigurationSealedFalse, og du må omkonfigurere; efter en fejlet DLL-indlæsning forbliver den True - Behandl Fontations-detektion som heuristik og hold en FreeType-fallback
- Skriv
PDFium.LoadLibraryogPDFium.Loadedmed unit-navnet for at undgå Win32- ogTComponent-navne-kollisioner
Fejler DLL'en, før konfigurationen overhovedet tæller, så start med diagnosticering af PDFium DLL-indlæsningsfejl i Delphi, og for hvordan komponenten finder den rigtige binærfil på hver platform, se indlæsning af PDFium native-biblioteket på ethvert mål. Når rendereren er på plads, dækker render cache og smooth zoom-taktikker, hvordan side-rendering holdes hurtig i en viewer
PDFium Component wrapper PDFium-motoren til Delphi og C++Builder med konfigurationstjek som disse, så nativ initialisering fejler som en Pascal-exception, du kan håndtere, frem for en proces-exit. Produktdetaljer og downloads står på produktsiden for PDFium Component for Delphi