In PDFium Component for Delphi schakelde het aanzetten van BrotliEnabled of IsolatePerDocument in TPdfLibraryConfiguration de meegeleverde Skia-build vroeger foutloos om naar de AGG-renderer, omdat beide opties FPDF_LIBRARY_CONFIG naar een versie tillen waarin PDFium m_RendererType letterlijk leest. Sinds v3.123.0 blijft de default-renderer de eigen default van de DLL, en sinds v3.125.0 levert een Skia- of Fontations-verzoek die de DLL niet kan honoreren een vangbare EPdfError op in plaats van het proces te doden
Geen van beide bugs kondigde zich aan. De eerste produceerde pagina's die er prima uitzagen, alleen gerenderd door een andere rasterizer, met net iets andere anti-aliasing en textranden dan de build die u heeft verscheept en getest. De tweede kondigde zich wel aan, luid, door het hostproces neer te halen van binnenuit de native-initialisatie. Beide komen van dezelfde plek: een geversioneerde C-structuur waarvan de velden pas meetellen zodra het versienummer dat zegt, en waarvan nulwaarden geen niet-ingesteld zijn maar echte keuzes
Hoe beslist FPDF_LIBRARY_CONFIG welke renderer PDFium gebruikt?
FPDF_InitLibraryWithConfig raadpleegt m_RendererType alleen wanneer het veld Version van de structuur 4 of hoger is, en vanaf die versie gebruikt hij de waarde exact zoals geschreven. Onder versie 4 negeert PDFium het veld en pakt de build-default, Skia in builds gecompileerd met PDF_USE_SKIA en overal elders AGG
Elk later veld volgt hetzelfde patroon. De structuur groeide één capaciteit per keer, en elke capaciteit kwam samen met een nieuw versienummer. PDFium Component bouwt de native structuur in LoadLibrary op basis van uw TPdfLibraryConfiguration en tilt de versie alleen zover als de opties die u zet vereisen
| Structuurversie | Veld dat hij toevoegt | Gezet door |
|---|---|---|
| 2 | m_pIsolate, m_v8EmbedderSlot | Altijd geschreven; V8Isolate, V8EmbedderSlot |
| 3 | m_pPlatform | V8Platform niet nil |
| 4 | m_RendererType | Renderer anders dan prpDefault |
| 5 | m_FontLibraryType | FontBackend anders dan pfbpDefault |
| 6 | m_BrotliEnabled | BrotliEnabled = True |
| 7 | m_IsolatePerDocument | IsolatePerDocument = True |
De val zit in de laatste twee rijen. Versies zijn cumulatief: een structuur van versie 6 is ook een structuur van versie 4 en versie 5, dus PDFium leest m_RendererType en m_FontLibraryType zelfs als u alleen om Brotli vroeg. Wat er op dat moment in die twee velden staat wordt de renderer en de font-backend, of u ze nu wilde kiezen of niet
Waarom schakelde het aanzetten van Brotli de renderer naar AGG?
Vóór v3.123.0 schreef PDFium Component FPDF_RENDERERTYPE_AGG in m_RendererType voor prpDefault, dus elke configuratie die de structuur naar versie 6 of 7 tillde forceerde AGG op een Skia-build. De runtimes pdfium.dll en pdfium.v8.dll die met de component meekomen zijn Skia-builds, dus dit trof de standaarddeployment, niet een exotische
De mapping zag er ongevaarlijk uit toen ze werd geschreven. Op versie 2 of 3 wordt het veld nooit gelezen, dus prpDefault betekende werkelijk wat de DLL maar doet. Zodra BrotliEnabled (versie 6) of IsolatePerDocument (versie 7) in beeld kwam, maakte dezelfde code van geen voorkeur een expliciet AGG-verzoek. Niets faalde. PDFium initialiseerde normaal, rendert elke pagina en gaf geen foutcode terug, want vanuit zijn gezichtspunt had de aanroeper om AGG gevraagd en AGG gekregen
Een pixelhash maakt de verwisseling zichtbaar waar screenshots dat niet doen. De eerste pagina van hetzelfde document renderen onder drie configuraties gaf:
- Defaultconfiguratie: hash
502D77C3711B4ACF BrotliEnabled= True metRendereropprpDefaultgelaten: hashF75B5EB4728ADE87- Expliciete
prpAgg: hashF75B5EB4728ADE87, identiek aan de Brotli-run
De fix in v3.123.0 is de publieke functie PdfNativeRendererType, die een TPdfRendererPreference oplost naar de waarde die in m_RendererType wordt geschreven. prpAgg en prpSkia mappen één-op-één. prpDefault mapt nu op Skia wanneer de geladen DLL FPDF_RenderPageSkia exporteert en anders op AGG. Die export wordt onder dezelfde conditie PDF_USE_SKIA gecompileerd als de Skia-default zelf, waardoor hij de enige bouweigenschap is die u van buiten de DLL kunt waarnemen. Na de fix levert de Brotli-configuratie dezelfde hash op als de default
De font-backend had hetzelfde probleem nooit. m_FontLibraryType wordt vanaf versie 5 gelezen, en zijn nulwaarde, FPDF_FONTBACKENDTYPE_FREETYPE, is ook de default van PDFium wanneer het veld helemaal niet wordt gelezen. FreeType schrijven voor pfbpDefault reproduceert daardoor exact de native default. Nulwaarden zijn niet altijd fout, ze zijn alleen nooit automatisch goed
Met v3.123.0 of later doet de opstartcode die u van nature zou schrijven nu wat hij zegt:
uses
PDFium;
procedure ConfigurePdfiumAtStartup;
var
Config: TPdfLibraryConfiguration;
begin
// Moet draaien voordat iets de nativebibliotheek laadt
Config := TPdfLibraryConfiguration.Default;
Config.BrotliEnabled := True; // tilt FPDF_LIBRARY_CONFIG naar versie 6
// Renderer blijft prpDefault: resolved naar Skia op builds die
// FPDF_RenderPageSkia exporteren en naar AGG op alleen-AGG-builds
SetLength(Config.UserFontPaths, 1);
Config.UserFontPaths[0] := 'C:\ProgramData\MyApp\Fonts';
ConfigurePdfLibrary(Config);
end;
Denk eraan dat BrotliEnabled streams /BrotliDecode van PDF 2.0 alleen decodeerbaar maakt wanneer de DLL zelf met PDF_ENABLE_BROTLI is gebouwd. De vlag is een verzoek, en op een build zonder Brotli-ondersteuning heeft hij geen effect. TPdfLibraryConfiguration.Hardened is hetzelfde als Default behalve dat AllowMachineTime False is, wat document-JavaScript blokkeert in het lezen van de echte klok; dat is een redelijk startpunt voor server-side verwerking van onbetrouwbare bestanden
Wat gebeurt er wanneer u een backend vraagt die de DLL niet bevat?
PDFium geeft geen fout terug voor een renderer of font-backend die in de build ontbreekt: FPDF_InitLibraryWithConfig laat een native CHECK vallen, die op Windows aan de oppervlakte komt als een breakpoint-exception en, zonder structured exception handler rond de aanroep, het proces beëindigt. De header zegt dat letterlijk, met de waarschuwing dat een niet-ondersteunde waarde net zo faalt met een onmiddellijke crash
De twee concrete gevallen zijn een alleen-AGG-build die FPDF_RENDERERTYPE_SKIA krijgt, en een build zonder Fontations die FPDF_FONTBACKENDTYPE_FONTATIONS krijgt. De meegeleverde Skia-runtime zit in de tweede groep: hij rendert met Skia maar gebruikt FreeType voor fonts. prpSkia samen met pfbpFontations erop vragen leverde aan de Delphi-kant External exception 80000003 op. Vangt de debugger of een exception-handler dat toevallig op, de situatie blijft onherstelbaar:
- PDFium blijft half-geïnitialiseerd achter
- De procesbrede configuratie is al verzegeld, dus
ConfigurePdfLibraryweigert een gecorrigeerde configuratie - Opnieuw proberen met een andere configuratie in hetzelfde proces is niet meer mogelijk
Dit is de spiegel van de Brotli-bug. Daar bevatte het veld een waarde die niemand had gekozen en accepteerde PDFium haar stilletjes. Hier bevat het veld een waarde die de aanroeper bewust koos en accepteert PDFium er helemaal geen discussie over. Beide zijn problemen die een wrapper vóór de native aanroep moet oplossen, want erna is er niets meer om te vangen
Hoe PDFium Component Skia en Fontations vooraf controleert
Sinds v3.125.0 valideert LoadLibrary de configuratie nadat de DLL-exports zijn gebonden en voordat FPDF_InitLibraryWithConfig wordt aangeroepen, en maakt van een niet-ondersteunde renderer of font-backend een EPdfError met een melding die de foutieve instelling en de alternatieven noemt. De DLL wordt ontlaadt en de configuratie wordt ontzegeld, dus de aanroeper kan andere instellingen kiezen en opnieuw laden
De beslissing zelf woont in de pure functie PdfLibraryConfigurationSupportError, die de configuratie plus twee booleans neemt die de build beschrijven en een lege string teruggeeft wanneer de combinatie veilig is. Omdat hij geen native toestand raakt, kunt u hem vanuit uw eigen tests met elke capaciteitencombinatie aanroepen. Binnen LoadLibrary komen de twee booleans uit verschillende soorten bewijs, en ze verdienen verschillende mate van vertrouwen:
- Skia wordt gedetecteerd uit de aanwezigheid van de export
FPDF_RenderPageSkia, hetzelfde signaal datPdfNativeRendererTypegebruikt. De export en de Skia-renderer worden onder één conditie gecompileerd, dus de controle is exact - Fontations heeft geen eigen export. Het enige spoor dat hij achterlaat zijn de Rust-fontcrates die hij in de binary trekt, dus PDFium Component scant het geladen bibliotheekbestand op de cratenamen
skrifaenread-fonts(ookread_fonts). De scan draait alleen wanneerpfbpFontationswordt gevraagd, en een bestand dat niet kan worden gelezen telt als geen Fontations
De Fontations-controle is een heuristiek, en hij kan in één richting ernaast zitten: een Fontations-build waar elk van die strings is uitgeplukt zou worden geweigerd hoewel hij had kunnen werken. Die afweging is met opzet gemaakt. Een vals negatief kost u een exception die u kunt vangen en een fallback naar FreeType. Een vals positief kost u het proces
Ontzegelen telt net zo zwaar als de controle. LoadLibrary verzegelt de configuratie aan het allereerste begin van het laden, dus zonder de reset zou een capaciteitsweigering ConfigurePdfLibrary elke retry laten beantwoorden met EPdfError PDFium-bibliotheekconfiguratie is al verzegeld. Het weigerpad roept eerst UnloadLibrary aan; diens FPDF_DestroyLibrary-aanroep is op dat punt veilig omdat PDFium nog niet is geïnitialiseerd en direct terugkeert. Andere laadfalingen, zoals een ontbrekende DLL of een architectuurmismatch, houden de zegel vast, dus een retry-lus moet de twee uit elkaar houden:
uses
SysUtils, PDFium;
function StartPdfiumPreferringSkia: TPdfRendererPreference;
var
Config: TPdfLibraryConfiguration;
begin
Config := TPdfLibraryConfiguration.Default;
Config.Renderer := prpSkia;
ConfigurePdfLibrary(Config);
try
PDFium.LoadLibrary; // unit-gekwalificeerd: Windows.LoadLibrary heeft dezelfde naam
Result := prpSkia;
except
on E: EPdfError do
begin
// Een capaciteitsweigering ontlaadt de DLL en ontzegelt de configuratie.
// Een DLL die helemaal niet laadt blijft verzegeld: opnieuw proberen helpt niet
if PdfLibraryConfigurationSealed then
raise;
Config.Renderer := prpAgg;
ConfigurePdfLibrary(Config);
PDFium.LoadLibrary;
Result := prpAgg;
end;
end;
end;
Let op de expliciete PDFium.LoadLibrary. In een unit die ook Windows of Winapi.Windows gebruikt, lost een ongekwalificeerde LoadLibrary op naar welke unit het laatst in de uses-clausule staat; is dat de Win32-functie, dan compileert de parameterloze aanroep niet met een argumententelfout die niets over PDFium zegt
Validatie die nog eerder plaatsvindt
ConfigurePdfLibrary weigert sommige combinaties voordat er überhaupt een DLL aan te pas komt, allemaal met EPdfError. Een expliciete FontBackend, pfbpFreeType inbegrepen, vereist Renderer = prpSkia, want PDFium raadpleegt de font-backend alleen voor de Skia-renderer. IsolatePerDocument vereist dat V8Isolate nil is, want PDFium maakt zijn eigen isolate per document en laat een native CHECK vallen als u er ook nog één aanreikt. Lege strings in UserFontPaths worden geweigerd. En elke aanroep na de eerste laadpoging faalt met PDFium-bibliotheekconfiguratie is al verzegeld
Die laatste regel heeft een praktisch gevolg: u kunt de DLL niet eerst aftasten en daarna configureren. GetSkiaRenderCapabilities, V8FeaturesAvailable, een document openen en de meeste andere ingangspunten roepen intern LoadLibrary aan, wat de configuratie ter plekke verzegelt. Later UnloadLibrary aanroepen heropent haar ook niet. Eerst configureren, dan laden, dan vragen stellen, en dat is precies de volgorde die een diagnostische routine hoort te volgen:
uses
SysUtils, PDFium, FPdfView;
function DescribePdfiumState: string;
var
Config: TPdfLibraryConfiguration;
Renderer: string;
begin
Config := GetPdfLibraryConfiguration; // een kopie, veilig te inspecteren
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;
// Dezelfde resolutie die LoadLibrary toepaste bij het bouwen van 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;
Die regel één keer bij de opstart loggen is goedkoop, en het is het eerste dat u wilt in een supportticket dat zegt de tekst ziet er op de server anders uit. PDFium.Loaded is om dezelfde reden gekwalificeerd als LoadLibrary: binnen een methode van een form of component bindt een kale Loaded aan TComponent.Loaded
Twee manieren waarop een geversioneerde C-configuratiestructuur misgaat
Elke geversioneerde configuratiestructuur, of dat nu FPDF_LIBRARY_CONFIG is, een Win32-record met cbSize, of een plugin-ABI, faalt op twee symmetrische manieren, en een wrapper moet tegen beide waken. De eerste is een veld vullen terwijl de versie te laag blijft; de tweede is de versie tillen terwijl een veld op een nulwaarde blijft staan die de library als een bewuste keuze leest
- Veld gezet, versie te laag. Schrijf
m_BrotliEnabled= 1 in een structuur van versie 2 en PDFium kijkt er nooit naar. De aanroep slaagt en Brotli-streams blijven ondecodeerbaar. Het verweer is de versie af te leiden uit de velden die werkelijk in gebruik zijn, watLoadLibrarydoet, in plaats van er één hard te coderen - Versie hoog genoeg, nulveld betekent iets. Til de versie naar 6 en elk veld tot versie 6 is nu actief.
FillCharzetm_RendererTypeop nul, dusFPDF_RENDERERTYPE_AGG, een echte renderer, geen niet-ingesteld. Het verweer is elk veld dat de gekozen versie dekt met een bewuste waarde te vullen, en default te resolven tegen de werkelijke build in plaats van haar aan te nemen
Voor waarden die de aangesprokene kunnen laten crashen volgt een derde regel: valideer ze vóór de aanroep tegen wat de binary aankan, met het sterkste beschikbare bewijs, en wees eerlijk in de code en de documentatie wanneer dat bewijs een heuristiek is. Een geëxporteerd symbool is bewijs. Een cratenaam in een stringtabel is een goede gok
Snelnaslag: de libraryconfiguratie van PDFium Component
- Roep
ConfigurePdfLibraryéén keer aan, voordat iets de DLL laadt; elke capaciteitsvraag of documentload verzegelt haar - Upgrade naar v3.123.0 of later als u
BrotliEnabledofIsolatePerDocumentzet en Skia-output verwacht van de meegeleverde runtimes - Laat
RendereropprpDefaultstaan tenzij u een specifieke rasterizer nodig heeft; hij resolve nu op elke structuurversie naar de build-default - Gebruik
PdfNativeRendererTypemetGetSkiaRenderCapabilities.PageRenderom te loggen welke renderer werkelijk actief is - Verwacht een
EPdfError, geen crash, voorprpSkiaop een alleen-AGG-DLL ofpfbpFontationsop een DLL zonder Fontations in v3.125.0 of later - Na een capaciteitsweigering is
PdfLibraryConfigurationSealedFalse en mag u herconfigureren; na een mislukte DLL-load blijft hij True - Behandel Fontations-detectie als heuristiek en houd een FreeType-fallback over
- Schrijf
PDFium.LoadLibraryenPDFium.Loadedmet de unitnaam om naamclashes met Win32 enTComponentte vermijden
Faalt de DLL voordat configuratie überhaupt ertoe doet, begin dan bij PDFium-DLL-laadfalingen in Delphi diagnosticeren, en voor hoe de component op elk platform de juiste binary vindt zie de PDFium-nativebibliotheek laden op elk doelplatform. Zodra de renderer geregeld is, behandelt render cache- en vloeiende zoom-tactieken hoe u paginarendering snel houdt in een viewer
PDFium Component wikkelt de PDFium-engine voor Delphi en C++Builder met dit soort configuratiecontroles, dus native-initialisatie faalt als een Pascal-exception die u kunt afhandelen in plaats van als een proceseinde. Productdetails en downloads staan op de productpagina PDFium Component for Delphi