Pro edition
Document — Deep Reference
At a glance
Section titled “At a glance”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.
Availability & licensing
Section titled “Availability & licensing”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.
Public API surface
Section titled “Public API surface”All module types live in the NextPDF\Pro\Document namespace. PageRange and MergeResult are Core value objects from NextPDF\Document.
| Symbol | Parameters | Default behavior | Returns | Throws or fails with | Notes |
|---|---|---|---|---|---|
PdfSplitter::split() | string $pdfData, list<PageRange> $ranges, int $maxBytes = 100_000_000, int $maxRanges = 1000 | Builds one standalone PDF segment per range | SplitResult | InvalidArgumentException on a missing %PDF header; OverflowException on the size, range-count, or closure guard | Guards run before any parsing |
PdfSplitter::splitEvery() | string $pdfData, int $pagesPerSegment | Derives contiguous N-page ranges; the last segment may be shorter | SplitResult | InvalidArgumentException when $pagesPerSegment < 1 or the header is missing | Delegates to split() with default ceilings |
PdfSplitter::extractPages() | string $pdfData, PageRange $range | Returns one range as standalone PDF bytes | string | InvalidArgumentException on a missing header; OverflowException on the closure guard | No ceiling parameters on this path |
PdfSplitter::mergeDocuments() | list<string> $pdfs, int $maxInputs = 100, int $maxBytesEach = 100_000_000 | Merges inputs in order into one renumbered PDF | MergeResult | InvalidArgumentException on an empty list or non-PDF input; OverflowException on the count, per-input size, or closure guard | Since 3.1.0; highest input version sets the output header |
SplitResult | readonly $segments, $ranges, $totalPages | Carries raw segment bytes plus source metadata | — | — | final readonly value object |
SplitResult::count() | — | Counts produced segments | int | — | — |
SplitResult::segment() | int $index | Returns one segment’s bytes | string | OutOfRangeException on an out-of-bounds index | Zero-based index |
PdfPortfolio::__construct() | string $viewMode = 'tile' | Validates the view mode at construction | — | InvalidArgumentException on a mode other than tile, detail, hidden | — |
PdfPortfolio::addSchema() | PortfolioField $field | Appends a schema column | self | — | Fluent |
PdfPortfolio::addEntry() | PortfolioEntry $entry | Appends a file entry | self | — | Fluent |
PdfPortfolio::getSchema() | — | Returns accumulated schema fields | list<PortfolioField> | — | — |
PdfPortfolio::getEntries() | — | Returns accumulated file entries | list<PortfolioEntry> | — | — |
PdfPortfolio::count() | — | Counts file entries | int | — | — |
PdfPortfolio::generateCollectionDictionary() | — | Emits the Collection dictionary string | string | — | Schema and sort blocks appear only when fields exist |
PortfolioEntry | $filename, $data, $description = '', $mimeType = 'application/octet-stream', $customFields = [] | Immutable file-entry value object | — | — | size() returns the data byte length |
PortfolioField | $name, PortfolioFieldType $type, $displayName = '', $order = 0, $visible = true | Immutable schema-column value object | — | — | effectiveDisplayName() falls back to $name |
PortfolioFieldType | String enum: Text, Date, Number, FileName, Description, Size, ModDate, CreationDate | Maps 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 155 | string (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,): MergeResultpublic function __construct( private readonly string $viewMode = 'tile',)
public function generateCollectionDictionary(): stringBehavior contract
Section titled “Behavior contract”Splitting and merging share one object-graph pipeline:
- Input must begin with the
%PDFheader. Size and count guards run before parsing and raiseOverflowExceptionon 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/Parentchain, so segments are self-contained. - Each page’s transitive indirect-reference closure is collected, excluding the
/Parentback-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
startxrefpointing at thexrefkeyword. mergeDocumentsrepeats 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
/Viewtoken is/T,/D, or/Hfor tile, detail, and hidden respectively. generateCollectionDictionary()emits/Type /Collection, the/Viewtoken, a/Schemablock when fields exist, and a/Sortdirective on the first schema field, ascending.- Each schema field emits
/Subtype(frompdfSubtype()),/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.
Edge cases & failure modes
Section titled “Edge cases & failure modes”- 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 withInvalidArgumentException.SplitResult::segment()rejects an out-of-bounds index withOutOfRangeException.- 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.
Conformance
Section titled “Conformance”Segment and merge output follows the page-object model of ISO 32000-2; the source annotates the relevant clauses. Externally checkable claims:
- Trailer layout,
startxrefbyte offset, and the%%EOFterminator follow ISO 32000-2:2020, §7.5.5 — referenceef0f2a4b563b84f81b3e6428612bc47c510d94fc8096849d339abf0f3247d845. - Collection dictionary
/Viewvalues (/T,/D,/H) follow ISO 32000-2:2020, §12.3.5 — reference5cefaaeb40f3ff98e3aba135ac57c9424a05c43144c1b9b5156bfd4295e08ddd. - Collection field
/Subtype,/N,/O, and/Ventries follow ISO 32000-2:2020, §12.3.5 (collection field dictionary) — reference6300fbfdc8a913a8dc6f6ae34eff99f2bd03c4313a77777cdd5a8dd856d9537a.
These statements describe implemented capability verified by the module’s tests.
Development notes
Section titled “Development notes”- All module classes are
final; the result and value-object types arereadonly. The splitter and Portfolio types date from 1.9.0;mergeDocuments()was added in 3.1.0. PageRangeandMergeResultare Core types, so call sites remain edition-portable.- Segment trailers carry
/Sizeand/Rootonly; no/IDfile identifier or/Infodictionary 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.
Publication boundary
Section titled “Publication boundary”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.