ContentStream: PDF 콘텐츠 스트림 이미터
한눈에 보기
섹션 제목: “한눈에 보기”ContentStream 모듈은 마크된 콘텐츠 연산자를 내보냅니다. 구조 태그와 아티팩트를 열고 닫으며, 중첩 깊이를 추적하고, 연산자 버퍼를 반환합니다.
composer require nextpdf/core:^3개념 개요
섹션 제목: “개념 개요”ContentStreamBuilder는 이 모듈에서 제공하는 유일한 클래스입니다. 페이지 콘텐츠 스트림의 마크된 콘텐츠 레이어를 빌드합니다. 콘텐츠 스트림은 페이지 콘텐츠를 연산자 시퀀스로 인코딩합니다(ISO 32000-2 §8). 빌더는 해당 콘텐츠를 감싸는 마크된 콘텐츠 연산자를 내보냅니다.
append()는 원시 연산자 바이트를 그대로 추가합니다. 빌더는 이 입력을 이스케이프하지 않습니다. 유효성 검증은 호출자의 책임입니다. 이 지점은 HTML 파이프라인과 Graphics 모듈이 자체 연산자를 삽입하는 곳입니다.
beginTag()는 구조 태그가 지정된 시퀀스를 엽니다. BDC 연산자를 MCID 속성 목록과 함께 내보내며, 이는 ISO 32000-2 §14.6을 따릅니다. endTag()는 대응하는 EMC 연산자를 내보냅니다. 빌더는 중첩 깊이를 추적합니다. 열린 시퀀스가 없는 상태에서 endTag()를 호출하면 PageLayoutException을 던지며, 균형이 맞지 않는 EMC를 쓰지 않습니다.
beginArtifact()는 아티팩트 시퀀스를 엽니다. 아티팩트는 ISO 32000-2 §14.8.2.2에 따라 구조 트리에서 제외되어야 하는 페이지 매김 장식(머리글, 바닥글, 페이지 번호, 괘선)을 담습니다. 하위 유형은 네 가지 ISO 값인 Pagination, Layout, Page, Background 중 하나입니다. 타입 안전한 ArtifactSubtype 열거형을 사용하는 것이 좋습니다. 문자열 오버로드는 열거형에 대해 검증되므로, 표준이 아닌 값은 즉시 실패합니다.
relabelTag()는 이전에 내보낸 태그를 제자리에서 다시 씁니다. finish()는 전체 버퍼를 반환하며, 마크된 콘텐츠의 균형이 맞지 않으면 예외를 던집니다. drain()은 증분 스트리밍을 위해 균형 검사 없이 현재까지의 버퍼를 반환합니다. peek()는 버퍼를 소비하지 않고 반환합니다. reset()은 상태를 지웁니다.
API 표면
섹션 제목: “API 표면”| 메서드 | 시그니처 | 역할 |
|---|---|---|
append() | append(string $raw): void | 원시 연산자 바이트를 그대로 추가합니다(이스케이프 없음) |
beginTag() | beginTag(string $structType, int $mcid): void | 새 BDC 구조 시퀀스를 엽니다 |
endTag() | endTag(): void | 가장 안쪽 시퀀스를 닫습니다 — EMC |
beginArtifact() | beginArtifact(ArtifactSubtype|string $type): void | 아티팩트 시퀀스를 엽니다 |
endArtifact() | endArtifact(): void | 가장 안쪽 아티팩트를 닫습니다 |
getMarkedContentDepth() | getMarkedContentDepth(): int | 현재 중첩 깊이를 반환합니다 |
relabelTag() | relabelTag(string $old, string $new, int $mcid): void | 내보낸 태그를 제자리에서 다시 씁니다 |
finish() | finish(): string | 전체 버퍼를 반환합니다. 균형이 맞지 않으면 예외를 던집니다 |
drain() | drain(): string | 균형 검사 없이 버퍼를 반환합니다 |
peek() | peek(): string | 버퍼를 소비하지 않고 반환합니다 |
reset() | reset(): void | 모든 상태를 지웁니다 |
전체 PHPDoc 표는 composer docs:generate-api-php -- --module=ContentStream을 실행해 확인하세요.
코드 샘플 — 빠른 시작
섹션 제목: “코드 샘플 — 빠른 시작”<?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();코드 샘플 — 프로덕션
섹션 제목: “코드 샘플 — 프로덕션”이 예제는 단락을 구조 태그로 감싸고, 바닥글을 아티팩트로 감쌉니다. 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()는 입력을 이스케이프하지 않습니다. 유효한 연산자 바이트만 전달하세요. 빌더는 호출자를 신뢰합니다.endTag()와endArtifact()는 언더플로 시 예외를 던집니다. 열려 있지 않은 시퀀스는 절대 닫지 마세요.finish()는 균형을 검사하며, 깊이가 0이 아니면 예외를 던집니다.drain()은 검사하지 않습니다. 증분 스트리밍에만drain()을 사용하세요.- 깊이 카운터는 태그와 아티팩트를 구분하지 않습니다.
EMC는 두 종류 중 가장 안쪽 시퀀스를 닫습니다. 엄격한 순서로 중첩하세요. beginArtifact()메서드의 문자열 오버로드는 열거형에 대해 검증됩니다. 표준이 아닌 하위 유형은 출력이 아니라 호출 시점에 실패합니다.relabelTag()는 내보낸 태그를 다시 씁니다. 태그를 내보낼 때 사용한 것과 동일한mcid를 사용하세요.
모든 작업은 O(1) 문자열 추가이며, relabelTag()만 O(buffer) 재작성입니다. 이 모듈은 하나의 문자열 버퍼와 하나의 정수 깊이 카운터를 유지합니다. 파싱이 없으며 버퍼 외 추가 할당도 없습니다. 참조 워크로드 예산은 벽시계 시간 1500ms, 최대 64MB입니다. 이 모듈의 사용량은 그보다 훨씬 낮습니다.
보안 참고 사항
섹션 제목: “보안 참고 사항”append()는 신뢰 경계입니다. 빌더는 바이트를 그대로 쓰므로, 상위 코드는 리터럴 문자열 연산자에 들어가는 모든 문자열을 이스케이프해야 합니다. 표준 이스케이퍼는 PdfStringEscaper::escapeLiteral()입니다(ADR-015). 이스케이프되지 않은 사용자 텍스트를 절대 append()에 전달하지 마세요. endTag(), endArtifact(), finish()의 균형 검사는 잘못된 형식의 마크된 콘텐츠 트리가 Writer에 도달하는 것을 방지합니다. 문서 위협 모델은 /modules/core/security/을 참조하세요.
적합성
섹션 제목: “적합성”이 모듈은 ISO 32000-2와 일치하는 마크된 콘텐츠 연산자 구조를 내보냅니다: §14.6에 따라 BDC/EMC 쌍과 MCID 속성 목록을, 그리고 §14.8.2.2에 따라 아티팩트 시퀀스를 내보냅니다. 이는 구현상의 사실입니다. 근거는 src/ContentStream/ContentStreamBuilder.php, src/Accessibility/ArtifactSubtype.php 열거형, 그리고 tests/Unit/ContentStream/ContentStreamBuilderMarkedContentBalanceCoverageTest 및 ContentStreamBuilderRelabelTagInvariantTest입니다. 이는 종단 간 PDF/UA-2 또는 PDF 2.0 적합성에 대한 주장이 아닙니다. 이 연산자들이 사용되는 태그된 PDF 구조는 외부 오라클로 검증됩니다: tests/Integration/Accessibility/VeraPdfUa2GoldenTest는 생성된 픽스처를 PDF/UA-2 프로필에 대해 veraPDF로 검사합니다. 이 오라클 테스트는 veraPDF 바이너리가 없으면 건너뛰므로, 옵트인 게이트입니다. 조건 없는 적합성을 주장하기보다, 이 모듈이 “마크된 콘텐츠 구조를 생성하며, PDF/UA-2 적합성은 veraPDF로 검증된다”라고 명시하세요.