PDFium Component frembringer Fast Web View (lineariserede) PDF'er fra Delphi gennem TPdf.SaveAsLinearized. Metoden gemmer det aktuelle dokument med PDFium og omskriver derefter resultatet efter ISO 32000-1 Annex F: første-side-objekter flyttes forrest, to cross-reference-sektioner skrives, og side-offset- og shared object-hint-tabeller fortæller en viser, hvilke byte-områder der skal hentes for en vilkårlig side
Grunden til, at nogen overhovedet bryder sig, er netværket. En 40 MB katalog serveret fra object storage eller en dokumentportal bør vise side ét efter de første få kilobytes, og at hoppe til side 380 bør koste et par HTTP Range-requests, ikke en fuld download. Visere kan kun det, når filen er lineariseret. De fleste PDF'er, der forlader en redigeringspipeline, er det ikke, for den naturlige måde at skrive en PDF på lægger cross-reference-tabellen til sidst og strør side-objekter ud, hvor writeren tilfældigt sendte dem
Hvorfor kan PDFium læse lineariserede filer, men ikke skrive dem?
PDFium har en komplet reader til linearisering og ingen writer til den. FPDFAvail_* availability-API'et opdager lineariserings-dictionaryen, afkoder hint-tabeller og beder værten om præcis de byte-områder, den behøver, hvilket er det, progressive download- og annullerings-workflowet i PDFium Component er bygget på. Den offentlige save-ABI tilbyder derimod kun en almindelig kopi-gem og en gem med en mål-PDF-version. Intet i den omordner objekter eller udsender hint-streams
PDFium Component lukker det hul uden at gen-implementere, hvad PDFium allerede gør godt. TPdf.SaveAsLinearized kalder først den eksisterende SaveAs, så PDFium stadig ejer den semantiske gem: redigerede sider, formularfelt-tilstand og versionshåndtering går alle gennem den kodevej, der er blevet motioneret i årevis. Komponenten kører derefter et Annex F-restruktureringsgennemløb over den stabile mellem-PDF og skriver det lineariserede resultat til ens mål. At splitte arbejdet sådan betyder, at linearisering aldrig behøver at forstå en annotation eller en formular, kun objekter, referencer og byte-offsets
At kalde TPdf.SaveAsLinearized fra Delphi
TPdf.SaveAsLinearized har to overloads, én til en TStream og én til et filnavn, og begge tager de samme valgfrie Option: TSaveOption- og PdfVersion: TPdfVersion-parametre som SaveAs. Begge returnerer True ved succes. Fil-overloaden skriver til en midlertidig fil i målbiblioteket og erstatter destinationen atomarisk, så en fejlet gem aldrig efterlader en halvfærdig PDF, hvor en god plejede at være
var
Pdf: TPdf;
begin
Pdf := TPdf.Create(nil);
try
Pdf.FileName := 'catalog-2026.pdf';
Pdf.Active := True;
// ... rediger sider, udfyld formularer, stempel, hvad pipelinen nu gør ...
if not Pdf.SaveAsLinearized('catalog-2026-web.pdf', saNoIncremental, pv17) then
raise Exception.Create('Linearized save failed');
finally
Pdf.Free;
end;
end;
Parameterreglerne er strenge med vilje. saIncremental rejser en fejl, for en inkrementel opdatering tilføjer en ny revision til slutningen af filen og ville straks ødelægge det foran-læssede layout. En målversion under PDF 1.2 afvises også; pvUnknown beholder source-versionen. Krypteret input behøver saRemoveSecurity, da writeren omnummererer objekter, og PDFs objektniveau-krypteringsnøgler er afledt af objektnummeret. Og fordi linearisering er en fuld omskrivning, bliver enhver eksisterende digital signatur ugyldig. Skal man levere signeret og lineariseret, så lineariser først og signér bagefter
Hvordan writeren grupperer objekter til Fast Web View
Linearisering er ikke blot at flytte side ét til toppen af filen. Writeren parser den aktuelle revision, uanset om dens cross-reference-data er en klassisk tabel, en hybridfil eller en cross-reference-stream, og ekspanderer hvert medlem af en aktiv object stream til et selvstændigt objekt. Den bygger så en referensgraf og sorterer objekter i fem grupper: dokumentniveau-objekter, der behøves for at åbne filen, første-side-objekter, objekter private for én senere side, objekter delt af flere senere sider og alt andet
Fælden i det grafgennemløb er /Parent. Hver side-dictionary peger op ad sidetræet, og en naiv traversal følger den pointer til den overordnede knude og så ned gennem /Kids ind i hver søsterside og dens content streams. Ét trin senere indeholder første-side-gruppen hele dokumentet, og filen er teknisk lineariseret men ubrugelig til range-først-adgang. PDFium Component ignorerer side-/Parent-linket og sidetræ-knuderne uden for den side, der indsamles, hvilket er det, der holder første-side-sektionen lille. På et 51-siders regressionssample med et dybt sidetræ er første-side-sektionen 5289 bytes, 0,48% af de 1105354 bytes output
Object streams og cross-reference-streams i input fladtrykkes til almindelige generation 0 indirekte objekter, hver reference omskrives til de nye numre, og outputtet bruger klassiske cross-reference-sektioner. Det koster noget filstørrelse sammenlignet med komprimerede object streams, men det forenkler hint-tabel-dækning og er det layout, enhver lineariserings-bevidst viser håndterer. Betyder kompakthed mere end Fast Web View for et givet output, har PDFium Component en separat object stream-writer til det job
Hvad kommer i lineariserings-dictionaryen og de to xref-sektioner?
En lineariseret fil skrevet af PDFium Component bærer to klassiske cross-reference-sektioner og en lineariserings-dictionary i selveste første objekt. Den første xref-sektion følger lineariserings-dictionaryen og dækker dokumentniveau- og første-side-objekterne, så en viser kan rendere side ét alene ud fra filens forside. Hoved-xref-sektionen sidder til sidst og dækker alt andet
Lineariserings-dictionaryen selv holder de tal, en viser behøver, før den læser noget som helst: /L fillængden, /H hint-stream-offset og -længde, /O første side-objektnummer, /E enden på første-side-sektionen, /N sideantallet og /T offset af den første post i hoved-xref-tabellen. Alle afhænger af det endelige layout, og dictionaryen sidder før layoutet. At skrive den med naturlig-bredde tal skaber en løkke: en længde, der vokser fra 999999 til 1000000, tilføjer en byte, forskubber hver efterfølgende offset, hvilket kan ændre et andet tals bredde, og så videre
PDFium Component bryder løkken ved at reservere faste 320 bytes til lineariserings-dictionaryen. Hvert objekt, hint-streamen, begge xref-sektioner og trailerne skrives først; når alle offsets er endelige, fyldes dictionaryen på plads og pad'es til den reserverede bredde. Én subtil detalje er /T: den skal pege på objekt 0-posten i hoved-xref-tabellen, ikke på xref-nøgleordet foran den. Strenge validatorer flagger nøgleords-offset, selvom tilgivende visere åbner filen alligevel
Hvorfor ignorerer hint-tabel-offsets hint-streamen?
Hint-tabel-offsets i en lineariseret PDF fortolkes, som om hint-streamen ikke fandtes i filen. Annex F definerer det sådan, så hint-streamen kan sidde mellem første-side-sektionen og resten uden at sin egen størrelse føder tilbage i tallene, den indeholder. Den praktiske konsekvens for en writer er let at få forkert: enhver reel offset, der ligger efter hint-streamen, skal reduceres, før den kommer i side-offset- eller shared object-hint-tabellen
Spørgsmålet er, med hvor meget den reduceres. Det fristende svar er stream-dataenes længde, for det er den del, der føles som hint-indholdet. Det korrekte svar er længden af hele det hint-indirekte objekt, inklusive N 0 obj, stream-dictionaryen, stream- og endstream-nøgleordene og endobj. Træk kun datalængden fra, og hver senere offset er ved siden af et par dusin bytes. Side ét renderer stadig, for den bor før hint-streamen, så bug'en overlever et hurtigt visuelt tjek og dukker kun op, når en viser hopper til en senere side og anmoder om et range, der starter midt i det forkerte objekt
Begge hint-tabeller er bit-pakkede med den mest signifikante bit først, igen efter Annex F. Komponenten verificerer resultatet, som en rigtig klient ville: et sparsomt range-lager leverer kun de første 1 KiB af 51-siders samplet, og PDFiums native hint-håndtering anmoder om resten. Den sidste side åbner uden, at hele filen nogensinde er til stede
At verificere en lineariseret PDF i Delphi
Det billigste tjek er at spørge PDFium selv. TPdf.InspectAvailability kører availability-API'et mod en stream uden at indlæse dokumentet, og dets Linearization-felt rapporterer plnLinearized, plnNotLinearized eller plnUnknown
function IsFastWebView(const FileName: string): Boolean;
var
Stream: TFileStream;
Info: TPdfAvailability;
begin
Stream := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite);
try
Info := TPdf.InspectAvailability(Stream);
Result := Info.Linearization = plnLinearized;
finally
Stream.Free;
end;
end;
Det bekræfter, at PDFium genkender layoutet; det beviser ikke, at hintene er korrekte. Til en ægte end-to-end-test, server filen gennem en range-begrænset transport og åbn en sen side med TPdfProgressiveDocument, på samme måde som guiden til streaming af store PDF'er on demand med PDFium VCL gør. Renderer siden efter kun at have hentet en lille brøkdel af filen, gør hint-tabellerne deres job. Det er også det værd at tilføje en anden uafhængig lineariserings-tjekker til release-pipelinen, for visere adskiller sig i, hvor tilgivende de er ved en fejlplaceret /T eller en let forkert offset
To grænser at huske. Object materializeren afviser stadig arrays på 2 GiB eller mere, så ekstremt store dokumenter behøver en anden strategi for nu. Og linearisering betaler sig kun, når serveren ærer HTTP Range-requests; bag en proxy, der stripper Accept-Ranges, downloades en lineariseret fil præcis som enhver anden
FAQ
Holder TPdf.SaveAsLinearized digitale signaturer gyldige?
Nej. TPdf.SaveAsLinearized omnummererer og omskriver hvert objekt, så eksisterende signaturer gøres ugyldige. Lineariser først, og påfør så signaturen
Kan PDFium Component linearisere en krypteret PDF?
Kun når man giver saRemoveSecurity med. Objektniveau-krypteringsnøgler afhænger af objektnumre, som writeren ændrer, så outputtet skrives uden kryptering
Hvilke PDF-versioner understøtter lineariseret output?
TPdf.SaveAsLinearized accepterer pvUnknown for at beholde source-versionen eller ethvert mål fra PDF 1.2 og op, og afviser saIncremental, for en tilføjet revision ville bryde layoutet
Fast Web View-output, progressiv indlæsning og availability-API'et er alle en del af PDFium Component til Delphi og C++Builder, som wrapper PDFium til Delphi-, C++Builder- og Lazarus-projekter