PDFium Component produceert Fast Web View-PDF's (geliniseerde PDF's) vanuit Delphi via TPdf.SaveAsLinearized. De methode slaat het huidige document op met PDFium en herschrijft daarna het resultaat volgens ISO 32000-1 Annex F: objecten van de eerste pagina schuiven naar voren, er worden twee cross-reference-secties geschreven, en page-offset- en shared-object-hint tables vertellen een viewer welke bytebereiken hij voor een willekeurige pagina moet ophalen
De reden dat iemand hierom geeft is het netwerk. Een catalogus van 40 MB vanuit objectopslag of een documentportaal zou pagina één moeten tonen na de eerste paar kilobytes, en naar pagina 380 springen zou een paar HTTP Range-verzoeken moeten kosten, geen volledige download. Viewers kunnen dat alleen wanneer het bestand geliniseerd is. De meeste PDF's die een bewerkingspijplijn verlaten zijn dat niet, want de natuurlijke manier om een PDF te schrijven zet de cross-reference-tabel achteraan en strooit paginaobjecten waar de schrijver ze toevallig uitzond
Waarom kan PDFium geliniseerde bestanden lezen maar niet schrijven?
PDFium heeft een volledige lezer voor linearisatie en geen schrijver ervoor. De availability-API FPDFAvail_* detecteert het linearisatie-woordenboek, decodeert hint tables en vraagt de host om precies de bytebereiken die hij nodig heeft — daarop is de progressive download- en annuleringsworkflow in PDFium Component gebouwd. De publieke save-ABI daarentegen biedt alleen een kale kopie-opslag en een opslag met een PDF-doelversie. Niets daarin herordent objecten of zendt hint-streams uit
PDFium Component dicht dat gat zonder te her-implementeren wat PDFium al goed doet. TPdf.SaveAsLinearized roept eerst de bestaande SaveAs aan, dus PDFium blijft eigenaar van de semantische opslag: bewerkte pagina's, formulierveldstatus en versieafhandeling lopen allemaal door het codepad dat al jaren wordt belopen. Daarna voert de component een Annex F-restructureerpassage uit over die stabiele tussenliggende PDF en schrijft het geliniseerde resultaat naar uw doel. Het werk zo opsplitsen betekent dat linearisatie nooit een annotatie of formulier hoeft te begrijpen, alleen objecten, verwijzingen en byte-offsets
TPdf.SaveAsLinearized aanroepen vanuit Delphi
TPdf.SaveAsLinearized heeft twee overloads, één voor een TStream en één voor een bestandsnaam, en beide nemen dezelfde optionele parameters Option: TSaveOption en PdfVersion: TPdfVersion als SaveAs. Beide geven True terug bij succes. De bestandsoverload schrijft naar een tijdelijk bestand in de doelmap en vervangt de bestemming atomair, dus een mislukte opslag laat nooit een half geschreven PDF achter waar eerst een goede stond
var
Pdf: TPdf;
begin
Pdf := TPdf.Create(nil);
try
Pdf.FileName := 'catalog-2026.pdf';
Pdf.Active := True;
// ... pagina's bewerken, formulieren vullen, stempelen, wat de pijplijn ook doet ...
if not Pdf.SaveAsLinearized('catalog-2026-web.pdf', saNoIncremental, pv17) then
raise Exception.Create('Linearized save failed');
finally
Pdf.Free;
end;
end;
De parameterregels zijn met opzet streng. saIncremental geeft een fout, want een incrementele update plakt een nieuwe revisie achteraan het bestand en zou de voorgeladen layout meteen verwoesten. Een doelversie onder PDF 1.2 wordt ook geweigerd; pvUnknown houdt de bronversie aan. Versleutelde invoer heeft saRemoveSecurity nodig, want de schrijver hernummert objecten en de objectniveau-versleutelingssleutels van PDF zijn afgeleid van het objectnummer. En omdat linearisatie een volledige herschrijving is, wordt elke bestaande digitale handtekening ongeldig. Moet u ondertekend én geliniseerd opleveren, lineariseer dan eerst en onderteken daarna
Hoe de schrijver objecten groepeert voor Fast Web View
Linearisatie is niet zomaar pagina één bovenaan het bestand zetten. De schrijver parst de huidige revisie — of diens cross-reference-data nu een klassieke tabel, een hybride bestand of een cross-reference-stream is — en puilt elk lid van een actieve objectstream uit tot een zelfstandig object. Daarna bouwt hij een referentiegrafiek en sorteert objecten in vijf groepen: documentniveau-objecten die nodig zijn om het bestand te openen, objecten van de eerste pagina, objecten die privé zijn aan één latere pagina, objecten die door meerdere latere pagina's worden gedeeld, en al het overige
De valkuil in die grafiekwandeling is /Parent. Elk paginawoordenboek wijst terug omhoog in de paginaboom, en een naïeve traversering volgt die verwijzing naar de ouderknoop en daarna omlaag via /Kids naar elke zusterpagina en haar content streams. Eén stap later bevat de groep van de eerste pagina het hele document, en het bestand is technisch geliniseerd maar nutteloos voor range-first-toegang. PDFium Component negeert de /Parent-link van de pagina en de paginaboomknopen buiten de verzamelde pagina, en dat houdt de sectie van de eerste pagina klein. Bij een regressiesteekproef van 51 pagina's met een diepe paginaboom is de eerste-paginasectie 5289 bytes, 0,48% van de uitvoer van 1105354 bytes
Objectstreams en cross-reference-streams in de invoer worden afgevlakt naar gewone indirecte objecten van generatie 0, elke verwijzing wordt herschreven naar de nieuwe nummers, en de uitvoer gebruikt klassieke cross-reference-secties. Dat kost wat bestandsgrootte vergeleken met gecomprimeerde objectstreams, maar het vereenvoudigt de hint-table-dekking en het is de layout die elke linearisatiebewuste viewer aankan. Telt compactheid voor een bepaalde uitvoer zwaarder dan Fast Web View, dan heeft PDFium Component daarvoor een aparte objectstream-schrijver
Wat komt er in het linearisatie-woordenboek en de twee xref-secties?
Een geliniseerd bestand geschreven door PDFium Component voert twee klassieke cross-reference-secties en een linearisatie-woordenboek in het aller eerste object. De eerste xref-sectie volgt op het linearisatie-woordenboek en dekt de documentniveau- en eerste-paginaobjecten, zodat een viewer pagina één kan renderen op basis van alleen de voorkant van het bestand. De hoofdxref-sectie zit achteraan en dekt al het overige
Het linearisatie-woordenboek zelf bewaart de getallen die een viewer nodig heeft voordat hij iets anders leest: /L de bestandslengte, /H de offset en lengte van de hint-stream, /O het objectnummer van de eerste pagina, /E het einde van de eerste-paginasectie, /N het paginaaantal en /T de offset van de eerste entry in de hoofdxref-tabel. Al die getallen hangen af van de uiteindelijke layout, en het woordenboek zit vóór de layout. Hem wegschrijven met getallen van natuurlijke breedte creëert een lus: een lengte die groeit van 999999 naar 1000000 voegt een byte toe, schuift elke volgende offset op, wat de breedte van een ander getal kan veranderen, enzovoort
PDFium Component doorbreekt de lus door een vaste ruimte van 320 bytes te reserveren voor het linearisatie-woordenboek. Elk object, de hint-stream, beide xref-secties en de trailers worden eerst geschreven; zodra alle offsets definitief zijn, wordt het woordenboek ter plekke ingevuld en opgevuld tot de gereserveerde breedte. Eén subtiel detail is /T: hij moet wijzen naar de object-0-entry van de hoofdxref-tabel, niet naar het xref-sleutelwoord dat eraan voorafgaat. Strenge validators vlaggen de offset van het sleutelwoord, ook al openen vergevingsgezinde viewers het bestand gewoon
Waarom negeren hint-table-offsets de hint-stream?
Hint-table-offsets in een geliniseerde PDF worden geïnterpreteerd alsof de hint-stream niet in het bestand bestaat. Annex F definieert het zo zodat de hint-stream tussen de eerste-paginasectie en de rest kan zitten zonder dat zijn eigen grootte terugwerkt op de getallen die hij bevat. De praktische consequentie voor een schrijver is makkelijk verkeerd te doen: elke echte offset die na de hint-stream ligt, moet worden verminderd voordat hij de page-offset- of shared-object-hint table ingaat
De vraag is: met hoeveel verminderen. Het verleidelijke antwoord is de lengte van de streamdata, want dat voelt als de hint-inhoud. Het juiste antwoord is de lengte van het volledige hint-indirecte object, inclusief N 0 obj, het streamwoordenboek, de sleutelwoorden stream en endstream, en endobj. Trek alleen de datalengte af en elke latere offset zit er tientallen bytes naast. Pagina één rendert nog steeds omdat hij vóór de hint-stream leeft, dus de bug overleeft een snelle visuele controle en verschijnt pas wanneer een viewer naar een latere pagina springt en een bereik opvraagt dat midden in het verkeerde object begint
Beide hint tables zijn bit-gepakt met de meest significante bit eerst, opnieuw volgens Annex F. De component verifieert het resultaat zoals een echte client zou doen: een sparse range store levert alleen de eerste 1 KiB van het 51-paginavoorbeeld, en PDFium's native hint-afhandeling vraagt de rest op. De laatste pagina opent zonder dat het volledige bestand ooit aanwezig is geweest
Een geliniseerde PDF verifiëren in Delphi
De goedkoopste controle is PDFium zelf vragen. TPdf.InspectAvailability draait de availability-API tegen een stream zonder het document te laden, en zijn veld Linearization meldt plnLinearized, plnNotLinearized of 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;
Dat bevestigt dat PDFium de layout herkent; het bewijst niet dat de hints correct zijn. Voor een echte end-to-end-test, serveer het bestand via een bereikbeperkt transport en open een late pagina met TPdfProgressiveDocument, op dezelfde manier als de gids over grote PDF's op aanvraag streamen met PDFium VCL. Rendert de pagina na het ophalen van slechts een klein deel van het bestand, dan doen de hint tables hun werk. Een tweede onafhankelijke linearisatiechecker aan de releasepijplijn toevoegen is ook het overwegen waard, want viewers verschillen in hoe vergevingsgezind ze zijn over een verplaatste /T of een net iets verkeerde offset
Twee grenzen om te onthouden. De object-materializer weigert nog steeds arrays van 2 GiB of meer, dus extreem grote documenten hebben voorlopig een andere strategie nodig. En linearisatie betaalt zich alleen uit wanneer de server HTTP Range-verzoeken honoreert; achter een proxy die Accept-Ranges stript, downloadt een geliniseerd bestand precies zoals elk ander
FAQ
Houdt TPdf.SaveAsLinearized digitale handtekeningen geldig?
Nee. TPdf.SaveAsLinearized hernummert en herschrijft elk object, dus bestaande handtekeningen worden ongeldig. Lineariseer eerst en breng daarna de handtekening aan
Kan PDFium Component een versleutelde PDF lineariseren?
Alleen wanneer u saRemoveSecurity doorgeeft. Objectniveau-versleutelingssleutels hangen af van objectnummers, en die verandert de schrijver, dus de uitvoer wordt zonder versleuteling geschreven
Welke PDF-versies ondersteunt geliniseerde uitvoer?
TPdf.SaveAsLinearized accepteert pvUnknown om de bronversie aan te houden, of elke doelversie vanaf PDF 1.2 omhoog, en weigert saIncremental omdat een toegevoegde revisie de layout zou breken
Fast Web View-uitvoer, progressief laden en de availability-API horen allemaal bij de PDFium Component for Delphi and C++Builder, die PDFium verpakt voor Delphi-, C++Builder- en Lazarus-projecten