Skip to content
getnextpdf.com

Pro edition

Document — Deep Reference

The Document module provides three Pro assembly primitives: page-range splitting, multi-document merge, and PDF Portfolio (Collection) dictionary construction. PdfSplitter extracts page ranges into standalone, structurally conformant PDFs and merges whole documents into one renumbered file. PdfPortfolio builds the Collection dictionary that presents embedded files with sortable schema columns. Every entry point bounds input size and object counts against hostile input.

This capability ships in NextPDF Pro (nextpdf/pro) and activates with a Pro-tier license envelope. A deployment without that entitlement does not load the capability’s classes. Compare editions and get a license.

All module types live in the NextPDF\Pro\Document namespace. PageRange and MergeResult are Core value objects from NextPDF\Document.

SymbolParametersDefault behaviorReturnsThrows or fails withNotes
PdfSplitter::split()string $pdfData, list<PageRange> $ranges, int $maxBytes = 100_000_000, int $maxRanges = 1000Builds one standalone PDF segment per rangeSplitResultInvalidArgumentException on a missing %PDF header; OverflowException on the size, range-count, or closure guardGuards run before any parsing
PdfSplitter::splitEvery()string $pdfData, int $pagesPerSegmentDerives contiguous N-page ranges; the last segment may be shorterSplitResultInvalidArgumentException when $pagesPerSegment < 1 or the header is missingDelegates to split() with default ceilings
PdfSplitter::extractPages()string $pdfData, PageRange $rangeReturns one range as standalone PDF bytesstringInvalidArgumentException on a missing header; OverflowException on the closure guardNo ceiling parameters on this path
PdfSplitter::mergeDocuments()list<string> $pdfs, int $maxInputs = 100, int $maxBytesEach = 100_000_000Merges inputs in order into one renumbered PDFMergeResultInvalidArgumentException on an empty list or non-PDF input; OverflowException on the count, per-input size, or closure guardSince 3.1.0; highest input version sets the output header
SplitResultreadonly $segments, $ranges, $totalPagesCarries raw segment bytes plus source metadatafinal readonly value object
SplitResult::count()Counts produced segmentsint
SplitResult::segment()int $indexReturns one segment’s bytesstringOutOfRangeException on an out-of-bounds indexZero-based index
PdfPortfolio::__construct()string $viewMode = 'tile'Validates the view mode at constructionInvalidArgumentException on a mode other than tile, detail, hidden
PdfPortfolio::addSchema()PortfolioField $fieldAppends a schema columnselfFluent
PdfPortfolio::addEntry()PortfolioEntry $entryAppends a file entryselfFluent
PdfPortfolio::getSchema()Returns accumulated schema fieldslist<PortfolioField>
PdfPortfolio::getEntries()Returns accumulated file entrieslist<PortfolioEntry>
PdfPortfolio::count()Counts file entriesint
PdfPortfolio::generateCollectionDictionary()Emits the Collection dictionary stringstringSchema and sort blocks appear only when fields exist
PortfolioEntry$filename, $data, $description = '', $mimeType = 'application/octet-stream', $customFields = []Immutable file-entry value objectsize() returns the data byte length
PortfolioField$name, PortfolioFieldType $type, $displayName = '', $order = 0, $visible = trueImmutable schema-column value objecteffectiveDisplayName() falls back to $name
PortfolioFieldTypeString enum: Text, Date, Number, FileName, Description, Size, ModDate, CreationDateMaps each case to a PDF /Subtype via pdfSubtype()string (S, D, N, F, Desc)Date-like cases share subtype D; numeric cases share N
PortfolioFieldType::pdfSubtype()Maps the case to its PDF Collection field /Subtype per ISO 32000-2:2020 Table 155string (S, D, N, F, or Desc)

Entry-point signatures:

public function split(string $pdfData, array $ranges, int $maxBytes = 100_000_000, int $maxRanges = 1000): SplitResult
public function mergeDocuments(
array $pdfs,
int $maxInputs = 100,
int $maxBytesEach = 100_000_000,
): MergeResult
public function __construct(
private readonly string $viewMode = 'tile',
)
public function generateCollectionDictionary(): string

Splitting and merging share one object-graph pipeline:

  • Input must begin with the %PDF header. Size and count guards run before parsing and raise OverflowException on breach.
  • Leaf pages are detected by scanning for page-object markers; page-tree nodes are excluded from the count.
  • The parser indexes every uncompressed indirect object with a stream-aware terminator scan. The first occurrence of an object id wins, so incremental-update overrides are not applied.
  • Inheritable page-tree attributes (/Resources, /MediaBox, /CropBox, /Rotate) are materialised onto each extracted page by walking its /Parent chain, so segments are self-contained.
  • Each page’s transitive indirect-reference closure is collected, excluding the /Parent back-edge, and renumbered into a fresh contiguous id space.
  • The serializer emits the header, Catalog, Pages tree, page objects, and closure objects, then a cross-reference table with byte-accurate offsets and a startxref pointing at the xref keyword.
  • mergeDocuments repeats the pipeline per input into one shared id space. The highest input PDF version sets the output header. It is the conformant replacement for the disabled Core merger, which stays fail-closed.
  • Output is deterministic. No timestamps or random identifiers are emitted, so identical input yields identical bytes.

Portfolio assembly:

  • The constructor validates the view mode. The emitted /View token is /T, /D, or /H for tile, detail, and hidden respectively.
  • generateCollectionDictionary() emits /Type /Collection, the /View token, a /Schema block when fields exist, and a /Sort directive on the first schema field, ascending.
  • Each schema field emits /Subtype (from pdfSubtype()), /N (escaped display name), /O (order), and /V (visibility).
  • Field names are sanitized to valid PDF name tokens; non-word characters become underscores. String values are escaped as PDF literal strings.
  • File entries are exposed through getEntries() for embedding by the writing layer. The Collection dictionary itself carries view, schema, and sort only.
  • A range that matches no pages yields a minimal one-page segment (612 x 792 MediaBox), not an error.
  • A document with no detectable page markers is counted as one page.
  • Pages stored inside object streams are not detected; only uncompressed indirect objects participate in extraction.
  • When duplicate object ids exist, the lowest-offset revision is used; later incremental-update revisions are ignored.
  • The per-segment reference closure is capped at 50,000 objects; a maliciously self-referential or fan-out graph raises OverflowException.
  • Default ceilings: 100 MB input, 1,000 ranges, 100 merge inputs. All are caller-tunable per call.
  • splitEvery() rejects a segment size below 1 with InvalidArgumentException.
  • SplitResult::segment() rejects an out-of-bounds index with OutOfRangeException.
  • Two schema field names that differ only in punctuation sanitize to the same dictionary key; the later field silently shadows the earlier one in the emitted schema.
  • This module performs no cryptographic operations; FIPS mode does not alter its behavior.

Segment and merge output follows the page-object model of ISO 32000-2; the source annotates the relevant clauses. Externally checkable claims:

  • Trailer layout, startxref byte offset, and the %%EOF terminator follow ISO 32000-2:2020, §7.5.5 — reference ef0f2a4b563b84f81b3e6428612bc47c510d94fc8096849d339abf0f3247d845.
  • Collection dictionary /View values (/T, /D, /H) follow ISO 32000-2:2020, §12.3.5 — reference 5cefaaeb40f3ff98e3aba135ac57c9424a05c43144c1b9b5156bfd4295e08ddd.
  • Collection field /Subtype, /N, /O, and /V entries follow ISO 32000-2:2020, §12.3.5 (collection field dictionary) — reference 6300fbfdc8a913a8dc6f6ae34eff99f2bd03c4313a77777cdd5a8dd856d9537a.

These statements describe implemented capability verified by the module’s tests.

  • All module classes are final; the result and value-object types are readonly. The splitter and Portfolio types date from 1.9.0; mergeDocuments() was added in 3.1.0.
  • PageRange and MergeResult are Core types, so call sites remain edition-portable.
  • Segment trailers carry /Size and /Root only; no /ID file identifier or /Info dictionary is emitted.
  • For incremental-update or signing workflows, hand segment bytes to the Writer module rather than post-editing them in place.
  • The module logs no document content.

This page documents externally observable behavior and the supported public API surface only. Internal namespace paths, helper classes, mechanism tables, runbook filenames, and ticket prefixes are out of scope.