Pular para o conteúdo

ContentStream: emissor de content streams de PDF

O módulo ContentStream emite operadores de conteúdo marcado (marked content) para Portable Document Format (PDF). Ele abre e fecha tags de estrutura e artefatos, acompanha a profundidade de aninhamento e retorna o buffer de operadores.

Terminal window
composer require nextpdf/core:^3

ContentStreamBuilder é a única classe do módulo. Ela constrói a camada de conteúdo marcado de um content stream de página. Um content stream codifica o conteúdo da página como uma sequência de operadores — ISO 32000-2 §8. Em seguida, o builder emite operadores de conteúdo marcado em torno desse conteúdo.

append() adiciona literalmente bytes brutos de operador. O builder não faz escape dessa entrada. Você é responsável pela validade dela. Use essa fronteira quando o pipeline de HTML e o módulo Graphics precisarem intercalar seus próprios operadores.

beginTag() abre uma sequência com tag de estrutura. Ele emite um operador BDC com uma lista de propriedades MCID, conforme a ISO 32000-2 §14.6. endTag() emite o operador EMC correspondente. O builder contabiliza a profundidade de aninhamento. Se você chamar endTag() sem nenhuma sequência aberta, ele lança PageLayoutException em vez de escrever um EMC desbalanceado.

beginArtifact() abre uma sequência de artefato. Use artefatos para elementos decorativos de paginação — cabeçalhos, rodapés, números de página e linhas — que devem ficar fora da árvore de estrutura, conforme a ISO 32000-2 §14.8.2.2. O subtipo é um de quatro valores ISO: Pagination, Layout, Page ou Background. Prefira o enum tipado ArtifactSubtype. A sobrecarga de string é validada contra o enum; portanto, um valor fora do padrão falha imediatamente.

relabelTag() reescreve, no lugar, uma tag emitida anteriormente. finish() retorna o buffer completo e lança uma exceção se o conteúdo marcado estiver desbalanceado. drain() retorna o buffer acumulado até o momento, sem a verificação de balanceamento, para streaming incremental. peek() retorna o buffer sem consumi-lo. reset() limpa o estado.

MétodoAssinaturaFunção
append()append(string $raw): voidAdiciona literalmente bytes brutos de operador (sem escape)
beginTag()beginTag(string $structType, int $mcid): voidAbre uma sequência BDC de estrutura
endTag()endTag(): voidFecha a sequência mais interna com EMC
beginArtifact()beginArtifact(ArtifactSubtype|string $type): voidAbre uma sequência de artefato
endArtifact()endArtifact(): voidFecha o artefato mais interno
getMarkedContentDepth()getMarkedContentDepth(): intRetorna a profundidade de aninhamento atual
relabelTag()relabelTag(string $old, string $new, int $mcid): voidReescreve uma tag emitida no lugar
finish()finish(): stringRetorna o buffer completo; lança uma exceção se estiver desbalanceado
drain()drain(): stringRetorna o buffer sem a verificação de balanceamento
peek()peek(): stringRetorna o buffer sem consumi-lo
reset()reset(): voidLimpa todo o estado

Execute composer docs:generate-api-php -- --module=ContentStream para gerar a tabela 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();

Use este padrão para envolver um parágrafo em uma tag de estrutura e um rodapé em um artefato. O padrão envia o buffer incrementalmente por streaming com 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() não faz escape da entrada. Passe apenas bytes de operador válidos. O builder confia no código que o chama.
  • endTag() e endArtifact() lançam exceção em caso de underflow. Nunca feche uma sequência que não esteja aberta.
  • finish() verifica o balanceamento e lança uma exceção quando a profundidade não é zero. drain() não verifica. Use drain() apenas para streaming incremental.
  • O contador de profundidade não distingue tags de artefatos. EMC fecha a sequência mais interna de qualquer um dos tipos. Aninhe as sequências em uma ordem estrita.
  • A sobrecarga de string de beginArtifact() é validada contra o enum. Um subtipo fora do padrão falha na chamada, não na saída.
  • relabelTag() reescreve uma tag emitida. Use o mesmo mcid que você usou para emiti-la.

Cada operação é um append de string O(1), exceto relabelTag(), que realiza uma reescrita O(buffer). O módulo mantém um buffer de string e um contador inteiro de profundidade. Ele não faz parsing e aloca apenas o buffer. O orçamento da carga de trabalho de referência é de 1500 ms de tempo de parede e 64 MB de pico. Este módulo permanece bem abaixo desse limite.

append() é a fronteira de confiança. O builder escreve os bytes literalmente; portanto, o código upstream deve fazer escape de qualquer string que chegue a um operador de string literal. O escaper canônico é PdfStringEscaper::escapeLiteral() (ADR-015). Nunca passe texto de usuário sem escape por append(). As verificações de balanceamento em endTag(), endArtifact() e finish() impedem que uma árvore de conteúdo marcado malformada chegue ao Writer. Consulte /modules/core/security/ para o modelo de ameaças do documento.

O módulo emite estruturas de operadores de conteúdo marcado consistentes com a ISO 32000-2: pares BDC/EMC com uma lista de propriedades MCID conforme a §14.6, e sequências de artefato conforme a §14.8.2.2. Estes são fatos de implementação. A evidência é src/ContentStream/ContentStreamBuilder.php, o enum src/Accessibility/ArtifactSubtype.php e tests/Unit/ContentStream/ContentStreamBuilderMarkedContentBalanceCoverageTest mais ContentStreamBuilderRelabelTagInvariantTest. Eles não constituem uma afirmação de conformidade ponta a ponta com PDF/UA-2 ou PDF 2.0. Um oráculo externo valida a estrutura de PDF marcado da qual esses operadores participam: tests/Integration/Accessibility/VeraPdfUa2GoldenTest verifica um fixture gerado contra o veraPDF para o perfil PDF/UA-2. Esse teste de oráculo é ignorado quando o binário do veraPDF está ausente; portanto, é um gate opcional. Declare que este módulo “produz estruturas de conteúdo marcado; a conformidade com PDF/UA-2 é validada pelo veraPDF” em vez de afirmar conformidade sem ressalvas.