Öffnen Sie ein 200-seitiges PDF über einen Link, und der Browser bleibt oft leer, bis der Großteil der Datei angekommen ist. Fast Web View ist der Adobe-Acrobat-Name für ein linearisiertes PDF (Anhang F von ISO 32000-1): Seite eins und ein kleiner Index sitzen am Anfang, sodass ein Viewer Seite eins früh zeichnet und spätere Seiten per HTTP-Range-Request nachlädt. HotPDF und PDFium Component können beide aus Delphi heraus linearisierte Dateien schreiben
Nichts davon ist ein eigenes Dateiformat. Ein linearisiertes PDF ist ein gewöhnliches PDF, das jeder konforme Reader ohne Sonderbehandlung öffnet; der Unterschied liegt in der Anordnung seiner Bytes und in zwei zusätzlichen Strukturen, dem Linearisierungs-Parameterwörterbuch und dem Hint-Stream. Zwei Bedingungen entscheiden darüber, ob ein Reader den Nutzen je zu spüren bekommt. Die Datei muss linearisiert sein und so bleiben, was eine angehängte Bearbeitung stillschweigend zunichtemacht, und der Webserver muss Byte-Range-Requests mit 206 Partial Content beantworten. Auf einer lokalen Platte, wo der Sprung an das Dateiende nichts kostet, verhält sich ein linearisiertes PDF genau wie jedes andere; Fast Web View ist deshalb eine reine Netzwerkoptimierung, die sich bei großen Dokumenten auszahlt, die direkt über eine URL geöffnet werden
Was wird in einem linearisierten PDF umgeordnet?
Ein linearisiertes PDF versammelt alles, was Seite eins braucht, in einem zusammenhängenden Block nahe dem Anfang der Datei, vor jeder anderen Seite. In einem gewöhnlichen PDF können die Objekte in beliebiger Reihenfolge liegen, denn die Querverweistabelle am Ende bildet jede Objektnummer auf ihr Byte-Offset ab: Ein Reader springt an das Ende, liest den startxref-Zeiger, lädt die Tabelle und findet von dort aus alles (der Überblick über die PDF-Dateistruktur führt durch dieses Layout aus Header, Body, Xref und Trailer). Das Design ist auf der Platte hervorragend und im Netzwerk schlecht, denn genau der Teil, der zuletzt ankommt, ist das Ende. Um Seite eins zu zeichnen, braucht ein herkömmlicher Reader das Seitenobjekt, seinen Content-Stream und die darin verwendeten Schriften und Bilder, und in einer ungeordneten Datei kann jede dieser Zutaten im letzten Megabyte liegen
Die Linearisierung fixiert die Reihenfolge. Auf den Header folgen das Linearisierungs-Dictionary und ein kleiner Xref-Abschnitt für die erste Seite, dann die dokumentsweitigen Objekte wie der Catalog, der primäre Hint-Stream und die erste Seite mit all ihren Ressourcen. Die übrigen Seiten folgen eine nach der anderen, danach die Objekte, die sich spätere Seiten teilen, und ganz am Ende die Haupt-Querverweistabelle. Ein Reader, der die Optimierung ignoriert, startet weiterhin am Ende und findet jedes Objekt über die Haupttabelle, weshalb eine linearisierte Datei vollständig kompatibel zu älterer Software bleibt. Die Objektnummerierung sieht im Hex-Editor deshalb verdreht aus: Der vordere Block trägt die höchsten Nummern, während alles dahinter ab 1 nummeriert ist
Das Linearisierungs-Dictionary und die Hint-Tabellen
Das erste Objekt nach dem %PDF-Header ist das Linearisierungs-Parameterwörterbuch, und Anhang F verlangt, dass es innerhalb der ersten 1024 Bytes sitzt, damit ein streamender Reader es im ersten Paket findet. Tabelle F.1 listet seine Einträge auf: /L ist die Länge der gesamten Datei in Bytes, /H Offset und Länge des primären Hint-Streams, /O die Objektnummer der ersten Seite, /E das Offset, an dem die erste Seite endet, /N die Seitenzahl und /T das Offset des ersten Eintrags in der Haupt-Querverweistabelle. Der /L-Eintrag hat mehr Gewicht, als es den Anschein hat. Weicht er von der realen Dateigröße ab, weist Tabelle F.1 den Reader an, das Dokument als gewöhnliches PDF zu behandeln und jeden Hint darin zu ignorieren
Der Hint-Stream ist das, was den Sprung zu Seite 40 billig macht. Er enthält eine Hint-Tabelle der Seiten-Offsets, die für jede Seite verzeichnet, wo ihre Objekte beginnen und wie viele Bytes sie belegen, und eine Hint-Tabelle für gemeinsam genutzte Objekte, die dasselbe für Schriften, Logos und andere Ressourcen leistet, die auf mehreren Seiten verwendet werden. Ein Reader, der Seite 40 will, schlägt ihren Bytebereich nach, fordert exakt diese Scheibe an und rendert, sobald sie ankommt, wobei er jedes gemeinsam genutzte Objekt, das er noch nicht vorhält, auf demselben Weg nachzieht. Zwei Details bringen selbstgestrickte Linearisierer zu Fall. Hint-Offsets sind logisch gerechnet, als ob der Hint-Stream selbst nicht existierte; ein Writer, der physische Offsets speichert, schießt jeden späteren Range-Request um die Größe des Hint-Objekts vorbei. Und ein Writer, der beim Einsammeln der Ressourcen einer Seite ihrem /Parent-Link folgt, wandert den Seitenbaum hinab und schleppt das gesamte Dokument in den Erstseiten-Abschnitt. Beide Dateien geben weiterhin vor, linearisiert zu sein. Keine lädt auch nur einen Tick schneller
Warum braucht Fast Web View die Mithilfe des Servers?
Fast Web View funktioniert nur, wenn der Server auf Anfrage beliebige Scheiben der Datei ausliefert. Der Reader sendet HTTP-Requests mit einem Range-Header und erwartet 206 Partial Content zurück; annonciert der Server nie Accept-Ranges: bytes, oder fasst ein vorgeschalteter Proxy oder ein CDN Range-Requests zu vollständigen Übertragungen zusammen, kann der Reader Seite 40 nicht selbständig abrufen und lädt stattdessen die ganze Datei herunter. Die Struktur im PDF ist dann korrekt und vollständig verschwendet, und genau diese Fehlfunktion wird am häufigsten als kaputte Linearisierung fehlgedeutet. Bevor Sie ein Dokument neu aufbauen, fordern Sie das erste Kilobyte genau der URL an, die der Viewer öffnet, zum Beispiel mit curl -r 0-1023, und prüfen Sie, dass der Status 206 lautet. Statische Datei-Hosts bekommen das meist von Hause aus richtig, während Anwendungsendpoints, die PDFs durch ihren eigenen Handler streamen, es häufig nicht tun
Wie prüft man, ob ein PDF Fast Web View hat?
Prüfen Sie die Bytes, statt dem Werkzeug zu vertrauen, das die Datei erzeugt hat, und prüfen Sie beide Hälften der Bedingung: Die Datei erklärt sich für linearisiert, und diese Erklärung passt noch zu der Datei auf der Platte. Drei Prüfungen decken praktisch jeden Fall ab, vom kurzen Blick auf dem Desktop bis zum automatisierten Gate in einer Veröffentlichungspipeline:
- Adobe Acrobat oder Reader: öffnen Sie Datei > Eigenschaften; der Reiter Beschreibung zeigt rechts unten
Fast Web View: YesoderNo. Acrobat meldet Yes nur, wenn die Struktur vorhanden und aktuell ist, deshalb zeigt eine nach der Linearisierung bearbeitete Datei No - qpdf auf der Kommandozeile:
qpdf --check-linearizationgleicht die Hint-Tabellen mit den tatsächlichen Objektpositionen ab, undqpdf --show-linearizationgibt das Dictionary und jeden Hint-Tabellen-Eintrag aus - Das erste Kilobyte der Datei: öffnen Sie sie in einem Text- oder Hex-Editor und suchen Sie innerhalb der ersten 1024 Bytes nach einem Objekt, das
/Linearized 1enthält; ist es vorhanden, vergleichen Sie seinen/L-Wert mit der Dateigröße, die das Betriebssystem meldet
# Prüft das Linearisierungs-Dictionary und die Hint-Tabellen gegen das Layout
qpdf --check-linearization report-web.pdf
# Gibt /L, /H, /O, /E, /N, /T und jeden Hint-Tabellen-Eintrag aus
qpdf --show-linearization report-web.pdf
Die qpdf-Prüfung ist diejenige, die es sich lohnt in eine Pipeline zu hängen, denn sie gleicht die Hint-Tabellen mit den tatsächlichen Objektpositionen ab; eine Prüfung, die nur bestätigt, dass das Dictionary existiert, segnet auch eine Datei ab, deren Offsets auf die falschen Bytes zeigen. Aus Delphi meldet THotPDF.IsLoadedLinearized in HotPDF, ob ein geladenes Dokument sich für linearisiert erklärt, und durchsucht dazu vor dem Parsen die ersten 1024 Bytes nach /Linearized. PDFium Component beantwortet dieselbe Frage über die Klassenfunktion TPdf.InspectAvailability, deren Feld Linearization plnLinearized, plnNotLinearized oder plnUnknown zurückgibt, ohne das Dokument überhaupt zu laden. Beide Flags melden, was die Datei erklärt; der /L-Vergleich sagt Ihnen, ob diese Erklärung noch zu den Bytes auf der Platte passt:
uses
System.SysUtils, System.Classes, System.Math, System.StrUtils, PDFium;
type
TFastWebView = (fwvNo, fwvStale, fwvYes);
// Eigener Helfer: PDFium entscheidet, ob die Datei linearisiert ist,
// und der /L-Vergleich erwischt nachträglich angehängte Änderungen
function FastWebViewState(const FileName: string): TFastWebView;
var
Stream: TFileStream;
Head: AnsiString;
Text: string;
P, Q: Integer;
begin
Result := fwvNo;
Stream := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite);
try
if TPdf.InspectAvailability(Stream).Linearization <> plnLinearized then
Exit;
SetLength(Head, Min(Stream.Size, 1024)); // Anhang F: die ersten 1024 Bytes
Stream.Position := 0;
if Length(Head) > 0 then
Stream.ReadBuffer(Head[1], Length(Head));
Text := string(Head);
P := Pos('/Linearized', Text);
if P = 0 then
Exit;
P := PosEx('/L ', Text, P + Length('/Linearized'));
if P = 0 then
Exit;
Inc(P, 3);
Q := P;
while (Q <= Length(Text)) and CharInSet(Text[Q], ['0'..'9']) do
Inc(Q);
if StrToInt64Def(Copy(Text, P, Q - P), -1) = Stream.Size then
Result := fwvYes
else
Result := fwvStale; // einmal linearisiert, danach verändert
finally
Stream.Free;
end;
end;
Fast Web View in Acrobat, qpdf und Delphi einschalten
Jedes Werkzeug, das die ganze Datei in Anhang-F-Reihenfolge neu schreibt, kann Fast Web View einschalten, und am wichtigsten ist, dass es als Letztes läuft. In Acrobat aktivieren Sie Beim Speichern unter für Fast Web View optimieren unter Voreinstellungen > Dokumente und verwenden danach Datei > Speichern unter statt Speichern, denn ein schlichtes Speichern hängt mitunter ein inkrementelles Update an. Auf einem Server oder in einem Build-Skript schreibt qpdf --linearize in.pdf out.pdf jedes PDF in linearisierte Form um und lässt die Eingabe unangetastet. Delphi-Code hat zwei Routen, je nachdem, ob Sie das Dokument erzeugen oder erhalten. HotPDF schreibt linearisierte Ausgabe direkt, wenn THotPDF.LinearizeOutput auf True steht; setzen Sie es zusammen mit den anderen Ausgabeoptionen vor BeginDoc, und das Anhang-F-Layout wird in EndDoc berechnet und geschrieben:
qpdf --linearize report-final.pdf report-web.pdf
var
PDF: THotPDF;
begin
PDF := THotPDF.Create(nil);
try
PDF.AutoLaunch := False; // nach EndDoc keinen Viewer öffnen
PDF.FileName := 'catalog-web.pdf';
PDF.LinearizeOutput := True; // vor BeginDoc setzen
PDF.BeginDoc; // Seite 1 existiert nach BeginDoc
PDF.CurrentPage.TextOut(72, 72, 0, 'Spring catalog');
PDF.AddPage;
PDF.CurrentPage.TextOut(72, 72, 0, 'Price list');
PDF.EndDoc; // das linearisierte Layout wird hier geschrieben
finally
PDF.Free;
end;
end;
Für Dateien, die bereits existieren, stellt PDFium Component TPdf.SaveAsLinearized bereit. Es führt den gewöhnlichen PDFium-Save in einen Zwischenspeicher aus, sodass Seitenbearbeitungen und Formularwerte denselben Weg nehmen wie jeder andere Save, und strukturiert diese Ausgabe gemäß Anhang F um, bevor Ihr Ziel geschrieben wird. Die Methode nimmt dieselben Parameter TSaveOption und TPdfVersion wie SaveAs, weist saIncremental und jede Version unter PDF 1.2 zurück, und ihre Überladung mit Dateinamen schreibt in eine temporäre Datei, die das Ziel erst ersetzt, nachdem der Save erfolgreich war. Weil der Writer die Datei aus der aktuellen Revision neu aufbaut, ist das auch der Weg, wie Sie Fast Web View in einer Datei wiederherstellen, die angehängte Bearbeitungen eingesammelt hat. Verschlüsselte Eingabe braucht saRemoveSecurity: Das Umnummerieren von Objekten macht die pro Objekt gültigen Schlüssel des Standard-Security-Handlers ungültig (ISO 32000-1 §7.6.2), deshalb wird die Web-Kopie ohne Verschlüsselung geschrieben
var
Pdf: TPdf;
begin
Pdf := TPdf.Create(nil);
try
Pdf.FileName := 'manual-rev3.pdf'; // enthält angehängte Bearbeitungen
Pdf.Active := True;
if not Pdf.SaveAsLinearized('manual-web.pdf', saNoIncremental) then
raise Exception.Create('Linearized save failed');
finally
Pdf.Free;
end;
end;
Inkrementelle Updates schalten Fast Web View stillschweigend ab
Ein inkrementelles Update ist der häufigste Grund, warum eine korrekt linearisierte Datei später Fast Web View verliert. Wenn ein Werkzeug über einen inkrementellen Save eine Signatur hinzufügt, ein Formularfeld ausfüllt oder eine Notiz anhängt, lässt es die ursprünglichen Bytes unangetastet und hängt die geänderten Objekte, einen neuen Querverweisabschnitt und einen neuen Trailer an das Ende. Das Linearisierungs-Dictionary am Anfang beschreibt weiterhin das alte Layout, sein /L passt nicht mehr zur Dateigröße, und ein konformer Reader fällt auf die Behandlung des Dokuments als gewöhnliches PDF zurück, obwohl die linearisierte Struktur noch intakt im ersten Teil der Datei sitzt. HotPDF hängt genau diese Art von Revision über BeginIncrementalUpdate und SaveIncrementalUpdate an, das richtige Werkzeug, solange ein Dokument noch im Fluss ist, und der falsche letzte Schritt für eine Datei auf dem Weg zu einem Webserver:
// Hängt eine Revision an: schnell, lässt die früheren Bytes intakt,
// und hinterlässt das Ergebnis ohne Fast Web View
PDF.BeginIncrementalUpdate('report.pdf');
PDF.AddPage;
PDF.CurrentPage.TextOut(72, 760, 0, 'Addendum');
PDF.SaveIncrementalUpdate('report-rev2.pdf');
Die brauchbare Regel lautet: inkrementell bearbeiten, solange ein Dokument im Fluss ist, einmal linearisieren und zwar als allerletzten Schritt, und Fast Web View für Dateien ausgeschaltet lassen, die gemailt oder heruntergeladen werden, bevor jemand sie öffnet, denn sie profitieren nicht vom größeren, nach vorn verlagerten Index. Signierte Dokumente erzwingen eine echte Entscheidung: Eine vollständige Neuschreibung verändert die Bytes, die der /ByteRange jeder Signatur abdeckt, und nachträgliches Signieren hängt ein Update an, das die /L-Diskrepanz sofort zurückbringt; bei Verträgen also die Signatur behalten und vollständige Downloads akzeptieren. Die Writer-Seite von Anhang F, einschließlich der Frage, wie die Offsets konvergieren, bevor das erste Byte geschrieben ist, und warum linearisierte Ausgabe auf Objekt-Streams verzichtet, behandeln linearisierte PDFs mit HotPDF-Hint-Tabellen schreiben und Fast-Web-View-Linearisierung mit PDFium Component in Delphi. LinearizeOutput und IsLoadedLinearized kommen mit der HotPDF-Delphi-Komponente, während SaveAsLinearized und InspectAvailability sind Teil der PDFium Component für Delphi, C++Builder und Lazarus