Teknisk artikkel

PDFium bibliotekskonfig: Når Brotli stille bytter til AGG

I PDFium Component for Delphi førte det å slå på BrotliEnabled eller IsolatePerDocument i TPdfLibraryConfiguration til at det medfølgende Skia-bygget ble byttet til AGG-rendereren uten noen feil, fordi begge alternativene løfter FPDF_LIBRARY_CONFIG til en versjon der PDFium leser m_RendererType bokstavelig. Siden v3.123.0 forblir standard-rendereren DLL-ens eget standardvalg, og siden v3.125.0 reiser en Skia- eller Fontations-forespørsel DLL-en ikke kan innfri, en fangbar EPdfError i stedet for å drepe prosessen

Ingen av buggene annonserte seg selv. Den første produserte sider som så fine ut, bare renderet av en annen rasterizer, med litt annerledes anti-aliasing og tekstkanter enn bygget du sendte ut og testet. Den andre annonserte seg selv, høylytt, ved å ta vertsprosessen ned fra inni native initialisering. Begge kommer fra samme sted: en versjonert C-struktur hvis felt bare teller når versjonstallet sier det, og hvis nullverdier ikke er «uindsatt», men reelle valg

Hvordan avgjør FPDF_LIBRARY_CONFIG hvilken renderer PDFium bruker?

FPDF_InitLibraryWithConfig konsulterer m_RendererType bare når strukturens Version-felt er 4 eller høyere, og fra den versjonen bruker den verdien nøyaktig slik den er skrevet. Under versjon 4 ignorerer PDFium feltet og velger byggets standard, som er Skia i bygg kompilert med PDF_USE_SKIA og AGG overalt ellers

Hvert senere felt følger samme mønster. Strukturen vokste én kapabilitet om gangen, og hver kapabilitet ankom sammen med et nytt versjonstall. PDFium Component bygger den native strukturen i LoadLibrary fra din TPdfLibraryConfiguration og løfter versjonen bare så langt som alternativene du har satt, krever

StrukturversjonFeltet den legger tilSatt av
2m_pIsolate, m_v8EmbedderSlotAlltid skrevet; V8Isolate, V8EmbedderSlot
3m_pPlatformV8Platform ikke nil
4m_RendererTypeRenderer annet enn prpDefault
5m_FontLibraryTypeFontBackend annet enn pfbpDefault
6m_BrotliEnabledBrotliEnabled = True
7m_IsolatePerDocumentIsolatePerDocument = True

Fellen ligger i de to siste radene. Versjoner er kumulative: en versjon 6-struktur er også en versjon 4- og en versjon 5-struktur, så PDFium leser m_RendererType og m_FontLibraryType selv om du bare spurte om Brotli. Hva enn som ligger i de to feltene i det øyeblikket, blir rendereren og font-backend-en, enten du mente å velge dem eller ikke

PDFium Component FPDF_LIBRARY_CONFIG versjonsstige fra versjon 2 til versjon 7 som viser hvilket TPdfLibraryConfiguration-alternativ som legger til m_RendererType, m_FontLibraryType, m_BrotliEnabled og m_IsolatePerDocument, og hvorfor kumulative versjoner gjør et nullstilt renderer-felt til et bevisst AGG-valg snarere enn en uindsatt verdi på ethvert bygg
Hvert alternativ løfter strukturversjonen og hvert tidligere felt forblir levende, så nullen i m_RendererType ankommer PDFium som en eksplisitt AGG-forespørsel

Hvorfor slo på av Brotli rendereren over til AGG?

Før v3.123.0 skrev PDFium Component FPDF_RENDERERTYPE_AGG inn i m_RendererType for prpDefault, så enhver konfigurasjon som skjøv strukturen til versjon 6 eller 7, tvang AGG på et Skia-bygg. pdfium.dll- og pdfium.v8.dll-kjøretidene som følger med komponenten, er Skia-bygg, så dette rammet standarddistribusjonen, ikke noen eksotisk en

Mappingen så harmløs ut da den ble skrevet. På versjon 2 eller 3 leses feltet aldri, så prpDefault betydde virkelig «hva DLL-en nå gjør». I det øyeblikket BrotliEnabled (versjon 6) eller IsolatePerDocument (versjon 7) kom inn i bildet, gjorde samme kode «ingen preferanse» om til en eksplisitt AGG-forespørsel. Ingenting feilet. PDFium initialiserte normalt, renderet hver side, og returnerte ingen feilkode, fordi fra dens synspunkt hadde kalleren bedt om AGG og fått AGG

En piksel-hash gjør byttet synlig der skjermbilder ikke gjør det. Å rendere første side av samme prøvedokument under tre konfigurasjoner ga:

  • Standardkonfigurasjon: hash 502D77C3711B4ACF
  • BrotliEnabled = True med Renderer igjen på prpDefault: hash F75B5EB4728ADE87
  • Eksplisitt prpAgg: hash F75B5EB4728ADE87, identisk med Brotli-kjøringen

Fiksen i v3.123.0 er den offentlige funksjonen PdfNativeRendererType, som løser en TPdfRendererPreference til verdien skrevet inn i m_RendererType. prpAgg og prpSkia mappes én til én. prpDefault mappes nå til Skia når den lastede DLL-en eksporterer FPDF_RenderPageSkia og til AGG ellers. Den eksporten er kompilert under samme PDF_USE_SKIA-betingelse som Skia-standarden selv, noe som gjør den til den eneste byggeegenskapen du kan observere utenfra DLL-en. Etter fiksen produserer Brotli-konfigurasjonen samme hash som standarden

PDFium Component piksel-hash-sammenligning som viser standard Skia-render-hash 502D77C3711B4ACF, konfigurasjonen med BrotliEnabled før v3.123.0 som matchet en eksplisitt prpAgg-kjøring med hash F75B5EB4728ADE87, og den rettede wrapperen som løser prpDefault gjennom FPDF_RenderPageSkia-eksporten tilbake til den opprinnelige Skia-hashen
En piksel-hash fanger det skjermbilder skjuler: å slå på Brotli renderet hver side med AGG, og den rettede standarden matcher nå den urørte konfigurasjonen

Font-backend-en hadde aldri samme problem. m_FontLibraryType leses fra versjon 5 og oppover, og nullverdien dens, FPDF_FONTBACKENDTYPE_FREETYPE, er også PDFiums standard når feltet ikke leses i det hele tatt. Å skrive FreeType for pfbpDefault gjenskaper derfor den native standarden nøyaktig. Nullverdier er ikke alltid feil, de er bare aldri automatisk riktige

Med v3.123.0 eller senere gjør oppsettskoden du naturlig ville skrevet, nå det den sier:

uses
  PDFium;

procedure ConfigurePdfiumAtStartup;
var
  Config: TPdfLibraryConfiguration;
begin
  // Må kjøre før noe laster det native biblioteket
  Config := TPdfLibraryConfiguration.Default;
  Config.BrotliEnabled := True;   // løfter FPDF_LIBRARY_CONFIG til versjon 6
  // Renderer forblir prpDefault: løses til Skia på bygg som eksporterer
  // FPDF_RenderPageSkia og til AGG på AGG-bare bygg
  SetLength(Config.UserFontPaths, 1);
  Config.UserFontPaths[0] := 'C:\ProgramData\MyApp\Fonts';
  ConfigurePdfLibrary(Config);
end;

Husk at BrotliEnabled bare gjør PDF 2.0 /BrotliDecode-strømmer dekodbare når DLL-en selv er bygget med PDF_ENABLE_BROTLI. Flaggget er en forespørsel, og på et bygg uten Brotli-støtte har det ingen effekt. TPdfLibraryConfiguration.Hardened er det samme som Default bortsett fra at AllowMachineTime er False, noe som blokkerer dokument-JavaScript fra å lese den virkelige klokken; det er et fornuftig utgangspunkt for server-side behandling av utruelige filer

Hva skjer når du ber om en backend DLL-en ikke inneholder?

PDFium returnerer ingen feil for en renderer- eller font-backend som mangler i bygget: FPDF_InitLibraryWithConfig feiler en native CHECK, som på Windows kommer til syne som et breakpoint-unntak og, uten en strukturert unntakshåndterer rundt kallet, terminerer prosessen. Hodet sier det selv, med en advarsel om at en ikke-støttet verdi «vil på samme måte feile med en umiddelbar krasj»

De to konkrete tilfellene er et AGG-bare bygg som mottar FPDF_RENDERERTYPE_SKIA, og et bygg uten Fontations som mottar FPDF_FONTBACKENDTYPE_FONTATIONS. Den medfølgende Skia-kjøretiden er i den andre gruppen: den renderer med Skia men bruker FreeType for fonter. Å be om prpSkia sammen med pfbpFontations mot den ga External exception 80000003 på Delphi-siden. Når feilsøkeren eller en unntakshåndterer tilfeldigvis fanger det, er situasjonen fortsatt umulig å redde:

  • PDFium er etterlatt halvinitialisert
  • Konfigurasjonen på prosessnivå er allerede forseglet, så ConfigurePdfLibrary nekter en korrigert konfigurasjon
  • Å prøve igjen med en annen konfigurasjon i samme prosess er ikke lenger mulig

Dette er det motsatte feilbildet av Brotli-buggen. Der holdt feltet en verdi ingen hadde valgt, og PDFium godtok den stille. Her holder feltet en verdi kalleren valgte bevisst, og PDFium diskuterer ingenting om den i det hele tatt. Begge er problemer en wrapper må løse før det native kallet, for etter det finnes det ingenting igjen å fange

Hvordan PDFium Component forhåndssjekker Skia og Fontations

Siden v3.125.0 validerer LoadLibrary konfigurasjonen etter binding av DLL-eksportene og før kallet til FPDF_InitLibraryWithConfig, og gjør en ikke-støttet renderer eller font-backend til en EPdfError med en melding som navngir den klandreverdige innstillingen og alternativene. DLL-en losses, og konfigurasjonen forsegles opp, så kalleren kan velge andre innstillinger og laste igjen

Beslutningen selv bor i den rene funksjonen PdfLibraryConfigurationSupportError, som tar konfigurasjonen pluss to booleans som beskriver bygget og returnerer en tom streng når kombinasjonen er trygg. Etter som den rører ingen native tilstand, kan du kalle den fra dine egne tester med enhver kapabilitetskombinasjon. Inne i LoadLibrary kommer de to booleanene fra ulike typer bevis, og de fortjener ulike tillitsnivåer:

  • Skia oppdages fra tilstedeværelsen av FPDF_RenderPageSkia-eksporten, samme signal PdfNativeRendererType bruker. Eksporten og Skia-rendereren er kompilert under én betingelse, så sjekken er eksakt
  • Fontations har ingen egen eksport. Eneste spor det etterlater, er Rust-font-cratene det drar inn i binærfilen, så PDFium Component skanner den lastede bibliotekfilen etter cratenavnene skrifa og read-fonts (også read_fonts). Skanningen kjører bare når pfbpFontations er forespurt, og en fil som ikke kan leses, teller som «ingen Fontations»

Fontations-sjekken er en heuristikk, og den kan ta feil i én retning: et Fontations-bygg strippet for hver av de strengene, ville blitt avvist selv om det ville fungert. Den avveiningen ble gjort med vilje. En falsk avvisning koster deg et unntak du kan fange og en fallback til FreeType. En falsk aksept koster deg prosessen

Å forsegles opp igjen betyr like mye som sjekken. LoadLibrary forsegler konfigurasjonen helt i starten av lastingen, så uten tilbakestillingen ville en kapabilitetsavvisning etterlate ConfigurePdfLibrary som svarer ethvert nytt forsøk med EPdfError «PDFium library configuration is already sealed». Avvisningsstien kaller UnloadLibrary først; dens FPDF_DestroyLibrary-kall er trygt på det punktet fordi PDFium ikke er initialisert ennå og returnerer umiddelbart. Andre lastefeil, som en manglende DLL eller en arkitekturmismatch, beholder forseglingen, så en retry-løkke må skille de to fra hverandre:

uses
  SysUtils, PDFium;

function StartPdfiumPreferringSkia: TPdfRendererPreference;
var
  Config: TPdfLibraryConfiguration;
begin
  Config := TPdfLibraryConfiguration.Default;
  Config.Renderer := prpSkia;
  ConfigurePdfLibrary(Config);
  try
    PDFium.LoadLibrary;   // enhetskvalifisert: Windows.LoadLibrary heter det samme
    Result := prpSkia;
  except
    on E: EPdfError do
    begin
      // En kapabilitetsavvisning losser DLL-en og forsegler opp konfigurasjonen.
      // En DLL som ikke lastet i det hele tatt, forblir forseglet: å prøve igjen hjelper ikke
      if PdfLibraryConfigurationSealed then
        raise;
      Config.Renderer := prpAgg;
      ConfigurePdfLibrary(Config);
      PDFium.LoadLibrary;
      Result := prpAgg;
    end;
  end;
end;

Merk den eksplisitte PDFium.LoadLibrary. I en enhet som også bruker Windows eller Winapi.Windows, løses et ukvalifisert LoadLibrary til den enheten som kommer sist i uses-klausulen; er det Win32-funksjonen, feiler det parameterløse kallet å kompilere med en argumentantallsfeil som ikke sier noe om PDFium

PDFium Component LoadLibrary forhåndssjekk-flyt der ConfigurePdfLibrary forsegler konfigurasjonen, kapabilitetssjekken tester FPDF_RenderPageSkia-eksporten og skrifa-strengbevis, en ikke-støttet forespørsel reiser en fangbar EPdfError og forsegler opp for nytt forsøk, mens en DLL som aldri laster holder PdfLibraryConfigurationSealed true
Valideringen kjører etter at eksportene binder og før initialisering, så en manglende backend feiler som en EPdfError du kan fange i stedet for en native CHECK som dreper prosessen

Validering som skjer enda tidligere

ConfigurePdfLibrary avviser noen kombinasjoner før noen DLL er involvert, alle med EPdfError. En eksplisitt FontBackend, inkludert pfbpFreeType, krever Renderer = prpSkia, fordi PDFium bare konsulterer font-backend-en for Skia-rendereren. IsolatePerDocument krever at V8Isolate er nil, siden PDFium lager sin egen isolate per dokument og feiler en native CHECK hvis du også hånder den én. Tomme strenger i UserFontPaths avvises. Og ethvert kall etter det første lasteforsøket feiler med «PDFium library configuration is already sealed»

Den siste regelen har en praktisk konsekvens: du kan ikke sonde DLL-en først og konfigurere den etterpå. GetSkiaRenderCapabilities, V8FeaturesAvailable, å åpne et dokument, og de fleste andre inngangspunkt kaller LoadLibrary internt, noe som forsegler konfigurasjonen på stedet. Å kalle UnloadLibrary senere åpner den heller ikke igjen. Konfigurer først, last så, og still så spørsmål, noe som er nøyaktig rekkefølgen en diagnostisk rutine bør følge:

uses
  SysUtils, PDFium, FPdfView;

function DescribePdfiumState: string;
var
  Config: TPdfLibraryConfiguration;
  Renderer: string;
begin
  Config := GetPdfLibraryConfiguration;   // en kopi, trygg å inspisere
  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 løsning LoadLibrary brukte da den bygde 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;

Å logge den linjen én gang ved oppstart er billig, og det er det første du vil ha i en støttesak som sier «teksten ser annerledes ut på serveren». PDFium.Loaded er kvalifisert av samme grunn som LoadLibrary: inne i en metode på et skjema eller en komponent, binder en bar Loaded til TComponent.Loaded

To måter en versjonert C-konfigurasjonsstruktur går galt på

Enhver versjonert konfigurasjonsstruktur, enten det er FPDF_LIBRARY_CONFIG, en Win32 cbSize-post, eller en plugin-ABI, feiler på to symmetriske måter, og en wrapper må vokte seg mot begge. Den første er å fylle et felt mens versjonen er for lav; den andre er å løfte versjonen mens et felt står på en nullverdi biblioteket leser som et bevisst valg

  1. Felt satt, versjon for lav. Skriv m_BrotliEnabled = 1 inn i en versjon 2-struktur, og PDFium ser aldri på den. Kallet lykkes, og Brotli-strømmer forblir udekbare. Forsvaret er å utlede versjonen fra feltene faktisk i bruk, noe som er det LoadLibrary gjør, snarere enn å hardkode én
  2. Versjon høy nok, nullfelt betyr noe. Løft versjonen til 6, og hvert felt opp til versjon 6 er nå levende. FillChar nullstiller m_RendererType til FPDF_RENDERERTYPE_AGG, noe som er en ekte renderer, ikke «uindsatt». Forsvaret er å skrive hvert felt den valgte versjonen dekker, med en bevisst verdi, og å løse «standard» mot det faktiske bygget i stedet for å anta det

En tredje regel følger for verdier som kan krasje den kalte: valider dem mot det binærfilen kan gjøre før kallet, med det sterkeste tilgjengelige beviset, og vær ærlig i koden og dokumentasjonen når det beviset er en heuristikk. Et eksportert symbol er et bevis. Et cratenavn i en strengtabell er en god gjetning

Hurtigreferanse: PDFium Component bibliotekskonfigurasjon

  • Kall ConfigurePdfLibrary én gang, før noe laster DLL-en; enhver kapabilitetsforespørsel eller dokumentlasting forsegler den
  • Oppgrader til v3.123.0 eller senere hvis du setter BrotliEnabled eller IsolatePerDocument og forventer Skia-utdata fra de medfølgende kjøretidene
  • La Renderer stå på prpDefault med mindre du trenger en spesifikk rasterizer; den løses nå til byggets standard på enhver strukturversjon
  • Bruk PdfNativeRendererType med GetSkiaRenderCapabilities.PageRender for å logge hvilken renderer som faktisk er aktiv
  • Forvent EPdfError, ikke en krasj, for prpSkia på en AGG-bare DLL eller pfbpFontations på en ikke-Fontations DLL i v3.125.0 eller senere
  • Etter en kapabilitetsavvisning er PdfLibraryConfigurationSealed False og du kan konfigurere på nytt; etter en feilet DLL-lasting forblir den True
  • Behandle Fontations-deteksjonen som heuristisk og ha en FreeType-fallback
  • Skriv PDFium.LoadLibrary og PDFium.Loaded med enhetsnavnet for å unngå navnekollisjoner med Win32 og TComponent

Feiler DLL-en før konfigurasjonen i det hele tatt betyr noe, start med diagnose av PDFium DLL-lastefeil i Delphi, og for hvordan komponenten finner riktig binærfil på hver plattform, se lasting av PDFium native bibliotek på ethvert mål. Når rendereren er avgjort, dekker render-cache og smidig zoom-taktikk hvordan du holder siderenderingen rask i en viser

PDFium Component wrapper PDFium-motoren for Delphi og C++Builder med konfigurasjonssjekker som disse, slik at native initialisering feiler som et Pascal-unntak du kan håndtere i stedet for en prosessavslutning. Produktdetaljer og nedlastinger finner du på produktsiden for PDFium Component for Delphi