Ir al contenido

Writer: serializador de PDF 2.0 + xref

El módulo Writer serializa un documento en bytes PDF. Selecciona una estrategia de versión, escribe el grafo de objetos y emite la estructura de referencias cruzadas y el tráiler.

Ventana de terminal
composer require nextpdf/core:^3

PdfWriter es el punto de entrada. El método write() recibe un objeto de valor DocumentData y devuelve el PDF completo como una cadena de bytes. El escritor ensambla el grafo de objetos, asigna números de objeto, registra los desplazamientos de bytes y escribe la estructura de referencias cruzadas al final.

El escritor usa una estrategia de serialización en cada llamada. La interfaz PdfSerializationStrategy define cuatro métodos: writeHeader(), getCatalogVersion(), writeXrefAndTrailer() y usesXrefStream(). La implementan tres estrategias. Pdf20StreamStrategy escribe la cabecera %PDF-2.0, fija la versión del catálogo en /2.0 y emite un stream de referencias cruzadas. Pdf17TableStrategy escribe %PDF-1.7 y una table de referencias cruzadas clásica. Pdf14TableStrategy escribe %PDF-1.4 y una tabla de referencias cruzadas. PdfWriter elige la estrategia mediante un match sobre DocumentData::$outputProfile. El valor predeterminado es Pdf20StreamStrategy.

El enum PdfOutputProfile contiene las tres versiones de destino: Pdf20, Pdf17 y Pdf14. El enum expone headerVersion(), catalogVersion(), allowsObjectStreams() y usesXrefStream(). Un modo de conformidad para archivado anula el perfil elegido antes de seleccionar la estrategia. Pdf14FeatureGuard rechaza las características de PDF 2.0 cuando el perfil es Pdf14.

Un stream de referencias cruzadas asigna cada número de objeto a su desplazamiento en bytes — ISO 32000-2 §7. Las actualizaciones incrementales agregan nuevos objetos al final del archivo — ISO 32000-2 §7.5.6. El escritor escapa cada cadena literal mediante el punto de unión canónico PdfStringEscaper::escapeLiteral(), que sigue la tabla de escape normativa de ISO 32000-2 §7.3.4.2 (ADR-015).

El escritor admite salida determinista. setDeterministicMode() fija los identificadores de objeto y el orden de las claves del diccionario. setReproducibleClock() fija la marca de tiempo del documento. Con ambos valores fijados, una entrada fija produce una salida idéntica byte a byte. El método writeChunked() devuelve un generador que produce el PDF en fragmentos de tamaño fijo. Streaming/StreamingPdfWriter escribe una página por vez en un stream proporcionado por quien llama, para documentos que superan el presupuesto de memoria.

Linearizer reescribe un PDF terminado en una disposición linealizada. Coloca la primera página al inicio para que un visor pueda mostrarla antes de completar la descarga total. shadowValidate() verifica la reescritura sin modificar la entrada.

Precaución. PdfWriter.php y Linearizer.php son críticos para el desplazamiento de bytes y el grafo de objetos (zonas de peligro del manifiesto). No se debe cambiar la numeración de objetos ni la aritmética de desplazamientos de xref sin pasar la suite golden del Writer.

ClaseMétodos claveFunción
PdfWriterwrite(DocumentData): string, writeChunked(DocumentData, int): Generator, setDeterministicMode(), setReproducibleClock(), setOutputColorProfile(), getLastXrefOffset(), getFileId()Serializador principal
PdfSerializationStrategy (interfaz)writeHeader(), getCatalogVersion(), writeXrefAndTrailer(), usesXrefStream()Contrato de la estrategia de versión
Pdf20StreamStrategywriteHeader()%PDF-2.0, getCatalogVersion()/2.0, usesXrefStream()trueEstrategia de xref-stream para PDF 2.0
Pdf17TableStrategywriteHeader()%PDF-1.7, tabla xrefEstrategia de xref-table para PDF 1.7
Pdf14TableStrategywriteHeader()%PDF-1.4, tabla xrefEstrategia de xref-table para PDF 1.4
PdfOutputProfile (enum)Pdf20, Pdf17, Pdf14; headerVersion(), catalogVersion(), allowsObjectStreams()Selector de versión de destino
PdfXrefWritergenerateFileId(), finalizeTrailerAndXref()ID de archivo + finalización de trailer/xref
Linearizerlinearize(string): string, shadowValidate(string): arrayReescritura para Fast Web View
Streaming\StreamingPdfWriteropen(), newPage(), close()Escritor de streaming de una sola pasada

Ejecutar composer docs:generate-api-php -- --module=Writer para obtener la tabla PHPDoc completa.

Fuente: examples/02-pdf-factory.php.

<?php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use NextPDF\Writer\PdfWriter;
$writer = new PdfWriter();
$pdfBytes = $writer->write($documentData);
file_put_contents('out.pdf', $pdfBytes);

El perfil predeterminado es PDF 2.0. La salida empieza con %PDF-2.0 y termina con un stream de referencias cruzadas.

Esto fija el modo determinista y un reloj reproducible para obtener una salida idéntica byte a byte, y después transmite en fragmentos de tamaño fijo.

<?php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use DateTimeImmutable;
use NextPDF\Writer\PdfWriter;
use NextPDF\Writer\ReproducibleClock;
$pinned = new DateTimeImmutable('2026-01-01T00:00:00Z');
$writer = new PdfWriter();
$writer->setDeterministicMode($pinned, 'nextpdf-fixed-file-id');
$writer->setReproducibleClock(new ReproducibleClock($pinned));
$out = fopen('php://output', 'wb');
foreach ($writer->writeChunked($documentData, chunkSize: 65536) as $chunk) {
fwrite($out, $chunk);
}
fclose($out);
  • Se ejecuta una estrategia por cada llamada a write(). El escritor restablece la estrategia a partir del perfil en cada llamada. La versión de una llamada anterior no se filtra.
  • Un modo de conformidad para archivado anula el perfil solicitado. Una generación PDF/A-3 fuerza PDF 1.7. Una generación PDF/A-4 fuerza PDF 2.0.
  • La salida idéntica byte a byte requiere fijar ambos valores. Deben establecerse el modo determinista y un reloj reproducible. Fijar solo uno no basta.
  • writeChunked() produce un generador. Debe consumirse por completo. Una lectura parcial produce un PDF truncado e inválido.
  • Linearizer reescribe los desplazamientos de referencias cruzadas. Ejecutar shadowValidate() primero en una canalización que no pueda tolerar una reescritura fallida.
  • Pdf14TableStrategy es final readonly. La ruta de PDF 1.4 rechaza las características de PDF 2.0 a través de Pdf14FeatureGuard en lugar de degradarlas.

La serialización es lineal respecto del número de objetos y del tamaño total en bytes. El stream de referencias cruzadas añade una pasada por la tabla de objetos. writeChunked() conserva el documento ensamblado, pero lo emite en porciones acotadas, así que el pico de memoria es el tamaño del documento más un fragmento. Streaming\StreamingPdfWriter no conserva el documento completo; es la ruta para entradas mayores que el presupuesto de memoria. El presupuesto de la carga de trabajo de referencia es de 1500 ms de tiempo de pared y 64 MB de pico. La linealización añade una segunda pasada completa y una pasada de medición. Debe presupuestarse explícitamente.

El escritor serializa un grafo de objetos en memoria que se considera de confianza. Las amenazas principales se concentran en sus entradas. Cada cadena literal pasa por el punto de escape canónico PdfStringEscaper::escapeLiteral() (ADR-015), de modo que los bytes de control incrustados no puedan salir de un token de cadena. El cifrado se integra a través de PdfEncryptionWriter y la entrada /Encrypt del tráiler. El cifrado de clave pública se rechaza con una excepción explícita en lugar de degradarse en silencio. Los modos determinista y de reloj reproducible eliminan de la salida los canales laterales asociados a la marca de tiempo y al orden. Consultar /modules/core/security/ para conocer el modelo de amenazas del documento y la frontera de confianza del cifrado.

El Writer produce estructuras de archivo PDF 2.0: la cabecera %PDF-2.0, una versión de catálogo /2.0, un stream de referencias cruzadas y el escape de cadenas literales según la tabla de escape de ISO 32000-2 §7.3.4.2. Son hechos de implementación. La evidencia está en src/Writer/Pdf20StreamStrategy.php, src/Writer/PdfSerializationStrategy.php y la selección de estrategia en src/Writer/PdfWriter.php; la cubren tests/Unit/Writer/ (192 pruebas, incluidas las suites Pdf20StreamStrategy, PdfXrefWriter y Linearizer*) y la línea base tests/Golden/PdfWriter/PdfWriterGoldenBaselineSmokeTest.

Esto no es una afirmación de conformidad total con PDF 2.0. La conformidad total con ISO 32000-2 es una propiedad de un documento completo validado por un oráculo externo, no del serializador por sí solo. La conformidad de extremo a extremo se afirma solo cuando la confirma un oráculo: tests/Integration/Accessibility/VeraPdfUa2GoldenTest valida un fixture generado contra veraPDF para PDF/UA-2, y tests/Standards/Profile/PdfRConformanceTest cubre el perfil PDF/R. La prueba golden de veraPDF se omite cuando el binario de veraPDF está ausente del runner, así que es una compuerta opcional basada en oráculo, no incondicional. Establecer VERAPDF_BINARY para ejecutarla. La selección del perfil de archivado (PDF/A-3 → PDF 1.7, PDF/A-4 → PDF 2.0) la determinan ADR-011 y el modo de conformidad, y la validan las suites de conformidad en /modules/core/conformance/. Fuera de esos perfiles respaldados por un oráculo, se debe declarar que el Writer «produce estructuras PDF 2.0; la conformidad la valida veraPDF para el perfil PDF/UA-2» en lugar de afirmar una conformidad sin matices.