Ga naar inhoud

Document: DParts, splitsen / samenvoegen en leveranciersuitbreidingen

De Document-module werkt met volledige Portable Document Format-bestanden (PDF), niet met pagina-inhoud. De module bouwt de Document Part-hiërarchie op waarmee gereguleerde workflows metadata koppelen. De module splitst een PDF in segmenten op basis van paginabereik, voegt meerdere PDF’s samen tot één bestand en registreert developer-extensies in de documentcatalogus.

Terminal window
composer require nextpdf/core:^3

Deze module werkt boven het niveau van pagina-inhoud. Waar Graphics en Content operatoren uitzenden, werkt Document op structureel niveau: paginabomen, de documentcatalogus en de Document Part-boom.

Een Document Part (DPart) is een logische partitie van een PDF. ISO 32000-2 definieert een DPart-hiërarchie waarvan de knooppunten Document Part Metadata (DPM) bevatten. Een gereguleerde workflow, bijvoorbeeld voor farmaceutische, juridische of archiveringsprocessen, kan metadata koppelen aan een reeks pagina’s in plaats van aan het hele bestand — §14.12. DPart is een onveranderbaar readonly-knooppunt: een blad verwijst naar een aaneengesloten reeks pagina-indexen, en een tussenliggend knooppunt groepeert onderliggende DPart-knooppunten tot een boom. DPartRoot is de wortel van de boom die de Writer serialiseert. De /Start- en /End-vermeldingen van een bladknooppunt zijn indirecte verwijzingen naar pagina-objecten, geen numerieke pagina-indexen — §14.12. DPart::resolveWithPageObjects() lost die vermeldingen op aan de hand van een door de writer aangeleverde mapping van pagina-index→objectnummer en geeft de verwijzingsvorm /Start (en optioneel /End) terug. De methode gebruikt de integervorm alleen als fallback op testpaden waar de mapping niet beschikbaar is.

PdfMerger en PdfSplitter vormen het oppervlak voor documentsamenstelling. PdfMerger combineert pagina-objecten uit meerdere invoer-PDF’s, hernummert objecten om botsingen te voorkomen en herbouwt één paginaboom en cross-reference-tabel. De paginaboom die de class produceert, is een gebalanceerd Pages-knooppunt met Kids en Count, plus het overerfbare attribuutmodel dat PDF definieert voor paginaboomknooppunten — §7.7.3. PdfSplitter doet het omgekeerde: de class extraheert paginabereiken naar zelfstandige SplitDocument-objecten. PageRange is het waardeobject dat door beide classes wordt gebruikt. Het is 1-gebaseerd, valideert zijn grenzen en biedt contains(), count() en toArray().

VendorExtensionRegistry, ExtensionsDictionary en DeveloperExtensionEntry modelleren de developer-extensions-dictionary in de documentcatalogus. Een engine gebruikt die dictionary om een leveranciersuitbreidingsniveau te declareren dat verder gaat dan de basisspecificatie. Het register weigert conflicterende herregistratie van hetzelfde leveranciersprefix met VendorExtensionRegistryConflictException. CollectionDictionary en CollectionSort modelleren een catalogusvermelding voor een PDF-collection (portable collection of portfolio).

ClassBelangrijkste methodenRol
DPartisLeaf(), hasMetadata(), resolveWithPageObjects(), write()Onveranderbaar Document Part-knooppunt (@since 1.12.0)
DPartRootisEmpty(), write()Wortel van de DPart-boom die de Writer serialiseert (@since 1.12.0)
PdfMergermerge(array $pdfFiles, int $maxFiles = 100, int $maxTotalBytes = 200_000_000), append()Samenvoegen van meerdere PDF’s met objecthernummering (@since 1.9.0)
PdfSplittersplit(), splitEvery(), extractPages()Splitsing op paginabereik naar SplitDocument (@since 1.9.0)
PageRangecontains(int $page), count(), toArray()1-gebaseerd waardeobject voor paginabereik
MergeResult / SplitResultisValid(), count(), document(), totalOutputSize()Resultaatobjecten van samenstelling
VendorExtensionRegistryregistratie van uitbreidingenRegister van developer-extensions (@since 2.2.0)
ExtensionsDictionarywithEntry(), entries(), isEmpty(), toPdfDictionary()Onveranderbare builder voor de extensions-dictionary (@since 2.0.0)
CollectionDictionarytoPdfDictionary()Catalogusvermelding voor portable collection (@since 2.0.0)

Voer composer docs:generate-api-php -- --module=Document uit om de volledige PHPDoc-tabel te genereren.

Splits een PDF in documenten van één pagina en inspecteer vervolgens het resultaat.

<?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),
);

Voeg meerdere PDF’s samen binnen een expliciet invoerbudget en valideer vervolgens het resultaat voordat de gecombineerde uitvoer wordt weggeschreven.

<?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);
  • PdfMerger::merge() en PdfSplitter::split() dwingen invoergrenzen af via ResourceGuard. Invoer met te veel bestanden of te veel bytes werpt een exception in plaats van stilzwijgend af te kappen. Kies maxFiles / maxTotalBytes bewust voor de werklast.
  • Een lege bestandenlijst of lege bereikenlijst werpt PageLayoutException. Behandel deze als configuratiefouten, niet als lege resultaten.
  • PageRange is 1-gebaseerd en inclusief. Bij een blad-DPart bevat de pages-lijst 0-gebaseerde pagina-indexen. De twee abstracties gebruiken verschillende indexbases. Converteer expliciet bij het wisselen tussen beide.
  • DPart is readonly. Construeer nieuwe knooppunten om een andere boom te bouwen, in plaats van een bestaand knooppunt te muteren. resolveWithPageObjects() geeft alleen de fallbackvorm met integerindex terug wanneer de pagina-objectmapping leeg is. Vertrouw niet op dat pad voor productie-uitvoer.
  • VendorExtensionRegistry werpt VendorExtensionRegistryConflictException bij een dubbel leveranciersprefix. Registreer elk prefix eenmaal.

Splitsen en samenvoegen schalen lineair met het aantal pagina’s en worden gedomineerd door parsing en objecthernummering, niet door de interne administratie van de module. De standaardreferentiewerklast past binnen een budget van 1500 ms wandkloktijd / 64 MB piek. Grote samenvoegingen worden vooral beperkt door het totale aantal invoerbytes. De maxTotalBytes-bewaking houdt het piekgeheugen begrensd. Het reproduceerbaarheidsprofiel is structural: een samengevoegde of gesplitste PDF krijgt een nieuwe trailer en /ID, zodat twee uitvoeringen structureel gelijk maar niet byte-identiek zijn.

PdfMerger::merge() en PdfSplitter::split() verwerken niet-vertrouwde PDF-bytes. Vóór het parsen leiden beide de invoer via ResourceGuard::assertSize() / assertCount(), die een denial of service door decompressie- of objectaantalsversterking begrenst. Stem de argumenten maxFiles, maxTotalBytes en maxBytes strak af op de deployment in plaats van te vertrouwen op de standaardwaarden. Behandel elke invoer-PDF als vijandig. Wanneer bronnen door de gebruiker worden aangeleverd, voer batchsamenstelling dan uit in een begrensde worker. Raadpleeg het dreigingsmodel van de engine in /modules/core/security/ voor de vertrouwensgrens.

De DPart-boom die deze module bouwt, volgt het Document Part-model in ISO 32000-2 §14.12, waarbij blad-/Start- en /End-vermeldingen volgens dezelfde clausule worden weggeschreven als indirecte verwijzingen naar pagina-objecten. Samengevoegde uitvoer gebruikt de structuur voor paginaboomknooppunten die is gedefinieerd in §7.7.3. Dit zijn implementatiefeiten die worden geproduceerd door src/Document/ en getest door tests/Unit/Document/ (DPartTest, DPartRootTest, DPartPageRefTest, DocumentPdfMergerDeepTest, DocumentPageRangeParseDeepTest). Ze vormen geen verklaring van end-to-end PDF 2.0-conformiteit. Volledige documentconformiteit wordt gevalideerd door de oracle- en golden-suites beschreven in /modules/core/conformance/.