Pro edition
Merge
At a glance
Section titled “At a glance”NextPDF\Pro\Merge\SmartMerger merges several PDFs into one, then applies
Pro enhancements: a consolidated bookmark tree from per-input labels,
content-hash page deduplication, and per-input page-range selection. Base
document assembly runs through the Pro object-graph merge engine. It
renumbers every input into one object space and writes a real
cross-reference table.
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.
The Merge classes are available whenever the Pro package is installed. No runtime capability flag gates this module.
Install
Section titled “Install”composer require nextpdf/pro:^3Conceptual overview
Section titled “Conceptual overview”SmartMerger accepts a list of MergeInput value objects. Each input
carries the source PDF bytes, an optional list of page ranges, and an
optional label. Inputs with page ranges are reduced to the selected pages
before merging. The combined document is produced by the Pro object-graph
merge engine, which renumbers every input into one contiguous object space
and emits a real cross-reference table; the Pro layer then adds the
requested enhancements.
SmartMergeConfig controls the enhancements:
- Bookmark consolidation inserts one outline entry per labeled input,
pointing at the start of that input’s section. This follows the document
catalog
/Outlinesmodel in ISO 32000-2:2020 §7.7.2. - Page deduplication removes byte-identical duplicate pages across inputs, compared by content hash.
- Link rewriting scans for internal
GoToactions in the merged output.
SmartMergeResult reports the merged bytes plus statistics: total pages,
source count, output size, bookmarks added, duplicates removed, links
detected, and the ordered input labels.
Why it works this way
Section titled “Why it works this way”Merging PDFs is not byte concatenation: each input carries its own object
numbers, cross-reference table, and page tree, so a naive splice loads in no
conforming reader. SmartMerger therefore delegates base assembly to the Pro
object-graph engine (PdfSplitter::mergeDocuments()), which renumbers every
input into one contiguous object space, rebuilds a single page tree, and emits
a real cross-reference table with true byte offsets. The Pro enhancements —
bookmark consolidation, deduplication, and link detection — then layer on that
verified output rather than reimplementing assembly. Whole-document
deduplication and detection-only link handling are deliberate scope boundaries
that keep the merge deterministic and safe on untrusted input.
Design background: The anatomy of a PDF file.
Behavior contract
Section titled “Behavior contract”- Input. A non-empty list of
MergeInput. An empty list raisesInvalidArgumentException. Input count and per-input byte size are bounded bySmartMergeConfig(maxInputs,maxBytesPerInput). - Output. A
SmartMergeResult.isValid()is true when the output begins with the%PDFheader. - Bookmark consolidation adds one entry per input that has a non-empty
label, when
consolidateBookmarksis enabled. - Deduplication is opt-in (
deduplicatePages, default off) and matches whole pages by content hash, not visually similar pages. - Link rewriting in the current release detects and counts internal
GoToactions; it does not perform full cross-document destination re-resolution. TreatlinksRewrittenas a detection count. - Determinism. For identical inputs and configuration the merged byte stream is stable, subject to the Pro merge engine’s documented determinism profile.
Public API surface
Section titled “Public API surface”| Type | Kind | Key members |
|---|---|---|
NextPDF\Pro\Merge\SmartMerger | final class | __construct(?PdfMerger $coreMerger = null, ?PdfSplitter $splitter = null), merge(array $inputs, SmartMergeConfig $config = new SmartMergeConfig()): SmartMergeResult |
NextPDF\Pro\Merge\MergeInput | final readonly class | __construct(string $pdfData, array $pageRanges = [], string $label = ''), hasPageRanges(): bool |
NextPDF\Pro\Merge\SmartMergeConfig | final readonly class | __construct(bool $consolidateBookmarks = true, bool $deduplicatePages = false, bool $rewriteLinks = true, int $maxInputs = 100, int $maxBytesPerInput = 100_000_000), default(), basic() |
NextPDF\Pro\Merge\SmartMergeResult | final readonly class | string $pdfData, int $totalPages, int $sourceCount, int $bookmarksAdded, int $duplicatesRemoved, int $linksRewritten, array $inputLabels, isValid(): bool, hasOptimizations(): bool |
Code sample — Quick start
Section titled “Code sample — Quick start”<?php
declare(strict_types=1);
use NextPDF\Pro\Merge\MergeInput;use NextPDF\Pro\Merge\SmartMerger;
$result = (new SmartMerger())->merge([ new MergeInput(file_get_contents('cover.pdf'), label: 'Cover'), new MergeInput(file_get_contents('body.pdf'), label: 'Body'),]);
echo $result->totalPages, " pages, ", $result->bookmarksAdded, " bookmarks\n";Code sample — Production
Section titled “Code sample — Production”<?php
declare(strict_types=1);
use NextPDF\Pro\Merge\MergeInput;use NextPDF\Pro\Merge\SmartMergeConfig;use NextPDF\Pro\Merge\SmartMerger;
function assemblePacket(array $sections): string{ $inputs = []; foreach ($sections as $label => $bytes) { $inputs[] = new MergeInput($bytes, label: (string) $label); }
$config = new SmartMergeConfig( consolidateBookmarks: true, deduplicatePages: true, rewriteLinks: false, maxInputs: 50, );
$result = (new SmartMerger())->merge($inputs, $config);
if (! $result->isValid()) { throw new RuntimeException('merge produced invalid output'); }
return $result->pdfData;}Edge cases & gotchas
Section titled “Edge cases & gotchas”- A single input is valid and merges to a normalized copy of that document.
- Deduplication compares whole-page byte content; pages that differ only by metadata or object numbering are not treated as duplicates.
- Page-range selection on an input is applied before merge ordering.
linksRewrittenis a detected-action count, not a guarantee that every cross-document link target was re-pointed.
Performance
Section titled “Performance”Cost is dominated by the Pro merge engine and scales with total input bytes
and page count. Deduplication adds one content hash per page. The
performance_budget front-matter is the per-merge reference.
Security notes
Section titled “Security notes”Input count and per-input size are bounded by SmartMergeConfig to limit
resource exhaustion from hostile inputs. Merging does not execute embedded
document scripts. See the Core security model for byte-stream parsing
hardening.
Conformance
Section titled “Conformance”| Claim | Spec clause | Status |
|---|---|---|
Consolidated bookmarks via /Outlines | ISO 32000-2:2020 §7.7.2 | Verified (unit suite) |
| Content-hash page deduplication | — | Verified (unit suite) |
| Full cross-document link re-resolution | — | Not supported (detection only) |
Core fallback / alternative
Section titled “Core fallback / alternative”For basic concatenation without Pro consolidation, the open-source Core
NextPDF\Document\PdfMerger is the supported standalone path. SmartMerger
does not delegate to it; the Pro merge runs on its own object-graph engine.
See /modules/core/document/.
Enterprise boundary note
Section titled “Enterprise boundary note”This module performs structural merging. It does not perform legal-hold assembly, redaction, or evidentiary chain-of-custody packaging; those are not provided here.
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.