Pro edition
Optimizer — Deep Reference
At a glance
Section titled “At a glance”This page is the deep reference for the public surface of NextPDF\Pro\Optimizer. It covers the analysis orchestrator, the optimization levels, the two scanners, and the result value objects. It states parameters, defaults, estimation arithmetic, and failure modes. Analysis is read-only: it estimates savings and produces no output document. Read the Optimizer capability page first for workflow guidance.
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.
Optimizer has no per-feature license flag. This is a Pro-edition capability. The optimization level is a runtime parameter, not a license switch.
Public API surface
Section titled “Public API surface”composer require nextpdf/pro:^3The nextpdf/premium metapackage installs the nextpdf/pro code; this module lives under the NextPDF\Pro\Optimizer namespace.
| Symbol | Parameters | Default behavior | Returns | Throws or fails with | Notes |
|---|---|---|---|---|---|
PdfOptimizer::__construct | OptimizationLevel $level = OptimizationLevel::Balanced | Builds an optimizer at the given level | PdfOptimizer | Nothing declared | Constructs its own scanner instances |
PdfOptimizer::analyze | string $pdfData | Read-only analysis at the configured level | OptimizationResult | OverflowException on input above 100,000,000 bytes; InvalidArgumentException from the scanners on invalid PDF data | Estimates only; produces no output document |
PdfOptimizer::withLevel | OptimizationLevel $level | Returns a new optimizer at the requested level | self | Nothing declared | The receiving instance is unchanged |
OptimizationLevel | cases Lossless, Balanced, Aggressive | String-backed enum of aggressiveness levels | — | — | Backing values lossless, balanced, aggressive |
OptimizationLevel::label | none | Human-readable level label | string | Nothing declared | For display use |
OptimizationLevel::imageQuality | none | Target image quality for the level | int | Nothing declared | 100, 75, or 50 |
OptimizationLevel::deduplicateStreams | none | Whether the level enables deduplication | bool | Nothing declared | false for Lossless only |
OptimizationResult::__construct | int $originalSize, int $optimizedSize, int $objectsRemoved, int $imagesBefore, int $imagesAfter, float $processingTimeMs | Immutable analysis result | OptimizationResult | Nothing declared | All properties are public and readonly |
OptimizationResult::savedBytes | none | Original size minus estimated optimized size | int | Nothing declared | Bytes |
OptimizationResult::savedPercent | none | Percentage size reduction | float | Nothing declared | 0.0 when the original size is zero |
OptimizationResult::summary | none | Multi-line human-readable report | string | Nothing declared | Sizes formatted as B, KB, or MB |
ObjectDeduplicator::findDuplicates | string $pdfData | Groups identical object bodies by SHA-256 hash | list<DuplicateGroup> | InvalidArgumentException on a missing %PDF header, input above 268,435,456 bytes, or more than 500,000 object markers | Returns only groups with two or more members |
ObjectDeduplicator::estimateSavings | list<DuplicateGroup> $groups | Sums duplicate count times object size per group | int | Nothing declared | Bytes |
ImageRecompressor::analyzeImages | string $pdfData | Extracts metadata for every image XObject | list<ImageAnalysis> | InvalidArgumentException on a missing %PDF header | Skips objects without explicit width and height |
ImageRecompressor::suggestCompression | ImageAnalysis $image, OptimizationLevel $level | Recommends a filter and estimates savings | ImageCompressionSuggestion | Nothing declared | Level-dependent heuristics; see the behavior contract |
DuplicateGroup::__construct | string $contentHash, list<int> $objectNumbers, int $objectSize | Immutable duplicate-group record | DuplicateGroup | Nothing declared | The first object number is the canonical kept object |
DuplicateGroup::duplicateCount | none | Group size minus the canonical object | int | Nothing declared | Objects removable by merging |
ImageAnalysis::__construct | int $objectNumber, int $width, int $height, string $colorSpace, int $bitsPerComponent, string $filter, int $streamSize | Immutable per-image metadata record | ImageAnalysis | Nothing declared | Fields mirror the image dictionary entries |
ImageAnalysis::estimatedDpi | float $displayWidthPt | Effective DPI at the given display width | float | Nothing declared | 0.0 when the display width is zero or negative |
ImageAnalysis::isOverResolution | float $displayWidthPt, int $targetDpi = 300 | Flags downsampling candidates above the target DPI | bool | Nothing declared | Strictly greater-than comparison |
ImageCompressionSuggestion::__construct | int $objectNumber, string $currentFilter, string $suggestedFilter, int $estimatedSavings, string $reason | Immutable recommendation record | ImageCompressionSuggestion | Nothing declared | reason is human-readable explanatory text |
Entry-point signatures
Section titled “Entry-point signatures”final class PdfOptimizer{ public function __construct( private OptimizationLevel $level = OptimizationLevel::Balanced, )
public function analyze(string $pdfData): OptimizationResult
public function withLevel(OptimizationLevel $level): self}enum OptimizationLevel: string{ case Lossless = 'lossless'; case Balanced = 'balanced'; case Aggressive = 'aggressive';
public function label(): string
public function imageQuality(): int
public function deduplicateStreams(): bool}final readonly class OptimizationResult{ public function __construct( public int $originalSize, public int $optimizedSize, public int $objectsRemoved, public int $imagesBefore, public int $imagesAfter, public float $processingTimeMs, )
public function savedBytes(): int
public function savedPercent(): float
public function summary(): string}final class ObjectDeduplicator{ public function findDuplicates(string $pdfData): array
public function estimateSavings(array $groups): int}final class ImageRecompressor{ public function analyzeImages(string $pdfData): array
public function suggestCompression( ImageAnalysis $image, OptimizationLevel $level, ): ImageCompressionSuggestion}Behavior contract
Section titled “Behavior contract”Orchestration
Section titled “Orchestration”PdfOptimizer::analyze accepts raw PDF bytes and is read-only. It first bounds the untrusted input at 100,000,000 bytes; oversized input raises OverflowException before any scan runs. It then runs deduplication analysis when the level allows it, always runs image analysis, and aggregates both into one OptimizationResult. withLevel returns a new optimizer; instances are never mutated.
Level semantics
Section titled “Level semantics”| Level | Image quality target | Deduplication | Intent |
|---|---|---|---|
Lossless | 100% | Off | No quality loss; byte-stable output intent |
Balanced | 75% | On | Moderate quality trade-off; the default |
Aggressive | 50% | On | Maximum reduction; downsampling; visible quality loss |
Lossless skips deduplication so output can remain byte-stable. The quality target feeds the image-suggestion arithmetic below.
Deduplication analysis
Section titled “Deduplication analysis”The deduplicator scans generation-zero indirect object definitions (N 0 obj through endobj). Each body is trimmed of surrounding whitespace, hashed with SHA-256, and grouped by hash. Definitions differing only in padding therefore still match. Only groups with two or more members are returned. Estimated savings per group equal the duplicate count times the single-body size, since all but the canonical object can be removed.
Image analysis
Section titled “Image analysis”An object is treated as an image when its body contains /Subtype /Image (with or without an internal space). Width and height are required; an object missing either is skipped. Color space defaults to DeviceRGB, bits per component to 8, and the filter to an empty string when absent. Stream size is measured between the stream and endstream markers; when no inline stream is found, the /Length value is used instead.
Suggestion heuristics
Section titled “Suggestion heuristics”- At the
Losslesslevel, the current filter is kept and estimated savings are zero. - For
DCTDecodesources, the suggestion re-encodes at the level’s quality. The estimate is stream size times (1 − quality/100) times 0.5. - For
FlateDecodesources, the suggestion converts toDCTDecode. The estimate is 40% of stream size atBalancedand 60% atAggressive. - For any other filter, or no filter, the suggestion converts to
FlateDecode. The estimate is 20% of stream size.
Result arithmetic
Section titled “Result arithmetic”- Objects removed equals the sum, over all duplicate groups, of members beyond the canonical first.
- Total savings equal deduplication savings plus the per-image suggestion estimates.
- The estimated optimized size is the original size minus total savings, floored at zero. Savings are non-negative, so the estimate never exceeds the original size.
- The images-after count subtracts, for each duplicate group containing an analyzed image, that group’s duplicate member count. The count is floored at zero.
- Processing time is measured with a monotonic clock and reported in milliseconds.
The DPI estimator divides pixel width by display width in inches (72 points per inch). A zero or negative display width yields 0.0. The over-resolution predicate compares the estimate against a target, 300 DPI by default.
Edge cases & failure modes
Section titled “Edge cases & failure modes”analyzereports potential only. Produce optimized output with the Writer module.- Empty input, or input not starting with the
%PDFheader, fails withInvalidArgumentException. - Input above 100,000,000 bytes fails with
OverflowExceptionat the orchestrator front door, before any scan. - The deduplicator independently rejects input above 268,435,456 bytes and more than 500,000 object markers. Both reject fail-closed with
InvalidArgumentException; nothing is truncated or partially scanned. - Only generation-zero object definitions participate. Objects with nonzero generation numbers are not scanned.
- A definition without a closing
endobjmarker is skipped. - Image objects without an explicit width and height are excluded from the image report.
- All savings figures are heuristics derived from object metadata, not measured recompression results.
- The lossless level intentionally reports small reductions; it preserves quality and skips deduplication.
- Analysis never decodes, executes, or renders embedded content. It reads object structure and metadata only.
- The only cryptographic primitive used is SHA-256, for duplicate-content grouping. The module defines no FIPS-specific behavior.
Conformance
Section titled “Conformance”Both scanners operate on the PDF object and image model of ISO 32000-2:2020. Deduplication targets indirect object definitions; their identifier structure is defined in ISO 32000-2:2020, 7.3.10, cited in this page’s citation record. Image analysis reads the parameters an image dictionary states explicitly — width, height, and bits per component — per ISO 32000-2:2020, 8.9.4, also cited.
These statements describe capability against the cited clauses.
Development notes
Section titled “Development notes”- The module source carries
@since 1.9.0; this reference documents the surface as shipped innextpdf/pro3.1.0. - All classes are
final; the result and analysis records are readonly value objects. Construct new instances instead of mutating. - The default level is
Balanced. Select another level through the constructor or the with-style method. - The front-door input bound is enforced by a Core input-size guard shared across NextPDF input surfaces.
- Analysis is string-based over bytes already in memory. The module performs no filesystem or network access.
- Internal mechanism detail stays in the source repository’s internal documentation and is out of scope for this manual.
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.
See also
Section titled “See also”- Optimizer — the capability page for workflow guidance and code samples.
- Writer — Deep Reference — produces the optimized output document.
- Accelerator — Deep Reference — batch optimization with sidecar offload at this module’s semantics.