Öffnen Sie ein PDF, das einige Jahre produktiven Einsatz hinter sich hat, finden Sie oft mehr Buchhaltung als Inhalt: tausende kleine Dictionary-Objekte, jedes mit eigenem obj-Header, dazu eine Cross-Reference-Tabelle, die 20 ASCII-Byte pro Objekt kostet. In einem Supportfall, den wir analysiert haben, entfiel bei einem 58 MB großen Policenarchiv weniger als die Hälfte der Bytes auf eigentlichen Seiteninhalt; der Rest war struktureller Overhead und alte Revisionen. HotPDF, eine native VCL-PDF-Bibliothek für Delphi und C++Builder, stellt die beiden Dateiformatmechanismen bereit, die beide Seiten dieses Problems angehen: Objekt-Streams für kompakte Speicherung und inkrementelle Updates für anhängende Änderungen, die nichts zerstören, was vorher vorhanden war.
Wie Objekt-Streams und xref-Streams die Datei neu strukturieren
Bis PDF 1.4 liegt jedes indirekte Objekt unkomprimiert im Body, und die Cross-Reference-Tabelle am Ende ist eine Textstruktur mit fester Breite: exakt 20 Byte pro Eintrag, ohne zulässige Kompression. Ein Dokument mit 200,000 Objekten trägt also schon rund 4 MB xref-Daten, bevor eine einzige Glyphe gezeichnet wird. PDF 1.5 führte zwei Ersetzungen ein, definiert in ISO 32000-1 §7.5.7 und §7.5.8: Objekt-Streams (/Type /ObjStm), die Nicht-Stream-Objekte in einem einzigen Flate-komprimierten Container sammeln, und Cross-Reference-Streams, die die Nachschlagetabelle selbst als komprimierte Binärdaten mit variabler Feldbreite speichern.
Die Einsparungen sind genau dort am größten, wo einfache Generatoren am meisten schaden: formularlastige Dokumente mit tausenden Felddictionaries, tiefen Outline-Bäumen und Tagged-PDF-Strukturelementen. Diese Objekte sind einzeln winzig und stark repetitiv, was sie nach dem gemeinsamen Packen zu idealem Flate-Eingang macht. Seiteninhalts-Streams waren bereits vor PDF 1.5 komprimierbar, daher verkleinern Objekt-Streams bilddominierte Dateien kaum; strukturdominierte Dateien verkleinern sie deutlich.
In HotPDF werden die beiden Funktionen mit einem Eigenschaftspaar eingeschaltet, und die Reihenfolge der Abhängigkeit ist wichtig:
var
Pdf: THotPDF;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.FileName := 'catalog-2026.pdf';
Pdf.UseXRefStream := True; // binary xref, prerequisite for ObjStm
Pdf.UseObjectStreams := True; // pack objects into /Type /ObjStm
Pdf.BeginDoc;
Pdf.CurrentPage.SetFont('Arial', [], 11);
Pdf.CurrentPage.TextOut(50, 760, 0, 'Compressed structure demo');
Pdf.EndDoc; // emits XRefStm + ObjStm containers
finally
Pdf.Free;
end;
end;
UseObjectStreams setzt voraus, dass UseXRefStream auf True steht, weil ein komprimiertes Objekt über einen type-2-xref-Eintrag adressiert wird, also Objekt-Stream-Nummer plus Index, und type-2-Einträge lassen sich in einer klassischen 20-Byte-Texttabelle schlicht nicht ausdrücken. UseObjectStreams allein zu setzen bringt nichts; beide Eigenschaften vor BeginDoc zu setzen ist die funktionierende Konfiguration.
Die Kompatibilitätsgrenze bei PDF 1.4
Beide Eigenschaften sind nicht zufällig standardmäßig False, denn genau hier geraten Teams mit alten Integrationen in Schwierigkeiten. Ein Reader, der nur PDF-1.4-Semantik beherrscht, meldet bei einem xref-Stream normalerweise nicht „komprimierte Objekte nicht unterstützt“, sondern eine beschädigte Cross-Reference-Tabelle oder verweigert die Datei vollständig, weil das erwartete Trailer-Keyword-Layout fehlt. Wenn Ihre Ausgabe an ein altes Fax-Gateway, einen Hardwaredrucker mit eingebettetem Interpreter oder einen nach PDF 1.4 geschriebenen nachgelagerten Parser geht, lassen Sie beide Flags für diesen Kanal ausgeschaltet und akzeptieren Sie die größere Datei. Für Archiv- und Web-Auslieferungskanäle, in denen alle gängigen Viewer seit zwei Jahrzehnten PDF 1.5 beherrschen, ist das Aktivieren nahezu kostenlose Kompression.
Ein betrieblicher Nebeneffekt sollte im Support bekannt sein: Sobald Dictionaries in Objekt-Streams gepackt werden, ist byteweises Vergleichen zweier generierter Dateien sinnlos, weil schon eine Änderung an einem Feld einen ganzen Container neu Flate-komprimieren kann. Supportanalysen sollten Dokumente logisch nach Objektinhalt vergleichen, nicht per Binärdiff.
Warum inkrementelle Updates existieren: Offsets, Signaturen, Audit-Trails
Eine digitale Signatur in PDF umfasst eine explizite /ByteRange: zwei Bereiche der physischen Datei, gemessen in absoluten Byte-Offsets, über die der CMS-Digest berechnet wurde. Schreiben Sie die Datei neu, selbst mit visuell identischem Inhalt, verschieben sich alle Offsets; der Digest passt nicht mehr, und die Signatur wird als gebrochen gemeldet. Genau deshalb definiert ISO 32000-1 §7.5.6 inkrementelle Updates: geänderte und neue Objekte werden nach dem bestehenden %%EOF angehängt, gefolgt von einem neuen Cross-Reference-Abschnitt, dessen /Prev-Eintrag auf den vorherigen zurückzeigt. Die ursprünglichen Bytes werden nie berührt, daher bleibt eine früher signierte Revision prüfbar, und Acrobat kann jede signierte Revision im Signaturbereich separat anzeigen.
HotPDF kapselt dies als eigenen Einstiegspunkt:
Pdf.BeginIncrementalUpdate('contract-signed.pdf');
Pdf.AddPage;
Pdf.CurrentPage.SetFont('Arial', [], 10);
Pdf.CurrentPage.TextOut(50, 760, 0, 'Addendum recorded 2026-06-11');
Pdf.SaveIncrementalUpdate('contract-updated.pdf'); // appends the delta only
Zwei Details werden hier leicht falsch gemacht. Erstens muss BeginIncrementalUpdate den Namen der Originaldatei erhalten; der angehängte xref-Abschnitt speichert Offsets, die nur relativ zu genau diesen ursprünglichen Bytes gültig sind. Zweitens ist das Speichern definitionsgemäß append-only, daher ist die Ausgabe immer größer als die Eingabe. Das ist kein zu optimierender Fehler, sondern genau die Eigenschaft, die ältere signierte Revisionen intakt hält.
Geladene Dokumente bearbeiten: LoadFromFile, nicht BeginDoc
Eine separate Falle trifft Entwickler, die HotPDF über die Erzeugungs-API gelernt haben. BeginDoc startet ein neues Dokument; es ist der falsche Aufruf, wenn ein bestehendes Dokument geändert werden soll. Dokumentchirurgie läuft über den Pfad für geladene Dokumente:
PageCount := Pdf.LoadFromFile('base.pdf');
Pdf.InsertPagesFromDocument(OtherDoc, '1-3', 5); // pages 1-3 after page 5
Pdf.MovePage(2, 5);
Pdf.SaveLoadedDocument('modified.pdf');
Das Symptom einer Vermischung der beiden Modelle ist eine Datei, die nur neue Inhalte enthält und nichts vom Original, weil BeginDoc problemlos ein frisches Dokument neben dem erstellt hat, von dem Sie dachten, Sie würden es bearbeiten. Behandeln Sie beim Code-Review LoadFromFile + SaveLoadedDocument als zusammengehörigen Wortschatz und BeginDoc + EndDoc als einen anderen; eine Prozedur, die beide für dasselbe Dokument verwendet, ist fast immer ein Fehler.
Wachstum begrenzen: wann eine angehängte Datei kompaktiert wird
Append-only-Speichern hat langfristige Kosten. Ein nächtlicher Job, der eine Statuszeile auf dasselbe PDF stempelt, erzeugt 365 Revisionen pro Jahr, und jede Revision zieht einen neuen xref-Abschnitt mit. Sobald die Revisionshistorie ihren Zweck erfüllt hat und keine Signatur erhalten bleiben muss, kann die Datei kompaktiert werden, indem sie über den Pfad für geladene Dokumente neu serialisiert wird:
Pdf.LoadFromFile('stamped.pdf');
Pdf.SaveLoadedDocument('compacted.pdf');
Das erneute Speichern ist ein vollständiger Rewrite, was bedeutet, dass frühere Revisionen absichtlich verworfen und vorhandene Signaturen in der Datei ungültig werden. Setzen Sie es deshalb hinter dieselbe Richtlinienprüfung, die Sie vor jeder destruktiven Operation verwenden. Eine vernünftige Produktionsregel, die sich bewährt hat: kompaktieren, wenn die Revisionsanzahl einen Schwellenwert überschreitet oder der angehängte Overhead einen Prozentsatz der Basisdatei übersteigt, und niemals Dateien kompaktieren, deren Signaturbereich nicht leer ist.
Das Ergebnis vor der Auslieferung prüfen
Die Verifikation dieses Funktionspaares ist angenehm mechanisch. Öffnen Sie die Ausgabe in Adobe Acrobat und prüfen Sie drei Dinge: Die Dokumenteigenschaften melden PDF 1.5 oder neuer, wenn Objekt-Streams aktiviert sind; der Signaturbereich validiert nach einem inkrementellen Update weiterhin jede zuvor signierte Revision; und Seitenanzahl sowie Lesezeichen haben einen Laden-Ändern-Speichern-Zyklus überlebt. Für Archivkanäle lassen Sie die Datei zusätzlich durch veraPDF laufen, denn komprimierte xref-Strukturen sind genau die Art von Detail, die ein strenger Parser sorgfältiger betrachtet als ein nachsichtiger Viewer. Wenn Sie auch sehr große Eingaben verarbeiten, lassen sich die Inspektionstechniken aus unserer Einführung in die Direct File API für große PDF-Workflows gut mit inkrementellem Speichern kombinieren, und die oben genannten Signaturmechaniken werden ausführlich in dem Artikel zu HotPDF, digitalen Signaturen und PAdES behandelt.
FAQ
Können ältere PDF-Reader durch Objekt-Streams Probleme bekommen?
Reader, die nur PDF 1.4 implementieren, können xref-Streams nicht parsen und melden die Datei meist als beschädigt. Lassen Sie UseXRefStream und UseObjectStreams für Kanäle, die alte Interpreter versorgen, auf dem Standardwert False, und aktivieren Sie sie für moderne Viewer- und Archivkanäle.
Bleibt meine digitale Signatur bei einem inkrementellen Update gültig?
Ja, genau dafür ist es gedacht: Neue Objekte werden hinter den signierten Bytes angehängt, sodass die signierte /ByteRange weiterhin den richtigen Digest ergibt. Ein vollständiger Rewrite, einschließlich Kompaktierung per Laden und erneutem Speichern, bricht jede vorhandene Signatur, auch wenn der sichtbare Inhalt unverändert bleibt.
Warum wächst meine Datei nach wiederholtem Speichern weiter?
Inkrementelles Speichern hängt bei jedem Speichern ein Delta an und gewinnt nie Speicherplatz zurück. Kompaktieren Sie gelegentlich mit LoadFromFile plus SaveLoadedDocument, sobald Revisionshistorie und Signaturen nicht mehr erhalten werden müssen.
Wohin als Nächstes
Beide Funktionen gehören zum Standardfunktionsumfang der HotPDF Component, zusammen mit den Erzeugungs-, Formular-, Verschlüsselungs- und Signier-APIs, die an anderer Stelle in diesem Blog gezeigt werden. Die Produktseite verweist auf die vollständige API-Dokumentation, wenn Sie die oben gezeigten Aufrufe auf Ihre eigene Dokumentpipeline übertragen möchten.