Document: DParts, split / merge y extensiones de proveedor
Visión general
Sección titulada «Visión general»El módulo Document trabaja sobre documentos PDF completos, en vez de sobre el contenido de las páginas. Permite construir la jerarquía de partes del documento a la que los flujos de trabajo regulados adjuntan metadatos, dividir un PDF en segmentos por rangos de páginas, combinar varios PDF en uno solo y registrar extensiones de desarrollador en el catálogo del documento.
Instalación
Sección titulada «Instalación»composer require nextpdf/core:^3Descripción conceptual
Sección titulada «Descripción conceptual»Este módulo se sitúa por encima del contenido de las páginas. Mientras Graphics y Content emiten operadores, Document trabaja a nivel estructural: árboles de páginas, catálogo del documento y árbol de partes del documento.
Una parte del documento (DPart) es una partición lógica de un PDF. ISO 32000-2 define una jerarquía DPart cuyos nodos contienen metadatos de partes del documento (DPM). Un flujo de trabajo regulado (por ejemplo, farmacéutico, jurídico o de archivo) puede asociar metadatos a un subrango de páginas en lugar de a todo el archivo — §14.12. DPart es un nodo inmutable readonly: una hoja referencia una secuencia contigua de índices de página; un nodo intermedio agrupa nodos DPart hijos en un árbol. DPartRoot es la raíz del árbol que serializa el Writer. Las entradas /Start y /End de un nodo hoja son referencias indirectas a objetos de página, no enteros de índice de página — §14.12. DPart::resolveWithPageObjects() realiza esa resolución frente a un mapa índice-de-página→número-de-objeto suministrado por el escritor y devuelve la forma de referencia de /Start (y, opcionalmente, de /End). Solo recurre a la forma entera en rutas de prueba donde el mapa no está disponible.
PdfMerger y PdfSplitter constituyen la superficie para componer documentos. PdfMerger combina objetos de página de varios PDF de entrada, renumera los objetos para evitar colisiones y reconstruye un único árbol de páginas y una tabla de referencias cruzadas. El árbol de páginas resultante es un nodo Pages equilibrado, con Kids y Count, y el modelo de atributos heredables que PDF define para los nodos del árbol de páginas — §7.7.3. PdfSplitter realiza la operación inversa: extrae rangos de páginas en objetos SplitDocument independientes. PageRange es el objeto de valor que consumen ambos. Usa base 1, valida sus límites y responde a contains(), count() y toArray().
VendorExtensionRegistry, ExtensionsDictionary y DeveloperExtensionEntry modelan el diccionario de extensiones de desarrollador en el catálogo del documento: el mecanismo por el cual un motor declara un nivel de extensión de proveedor más allá de la especificación base. El registro rechaza un nuevo registro conflictivo del mismo prefijo de proveedor con VendorExtensionRegistryConflictException. CollectionDictionary y CollectionSort modelan una entrada de colección PDF en el catálogo (colección portátil / portafolio).
Superficie de la API
Sección titulada «Superficie de la API»| Clase | Métodos clave | Función |
|---|---|---|
DPart | isLeaf(), hasMetadata(), resolveWithPageObjects(), write() | Nodo de parte del documento inmutable (@since 1.12.0) |
DPartRoot | isEmpty(), write() | Raíz del árbol DPart que el Writer serializa (@since 1.12.0) |
PdfMerger | merge(array $pdfFiles, int $maxFiles = 100, int $maxTotalBytes = 200_000_000), append() | Combinación de varios PDF con renumeración de objetos (@since 1.9.0) |
PdfSplitter | split(), splitEvery(), extractPages() | División por rangos de páginas en SplitDocument (@since 1.9.0) |
PageRange | contains(int $page), count(), toArray() | Objeto de valor de rango de páginas basado en 1 |
MergeResult / SplitResult | isValid(), count(), document(), totalOutputSize() | Objetos de resultado de composición |
VendorExtensionRegistry | registro de extensiones | Registro de extensiones de desarrollador (@since 2.2.0) |
ExtensionsDictionary | withEntry(), entries(), isEmpty(), toPdfDictionary() | Constructor inmutable del diccionario de extensiones (@since 2.0.0) |
CollectionDictionary | toPdfDictionary() | Entrada de catálogo de colección portátil (@since 2.0.0) |
Ejecutar composer docs:generate-api-php -- --module=Document para obtener la tabla completa de PHPDoc.
Ejemplo de código — Inicio rápido
Sección titulada «Ejemplo de código — Inicio rápido»Dividir un PDF en documentos de una sola página e inspeccionar el 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),);Ejemplo de código — Producción
Sección titulada «Ejemplo de código — Producción»Combinar varios PDF con un presupuesto de entrada explícito y, a continuación, comprobar la validez antes de escribir la salida 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);Casos límite y trampas
Sección titulada «Casos límite y trampas»PdfMerger::merge()yPdfSplitter::split()aplican límites a la entrada medianteResourceGuard. Una entrada que excede el recuento o el tamaño provoca un error en lugar de truncarse silenciosamente. AjustarmaxFiles/maxTotalBytesdeliberadamente según la carga de trabajo.- Una lista de archivos vacía o una lista de rangos vacía provoca
PageLayoutException: son errores de configuración, no resultados vacíos. PageRangeusa base 1 y es inclusivo. La listapagesde unDParthoja contiene índices de página basados en 0. Las dos abstracciones no comparten la misma base de índice. Convertir los índices explícitamente al pasar de una a otra.DPartesreadonly. Construir un árbol diferente significa crear nodos nuevos, no mutar uno existente.resolveWithPageObjects()devuelve la forma alternativa de índice entero solo cuando el mapa de objetos de página está vacío. No depender de esa ruta en la salida de producción.VendorExtensionRegistryprovocaVendorExtensionRegistryConflictExceptionante un prefijo de proveedor duplicado. Registrar cada prefijo una sola vez.
Rendimiento
Sección titulada «Rendimiento»La división y la combinación son lineales con respecto al número de páginas y están dominadas por el análisis y la renumeración de objetos, no por la gestión interna del módulo. La carga de trabajo de referencia predeterminada se mantiene dentro de un presupuesto de 1500 ms de tiempo real / 64 MB de pico. Las combinaciones grandes están limitadas principalmente por el total de bytes de entrada. La protección maxTotalBytes existe para mantener acotado el pico de memoria. El perfil de reproducibilidad es structural: un PDF combinado o dividido lleva un tráiler y un /ID nuevos, por lo que dos ejecuciones son estructuralmente iguales, pero no idénticas byte a byte.
Notas de seguridad
Sección titulada «Notas de seguridad»PdfMerger::merge() y PdfSplitter::split() procesan bytes de PDF no confiables. Ambos pasan la entrada por ResourceGuard::assertSize() / assertCount() antes de analizarla, lo que acota el riesgo de denegación de servicio por amplificación de descompresión o por recuento de objetos. Mantener ajustados los argumentos maxFiles, maxTotalBytes y maxBytes según el despliegue, en lugar de depender de los valores predeterminados. Tratar cada PDF de entrada como hostil. Ejecutar la composición por lotes en un trabajador restringido cuando las fuentes provengan del usuario. Consultar el modelo de amenazas del motor en /modules/core/security/ para conocer el límite de confianza.
Conformidad
Sección titulada «Conformidad»El árbol DPart que construye este módulo sigue el modelo de partes del documento de ISO 32000-2 §14.12, con las entradas hoja /Start y /End emitidas como referencias indirectas a objetos de página según la misma cláusula. La salida combinada utiliza la estructura de nodo del árbol de páginas definida en §7.7.3. Estos son hechos de implementación producidos por src/Document/ y ejercitados por tests/Unit/Document/ (DPartTest, DPartRootTest, DPartPageRefTest, DocumentPdfMergerDeepTest, DocumentPageRangeParseDeepTest). No constituyen una declaración de conformidad integral con PDF 2.0. Las suites de oráculo y de referencia descritas en /modules/core/conformance/ validan la conformidad del documento completo.
Véase también
Sección titulada «Véase también»- Módulo Core
- Módulo Writer — serializa el árbol DPart y el árbol de páginas.
- Módulo Metadata — XMP que se empareja con DPM.
- Módulo Navigation
- Resumen de conformidad
- Modelo de seguridad del motor