Technisch artikel

PDF-linearisatie en Snelle webweergave: Hoe het werkt

Zet een gescand rapport van 80 MB achter een link, open het in een browser en kijk wat er gebeurt: de viewer toont een leeg venster totdat een groot deel van de bytes is binnengehaald en tekent dan in één keer pagina één. Spring naar pagina 40 en bij een slecht opgebouwd bestand start de hele download mogelijk opnieuw. Het frustrerende hiervan is dat de lezer eigenlijk alleen de eerste pagina wilde zien. Linearisatie is het structurele antwoord op dit probleem. Het herschikt een PDF zodanig dat een viewer de startpagina kan weergeven vanuit een klein voorvoegsel van het bestand en de rest op verzoek kan ophalen. Daarom brengt Adobe deze functie op de markt als "Fast Web View" (Snelle webweergave).

Dit is geen ander bestandsformaat. Een gelineariseerde PDF is een gewone PDF die een conforme reader zonder speciale behandeling zal openen. De truc zit volledig in de volgorde van de bytes en in twee extra structuren die het bestand bevat. ISO 32000-1 specificeert de hele indeling in Bijlage F. Zodra u de lay-out heeft gezien, lijkt het gedrag niet langer op magie, maar op een bewuste inruil van bestandsvolgorde voor een snellere weergave van de eerste weergave (first-paint latency).

Wat linearisatie daadwerkelijk herschikt

Een normale PDF kan zijn objecten in bijna elke volgorde verspreiden. De kruisverwijzingstabel (cross-reference table) aan het einde van het bestand maakt dit mogelijk: een reader zoekt naar het einde, leest de startxref-pointer, laadt de xref en kan van daaruit elk object lokaliseren via de offset. Dit ontwerp is uitstekend voor lokale bestanden, waarbij zoeken naar het einde niets kost, en slecht voor een bestand dat via een netwerk wordt gestreamd, waarbij het einde precies het deel is dat als laatste aankomt. Om pagina één weer te geven, heeft een conventionele reader het pagina-object, de inhoudsstroom, de lettertypen waarnaar wordt verwezen en alle afbeeldingen die worden getekend nodig. In een ongeordend bestand kunnen deze zich overal bevinden, inclusief in de laatste megabyte.

Linearisatie lost deze volgorde op. De objecten die nodig zijn om de eerste pagina weer te geven, worden verzameld in een aaneengesloten blok aan het begin, direct na een kleine kopsectie, zodat ze vroeg in de bytestroom aankomen. Al het andere, de resterende pagina's en de bronnen die ze delen, volgt in een voorspelbare volgorde. Een tweede, complete kruisverwijzingstabel bevindt zich nog steeds aan het einde voor readers die de optimalisatie negeren, maar een gelineariseerd bestand plaatst ook een kruisverwijzing voor de eerste pagina en de parameters die een streaming reader nodig heeft aan het begin. De reader hoeft niet langer de staart te bereiken voordat hij iets kan tekenen.

De objectenset van de eerste pagina en het parameterwoordenboek voor linearisatie

Het allereerste object in een gelineariseerd bestand, na de %PDF-header, is het parameterwoordenboek voor linearisatie. Hier zoekt een streaming reader naar om te beslissen of de optimalisatie aanwezig is en hoe deze moet worden gebruikt. Het woordenboek registreert de lengte van het gehele bestand, de byte-offset waar het hoofdgedeelte van de kruisverwijzing begint, het objectnummer van de eerste pagina, en de locatie en lengte van de hintstroom (hint stream) die volgt. Met deze getallen weet een reader, alleen al uit de eerste kilobytes, hoeveel hij moet ophalen om pagina één weer te geven en waar hij moet zoeken naar de index waarmee hij naar een andere plek kan springen.

Bijlage F is streng over wat "eerste pagina" hier betekent. Het eerste paginagedeelte moet het pagina-object zelf, de inhoudsstromen en de bronnen waarnaar die stromen verwijzen bevatten, zodat de pagina zelfvoorzienend is zodra dat voorvoegsel is gedownload. Gedeelde bronnen, een lettertype dat op elke pagina wordt gebruikt, een logo dat zich herhaalt in een koptekst, worden speciaal behandeld: ze verschijnen vroeg genoeg om de eerste pagina te bedienen, maar worden gemarkeerd als gedeeld, zodat de reader ze niet opnieuw ophaalt wanneer hij later pagina 30 weergeeft. Dat onderscheid tussen pagina-privé en gedeelde objecten is het onderdeel dat de meeste zelfgemaakte "optimizers" verkeerd doen, en als ze het verkeerd doen, ontstaat er een bestand dat beweert gelineariseerd te zijn, maar nog steeds vastloopt.

Hintstromen: de index die paginasprongen goedkoop maakt

Het snel weergeven van pagina één is slechts de helft van de waarde. De andere helft is springen naar een willekeurige pagina zonder alles ertussenin te downloaden, en dat is wat de hintstromen bieden. Een gelineariseerd bestand bevat een hinttabel voor de pagina-offset en een hinttabel voor gedeelde objecten, opgeslagen als een stream waarnaar wordt verwezen vanuit het parameterwoordenboek. De hinttabel voor de pagina-offset registreert voor elke pagina waar de objecten in het bestand beginnen en hoe lang ze doorlopen. De hinttabel voor gedeelde objecten doet hetzelfde voor resources die op meerdere pagina's worden gebruikt.

Gegeven deze tabellen zal een reader die pagina 40 wil hebben, het bestand niet sequentieel ontleden. Hij raadpleegt de hinttabel om te leren welk bytebereik pagina 40 inneemt, vraagt de server om exact dat bereik, en rendert de pagina zodra die bytes arriveren. Alle gedeelde resources die hij nog niet heeft, haalt hij via hetzelfde mechanisme op. De hintstroom is in feite een random-access-kaart (willekeurige toegangskaart) die over het document is gelegd. Dit is de reden waarom een goed gelineariseerd bestand van 500 pagina's responsief aanvoelt over een langzame verbinding, terwijl een niet-geoptimaliseerd bestand van dezelfde grootte dat niet doet.

Waarom de server moet meewerken

Linearisatie gaat ervan uit dat het transport willekeurige delen van het bestand kan leveren. Het is de moeite waard om deze aanname te controleren voordat u het formaat de schuld geeft van slechte resultaten. Het mechanisme is HTTP-byteserving: de reader verstuurt bereikverzoeken (range requests) en de server beantwoordt deze met 206 Partial Content-reacties. Als de server geen Accept-Ranges: bytes adverteert, of als een proxy of CDN ervoor bereikverzoeken reduceert tot volledige overdrachten, heeft de reader geen mogelijkheid om pagina 40 afzonderlijk op te halen en valt hij terug op het downloaden van het hele bestand. De structuur binnen de PDF is dan perfect correct en volledig nutteloos.

Dit is de fout die het vaakst ten onrechte wordt gediagnosticeerd als "linearisatie werkt niet". Het bestand is in orde; het leveringspad niet. Controleer voordat u een document opnieuw opbouwt met een voorwaardelijk verzoek of de host daadwerkelijk gedeeltelijke inhoud (partial content) retourneert voor de URL die de reader bezoekt. Veel statische hosts doen dit standaard, en veel verkeerd geconfigureerde applicatieservers en cachinglagen niet.

Incrementele updates verbreken in stilte de linearisatie

Hier is de beperking die mensen verrast die gelineariseerde bestanden correct genereren en zich vervolgens afvragen waarom de optimalisatie verdwijnt. Linearisatie is afhankelijk van één zorgvuldig geordende lay-out met de index aan het begin. Een incrementele update schendt dat per definitie. Wanneer een tool een handtekening toevoegt, een formulierveld invult of een annotatie toevoegt via een incrementele opslag, wordt het bestand niet herschreven. De tool voegt de gewijzigde objecten, een nieuwe kruisverwijzingssectie en een nieuwe trailer aan het einde toe, waarbij de oorspronkelijke bytes onaangetast blijven. Dat toevoegen is het hele punt van incrementele updates: het is snel, en het behoudt de eerdere revisie voor audits of validatie van handtekeningen.

Het bijwerking is dat het bestand nu zijn nieuwste kruisverwijzingsgegevens aan het einde heeft staan, na het zorgvuldig geplaatste blok voor de eerste pagina, en het parameterwoordenboek voor linearisatie aan het begin beschrijft een lay-out die niet langer overeenkomt met het bestand. Een conforme reader detecteert de discrepantie en behandelt het document als een normale, niet-gelineariseerde PDF. De Snelle webweergave is verdwenen, hoewel de oorspronkelijke gelineariseerde structuur nog steeds aanwezig is in de eerste helft van het bestand. Als u meerdere updates toevoegt, stapelt elk een nieuwe revisie op het einde en de kloof tussen de verouderde index vooraan en de werkelijke staat wordt groter.

Als uw workflow zowel bewerkingen als Snelle webweergave nodig heeft, volgt de regel rechtstreeks uit de structuur: bewerk incrementeel zolang het document aan verandering onderhevig is, en herlineariseer vervolgens één keer aan het einde. Een volledige herschrijving is wat de lay-out herstelt. In HotPDF-termen betekent dat dat een bewerking in uitvoering via BeginIncrementalUpdate en SaveIncrementalUpdate gaat, wat een delta toevoegt, terwijl de afwerkingsstap het hele document laadt en het vers serialiseert met LoadFromFile gevolgd door SaveLoadedDocument, wat de opgebouwde oude revisies laat vallen en één schone lay-out produceert. Dezelfde afweging komt naar voren bij objectstromen (object streams): het inschakelen van UseObjectStreams in combinatie met UseXRefStream comprimeert de kruisverwijzing en pakt objecten strak in. Dit helpt bij de bestandsgrootte, maar net als elke structurele keuze moet dit worden toegepast tijdens de laatste herschrijving en niet achteraf aan een toegevoegde revisie worden vastgemaakt.

// In-flight edits: append a delta, keep prior revisions intact.
// This leaves the file NOT linearized.
Pdf.BeginIncrementalUpdate('report.pdf');
Pdf.AddPage;
Pdf.CurrentPage.TextOut(72, 760, 0, 'Addendum');
Pdf.SaveIncrementalUpdate('report.pdf');

// Finishing step: full re-serialization produces one clean layout,
// dropping the stacked revisions. Re-run your linearizer on the output.
Pdf.LoadFromFile('report.pdf');
Pdf.SaveLoadedDocument('report-final.pdf');

HotPDF stelt geen een-aanroep "lineariseren"-routine beschikbaar. Het praktische patroon is dus om een schoon, volledig herschreven bestand te produceren en er een speciale optimizer over te laten draaien. Commandoregelhulpmiddelen (command-line tools) behandelen de herschikking rechtstreeks. qpdf herschrijft een bestand naar een gelineariseerde vorm met één enkele vlag:

qpdf --linearize report-final.pdf report-web.pdf

Hoe u kunt zien of een bestand gelineariseerd is

Vertrouw niet op de bestandsnaam of de tool die beweert het te hebben geproduceerd; verifieer de bytes. De meest directe controle is de kop van het bestand: open het en zoek naar het parameterwoordenboek voor linearisatie als het eerste object na de header, dat de /Linearized-sleutel bevat. Een handige snelkoppeling voor lezers is het dialoogvenster Documenteigenschappen in Acrobat, dat alleen "Snelle webweergave: Ja" meldt als de structuur echt aanwezig en actueel is.

Voor gescripte controles rapporteert qpdf zowel de aanwezigheid als de integriteit van de structuur. Dit is van belang omdat een bestand een linearisatiewoordenboek kan bevatten dat de lay-out niet langer weerspiegelt, precies de toestand die een incrementele update achterlaat:

# Reports "File is linearized" and validates hint tables against the layout
qpdf --check report-web.pdf

# Dumps the linearization parameters and hint data in detail
qpdf --show-linearization report-web.pdf

De validatiestap is degene die zijn nut bewijst. Een test die alleen bevestigt dat het woordenboek bestaat, zal zonder meer een bestand goedkeuren waarvan de index naar de verkeerde offsets wijst; een controle die de hinttabellen vergelijkt met de werkelijke objectposities, vertelt u of de optimalisatie standhoudt onder de bereikverzoeken van een echte reader.

Linearisatie blijft de moeite waard om toe te passen op elk groot document dat via het web wordt aangeboden, vooral voor mobiele lezers met wisselvallige verbindingen, en het kost een paar procent van de bestandsgrootte voor de voorgeladen index. De twee dingen om in gedachten te houden, zijn dat de structuur in de PDF én de byteserving erbuiten beide correct moeten zijn, en dat elke wijziging achteraf de optimalisatie ongedaan maakt totdat u het bestand herschrijft. Behandel herlinearisatie als de laatste stap in de pijplijn, nadat alle andere wijzigingen zijn doorgevoerd. Het gedrag van kruisverwijzingen, objectstromen en incrementele updates dat hier wordt beschreven, maakt deel uit van het structurele model dat de HotPDF-component voor Delphi en C++Builder implementeert; zie hoe een PDF is gestructureerd voor de bredere achtergrond over bestandsindeling, en zie grote PDF's verwerken vanuit Delphi voor de incrementele updates en de workflow voor grote bestanden in code.