Skip to content

Optimizer Options

optimize.New turns an optimize.Options value into a chain of passes, and PdfWriter.SetOptimizer attaches that chain to the writer. Nothing runs until Write; the chain receives the assembled object list and rewrites it in place, so optimization is a property of how the file is written rather than a separate step over the document.

Every option is off by default. Enabling one appends its pass to the chain.

OptionPassLossless
CombineDuplicateDirectObjectsReplaces direct objects with the same data hash by a single shared object.Yes
CombineDuplicateStreamsSame, for streams with identical data.Yes
CombineIdenticalIndirectObjectsCollapses indirect objects that serialize identically.Yes
CompressStreamsFlate-encodes streams that carry no filter.Yes
UseObjectStreamsPacks eligible objects into object streams, with cross-reference streams.Yes
CleanUnusedResourcesDrops resource entries no content stream references: images, form XObjects, fonts, ExtGState.Yes
CleanFontsRewrites embedded TrueType programs with only the tables that are needed.Yes
SubsetFontsRewrites embedded TrueType programs with only the glyphs the document uses.Yes
CleanContentstreamRemoves marked-content operators and shortens some operands.No, see below
ImageQuality (1-100)Re-encodes images as JPEG at that quality.No
ImageUpperPPIDownsamples images drawn at a higher effective PPI than the limit.No

Only ImageQuality and ImageUpperPPI change what the page looks like. The rest are structural. CleanContentstream does not change rendering either, but it does discard structure information, which is a different kind of loss.

Doing it

pdfWriter, err := reader.ToWriter(nil)
if err != nil {
    return err
}

pdfWriter.SetOptimizer(optimize.New(optimize.Options{
    CombineDuplicateDirectObjects:   true,
    CombineIdenticalIndirectObjects: true,
    CombineDuplicateStreams:         true,
    CompressStreams:                 true,
    UseObjectStreams:                true,
    ImageQuality:                    80,
    ImageUpperPPI:                   100,
    CleanUnusedResources:            true,
}))

return pdfWriter.WriteToFile(outputPath)

The order of the fields in the struct literal is irrelevant. optimize.New fixes the pass order: fonts, content streams, image PPI, image quality, the four deduplication passes, object streams, stream compression, unused resources. Font work happens before anything has been deduplicated, and compression happens last so it sees the final streams.

Both image options can be set together. ImageUpperPPI downsamples first, then ImageQuality re-encodes what is left, so the two compound.

optimize.Chain is exported and takes any model.Optimizer through Append, which is the way to run a subset in a different order or add your own pass.

Limitations

A pass that fails does not fail the write. Chain.Optimize logs the error at debug level and moves on with the objects it had, so a document that optimizes badly comes out correct but larger, and nothing is returned to tell you. Turning on debug logging is the only way to see which pass gave up.

CleanContentstream strips BDC, BMC and EMC from page and form XObject content streams. Those operators are what associate page content with the structure tree, so this option breaks tagging, and with it PDF/UA and the A conformance levels of PDF/A. It also removes optional content membership markers. Leave it off for tagged output.

ImageQuality skips images that are used as a soft mask by another image, and skips JBIG2 and CCITT fax images on the grounds that they are already well compressed. If the JPEG re-encode comes out larger than the original stream, the original is kept.

ImageUpperPPI works out the effective resolution of each image by processing page content streams and reading the current transformation matrix at every Do. An image that is never drawn from a page content stream, for example one referenced only from a form XObject or an annotation appearance stream, is never measured and is left at its original size. Soft mask images are skipped here too.

CleanUnusedResources only touches page resource dictionaries, and it pools the names it finds across the whole document. A resource name drawn on any one page is kept in the resources of every page that lists it, and resources inside a form XObject are not examined at all.

CleanFonts and SubsetFonts only handle TrueType-flavored sfnt programs. OpenType fonts with PostScript outlines, the ones whose program starts with OTTO, are skipped, and so is any font whose rewritten program would be larger than the original. See font subsetting for the rest.

Optimization operates on the object list, not on the file layout. If you also want the output arranged for progressive download, see linearization.

Run the example

pdf_optimize.go reads a file, attaches the chain shown above and prints the size before and after. Everything specific to optimization is the single SetOptimizer call in main.

git clone https://github.com/unidoc/unipdf-examples.git
cd unipdf-examples/compress
go run pdf_optimize.go <input.pdf> <output.pdf>

If this is your first time using UniPDF, follow the getting started guide to create an API key and set up your development environment.

View the full source

The same directory holds pdf_remove_unused_resources.go, which runs CleanUnusedResources on its own. That is the pass to reach for when a document has been through several edits and carries resources nothing draws any more.

Sample output

Original file: input.pdf
Original size: 266845 bytes
Optimized file: output.pdf
Optimized size: 207347 bytes
Compression ratio: 22.30%
Processing time: 53.17 ms
Last updated on