Pular para o conteúdo

Document: DParts, divisão, mesclagem e extensões de fornecedor

O módulo Document trabalha com arquivos Portable Document Format (PDF) completos, não com o conteúdo das páginas. Ele constrói a hierarquia de Document Part usada por fluxos de trabalho regulamentados para anexar metadados. Ele divide um PDF em segmentos por intervalo de páginas, mescla vários PDFs em um único arquivo e registra extensões de desenvolvedor no catálogo do documento.

Terminal window
composer require nextpdf/core:^3

Este módulo fica acima do conteúdo das páginas. Enquanto Graphics e Content emitem operadores, o Document atua no nível estrutural: árvores de páginas, catálogo do documento e árvore de Document Part.

Uma Document Part (DPart) é uma partição lógica em um PDF. A ISO 32000-2 define uma hierarquia de DPart cujos nós carregam Document Part Metadata (DPM). Um fluxo de trabalho regulamentado, como um fluxo farmacêutico, jurídico ou de arquivamento, pode associar metadados a um subintervalo de páginas em vez de ao arquivo como um todo — §14.12. DPart é um nó imutável readonly: uma folha referencia uma sequência contígua de índices de páginas, e um nó intermediário agrupa nós DPart filhos em uma árvore. DPartRoot é a raiz da árvore que o Writer serializa. As entradas /Start e /End de um nó folha são referências indiretas a objetos de página, e não inteiros de índice de página — §14.12. DPart::resolveWithPageObjects() resolve essas entradas em relação a um mapa índice-de-página→número-de-objeto fornecido pelo Writer e retorna a forma de referência /Start (e a opcional /End). Ele recorre à forma inteira apenas em caminhos de teste nos quais o mapa não está disponível.

PdfMerger e PdfSplitter formam a superfície de composição de documentos. PdfMerger combina objetos de página de vários PDFs de entrada, renumera os objetos para evitar colisões e reconstrói uma única árvore de páginas e uma única tabela de referência cruzada. A árvore de páginas que ela produz é um nó Pages balanceado com Kids e Count, além do modelo de atributos herdáveis que o PDF define para nós de árvore de páginas — §7.7.3. PdfSplitter faz o inverso: extrai intervalos de páginas para objetos SplitDocument independentes. PageRange é o objeto de valor consumido por ambas as classes. Ele é baseado em 1, valida seus limites e responde a contains(), count() e toArray().

VendorExtensionRegistry, ExtensionsDictionary e DeveloperExtensionEntry modelam o dicionário de extensões de desenvolvedor no catálogo do documento. Um engine usa esse dicionário para declarar um nível de extensão de fornecedor além da especificação base. O registro rejeita um novo registro conflitante do mesmo prefixo de fornecedor com VendorExtensionRegistryConflictException. CollectionDictionary e CollectionSort modelam uma entrada de catálogo de collection (coleção portátil ou portfólio) de PDF.

ClasseMétodos principaisFunção
DPartisLeaf(), hasMetadata(), resolveWithPageObjects(), write()Nó imutável de Document Part (@since 1.12.0)
DPartRootisEmpty(), write()Raiz da árvore de DPart que o Writer serializa (@since 1.12.0)
PdfMergermerge(array $pdfFiles, int $maxFiles = 100, int $maxTotalBytes = 200_000_000), append()Mesclagem de vários PDFs com renumeração de objetos (@since 1.9.0)
PdfSplittersplit(), splitEvery(), extractPages()Divisão por intervalo de páginas em SplitDocument (@since 1.9.0)
PageRangecontains(int $page), count(), toArray()Objeto de valor de intervalo de páginas baseado em 1
MergeResult / SplitResultisValid(), count(), document(), totalOutputSize()Objetos de resultado de composição
VendorExtensionRegistryregistro de extensõesRegistro de extensões de desenvolvedor (@since 2.2.0)
ExtensionsDictionarywithEntry(), entries(), isEmpty(), toPdfDictionary()Construtor imutável de dicionário de extensões (@since 2.0.0)
CollectionDictionarytoPdfDictionary()Entrada de catálogo de coleção portátil (@since 2.0.0)

Execute composer docs:generate-api-php -- --module=Document para gerar a tabela PHPDoc completa.

Divida um PDF em documentos de página única e, em seguida, inspecione o resultado.

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

Mescle vários PDFs dentro de um orçamento de entrada explícito e, em seguida, valide o resultado antes de gravar a saída combinada.

<?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() impõem limites de entrada por meio de ResourceGuard. Entradas com arquivos ou bytes demais lançam uma exceção em vez de truncar silenciosamente. Defina maxFiles / maxTotalBytes de forma deliberada para a carga de trabalho.
  • Uma lista de arquivos vazia ou uma lista de intervalos vazia lança PageLayoutException. Trate esses casos como erros de configuração, não como resultados vazios.
  • PageRange é baseado em 1 e inclusivo. Em um DPart folha, a lista pages contém índices de páginas baseados em 0. As duas abstrações usam bases de índice diferentes. Converta explicitamente quando passar de uma para a outra.
  • DPart é readonly. Para construir uma árvore diferente, crie novos nós em vez de alterar um nó existente. resolveWithPageObjects() retorna a forma alternativa de índice inteiro apenas quando o mapa de objetos de página está vazio. Não dependa desse caminho na saída de produção.
  • VendorExtensionRegistry lança VendorExtensionRegistryConflictException para um prefixo de fornecedor duplicado. Registre cada prefixo uma única vez.

A divisão e a mesclagem escalam linearmente com a contagem de páginas e são dominadas pela análise e pela renumeração de objetos, não pelo controle interno do módulo. A carga de trabalho de referência padrão cabe em um orçamento de 1500 ms de tempo de parede / 64 MB de pico. Mesclagens grandes são limitadas principalmente pelo total de bytes de entrada. A proteção maxTotalBytes mantém o pico de memória limitado. O perfil de reprodutibilidade é structural: um PDF mesclado ou dividido carrega um trailer e um /ID novos, de modo que duas execuções são estruturalmente iguais, mas não byte a byte idênticas.

PdfMerger::merge() e PdfSplitter::split() consomem bytes de PDF não confiáveis. Antes da análise, ambos passam a entrada por ResourceGuard::assertSize() / assertCount(), o que limita uma negação de serviço por amplificação de descompactação ou de contagem de objetos. Mantenha os argumentos maxFiles, maxTotalBytes e maxBytes restritos para a implantação em vez de depender dos valores padrão. Trate todo PDF de entrada como hostil. Quando as origens forem fornecidas pelo usuário, execute a composição em lote em um worker com recursos restritos. Consulte o modelo de ameaças do engine em /modules/core/security/ para conhecer a fronteira de confiança.

A árvore de DPart que este módulo constrói segue o modelo de Document Part da ISO 32000-2 §14.12, com as entradas folha /Start e /End emitidas como referências indiretas a objetos de página sob a mesma cláusula. A saída mesclada usa a estrutura de nó de árvore de páginas definida em §7.7.3. Estes são fatos de implementação produzidos por src/Document/ e exercitados por tests/Unit/Document/ (DPartTest, DPartRootTest, DPartPageRefTest, DocumentPdfMergerDeepTest, DocumentPageRangeParseDeepTest). Eles não constituem uma declaração de conformidade de ponta a ponta com o PDF 2.0. A conformidade do documento completo é validada pelas suítes oracle e golden descritas em /modules/core/conformance/.