Pro edition
AST — Deep Reference
At a glance
Section titled “At a glance”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.
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. Build behavior is governed entirely by AstBuildOptions.
Public API surface
Section titled “Public API surface”| Symbol | Parameters | Default behavior | Returns | Throws or fails with | Notes |
|---|---|---|---|---|---|
AstBuilder::__construct | PdfReader $reader, AstBuildOptions $options, ?AstCache $cache = null | Binds a loaded reader to build options; caching is optional | AstBuilder | — | A null cache means every build() call rebuilds. |
AstBuilder::build | string $sourceHash (full SHA-256 hex of the PDF bytes) | Cache lookup, encryption rejection, structure-tree path, untagged fallback, bounding-box attachment, cache store | AstDocument | AstUnsupportedEncryptionException, AstBuildLimitException, AstBuildTimeoutException | A 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 = false | Immutable configuration value object | AstBuildOptions | — | estimatedTokenBudget is an informational hint; it is not enforced. |
AstBuildOptions::pageRangeContains | int $pageIndex | True when the 0-based index falls inside the configured range | bool | — | Null bounds are open-ended; both null means all pages. |
AstBuildOptions::hash | — | Stable SHA-256 over all option values | string | — | Equal values yield equal hashes across instances; used as the cache-key segment. |
AstCache::__construct | CacheInterface $backend | Wraps any PSR-16 backend | AstCache | — | — |
AstCache::buildKey | string $sourceHash, AstBuildOptions $options | Key = nextpdf_ast_v1_ + first 32 hex of source hash + _ + first 16 hex of options hash | string | — | Option changes invalidate cached results automatically. |
AstCache::get | string $cacheKey | Decodes a JSON payload through strict per-field validation | ?AstDocument | Never throws; failures return null | Malformed or tampered payloads fail closed as a cache miss. |
AstCache::set | string $cacheKey, AstDocument $document | Stores JSON with a 24-hour TTL, then verifies by immediate read-back | void | AstWriteVerificationException (Exception namespace) | Backend write failure or a failed round-trip raises. |
AstCache::delete | string $cacheKey | Best-effort removal | void | Never throws | Backend deletion failures are swallowed. |
AstCache::has | string $cacheKey | Best-effort existence check | bool | Never throws; failures return false | — |
AstMutator::updateNode | AstDocument $document, string $nodeId, array $updates | Replaces text_content, records an Updated entry | AstDocument (new instance) | InvalidArgumentException | Only the text_content key is applied; unknown keys are ignored. |
AstMutator::deleteNode | AstDocument $document, string $nodeId | Removes the node from the in-memory tree, records a Deleted entry | AstDocument (new instance) | InvalidArgumentException | In-memory removal only; see the redaction caveat below. |
AstMutator::getMutationLog | — | Returns the shared log instance | MutationLog | — | Pass the same log to AstWriter. |
AstMutator::resetLog | — | Discards all recorded mutations | void | — | Starts a fresh log. |
MutationLog | record, all, isEmpty, count, forNode, mutatedNodeIds | Append-only in-memory log, insertion order preserved | per method | — | forNode returns the most recent entry for a node; the last entry wins. |
MutationEntry::__construct | string $nodeId, MutationType $type, ?AstNode $originalNode, ?AstNode $mutatedNode, DateTimeImmutable $timestamp | Immutable record of one mutation | MutationEntry | — | originalNode is null for Inserted; mutatedNode is null for Deleted. |
MutationType | enum cases Updated, Inserted, Deleted | String-backed classification | — | — | Deleted under OVERLAY hides content; it does not erase bytes. |
AstWriter::write | string $originalPdfBytes, MutationLog $log | Appends an incremental update whose overlay streams cover mutated bounding boxes | string (modified PDF bytes) | AstWriteException | An empty log returns the input unchanged. Inserted entries and entries without a bounding box are skipped. |
AstWriter::writeAndVerify | string $originalPdfBytes, MutationLog $log | Runs write(), then a structural output check | string (verified PDF bytes) | AstWriteException, AstWriteVerificationException (Writer namespace) | Verification is structural, not semantic. |
AstPdfEmitter::emit | AstNode $root, BinaryBuffer $buffer, ObjectRegistry $registry, array $pageObjects | Writes a StructTreeRoot, StructElem chain, and ParentTree for the supplied tree | EmitResult | AstEmitException | The root must be a Document node with children. Round-trip emitter for structure-tree verification. |
EmitResult::__construct | int $structTreeRootObject, int $rootElementObject, int $parentTreeObject, int $elementObjectCount, int $parentTreeNextKey | Immutable record of the emitted object identifiers | EmitResult | — | — |
public function build(string $sourceHash): AstDocumentpublic function updateNode(AstDocument $document, string $nodeId, array $updates): AstDocumentpublic function deleteNode(AstDocument $document, string $nodeId): AstDocumentpublic function write(string $originalPdfBytes, MutationLog $log): stringpublic function writeAndVerify(string $originalPdfBytes, MutationLog $log): stringException hierarchy
Section titled “Exception hierarchy”NextPDF\Pro\Ast\Exception\AstExceptionextendsRuntimeException— base of the build hierarchy.AstBuildLimitExceptionextendsAstException— a node, depth, or memory ceiling was exceeded.AstBuildTimeoutExceptionextendsAstBuildLimitException— the wall-clock build timeout elapsed.AstNoStructTreeExceptionextendsAstException— no structure tree present.AstBuilder::build()catches it internally and falls back; callers ofbuild()do not observe it.AstUnsupportedEncryptionExceptionextendsAstException— the input PDF is encrypted.NextPDF\Pro\Ast\Exception\AstWriteVerificationExceptionextendsAstException— cache write verification failed.NextPDF\Pro\Ast\Writer\AstWriteExceptionextendsRuntimeException— writer input or structure failure.NextPDF\Pro\Ast\Writer\AstWriteVerificationExceptionextendsAstWriteException— 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.
Behavior contract
Section titled “Behavior contract”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.
Edge cases & failure modes
Section titled “Edge cases & failure modes”- 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 raisesAstBuildTimeoutException, 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.AstMutatorraisesInvalidArgumentExceptionwhen the node id is not found. Unknown update keys are silently ignored; onlytext_contentis applied.AstWriter::write()raisesAstWriteExceptionwhen the input lacks a%PDF-header or a locatablestartxref. 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()raisesAstEmitExceptionwhen 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.
Conformance
Section titled “Conformance”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).
Development notes
Section titled “Development notes”- Compose one
AstBuilderper loadedPdfReader. Reuse anAstCacheacross builds to amortize parsing; the key design makes option changes self-invalidating. - Share one
MutationLogbetween anAstMutatorand theAstWriterso the writer applies exactly the recorded session. CallresetLog()between independent editing sessions. - Set
useHeuristicto 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\Exceptionhierarchy and write failures via theNextPDF\Pro\Ast\Writerhierarchy; the two do not share a base belowRuntimeException.
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.