Salta ai contenuti

Document: DPart, split / merge ed estensioni vendor

Il modulo Document opera su interi documenti PDF anziché sul contenuto delle singole pagine. Costruisce la gerarchia delle parti di documento a cui i flussi regolamentati associano i metadati, suddivide un PDF in segmenti per intervalli di pagine, unisce più PDF in un unico file e registra le estensioni sviluppatore nel catalogo del documento.

Terminal window
composer require nextpdf/core:^3

Questo modulo si colloca al di sopra del contenuto delle pagine. Mentre Graphics e Content emettono operatori, Document lavora a livello strutturale: alberi delle pagine, catalogo del documento e albero delle parti di documento.

Una parte di documento (DPart) è una partizione logica di un PDF. ISO 32000-2 definisce una gerarchia DPart i cui nodi contengono i metadati della parte di documento (DPM). Un flusso regolamentato, ad esempio farmaceutico, legale o archivistico, può associare i metadati a un sottointervallo di pagine anziché all’intero file — §14.12. DPart è un nodo immutabile readonly: una foglia fa riferimento a una sequenza contigua di indici di pagina; un nodo intermedio raggruppa nodi figli DPart in un albero. DPartRoot è la radice dell’albero serializzato dal Writer. Le voci /Start e /End di un nodo foglia sono riferimenti indiretti a oggetti pagina, non indici di pagina espressi come interi — §14.12. DPart::resolveWithPageObjects() esegue questa risoluzione rispetto a una mappa indice-di-pagina→numero-di-oggetto fornita dal writer e restituisce la forma di riferimento /Start (e, facoltativamente, /End). Usa la forma intera solo nei percorsi di test in cui la mappa non è disponibile.

PdfMerger e PdfSplitter costituiscono la superficie di composizione del documento. PdfMerger combina gli oggetti pagina di più PDF in ingresso, rinumera gli oggetti per evitare collisioni e ricostruisce un unico albero delle pagine e un’unica tabella di riferimenti incrociati. L’albero delle pagine che produce è un nodo Pages bilanciato, con Kids e Count, e con il modello di attributi ereditabili che il PDF definisce per i nodi dell’albero delle pagine — §7.7.3. PdfSplitter compie l’operazione inversa: estrae intervalli di pagine in oggetti SplitDocument autonomi. PageRange è l’oggetto valore usato da entrambi. Usa una numerazione in base 1, convalida i propri limiti ed espone contains(), count() e toArray().

VendorExtensionRegistry, ExtensionsDictionary e DeveloperExtensionEntry modellano il dizionario delle estensioni sviluppatore nel catalogo del documento — il meccanismo con cui un motore dichiara un livello di estensione vendor al di sopra della specifica di base. Il registro rifiuta una nuova registrazione in conflitto per lo stesso prefisso vendor con VendorExtensionRegistryConflictException. CollectionDictionary e CollectionSort modellano una voce di catalogo per una collezione PDF (collezione portabile / portfolio).

ClasseMetodi principaliRuolo
DPartisLeaf(), hasMetadata(), resolveWithPageObjects(), write()Nodo immutabile della parte di documento (@since 1.12.0)
DPartRootisEmpty(), write()Radice dell’albero DPart che il Writer serializza (@since 1.12.0)
PdfMergermerge(array $pdfFiles, int $maxFiles = 100, int $maxTotalBytes = 200_000_000), append()Unione multi-PDF con rinumerazione degli oggetti (@since 1.9.0)
PdfSplittersplit(), splitEvery(), extractPages()Suddivisione per intervalli di pagine in SplitDocument (@since 1.9.0)
PageRangecontains(int $page), count(), toArray()Oggetto valore per intervalli di pagine in base 1
MergeResult / SplitResultisValid(), count(), document(), totalOutputSize()Oggetti risultato della composizione
VendorExtensionRegistryregistrazione delle estensioniRegistro delle estensioni sviluppatore (@since 2.2.0)
ExtensionsDictionarywithEntry(), entries(), isEmpty(), toPdfDictionary()Costruttore immutabile del dizionario delle estensioni (@since 2.0.0)
CollectionDictionarytoPdfDictionary()Voce di catalogo per collezione portabile (@since 2.0.0)

Eseguire composer docs:generate-api-php -- --module=Document per ottenere la tabella PHPDoc completa.

Suddividere un PDF in documenti a pagina singola e ispezionare il risultato.

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

Unire più PDF nel rispetto di un budget di input esplicito, quindi verificarne la validità prima di scrivere l’output combinato.

<?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() e PdfSplitter::split() impongono i limiti di input tramite ResourceGuard. Un input che supera i limiti di numero o dimensione genera un’eccezione anziché troncare silenziosamente. Impostare maxFiles / maxTotalBytes in modo intenzionale per il proprio carico di lavoro.
  • Un elenco di file vuoto o un elenco di intervalli vuoto genera PageLayoutException — si tratta di errori di configurazione, non di risultati vuoti.
  • PageRange usa una numerazione in base 1 ed è inclusivo. L’elenco pages di un DPart foglia contiene indici di pagina in base 0. Le due astrazioni non condividono la stessa base di indicizzazione. Convertire esplicitamente quando si passa dall’una all’altra.
  • DPart è readonly. Costruire un albero diverso significa creare nuovi nodi, non mutarne uno esistente. resolveWithPageObjects() restituisce la forma di ripiego con indice intero solo quando la mappa degli oggetti pagina è vuota. Non fare affidamento su questo percorso nell’output di produzione.
  • VendorExtensionRegistry genera VendorExtensionRegistryConflictException in presenza di un prefisso vendor duplicato. Registrare ogni prefisso una sola volta.

La suddivisione e l’unione sono lineari rispetto al numero di pagine e dominate dall’analisi e dalla rinumerazione degli oggetti, non dalla gestione interna del modulo. Il carico di lavoro di riferimento predefinito rientra in un budget di 1500 ms di tempo reale / 64 MB di picco. Le unioni di grandi dimensioni sono vincolate principalmente dal numero totale di byte in ingresso. La protezione maxTotalBytes serve a mantenere limitato il picco di memoria. Il profilo di riproducibilità è structural: un PDF unito o suddiviso contiene un nuovo trailer e un nuovo /ID, perciò due esecuzioni sono strutturalmente uguali ma non identiche byte per byte.

PdfMerger::merge() e PdfSplitter::split() elaborano byte PDF non attendibili. Entrambi sottopongono l’input a ResourceGuard::assertSize() / assertCount() prima dell’analisi, limitando il rischio di denial of service tramite amplificazione della decompressione o numero di oggetti. Mantenere restrittivi gli argomenti maxFiles, maxTotalBytes e maxBytes per il deployment anziché affidarsi ai valori predefiniti. Trattare ogni PDF in ingresso come ostile. Eseguire la composizione batch in un worker vincolato quando le origini sono fornite dall’utente. Per il confine di fiducia, consultare il modello di minaccia del motore in /modules/core/security/.

L’albero DPart costruito da questo modulo segue il modello di parte di documento di ISO 32000-2 §14.12, con le voci /Start e /End delle foglie emesse come riferimenti indiretti a oggetti pagina secondo la stessa clausola. L’output unito utilizza la struttura dei nodi dell’albero delle pagine definita nel §7.7.3. Si tratta di proprietà implementative prodotte da src/Document/ ed esercitate da tests/Unit/Document/ (DPartTest, DPartRootTest, DPartPageRefTest, DocumentPdfMergerDeepTest, DocumentPageRangeParseDeepTest). Non costituiscono una dichiarazione di conformità PDF 2.0 end-to-end. La conformità dell’intero documento è convalidata dalle suite oracle e golden descritte in /modules/core/conformance/.