Skip to content
getnextpdf.com

Pro edition

Optimizer — Deep Reference

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.

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.

Terminal window
composer require nextpdf/pro:^3

The nextpdf/premium metapackage installs the nextpdf/pro code; this module lives under the NextPDF\Pro\Optimizer namespace.

SymbolParametersDefault behaviorReturnsThrows or fails withNotes
PdfOptimizer::__constructOptimizationLevel $level = OptimizationLevel::BalancedBuilds an optimizer at the given levelPdfOptimizerNothing declaredConstructs its own scanner instances
PdfOptimizer::analyzestring $pdfDataRead-only analysis at the configured levelOptimizationResultOverflowException on input above 100,000,000 bytes; InvalidArgumentException from the scanners on invalid PDF dataEstimates only; produces no output document
PdfOptimizer::withLevelOptimizationLevel $levelReturns a new optimizer at the requested levelselfNothing declaredThe receiving instance is unchanged
OptimizationLevelcases Lossless, Balanced, AggressiveString-backed enum of aggressiveness levelsBacking values lossless, balanced, aggressive
OptimizationLevel::labelnoneHuman-readable level labelstringNothing declaredFor display use
OptimizationLevel::imageQualitynoneTarget image quality for the levelintNothing declared100, 75, or 50
OptimizationLevel::deduplicateStreamsnoneWhether the level enables deduplicationboolNothing declaredfalse for Lossless only
OptimizationResult::__constructint $originalSize, int $optimizedSize, int $objectsRemoved, int $imagesBefore, int $imagesAfter, float $processingTimeMsImmutable analysis resultOptimizationResultNothing declaredAll properties are public and readonly
OptimizationResult::savedBytesnoneOriginal size minus estimated optimized sizeintNothing declaredBytes
OptimizationResult::savedPercentnonePercentage size reductionfloatNothing declared0.0 when the original size is zero
OptimizationResult::summarynoneMulti-line human-readable reportstringNothing declaredSizes formatted as B, KB, or MB
ObjectDeduplicator::findDuplicatesstring $pdfDataGroups identical object bodies by SHA-256 hashlist<DuplicateGroup>InvalidArgumentException on a missing %PDF header, input above 268,435,456 bytes, or more than 500,000 object markersReturns only groups with two or more members
ObjectDeduplicator::estimateSavingslist<DuplicateGroup> $groupsSums duplicate count times object size per groupintNothing declaredBytes
ImageRecompressor::analyzeImagesstring $pdfDataExtracts metadata for every image XObjectlist<ImageAnalysis>InvalidArgumentException on a missing %PDF headerSkips objects without explicit width and height
ImageRecompressor::suggestCompressionImageAnalysis $image, OptimizationLevel $levelRecommends a filter and estimates savingsImageCompressionSuggestionNothing declaredLevel-dependent heuristics; see the behavior contract
DuplicateGroup::__constructstring $contentHash, list<int> $objectNumbers, int $objectSizeImmutable duplicate-group recordDuplicateGroupNothing declaredThe first object number is the canonical kept object
DuplicateGroup::duplicateCountnoneGroup size minus the canonical objectintNothing declaredObjects removable by merging
ImageAnalysis::__constructint $objectNumber, int $width, int $height, string $colorSpace, int $bitsPerComponent, string $filter, int $streamSizeImmutable per-image metadata recordImageAnalysisNothing declaredFields mirror the image dictionary entries
ImageAnalysis::estimatedDpifloat $displayWidthPtEffective DPI at the given display widthfloatNothing declared0.0 when the display width is zero or negative
ImageAnalysis::isOverResolutionfloat $displayWidthPt, int $targetDpi = 300Flags downsampling candidates above the target DPIboolNothing declaredStrictly greater-than comparison
ImageCompressionSuggestion::__constructint $objectNumber, string $currentFilter, string $suggestedFilter, int $estimatedSavings, string $reasonImmutable recommendation recordImageCompressionSuggestionNothing declaredreason is human-readable explanatory text
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
}

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.

LevelImage quality targetDeduplicationIntent
Lossless100%OffNo quality loss; byte-stable output intent
Balanced75%OnModerate quality trade-off; the default
Aggressive50%OnMaximum reduction; downsampling; visible quality loss

Lossless skips deduplication so output can remain byte-stable. The quality target feeds the image-suggestion arithmetic below.

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.

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.

  • At the Lossless level, the current filter is kept and estimated savings are zero.
  • For DCTDecode sources, the suggestion re-encodes at the level’s quality. The estimate is stream size times (1 − quality/100) times 0.5.
  • For FlateDecode sources, the suggestion converts to DCTDecode. The estimate is 40% of stream size at Balanced and 60% at Aggressive.
  • For any other filter, or no filter, the suggestion converts to FlateDecode. The estimate is 20% of stream size.
  • 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.

  • analyze reports potential only. Produce optimized output with the Writer module.
  • Empty input, or input not starting with the %PDF header, fails with InvalidArgumentException.
  • Input above 100,000,000 bytes fails with OverflowException at 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 endobj marker 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.

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.

  • The module source carries @since 1.9.0; this reference documents the surface as shipped in nextpdf/pro 3.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.

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.