Skip to content
getnextpdf.com

Pro edition

AST — Deep Reference

This page is the deep reference for the Pro AST module. It covers the public build, cache, mutation, write, and emit surfaces, their behavior contracts, and their failure modes. The module parses a loaded PDF into an immutable AstDocument tree, applies logged in-memory mutations, and writes overlay-based incremental updates. AstDocument and AstNode are Core value types in the NextPDF\Ast namespace; this module produces and consumes them.

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. Build behavior is governed entirely by AstBuildOptions.

SymbolParametersDefault behaviorReturnsThrows or fails withNotes
AstBuilder::__constructPdfReader $reader, AstBuildOptions $options, ?AstCache $cache = nullBinds a loaded reader to build options; caching is optionalAstBuilderA null cache means every build() call rebuilds.
AstBuilder::buildstring $sourceHash (full SHA-256 hex of the PDF bytes)Cache lookup, encryption rejection, structure-tree path, untagged fallback, bounding-box attachment, cache storeAstDocumentAstUnsupportedEncryptionException, AstBuildLimitException, AstBuildTimeoutExceptionA cache hit returns without re-parsing.
AstBuildOptions::__construct?int $pageRangeStart = null, ?int $pageRangeEnd = null, int $maxNodes = 100_000, int $maxDepth = 200, ?int $estimatedTokenBudget = null, int $maxMemoryBytes = 268435456, float $timeoutSeconds = 30.0, bool $useHeuristic = falseImmutable configuration value objectAstBuildOptionsestimatedTokenBudget is an informational hint; it is not enforced.
AstBuildOptions::pageRangeContainsint $pageIndexTrue when the 0-based index falls inside the configured rangeboolNull bounds are open-ended; both null means all pages.
AstBuildOptions::hashStable SHA-256 over all option valuesstringEqual values yield equal hashes across instances; used as the cache-key segment.
AstCache::__constructCacheInterface $backendWraps any PSR-16 backendAstCache
AstCache::buildKeystring $sourceHash, AstBuildOptions $optionsKey = nextpdf_ast_v1_ + first 32 hex of source hash + _ + first 16 hex of options hashstringOption changes invalidate cached results automatically.
AstCache::getstring $cacheKeyDecodes a JSON payload through strict per-field validation?AstDocumentNever throws; failures return nullMalformed or tampered payloads fail closed as a cache miss.
AstCache::setstring $cacheKey, AstDocument $documentStores JSON with a 24-hour TTL, then verifies by immediate read-backvoidAstWriteVerificationException (Exception namespace)Backend write failure or a failed round-trip raises.
AstCache::deletestring $cacheKeyBest-effort removalvoidNever throwsBackend deletion failures are swallowed.
AstCache::hasstring $cacheKeyBest-effort existence checkboolNever throws; failures return false
AstMutator::updateNodeAstDocument $document, string $nodeId, array $updatesReplaces text_content, records an Updated entryAstDocument (new instance)InvalidArgumentExceptionOnly the text_content key is applied; unknown keys are ignored.
AstMutator::deleteNodeAstDocument $document, string $nodeIdRemoves the node from the in-memory tree, records a Deleted entryAstDocument (new instance)InvalidArgumentExceptionIn-memory removal only; see the redaction caveat below.
AstMutator::getMutationLogReturns the shared log instanceMutationLogPass the same log to AstWriter.
AstMutator::resetLogDiscards all recorded mutationsvoidStarts a fresh log.
MutationLogrecord, all, isEmpty, count, forNode, mutatedNodeIdsAppend-only in-memory log, insertion order preservedper methodforNode returns the most recent entry for a node; the last entry wins.
MutationEntry::__constructstring $nodeId, MutationType $type, ?AstNode $originalNode, ?AstNode $mutatedNode, DateTimeImmutable $timestampImmutable record of one mutationMutationEntryoriginalNode is null for Inserted; mutatedNode is null for Deleted.
MutationTypeenum cases Updated, Inserted, DeletedString-backed classificationDeleted under OVERLAY hides content; it does not erase bytes.
AstWriter::writestring $originalPdfBytes, MutationLog $logAppends an incremental update whose overlay streams cover mutated bounding boxesstring (modified PDF bytes)AstWriteExceptionAn empty log returns the input unchanged. Inserted entries and entries without a bounding box are skipped.
AstWriter::writeAndVerifystring $originalPdfBytes, MutationLog $logRuns write(), then a structural output checkstring (verified PDF bytes)AstWriteException, AstWriteVerificationException (Writer namespace)Verification is structural, not semantic.
AstPdfEmitter::emitAstNode $root, BinaryBuffer $buffer, ObjectRegistry $registry, array $pageObjectsWrites a StructTreeRoot, StructElem chain, and ParentTree for the supplied treeEmitResultAstEmitExceptionThe root must be a Document node with children. Round-trip emitter for structure-tree verification.
EmitResult::__constructint $structTreeRootObject, int $rootElementObject, int $parentTreeObject, int $elementObjectCount, int $parentTreeNextKeyImmutable record of the emitted object identifiersEmitResult
public function build(string $sourceHash): AstDocument
public function updateNode(AstDocument $document, string $nodeId, array $updates): AstDocument
public function deleteNode(AstDocument $document, string $nodeId): AstDocument
public function write(string $originalPdfBytes, MutationLog $log): string
public function writeAndVerify(string $originalPdfBytes, MutationLog $log): string
  • NextPDF\Pro\Ast\Exception\AstException extends RuntimeException — base of the build hierarchy.
  • AstBuildLimitException extends AstException — a node, depth, or memory ceiling was exceeded.
  • AstBuildTimeoutException extends AstBuildLimitException — the wall-clock build timeout elapsed.
  • AstNoStructTreeException extends AstException — no structure tree present. AstBuilder::build() catches it internally and falls back; callers of build() do not observe it.
  • AstUnsupportedEncryptionException extends AstException — the input PDF is encrypted.
  • NextPDF\Pro\Ast\Exception\AstWriteVerificationException extends AstException — cache write verification failed.
  • NextPDF\Pro\Ast\Writer\AstWriteException extends RuntimeException — writer input or structure failure.
  • NextPDF\Pro\Ast\Writer\AstWriteVerificationException extends AstWriteException — post-write structural verification failed.

Two distinct AstWriteVerificationException classes exist in different namespaces. AstCache::set() raises the Exception-namespace class; AstWriter::writeAndVerify() raises the Writer-namespace class. Match the namespace in catch clauses.

AstBuilder::build($sourceHash) requires the full SHA-256 hex of the source bytes. The pipeline is: optional cache lookup, encryption rejection, structure-tree path, untagged fallback, bounding-box attachment, optional cache store.

The cache key combines the source hash with the AstBuildOptions hash. The options hash is stable across instances with identical values, so identical inputs and options return the same tree. When no cache is supplied, every call rebuilds. Cached payloads are JSON, never native PHP serialization: the read path validates each field and instantiates only AST value types, so a poisoned cache entry cannot trigger object injection and degrades to a cache miss.

The structure-tree path runs when a structure tree is present. Resource ceilings — node count, depth, memory delta, and wall-clock time — are enforced during structure-tree reading and raise AstBuildLimitException or AstBuildTimeoutException. If the reader reports no structure tree, the builder switches to the untagged path: the heuristic builder when useHeuristic is true, otherwise the bare fallback builder. Bounding boxes are attached by analyzing each in-range page’s content stream; a page whose content stream cannot be parsed is skipped and leaves the rest of the tree intact.

AstNode is immutable. Tree updates rebuild affected nodes bottom-up; unchanged subtrees are returned by identity. AstMutator follows the same contract: each mutation returns a new AstDocument, rebuilds only the root-to-target path, and records a MutationEntry in the shared MutationLog.

AstWriter applies a MutationLog in OVERLAY mode as an append-only incremental update: new overlay content streams, updated page objects, a cross-reference section covering only new objects, and a trailer whose /Prev points at the prior startxref. The original bytes are left intact, per the incremental-update model of ISO 32000-2:2020, 7.5.6. Replacement text drawn for Updated entries escapes \, (, and ) in literal strings, per ISO 32000-2:2020, 7.3.4.2.

AstPdfEmitter::emit() is the symmetric inverse of structure-tree reading: trees produced by the reader round-trip to structurally equivalent trees, modulo node-id renumbering and documented canonicalisation classes. MCIDs present on nodes are re-emitted verbatim, never re-allocated.

  • Encrypted input is rejected before any tree work; there is no partial-tree result for encrypted PDFs. Decrypt first.
  • Resource ceilings: max nodes (default 100,000), max depth (default 200), max memory (default 256 MiB), wall-clock timeout (default 30 s). Exceeding a ceiling raises AstBuildLimitException; the timeout raises AstBuildTimeoutException, a subclass.
  • The page range is 0-based and inclusive; null bounds mean all pages.
  • A page whose content stream cannot be parsed is skipped during bounding-box attachment; the rest of the tree is unaffected.
  • AstCache::get() never throws: malformed, tampered, or non-string payloads return null and force a rebuild. AstCache::set() fails loudly when the backend write or the immediate read-back fails.
  • AstMutator raises InvalidArgumentException when the node id is not found. Unknown update keys are silently ignored; only text_content is applied.
  • AstWriter::write() raises AstWriteException when the input lacks a %PDF- header or a locatable startxref. Entries without a bounding box are skipped silently. Pages that cannot be located by object scan — for example under compressed cross-reference streams — are skipped; if no overlay can be applied, the input bytes are returned unchanged.
  • OVERLAY output is not redaction. The white rectangle and redrawn text are appended; the original content bytes remain in the file and are recoverable by raw extraction. Do not use it for GDPR Art. 17 erasure or legal redaction. A reconstruct-mode writer exists in the source tree but is marked internal, is not production-ready, and is outside the supported API surface.
  • Overlay geometry assumes A4 portrait (595 x 842 pt) because the writer does not read the page MediaBox. On non-A4 pages the overlay may be slightly misaligned; the output remains structurally valid.
  • writeAndVerify() checks structure only: header, trailing %%EOF, and output growth. It does not semantically re-parse the mutated document.
  • AstPdfEmitter::emit() raises AstEmitException when the root is not a Document node or has no children. OBJR (annotation) companion entries are not emitted in this release.
  • This module performs no cryptographic operations and defines no FIPS-specific behavior. SHA-256 appears only as content addressing for cache keys.

The structure-tree path reads the tagged-PDF logical structure facilities defined by ISO 32000-2. The writer’s incremental-update layout follows ISO 32000-2:2020, 7.5.6 (cited below), and its literal-string escaping follows ISO 32000-2:2020, 7.3.4.2 (cited below).

  • Compose one AstBuilder per loaded PdfReader. Reuse an AstCache across builds to amortize parsing; the key design makes option changes self-invalidating.
  • Share one MutationLog between an AstMutator and the AstWriter so the writer applies exactly the recorded session. Call resetLog() between independent editing sessions.
  • Set useHeuristic to true for untagged documents when layout-derived grouping is preferable to the bare fallback tree.
  • Builds are deterministic for identical bytes and options; rely on this for snapshot-style tests.
  • Catch build failures via the NextPDF\Pro\Ast\Exception hierarchy and write failures via the NextPDF\Pro\Ast\Writer hierarchy; the two do not share a base below RuntimeException.

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.