Pro edition
Projection — Deep Reference
At a glance
Section titled “At a glance”This page is the deep reference for the Pro Projection module. It documents the public tokenize, emit, and round-trip surface, the intent gate, and content-stream round-trip semantics. ContentProjectionWriter lexes a PDF content stream into a flat, ordered token list, then re-serializes a token list into a new content stream. The model is one-way: emission produces a new stream, never an in-place edit of the original.
Note. “Projection” here means content-stream token projection, not coordinate or geospatial projection.
Availability & licensing
Section titled “Availability & licensing”This capability ships in NextPDF Pro (nextpdf/pro) and activates with a Pro-tier license envelope. A deployment without that entitlement does not load the capability’s classes. Compare editions and get a license.
No per-feature license flag exists. This is a Pro-edition capability. Emission additionally requires an explicit ProjectionIntent argument enforced by the type system, not a license switch.
Public API surface
Section titled “Public API surface”composer require nextpdf/pro:^3The module lives in the NextPDF\Pro\Projection namespace. All operations on ContentProjectionWriter are static.
| Symbol | Parameters | Default behavior | Returns | Throws or fails with | Notes |
|---|---|---|---|---|---|
ContentProjectionWriter::tokenize | string $contentStream | Lexes the stream into a flat, ordered token list; normalizes white space, drops comments, skips unrecognized bytes | list<ContentToken> | None; malformed or control bytes are skipped, not rejected | Read-only; requires no intent. |
ContentProjectionWriter::emit | list<ContentToken> $tokens, ProjectionIntent $intent | Serializes tokens into a new content stream; output is independent of the intent value | string | None in body; a missing or non-ProjectionIntent argument fails at the type boundary | Intent is a call-site gate, not a runtime switch. |
ContentProjectionWriter::roundTrip | string $contentStream | Tokenizes then re-emits with no modification; the validation gate | string | None | Output is not byte-identical; operator sequence and operand values are preserved. |
ContentToken::__construct | ContentTokenType $type, string|int|float|bool|null $value = null | Builds an immutable token; performs no validation | ContentToken | None; a type-incompatible $value fails at the type boundary | readonly; type and value are public. |
ContentToken::isTextOperator | — | Reports whether the token is a text operator (BT, ET, Tj, TJ, Td, TD, Tm, T*, Tf, Tc, Tw, Tz, TL, Tr, Ts, ', ") | bool | None; returns false for non-operator tokens | — |
ContentToken::isTextShowingOperator | — | Reports whether the token is a text-showing operator (Tj, TJ, ', ") | bool | None; returns false for non-operator tokens | Subset of the text operators. |
ContentTokenType | — (string-backed enum) | Enumerates token discriminators: LiteralString, HexString, Number, Name, Operator, ArrayBegin, ArrayEnd, DictBegin, DictEnd, Boolean, Null | — | — | Backing values are stable identifiers. |
ProjectionIntent | — (pure enum) | Enumerates the two permitted emission intents: Sanitization, SteganographicEmbedding | — | — | No generic case, so static analysis flags undeclared use. |
public static function tokenize(string $contentStream): arraypublic static function emit(array $tokens, ProjectionIntent $intent): stringpublic static function roundTrip(string $contentStream): stringenum ProjectionIntent{ case Sanitization; case SteganographicEmbedding;}public function __construct( public ContentTokenType $type, public string|int|float|bool|null $value = null,) {}
public function isTextOperator(): boolpublic function isTextShowingOperator(): boolBehavior contract
Section titled “Behavior contract”ContentProjectionWriter::tokenize($contentStream) lexes the stream into a flat, ordered list<ContentToken>. It covers literal strings, hex strings, names, numbers, array and dictionary delimiters, booleans, null, and operators. White space and comments are consumed and dropped; an unrecognized byte advances the cursor without producing a token. The pass is read-only and needs no intent.
emit($tokens, $intent) serializes a token list back into content-stream bytes and requires a ProjectionIntent. The intent is a call-site declaration only: the emitted bytes are identical regardless of which case is passed. Numbers keep their integer/float distinction — integers emit verbatim, floats emit with up to six fractional digits and trailing zeros trimmed. Literal strings are re-escaped, hex strings emit as uppercase hex, and names carry their leading solidus. Each operator is followed by a newline; array and dictionary delimiters suppress the adjacent separator.
roundTrip($contentStream) tokenizes then re-emits with no change. It is the validation gate: confirm a clean result before trusting any modify-and-emit sequence. The output is not byte-identical to the input — white space is normalized and comments are gone — but the operator sequence and operand values are preserved.
ProjectionIntent has exactly two cases: Sanitization (destructive, irreversible redaction) and SteganographicEmbedding (hidden payload embedding). There is no generic case, so static analysis can flag any emission that lacks a declared, known purpose. ContentToken is an immutable readonly value carrying a type discriminator and a decoded value; isTextOperator() and isTextShowingOperator() classify operator tokens and return false for every non-operator token.
Edge cases & failure modes
Section titled “Edge cases & failure modes”- Confirm a clean round-trip before any modify-and-emit sequence. Treat a failing round-trip as a stop condition.
- The
Sanitizationintent is irreversible. Removed tokens are absent from the output and cannot be recovered from it. - Intent does not change output.
emit()produces the same bytes for either case; the argument is a call-site gate. Redaction and steganographic edits are applied by the caller mutating the token list before emission. - The emitter normalizes white space and drops comments, so byte-level comparison with the original differs even for an unmodified round-trip.
- Float operands are formatted with at most six fractional digits and then trimmed. Values needing more precision are rounded on emission; integers are exact.
- Input literal-string escapes decoded include
\n,\r,\t,\b,\f, escaped delimiters, and up to three-digit octal escapes clamped to one byte. - A hex string with an odd digit count is padded with a trailing zero on input, matching the ISO hexadecimal-string rule.
- Malformed or control bytes are skipped, not rejected;
tokenize()throws no exception on unexpected input. - This module performs no cryptographic operations and defines no FIPS-specific behavior.
Conformance
Section titled “Conformance”Tokenization treats the stream as a sequence of operators and operands in standard PDF object syntax, per ISO 32000-2:2020, 8.2. Byte-to-token grouping follows the lexical character classes of ISO 32000-2:2020, 7.2. An odd-length hex string pads the final digit as zero, per ISO 32000-2:2020, 7.3.4.3. These clauses are recorded in this page’s citation record.
These statements describe capability against the cited clauses.
Development notes
Section titled “Development notes”- Available since the module’s 1.10.0 release; all three operations are static entry points on
ContentProjectionWriter. - Tokenize and emit are linear in content-stream length. There is no published throughput figure; measure with representative streams.
- The flat token model — one token per lexical element, not operator-grouped — is what enables surgical edits such as adjusting a single number inside a TJ array. Operator-grouped representations live elsewhere in the Pro tree and are out of scope here.
ContentTokenis immutable. Build a modified list by constructing new tokens rather than mutating existing ones.- Keep the round-trip gate in your pipeline: a passing
roundTrip()is the precondition the module is designed around before any destructive edit.
Publication boundary
Section titled “Publication boundary”This page documents externally observable behavior and the supported public API surface only. Internal namespace paths, helper classes, mechanism tables, runbook filenames, and ticket prefixes are out of scope.