Zum Inhalt springen

ContentStream: PDF-Content-Stream-Emitter

Das ContentStream-Modul gibt Marked-Content-Operatoren aus. Es öffnet und schließt Struktur-Tags sowie Artefakte, verfolgt die Verschachtelungstiefe und gibt den Operator-Puffer zurück.

Terminal-Fenster
composer require nextpdf/core:^3

ContentStreamBuilder ist die einzige Klasse in diesem Modul. Sie baut die Marked-Content-Schicht eines Content-Streams einer Seite auf. Ein Content-Stream kodiert den Seiteninhalt als eine Folge von Operatoren — ISO 32000-2 §8. Der Builder gibt die Marked-Content-Operatoren um diesen Inhalt herum aus.

append() fügt rohe Operator-Bytes unverändert hinzu. Der Builder maskiert diese Eingabe nicht. Für die Gültigkeit dieser Bytes ist der Aufrufer verantwortlich. An dieser Stelle bringen die HTML-Pipeline und das Graphics-Modul ihre eigenen Operatoren ein.

beginTag() öffnet eine getaggte Struktursequenz und gibt einen BDC-Operator mit einer MCID-Eigenschaftsliste aus, gemäß ISO 32000-2 §14.6. endTag() gibt den passenden EMC-Operator aus. Der Builder zählt die Verschachtelungstiefe. Ein endTag() ohne geöffnete Sequenz wirft PageLayoutException, anstatt ein unbalanciertes EMC zu schreiben.

beginArtifact() öffnet eine Artefaktsequenz. Ein Artefakt enthält Dekorationselemente der Paginierung — Kopfzeilen, Fußzeilen, Seitenzahlen, Linien —, die außerhalb des Strukturbaums bleiben müssen, gemäß ISO 32000-2 §14.8.2.2. Der Subtyp ist einer von vier ISO-Werten: Pagination, Layout, Page oder Background. Verwenden Sie bevorzugt das typisierte ArtifactSubtype-Enum. Die String-Überladung wird anhand des Enums validiert, sodass ein nicht standardkonformer Wert sofort fehlschlägt.

relabelTag() schreibt ein zuvor ausgegebenes Tag an Ort und Stelle um. finish() gibt den vollständigen Puffer zurück und wirft eine Ausnahme, wenn der Marked Content unbalanciert ist. drain() gibt den bisherigen Puffer ohne Balanceprüfung zurück und ist für inkrementelles Streaming gedacht. peek() gibt den Puffer zurück, ohne ihn zu konsumieren. reset() leert den Zustand.

MethodeSignaturRolle
append()append(string $raw): voidFügt rohe Operator-Bytes unverändert hinzu (ohne Maskierung)
beginTag()beginTag(string $structType, int $mcid): voidÖffnet eine BDC-Struktursequenz
endTag()endTag(): voidSchließt die innerste Sequenz mit EMC
beginArtifact()beginArtifact(ArtifactSubtype|string $type): voidÖffnet eine Artefaktsequenz
endArtifact()endArtifact(): voidSchließt das innerste Artefakt
getMarkedContentDepth()getMarkedContentDepth(): intGibt die aktuelle Verschachtelungstiefe zurück
relabelTag()relabelTag(string $old, string $new, int $mcid): voidSchreibt ein ausgegebenes Tag an Ort und Stelle um
finish()finish(): stringGibt den vollständigen Puffer zurück; wirft bei fehlender Balance eine Ausnahme
drain()drain(): stringGibt den Puffer ohne Balanceprüfung zurück
peek()peek(): stringGibt den Puffer zurück, ohne ihn zu konsumieren
reset()reset(): voidLeert den gesamten Zustand

Führen Sie composer docs:generate-api-php -- --module=ContentStream aus, um die vollständige PHPDoc-Tabelle zu erhalten.

<?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();

Dieses Beispiel umschließt einen Absatz mit einem Struktur-Tag und eine Fußzeile mit einem Artefakt. Es streamt den Puffer inkrementell mit 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() maskiert die Eingabe nicht. Übergeben Sie nur gültige Operator-Bytes. Der Builder vertraut dem Aufrufer.
  • endTag() und endArtifact() werfen bei Unterlauf eine Ausnahme. Schließen Sie nie eine Sequenz, die nicht offen ist.
  • finish() prüft die Balance und wirft eine Ausnahme, wenn die Tiefe nicht null ist. drain() prüft nicht. Verwenden Sie drain() nur für inkrementelles Streaming.
  • Der Tiefenzähler unterscheidet nicht zwischen Tags und Artefakten. EMC schließt die innerste Sequenz beider Arten. Verschachteln Sie beide Arten in strikter Reihenfolge.
  • Die String-Überladung von beginArtifact() wird anhand des Enums validiert. Ein nicht standardkonformer Subtyp schlägt beim Aufruf fehl, nicht in der Ausgabe.
  • relabelTag() schreibt ein ausgegebenes Tag um. Verwenden Sie dieselbe mcid, mit der es ausgegeben wurde.

Jede Operation ist entweder ein String-Append mit O(1) oder, bei relabelTag(), ein Umschreiben mit O(Puffer). Das Modul hält einen String-Puffer und einen Integer-Tiefenzähler. Es gibt kein Parsing und keine Allokationen über den Puffer hinaus. Das Budget für die Referenzlast beträgt 1500 ms Wandzeit und 64 MB Spitzenverbrauch. Dieses Modul liegt weit darunter.

append() ist die Vertrauensgrenze. Der Builder schreibt die Bytes unverändert; deshalb muss der vorgelagerte Code jeden String maskieren, der einen Literal-String-Operator erreicht. Der kanonische Maskierer ist PdfStringEscaper::escapeLiteral() (ADR-015). Übergeben Sie niemals unmaskierten Benutzertext durch append(). Die Balanceprüfungen in endTag(), endArtifact() und finish() verhindern, dass ein fehlerhafter Marked-Content-Baum den Writer erreicht. Das Bedrohungsmodell des Dokuments finden Sie unter /modules/core/security/.

Das Modul gibt Marked-Content-Operatorstrukturen aus, die mit ISO 32000-2 übereinstimmen: BDC/EMC-Paare mit einer MCID-Eigenschaftsliste gemäß §14.6 und Artefaktsequenzen gemäß §14.8.2.2. Das sind Implementierungsfakten. Der Nachweis liegt in src/ContentStream/ContentStreamBuilder.php, im src/Accessibility/ArtifactSubtype.php-Enum sowie in tests/Unit/ContentStream/ContentStreamBuilderMarkedContentBalanceCoverageTest plus ContentStreamBuilderRelabelTagInvariantTest. Diese Fakten behaupten keine durchgängige PDF/UA-2- oder PDF 2.0-Konformität. Die getaggte PDF-Struktur, an der diese Operatoren beteiligt sind, wird von einem externen Orakel validiert: tests/Integration/Accessibility/VeraPdfUa2GoldenTest prüft ein generiertes Fixture gegen veraPDF für das PDF/UA-2-Profil. Dieser Orakel-Test wird übersprungen, wenn die veraPDF-Binärdatei fehlt; er ist also ein Opt-in-Gate. Formulieren Sie, dass dieses Modul „Marked-Content-Strukturen erzeugt; die PDF/UA-2-Konformität wird durch veraPDF validiert“, statt eine uneingeschränkte Konformität zu behaupten.