Document: DParts, Aufteilen / Zusammenführen und Anbietererweiterungen
Auf einen Blick
Abschnitt betitelt „Auf einen Blick“Das Modul Document arbeitet mit ganzen PDF-Dokumenten statt mit Seiteninhalten. Es baut die Document-Part-Hierarchie auf, an die regulierte Workflows Metadaten anhängen, teilt PDFs anhand von Seitenbereichen in Segmente, führt mehrere PDFs zu einer einzigen Datei zusammen und registriert Entwicklererweiterungen im Dokumentkatalog.
Installation
Abschnitt betitelt „Installation“composer require nextpdf/core:^3Konzeptioneller Überblick
Abschnitt betitelt „Konzeptioneller Überblick“Dieses Modul liegt oberhalb des Seiteninhalts. Während Graphics und Content Operatoren ausgeben, arbeitet Document auf der Strukturebene: Seitenbäume, Dokumentkatalog und Document-Part-Baum.
Ein Document Part (DPart) ist eine logische Partition einer PDF-Datei. ISO 32000-2 definiert eine DPart-Hierarchie, deren Knoten Document Part Metadata (DPM) tragen. Ein regulierter Workflow, etwa im Pharma-, Rechts- oder Archivbereich, kann Metadaten mit einem Teilbereich von Seiten statt mit der gesamten Datei verknüpfen — §14.12. DPart ist ein unveränderlicher readonly-Knoten: Ein Blatt verweist auf einen zusammenhängenden Bereich von Seitenindizes; ein Zwischenknoten gruppiert untergeordnete DPart-Knoten zu einem Baum. DPartRoot ist die Baumwurzel, die der Writer serialisiert. Die Einträge /Start und /End eines Blattknotens sind indirekte Referenzen auf Seitenobjekte, keine ganzzahligen Seitenindizes — §14.12. DPart::resolveWithPageObjects() nimmt diese Auflösung anhand einer vom Writer bereitgestellten Seitenindex→Objektnummer-Zuordnung vor und gibt die /Start- (und optional die /End-)Referenzform zurück. Auf die Ganzzahlform greift sie nur in Testpfaden zurück, in denen die Zuordnung nicht verfügbar ist.
PdfMerger und PdfSplitter bilden die Schnittstelle für die Dokumentkomposition. PdfMerger kombiniert Seitenobjekte aus mehreren Eingabe-PDFs, nummeriert Objekte neu, um Kollisionen zu vermeiden, und baut einen einzigen Seitenbaum sowie eine einzige Querverweistabelle neu auf. Der dabei erzeugte Seitenbaum ist ein ausgewogener Pages-Knoten mit Kids und Count sowie dem vererbbaren Attributmodell, das PDF für Seitenbaumknoten definiert — §7.7.3. PdfSplitter kehrt diesen Vorgang um: Er extrahiert Seitenbereiche in eigenständige SplitDocument-Objekte. PageRange ist das Value Object, das beide verwenden. Es ist 1-basiert, validiert seine Grenzen und beantwortet contains(), count() und toArray().
VendorExtensionRegistry, ExtensionsDictionary und DeveloperExtensionEntry modellieren das Developer-Extensions-Dictionary im Dokumentkatalog — den Mechanismus, mit dem eine Engine eine Anbietererweiterungsstufe jenseits der Basisspezifikation deklariert. Die Registry weist eine widersprüchliche erneute Registrierung desselben Anbieterpräfixes mit VendorExtensionRegistryConflictException zurück. CollectionDictionary und CollectionSort modellieren einen PDF-Collection-Katalogeintrag (Portable Collection / Portfolio).
API-Oberfläche
Abschnitt betitelt „API-Oberfläche“| Klasse | Schlüsselmethoden | Rolle |
|---|---|---|
DPart | isLeaf(), hasMetadata(), resolveWithPageObjects(), write() | Unveränderlicher Document-Part-Knoten (@since 1.12.0) |
DPartRoot | isEmpty(), write() | DPart-Baumwurzel, die der Writer serialisiert (@since 1.12.0) |
PdfMerger | merge(array $pdfFiles, int $maxFiles = 100, int $maxTotalBytes = 200_000_000), append() | Zusammenführen mehrerer PDFs mit Objektneunummerierung (@since 1.9.0) |
PdfSplitter | split(), splitEvery(), extractPages() | Aufteilung nach Seitenbereichen in SplitDocument (@since 1.9.0) |
PageRange | contains(int $page), count(), toArray() | 1-basiertes Value Object für Seitenbereiche |
MergeResult / SplitResult | isValid(), count(), document(), totalOutputSize() | Ergebnisobjekte der Komposition |
VendorExtensionRegistry | Erweiterungsregistrierung | Registry für Entwicklererweiterungen (@since 2.2.0) |
ExtensionsDictionary | withEntry(), entries(), isEmpty(), toPdfDictionary() | Unveränderlicher Builder für das Extensions-Dictionary (@since 2.0.0) |
CollectionDictionary | toPdfDictionary() | Katalogeintrag für Portable Collection (@since 2.0.0) |
Führen Sie composer docs:generate-api-php -- --module=Document aus, um die vollständige PHPDoc-Tabelle zu generieren.
Codebeispiel — Schnellstart
Abschnitt betitelt „Codebeispiel — Schnellstart“Teilen Sie eine PDF-Datei in einseitige Dokumente auf und prüfen Sie das Ergebnis.
<?php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use NextPDF\Document\PageRange;use NextPDF\Document\PdfSplitter;
$splitter = new PdfSplitter();$result = $splitter->splitEvery(file_get_contents('/srv/in/report.pdf'), 1);
foreach (range(0, $result->count() - 1) as $index) { $segment = $result->document($index); file_put_contents("/srv/out/page-{$index}.pdf", $segment->pdfData);}
$singlePage = $splitter->extractPages( file_get_contents('/srv/in/report.pdf'), new PageRange(2, 4),);Codebeispiel — Produktion
Abschnitt betitelt „Codebeispiel — Produktion“Führen Sie mehrere PDFs unter einem expliziten Eingabebudget zusammen und prüfen Sie die Gültigkeit, bevor Sie die kombinierte Ausgabe schreiben.
<?php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use NextPDF\Document\PdfMerger;use NextPDF\Exception\PageLayoutException;
/** @var list<string> $sources Raw PDF byte strings to combine. */$sources = array_map( static fn (string $path): string => file_get_contents($path), glob('/srv/batch/*.pdf') ?: [],);
$merger = new PdfMerger();
try { // Bound the merge: at most 50 files, 100 MB total. $merged = $merger->merge($sources, maxFiles: 50, maxTotalBytes: 100_000_000);} catch (PageLayoutException $e) { throw new \RuntimeException('Merge rejected: empty or invalid input set.', previous: $e);}
if (!$merged->isValid()) { throw new \RuntimeException('Merged document failed structural validation.');}
file_put_contents('/srv/out/combined.pdf', $merged->pdfData);Sonderfälle & Stolpersteine
Abschnitt betitelt „Sonderfälle & Stolpersteine“PdfMerger::merge()undPdfSplitter::split()setzen Eingabegrenzen überResourceGuarddurch. Eine Eingabe mit zu vielen Dateien oder zu vielen Bytes löst eine Ausnahme aus, statt stillschweigend abgeschnitten zu werden. Stellen SiemaxFiles/maxTotalBytesbewusst auf Ihre Arbeitslast ein.- Eine leere Dateiliste oder eine leere Bereichsliste löst
PageLayoutExceptionaus — das sind Konfigurationsfehler, keine leeren Ergebnisse. PageRangeist 1-basiert und inklusiv. Diepages-Liste eines Blatt-DPartenthält 0-basierte Seitenindizes. Die beiden Abstraktionen verwenden unterschiedliche Indexbasen. Konvertieren Sie explizit, wenn Sie zwischen ihnen wechseln.DPartistreadonly. Wenn Sie einen anderen Baum benötigen, konstruieren Sie neue Knoten, statt einen bestehenden zu verändern.resolveWithPageObjects()liefert die Ganzzahl-Index-Fallback-Form nur, wenn die Seitenobjekt-Zuordnung leer ist. Verlassen Sie sich in der Produktionsausgabe nicht auf diesen Pfad.VendorExtensionRegistrylöst bei einem doppelten AnbieterpräfixVendorExtensionRegistryConflictExceptionaus. Registrieren Sie jedes Präfix nur einmal.
Leistung
Abschnitt betitelt „Leistung“Aufteilen und Zusammenführen sind linear in der Seitenzahl und werden vom Parsing und der Objektneunummerierung dominiert, nicht von der internen Buchführung des Moduls. Die standardmäßige Referenzarbeitslast bleibt innerhalb eines Budgets von 1500 ms Wandzeit / 64 MB Spitze. Große Zusammenführungen werden in erster Linie durch die Gesamtzahl der Eingabebytes begrenzt. Der maxTotalBytes-Schutz hält den Spitzenspeicher beschränkt. Das Reproduzierbarkeitsprofil ist structural: Eine zusammengeführte oder aufgeteilte PDF trägt einen frischen Trailer und ein frisches /ID, sodass zwei Läufe strukturell gleich, aber nicht byte-identisch sind.
Sicherheitshinweise
Abschnitt betitelt „Sicherheitshinweise“PdfMerger::merge() und PdfSplitter::split() verarbeiten nicht vertrauenswürdige PDF-Bytes. Beide leiten die Eingabe vor dem Parsing durch ResourceGuard::assertSize() / assertCount(), wodurch ein Denial of Service durch Dekomprimierungs- oder Objektzahl-Amplifikation begrenzt wird. Halten Sie die Argumente maxFiles, maxTotalBytes und maxBytes für das Deployment eng gefasst, statt sich auf die Standardwerte zu verlassen. Behandeln Sie jede Eingabe-PDF als feindselig. Führen Sie die Batch-Komposition in einem beschränkten Worker aus, wenn die Quellen von Benutzern bereitgestellt werden. Das Bedrohungsmodell der Engine zur Vertrauensgrenze finden Sie unter /modules/core/security/.
Konformität
Abschnitt betitelt „Konformität“Der DPart-Baum, den dieses Modul aufbaut, folgt dem Document-Part-Modell aus ISO 32000-2 §14.12, wobei die /Start- und /End-Einträge von Blättern gemäß derselben Klausel als indirekte Referenzen auf Seitenobjekte ausgegeben werden. Die zusammengeführte Ausgabe verwendet die in §7.7.3 definierte Seitenbaumknotenstruktur. Dies sind Implementierungsfakten, die von src/Document/ erzeugt und durch tests/Unit/Document/ (DPartTest, DPartRootTest, DPartPageRefTest, DocumentPdfMergerDeepTest, DocumentPageRangeParseDeepTest) geprüft werden. Sie sind keine Aussage über die End-to-End-Konformität mit PDF 2.0. Die vollständige Dokumentkonformität wird durch die Oracle- und Golden-Suites validiert, die in /modules/core/conformance/ beschrieben sind.
Siehe auch
Abschnitt betitelt „Siehe auch“- Core-Modul
- Writer-Modul — serialisiert den DPart-Baum und den Seitenbaum.
- Metadata-Modul — XMP, das zu DPM passt.
- Navigation-Modul
- Konformitätsüberblick
- Sicherheitsmodell der Engine