Skip to content
getnextpdf.com

Pro edition

Projection — Deep Reference

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.

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.

Terminal window
composer require nextpdf/pro:^3

The module lives in the NextPDF\Pro\Projection namespace. All operations on ContentProjectionWriter are static.

SymbolParametersDefault behaviorReturnsThrows or fails withNotes
ContentProjectionWriter::tokenizestring $contentStreamLexes the stream into a flat, ordered token list; normalizes white space, drops comments, skips unrecognized byteslist<ContentToken>None; malformed or control bytes are skipped, not rejectedRead-only; requires no intent.
ContentProjectionWriter::emitlist<ContentToken> $tokens, ProjectionIntent $intentSerializes tokens into a new content stream; output is independent of the intent valuestringNone in body; a missing or non-ProjectionIntent argument fails at the type boundaryIntent is a call-site gate, not a runtime switch.
ContentProjectionWriter::roundTripstring $contentStreamTokenizes then re-emits with no modification; the validation gatestringNoneOutput is not byte-identical; operator sequence and operand values are preserved.
ContentToken::__constructContentTokenType $type, string|int|float|bool|null $value = nullBuilds an immutable token; performs no validationContentTokenNone; a type-incompatible $value fails at the type boundaryreadonly; type and value are public.
ContentToken::isTextOperatorReports whether the token is a text operator (BT, ET, Tj, TJ, Td, TD, Tm, T*, Tf, Tc, Tw, Tz, TL, Tr, Ts, ', ")boolNone; returns false for non-operator tokens
ContentToken::isTextShowingOperatorReports whether the token is a text-showing operator (Tj, TJ, ', ")boolNone; returns false for non-operator tokensSubset of the text operators.
ContentTokenType— (string-backed enum)Enumerates token discriminators: LiteralString, HexString, Number, Name, Operator, ArrayBegin, ArrayEnd, DictBegin, DictEnd, Boolean, NullBacking values are stable identifiers.
ProjectionIntent— (pure enum)Enumerates the two permitted emission intents: Sanitization, SteganographicEmbeddingNo generic case, so static analysis flags undeclared use.
public static function tokenize(string $contentStream): array
public static function emit(array $tokens, ProjectionIntent $intent): string
public static function roundTrip(string $contentStream): string
enum ProjectionIntent
{
case Sanitization;
case SteganographicEmbedding;
}
public function __construct(
public ContentTokenType $type,
public string|int|float|bool|null $value = null,
) {}
public function isTextOperator(): bool
public function isTextShowingOperator(): bool

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.

  • Confirm a clean round-trip before any modify-and-emit sequence. Treat a failing round-trip as a stop condition.
  • The Sanitization intent 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.

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.

  • 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.
  • ContentToken is 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.

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.