Ir al contenido

ContentStream: emisor de flujos de contenido PDF

El módulo ContentStream emite operadores de contenido marcado. Abre y cierra etiquetas de estructura y artefactos, mantiene la profundidad de anidamiento y devuelve el búfer de operadores.

Ventana de terminal
composer require nextpdf/core:^3

ContentStreamBuilder es la única clase de este módulo. Construye la capa de contenido marcado de un flujo de contenido de página. Un flujo de contenido codifica el contenido de la página como una secuencia de operadores, según ISO 32000-2 §8. El builder emite los operadores de contenido marcado que envuelven ese contenido.

append() agrega bytes de operador en bruto de forma literal. El builder no aplica escape a esta entrada. El código que lo invoca es responsable de su validez. Este es el punto en el que la pipeline HTML y el módulo Graphics intercalan sus propios operadores.

beginTag() abre una secuencia etiquetada de estructura. Emite un operador BDC con una lista de propiedades MCID, según ISO 32000-2 §14.6. endTag() emite el operador EMC correspondiente. El builder mantiene la profundidad de anidamiento. Un endTag() sin secuencias abiertas lanza PageLayoutException en lugar de escribir un EMC sin equilibrar.

beginArtifact() abre una secuencia de artefacto. Un artefacto contiene la decoración de paginación —encabezados, pies de página, números de página, líneas— que debe quedar fuera del árbol de estructura, según ISO 32000-2 §14.8.2.2. El subtipo es uno de los cuatro valores ISO: Pagination, Layout, Page o Background. Es preferible usar el enum tipado ArtifactSubtype. La sobrecarga de cadena se valida contra el enum, por lo que un valor no estándar falla de inmediato.

relabelTag() reescribe in situ una etiqueta emitida anteriormente. finish() devuelve el búfer completo y lanza una excepción si el contenido marcado no está equilibrado. drain() devuelve el búfer acumulado hasta el momento, sin comprobar el equilibrio, para streaming incremental. peek() devuelve el búfer sin consumirlo. reset() limpia el estado.

MétodoFirmaFunción
append()append(string $raw): voidAgrega bytes de operador en bruto de forma literal (sin escape)
beginTag()beginTag(string $structType, int $mcid): voidAbre una secuencia BDC de estructura
endTag()endTag(): voidCierra la secuencia más interna con EMC
beginArtifact()beginArtifact(ArtifactSubtype|string $type): voidAbre una secuencia de artefacto
endArtifact()endArtifact(): voidCierra el artefacto más interno
getMarkedContentDepth()getMarkedContentDepth(): intDevuelve la profundidad de anidamiento actual
relabelTag()relabelTag(string $old, string $new, int $mcid): voidReescribe in situ una etiqueta emitida
finish()finish(): stringDevuelve el búfer completo; lanza una excepción si no está equilibrado
drain()drain(): stringDevuelve el búfer sin la comprobación de equilibrio
peek()peek(): stringDevuelve el búfer sin consumirlo
reset()reset(): voidLimpia todo el estado

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

<?php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use NextPDF\ContentStream\ContentStreamBuilder;
$builder = new ContentStreamBuilder();
$builder->beginTag('P', mcid: 0);
$builder->append("BT /F1 12 Tf 72 720 Td (Hello) Tj ET\n");
$builder->endTag();
$pageContent = $builder->finish();

Este ejemplo envuelve un párrafo en una etiqueta de estructura y un pie de página en un artefacto. Transmite el búfer de forma incremental con drain().

<?php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use NextPDF\Accessibility\ArtifactSubtype;
use NextPDF\ContentStream\ContentStreamBuilder;
$builder = new ContentStreamBuilder();
$builder->beginTag('H1', mcid: 0);
$builder->append($titleOperators);
$builder->endTag();
$builder->beginArtifact(ArtifactSubtype::Pagination);
$builder->append($footerOperators);
$builder->endArtifact();
if ($builder->getMarkedContentDepth() !== 0) {
throw new RuntimeException('Unbalanced marked content before flush.');
}
$chunk = $builder->drain();
  • append() no aplica escape a la entrada. Pasar solo bytes de operador válidos. El builder confía en el código que lo invoca.
  • endTag() y endArtifact() lanzan una excepción en caso de subdesbordamiento. Nunca cerrar una secuencia que no esté abierta.
  • finish() comprueba el equilibrio y lanza una excepción cuando la profundidad no es cero. drain() no comprueba. Usar drain() solo para streaming incremental.
  • El contador de profundidad no distingue las etiquetas de los artefactos. EMC cierra la secuencia más interna de cualquiera de los dos tipos. Anidarlas en orden estricto.
  • La sobrecarga de cadena de beginArtifact() se valida contra el enum. Un subtipo no estándar falla en la llamada, no en la salida.
  • relabelTag() reescribe una etiqueta emitida. Usar el mismo mcid que se usó para emitirla.

Cada operación consiste en añadir una cadena en O(1), o en una reescritura O(buffer) en el caso de relabelTag(). El módulo mantiene un búfer de cadena y un contador entero de profundidad. No hay análisis sintáctico ni asignación de memoria más allá del búfer. El presupuesto de la carga de trabajo de referencia es de 1500 ms de tiempo real y 64 MB de pico. Este módulo se mantiene muy por debajo de ese límite.

append() es el límite de confianza. El builder escribe los bytes de forma literal, así que el código anterior debe aplicar escape a cualquier cadena que llegue a un operador de cadena literal. El escapador canónico es PdfStringEscaper::escapeLiteral() (ADR-015). Nunca se debe pasar texto de usuario sin escape a través de append(). Las comprobaciones de equilibrio en endTag(), endArtifact() y finish() evitan que un árbol de contenido marcado malformado llegue al Writer. Consultar /modules/core/security/ para el modelo de amenazas del documento.

El módulo emite estructuras de operadores de contenido marcado coherentes con ISO 32000-2: pares BDC/EMC con una lista de propiedades MCID según §14.6, y secuencias de artefacto según §14.8.2.2. Se trata de hechos de implementación. La evidencia está en src/ContentStream/ContentStreamBuilder.php, el enum src/Accessibility/ArtifactSubtype.php y tests/Unit/ContentStream/ContentStreamBuilderMarkedContentBalanceCoverageTest junto con ContentStreamBuilderRelabelTagInvariantTest. No constituyen una afirmación de conformidad integral con PDF/UA-2 ni con PDF 2.0. La estructura de PDF etiquetado en la que participan estos operadores se valida mediante un oráculo externo: tests/Integration/Accessibility/VeraPdfUa2GoldenTest comprueba un fixture generado contra veraPDF para el perfil PDF/UA-2. Ese test de oráculo se omite cuando el binario de veraPDF no está presente, por lo que es una compuerta opcional. Debe indicarse que este módulo «produce estructuras de contenido marcado; la conformidad con PDF/UA-2 se valida mediante veraPDF» en lugar de afirmar una conformidad sin matices.