ContentStream: emisor de flujos de contenido PDF
De un vistazo
Sección titulada «De un vistazo»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.
Instalación
Sección titulada «Instalación»composer require nextpdf/core:^3Panorama conceptual
Sección titulada «Panorama conceptual»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.
Superficie de la API
Sección titulada «Superficie de la API»| Método | Firma | Función |
|---|---|---|
append() | append(string $raw): void | Agrega bytes de operador en bruto de forma literal (sin escape) |
beginTag() | beginTag(string $structType, int $mcid): void | Abre una secuencia BDC de estructura |
endTag() | endTag(): void | Cierra la secuencia más interna con EMC |
beginArtifact() | beginArtifact(ArtifactSubtype|string $type): void | Abre una secuencia de artefacto |
endArtifact() | endArtifact(): void | Cierra el artefacto más interno |
getMarkedContentDepth() | getMarkedContentDepth(): int | Devuelve la profundidad de anidamiento actual |
relabelTag() | relabelTag(string $old, string $new, int $mcid): void | Reescribe in situ una etiqueta emitida |
finish() | finish(): string | Devuelve el búfer completo; lanza una excepción si no está equilibrado |
drain() | drain(): string | Devuelve el búfer sin la comprobación de equilibrio |
peek() | peek(): string | Devuelve el búfer sin consumirlo |
reset() | reset(): void | Limpia todo el estado |
Ejecutar composer docs:generate-api-php -- --module=ContentStream para obtener la tabla completa de PHPDoc.
Ejemplo de código — Inicio rápido
Sección titulada «Ejemplo de código — Inicio rápido»<?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();Ejemplo de código — Producción
Sección titulada «Ejemplo de código — Producción»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();Casos límite y trampas
Sección titulada «Casos límite y trampas»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()yendArtifact()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. Usardrain()solo para streaming incremental.- El contador de profundidad no distingue las etiquetas de los artefactos.
EMCcierra 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 mismomcidque se usó para emitirla.
Rendimiento
Sección titulada «Rendimiento»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.
Notas de seguridad
Sección titulada «Notas de seguridad»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.
Conformidad
Sección titulada «Conformidad»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.