Skip to content
getnextpdf.com

Pro edition

Filter — Deep Reference

This page is the contract-level reference for the NextPDF Pro Filter module, namespace NextPDF\Pro\Filter. The surface consists of two classes. DecodeParms parses a PDF /DecodeParms dictionary fragment into an immutable, bounds-checked value object. PngPredictor reverses the PNG predictor family (tags 10-15) on FlateDecoded stream bytes. The module serves the Pro Diff and Classifier extractors. It is not a general stream-filter framework. This page states the public API, the observable behavior contract, and the typed failure modes. Usage guidance and code samples live on the Filter capability page.

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 runtime capability flag gates this module. The Filter classes are available whenever nextpdf/pro is installed.

SymbolParametersDefault behaviorReturnsThrows or fails withNotes
DecodeParms::__constructint $predictor = 1, int $columns = 1, int $colors = 1, int $bitsPerComponent = 8Defaults encode “no predictor”final readonly; all four properties are public and immutable
DecodeParms::fromDictionary()string $raw — raw dictionary text, surrounding object body toleratedAbsent keys keep their defaults; matching is whitespace-tolerantselfInvalidArgumentExceptionParse-time chokepoint; bounds listed in the behavior contract
DecodeParms::isPngPredictor()nonePure predicate; no I/Obooltrue for predictor 10-15Branch on this before calling the reverse filter
PngPredictorStatelessfinal; the single entry point is the static inverse()
PngPredictor::inverse()string $raw, int $columns, int $colors, int $bitsPerComponent, int $predictorReverse-filters row by row on the per-row tag; empty input returns an empty stringstring — reconstructed payload with filter tags strippedInvalidArgumentExceptionAccepts predictor 10-15 only; the TIFF predictor is out of scope
public function __construct(
public int $predictor = 1,
public int $columns = 1,
public int $colors = 1,
public int $bitsPerComponent = 8,
) {}
public static function fromDictionary(string $raw): self
public function isPngPredictor(): bool
public static function inverse(
string $raw,
int $columns,
int $colors,
int $bitsPerComponent,
int $predictor,
): string

DecodeParms::fromDictionary() matches four recognized keys as integers in raw dictionary text: /Predictor, /Columns, /Colors, and /BitsPerComponent. These are the predictor parameters ISO 32000-2:2020 §7.4.4.4 defines for the LZWDecode and FlateDecode filters. Matching is whitespace-tolerant and survives surrounding PDF tokens. Absent keys keep their defaults: predictor 1, columns 1, colors 1, bits-per-component 8. Present values are validated fail-closed at parse time, before any geometry can reach the reverse filter’s row allocation:

  • A present negative value for any recognized key is rejected.
  • /Columns above 1,000,000 is rejected.
  • /Colors above 32 is rejected.
  • /BitsPerComponent outside {1, 2, 4, 8, 16} is rejected.
  • A derived row stride above 64,000,000 bytes is rejected.

isPngPredictor() returns true when the parsed predictor is 10 through 15. Predictor 1 (no prediction) and predictor 2 (the TIFF group) return false.

PngPredictor::inverse() consumes a FlateDecoded byte stream in which each row is preceded by a one-byte filter tag. It emits the reconstructed payload with tags stripped. Row payload width is ceil(columns * colors * bitsPerComponent / 8) bytes; the row stride adds one tag byte. The left-neighbor offset (bytes per pixel) is max(1, floor(colors * bitsPerComponent / 8)), so sub-byte packings floor to one byte. Filtering operates on whole bytes regardless of bit depth, matching PNG filter semantics.

TagFilterReconstruction
0Nonepassthrough
1Subrecon[x] = filt[x] + recon[x-bpp]
2Uprecon[x] = filt[x] + prior[x]
3Averagerecon[x] = filt[x] + floor((recon[x-bpp] + prior[x]) / 2)
4Paethrecon[x] = filt[x] + Paeth(left, up, up-left)

All sums are taken modulo 256. For the first row, and for bytes left of the first pixel, the missing neighbor reads as zero, per W3C PNG §9.2. The reverse operation is driven entirely by the per-row tag. That is the conforming behavior for both fixed predictors (10-14) and Optimum (15) under ISO 32000-2:2020 §7.4.4.4, so writer tag variance is tolerated.

Parameter validation runs in two layers by design. DecodeParms is the parse-time chokepoint and rejects hostile magnitudes first. PngPredictor::inverse() retains its own checks as a second layer: range checks on all four parameters, overflow guards that compare single factors against PHP_INT_MAX before forming the stride product, the same 64,000,000-byte per-row ceiling, and an input-proportional bound that rejects a declared stride larger than the entire input before any row buffer is allocated.

Both entry points are pure static functions of their inputs. There is no I/O, no logging, and no global state. Runtime is linear in input length with a small per-byte constant. /DecodeParms parsing is a few bounded regular-expression matches. Budgets are stated in the frontmatter performance_budget.

Every failure in this module raises InvalidArgumentException with the offending value named in the message.

  • fromDictionary() rejects a present negative value for any recognized key.
  • fromDictionary() rejects /Columns above 1,000,000 and /Colors above 32.
  • fromDictionary() rejects /BitsPerComponent outside {1, 2, 4, 8, 16} and a derived row stride above 64,000,000 bytes.
  • inverse() rejects a predictor outside 10-15. The TIFF predictor (2) is never reverse-filtered here; branch on isPngPredictor() first.
  • inverse() rejects columns or colors below 1 and bitsPerComponent outside the legal set.
  • inverse() rejects geometry whose stride product would overflow the platform integer, before any allocation.
  • inverse() rejects a row stride above the 64,000,000-byte per-row ceiling, independent of actual input length.
  • inverse() returns an empty string for empty input; that is not an error.
  • inverse() fails a declared row stride larger than the whole input as a truncated row at offset 0.
  • inverse() fails a trailing partial row as a truncated row, naming the offset and byte counts.
  • inverse() fails an unknown per-row filter tag (not 0-4) with the tag value and row offset.
  • A mismatch between declared /DecodeParms geometry and the actual stream layout surfaces as a parameter or truncation error, never as silently corrupt output.
  • The Average filter uses integer division, matching the PNG specification’s floor semantics.
  • No cryptographic operation occurs in this module. Behavior is identical in FIPS-constrained deployments.
ClaimStandardClause
The /Predictor filter parameter selects the predictor algorithm; permitted values come from the predictor-values table.ISO 32000-2:2020§7.4.4.4
PDF defines two predictor groups: the TIFF group is the single Predictor 2 function; the PNG group is tags 10-15.ISO 32000-2:2020§7.4.4.4
/BitsPerComponent valid values are 1, 2, 4, 8, and 16 with default 8; /Colors is 1 or greater with default 1; /Columns defaults to 1.ISO 32000-2:2020§7.4.4.4
Reconstruction functions for filter types 0-4 operate byte-wise modulo 256; absent left and prior-row bytes read as zero.W3C PNG (Third Edition)§9.2
The Paeth filter type computes the PaethPredictor of the left, above, and upper-left neighbors and chooses the closest.W3C PNG (Third Edition)§9.4

All clauses are paraphrased; NextPDF does not reproduce normative text. Conformance of the reconstruction math and the parameter defaults is exercised by the unit suite. A full PDF stream-filter framework, and reversal of the TIFF predictor, are out of scope for this module.

  • Both classes ship since nextpdf/pro 3.0.0 and are current in 3.1.0.
  • The module is consumed by the Pro Diff and Classifier extractors when their inputs carry a predictor.
  • Branch on isPngPredictor() before calling inverse(); predictor 1 and the TIFF predictor need no PNG reversal.
  • The module bounds its own per-row allocation. Callers reversing predictors on untrusted streams should still bound the decompressed input size upstream, as the Pro extractors do.
  • Fixed predictors (10-14) and Optimum (15) share one code path; the per-row tag drives reconstruction in both cases.
  • Internal mechanism detail stays in the source repository’s internal documentation and is out of scope for this manual.

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.