Ein Zwei-Gigabyte-PDF auf die naheliegende Art zusammenzuführen oder aufzuteilen kostet Sie zwei Dinge zugleich: Laufzeit und Adressraum. Die naheliegende Art ist, jede Eingabe zu laden, die Arbeit zu erledigen, die Ausgabe zu schreiben. Das Laden ist der Punkt, an dem es bricht. Ein Scan-Archiv, das von 300 auf 600 DPI wechselt, verdoppelt seine lineare Auflösung und vervierfacht sich auf der Festplatte ungefähr, sodass derselbe Assemblierungsjob, der das ganze Jahr über 400-MB-Dateien bewältigt hat, zu thrashen beginnt, sobald eine Eingabe ein Gigabyte überschreitet, oft während er nichts weiter tut, als Seiten zu zählen. Die Aufgabe ist nie schwerer geworden. Öffnen, zählen, Bereiche wählen, aneinanderhängen, das ist alles. Das vollständige Laden des Objektbaums hat schlicht aufgehört, in dieser Größenordnung ein vernünftiger Standard zu sein. PDF Library for Delphi, die PDF-Bibliothek von losLab für Delphi und C++Builder, antwortet darauf mit ihrer Direct-Access-Schicht: einer Familie von Funktionen mit DA-Präfix, hinter der ein Streaming-Reader steht, der die Querverweistabelle an Ort und Stelle durchläuft, statt das ganze Dokument im Speicher aufzubauen
Wohin der Speicher bei einem vollständigen Laden geht
Ein PDF „normal“ zu laden bedeutet, die xref zu parsen, jedes indirekte Objekt in einen Baum im Speicher aufzulösen, Objektströme zu dekodieren und Seitenbaum, Schriften und Annotationen zu Objekten zu verdrahten, die Sie manipulieren können. Für Bearbeitungs-Workflows ist das der richtige Kompromiss. Für Merge-, Split- und Inspektionsarbeit ist es größtenteils Verschwendung. Ein Scan-Archiv mit 30.000 Seiten kann Millionen indirekter Objekte enthalten, und ein Split-Job muss ein paar Hundert davon lesen: die Seitenknoten im angeforderten Bereich plus das, was diese Knoten referenzieren
Die Direct-Access-Schicht kehrt das Modell um. DAOpenFile und DAOpenFileReadOnly parsen Trailer und xref, ein paar Kilobyte am Ende der Datei, und liefern ein Datei-Handle. Objekte werden verzögert geholt, wenn ein Aufruf sie braucht. Die praktische Folge ist, dass das Öffnen einer Multi-Gigabyte-Datei ungefähr so lange dauert wie das Öffnen einer kleinen, und der Speicher folgt dem, was Sie anfassen, statt dem, was die Datei enthält
Eine riesige Datei sondieren, ohne sie zu laden
Das folgende Muster stammt aus dem bibliothekseigenen Benchmark für große Dateien: schreibgeschützt öffnen, Fragen stellen, schließen. Ein Dokumentbaum entsteht nie
var
Lib: TPDFlib;
Handle, Pages: Integer;
begin
Lib := TPDFlib.Create;
try
Handle := Lib.DAOpenFileReadOnly('archive-2025.pdf', '');
if Handle = 0 then
raise Exception.Create('Direct access open failed');
Pages := Lib.DAGetPageCount(Handle);
Writeln('pages : ', Pages);
Writeln('title : ', Lib.DAGetInformation(Handle, 'Title'));
Lib.DACloseFile(Handle);
finally
Lib.Free;
end;
end;
Der Nur-Lese-Modus verdient den Vorzug, wann immer möglich: Er lässt die Eingangsstufe laufen, während andere Prozesse die Datei halten, und er dokumentiert die Absicht. Eine Sondierungsstufe, die versehentlich eine verändernde Funktion aufruft, scheitert schnell, statt das Archiv zu beschädigen
PageRef ist ein Objekt-Handle, keine Seitennummer
Der mit Abstand häufigste Fehler mit der DA-API ist, eine Seitennummer zu übergeben, wo eine Funktion eine PageRef erwartet. Fast jeder seitenbezogene DA-Aufruf nimmt ein Referenz-Handle auf das Seitenobjekt statt einer Seitennummer: DAExtractPageText, DARenderPageToFile, DARotatePage und DACapturePage erwarten alle eine Ref. Sie erhalten eine, indem Sie die für Menschen gedachte Nummer über DAFindPage übersetzen:
PageRef := Lib.DAFindPage(Handle, 250); // Seitennummer -> Objekt-Handle
if PageRef <> 0 then
begin
Text := Lib.DAExtractPageText(Handle, PageRef, 0);
Lib.DARenderPageToFile(Handle, PageRef, 5, 150, 'page250.png');
end;
Stattdessen die rohe Zahl 250 zu übergeben, löst keinen Fehler aus. Es adressiert das Objekt, das zufällig hinter diesem Handle-Wert sitzt, was an einem guten Tag sichtbar scheitert und an einem schlechten Tag Text der falschen Seite in ein kundenseitiges Dokument extrahiert. Wenn Sie die DA-Schicht in eigenen Dienstcode kapseln, machen Sie die Übersetzung unumgehbar: Nehmen Sie an der Grenze Seitennummern entgegen, rufen Sie sofort DAFindPage auf und reichen Sie intern nur Refs weiter
Hunderte Dateien über eine benannte Liste zusammenführen
Für zwei Dateien genügt MergeFiles(First, Second, Output). Batch-Assemblierung skaliert besser über Dateilisten: Eingaben unter einem Listennamen registrieren, dann die Liste in einem Durchgang zusammenführen
Lib.AddToFileList('Statements', 'jan.pdf');
Lib.AddToFileList('Statements', 'feb.pdf');
Lib.AddToFileList('Statements', 'mar.pdf');
Lib.MergeFileList('Statements', 'q1-statements.pdf');
// Das Ergebnis auf die günstige Art prüfen: wieder Direktzugriff
Handle := Lib.DAOpenFileReadOnly('q1-statements.pdf', '');
Writeln('merged pages: ', Lib.DAGetPageCount(Handle));
Lib.DACloseFile(Handle);
Die Merge-Familie hat drei Varianten, und der Unterschied ist nicht allein die Geschwindigkeit. MergeFileListFast überspringt den Erhalt des Strukturbaums; MergeFileListStrict erzwingt den strikten Modus; die Version ohne Suffix ist der ausgewogene Standard. Die operative Regel, die daraus folgt: Ist irgendeine Eingabe ein Tagged PDF, dessen Barrierefreiheitsstruktur überleben muss, wobei alles für PDF/UA Erzeugte der offensichtliche Fall ist, greifen Sie zur Standard- oder Strict-Variante, denn Fast verwirft den Strukturbaum stillschweigend. Für reine Scan-Archive ohne Tagging ist Fast kostenlose Leistung. Entscheiden Sie pro Pipeline, nicht nach Entwicklerlaune, und halten Sie die verwendete Variante im Job-Protokoll fest
Aufteilen ohne Laden: Bereichsextraktion
Das Aufteilen folgt derselben Philosophie ohne Laden. ExtractFilePages(InputFileName, Password, OutputFileName, RangeList) zieht einen Seitenbereich direkt von Datei zu Datei, mit einer Bereichsliste wie '1-500', '501-1000' oder kommagetrennten Auswahlen, und die Quelle wird nie zu einem Dokumentbaum. Ist ein Dokument aus anderen Gründen bereits geladen, erzeugt ExtractPageRanges aus dem aktuellen ein neues Dokument im Speicher, und CopyPageRanges zieht Bereiche per ID aus einem anderen geladenen Dokument herüber. Für das Aufteilen konsolidierter Druckströme pro Kontoauszug ist die Datei-zu-Datei-Form diejenige, die verhindert, dass eine 4-GB-Eingabe je in den RAM aufgebläht wird
Dateien, die über ihre Geometrie lügen
Pipelines für große Dateien treffen beschädigte Dateien in einer Häufigkeit, die Pipelines für kleine Dateien nie sehen, einfach weil die Eingaben durch mehr Systeme laufen. Zwei Fehlerformen verdienen explizite Behandlung
Erstens verschobene Header. Mail-Gateways und Druck-Spooler stellen einem PDF manchmal Bytes voran, sodass die %PDF-Markierung nicht mehr bei Offset 0 sitzt und jeder xref-Offset in der Datei um denselben Betrag falsch ist. Der Streaming-Reader erkennt das und legt es offen (DAShiftedHeader auf der flachen Ebene, ShiftedHeader auf TSmartPDFReader) und gleicht es dann beim Lesen aus. Selbstgestrickte Offset-Arithmetik tut das typischerweise nicht, weshalb „funktioniert mit jeder Datei, die wir erzeugen, scheitert bei Dateien von Kunde X“ das klassische Symptom ist
Zweitens defekte Querverweistabellen. DACopyFile(InputFileName, OutputFileName, PageCount) streamt die gesamte Datei in eine neue Kopie und baut dabei die xref neu auf, wobei die Seitenzahl als Nebenprodukt zurückkommt. Als Normalisierungsstufe vor einem wählerischen nachgelagerten Verbraucher ausgeführt, verwandelt sie eine Klasse sporadischer Parse-Fehler in einen einzigen vorhersehbaren Reparaturschritt. Und wenn Ihre eigenen Änderungen gespeichert werden müssen, schreibt DAAppendFile sie als inkrementelles Update und hängt eine neue Revision an, statt Gigabytes neu zu schreiben, was die Speicherkosten proportional zur Änderung hält statt zur Datei
Auslieferungsdetails: Linearisierung und Komposition
Zwei angrenzende Fähigkeiten runden eine Pipeline für große Dateien ab. Wird die assemblierte Ausgabe über HTTP zur Anzeige im Browser ausgeliefert, organisiert LinearizeFile sie für Byte-Range-Streaming neu, sodass die erste Seite erscheint, bevor der Rest eines 500-MB-Pakets fertig heruntergeladen ist. Führen Sie es als letzte Stufe aus, nach allem Zusammenführen, denn jede spätere Änderung hebt die Linearisierung wieder auf. Und wenn Pakete Komposition statt bloßer Verkettung brauchen, etwa ein Deckblatt hinter jedem Kontoauszug oder zwei Quellseiten auf einen Ausgabebogen ausgeschossen, verwandelt DACapturePage jede Seite in eine wiederverwendbare Vorlage, die DADrawCapturedPage an einem beliebigen Rechteck auf einer Zielseite platziert, weiterhin ohne vollständiges Laden der Multi-Gigabyte-Quelle
Grenzen und was schreibgeschützt bleibt
Das Format selbst geht lange vor Direct Access aus dem Platz. Offsets sind in der gesamten DA-Schicht Int64, die echten Obergrenzen sind also der verfügbare Speicherplatz und das zehnstellige xref-Offset-Feld klassischer (nicht als Stream vorliegender) Querverweistabellen. Multi-Gigabyte-Scan-Archive sind in der Praxis unspektakulär, und der Speicher bleibt unabhängig von der Dateigröße begrenzt, weil Objekte nur gelesen werden, wenn ein Aufruf danach fragt
Zwei Fragen kommen oft genug auf, um sie direkt zu beantworten. Das Zusammenführen über den Standardpfad nimmt die Dokumentstruktur mit, Lesezeichen und Links überleben also; die Fast-Variante ist diejenige, die den Strukturbaum gegen Geschwindigkeit tauscht, und genau deshalb bleibt sie ungetaggten Eingaben vorbehalten. Die sichere Gewohnheit ist, die zusammengeführte Ausgabe zu öffnen, ihre Gliederung zu durchlaufen und ein paar interne Links stichprobenartig zu prüfen, bevor man sie ausliefert. Zum Bearbeiten: Es gibt einen nützlichen Mittelweg zwischen Nur-Lese-Sondierung und vollständigem Laden. Operationen auf Seitenebene arbeiten direkt auf dem Handle, darunter DARotatePage, DAMovePage und DAHidePage, ebenso das Lesen von Formularfeldern, und DAAppendFile persistiert diese Änderungen als inkrementelle Revision. Bearbeitung auf Inhaltsebene, alles, was die Zeichenoperatoren innerhalb einer Seite umschreibt, gehört weiterhin zur vollständigen Dokumentschicht
Verwandte Artikel
Muss Ihre zusammengeführte Ausgabe barrierefrei bleiben, behandelt der Artikel zur Barrierefreiheit mit Tagged PDF den Hintergrund des Strukturbaums und erklärt genau, was die Fast-Merge-Variante verwerfen würde. Zum Herausziehen von Inhalten aus den aufgeteilten Bereichen siehe den Leitfaden zur Text-, Bild- und Schriftextraktion
Die vollständige Liste der Direct-Access-Funktionen wird mit der Bibliothek ausgeliefert; Editionen und Testversionen finden Sie auf der Produktseite von PDF Library for Delphi