New in Chilkat v11.7.0

PDF Optimization: Pdf.Optimize

Measure and shrink a PDF without changing how any page looks. audit reports where the bytes go and what each option would save; optimize recompresses, deduplicates, subsets and tidies, and reports what it did. Every option is lossless.

bool Optimize(string op, JsonObject jsonIn, JsonObject jsonOut)

The operations

opWhat it doesjsonInjsonOut
auditReports where the file's bytes go and what each option would save. Changes nothing; allowed on any PDF.The same options as optimize (which savings to estimate)Space by category, estimated savings per option
optimizeApplies the selected options to the PDF in memory. The result is written by WriteFile or WriteBd.Preset and switchesWhat was done and bytes saved, per option

Most of the work (deduplicating, removing unused objects and resources, merging content streams, subsetting fonts, recompressing streams) is done in memory during the optimize call, so its savings are exact. Two options take effect when the file is written — objectStreams and dropOldRevisions — so their savings are estimates until the PDF is saved. JPEG, JPEG 2000, JBIG2 and CCITT image data are never re-encoded; their bytes pass through unchanged.

Quick start

An empty jsonIn applies the lossless preset: every option at its default.

Chilkat.Pdf pdf = new Chilkat.Pdf();
pdf.LoadFile("in.pdf");

Chilkat.JsonObject jsonIn = new Chilkat.JsonObject();
Chilkat.JsonObject jsonOut = new Chilkat.JsonObject();

if (pdf.Optimize("optimize", jsonIn, jsonOut)) {
    pdf.WriteFile("out.pdf");
    Console.WriteLine("Saved about " + jsonOut.IntOf("bytesSaved") + " bytes");
}

Audit first, optimize only if it is worth it

pdf.Optimize("audit", jsonIn, jsonOut);
int fileSize = jsonOut.IntOf("fileSize");
int potential = jsonOut.IntOf("estimatedSavings.total");
if (potential > fileSize / 10) {               // more than 10%
    pdf.Optimize("optimize", jsonIn, jsonOut);
    pdf.WriteFile("out.pdf");
}

The options (jsonIn)

General

MemberValues (default in bold)Description
presetlosslessThe only preset: every switch below at its default. Any switch can be set to override it.
pagesall, or a range list such as 1,3,5-9,12-Limits the page-level work (page content and resources) to these pages. Document-wide work always applies to the whole file.

Streams

MemberDefaultWhat it does
compressUncompressedtrueFlate-encodes streams stored with no filter.
recompressFlatetrueRe-deflates Flate streams at compressionLevel; keeps the result only if it is smaller.
replaceLegacyFilterstrueRe-encodes LZW, ASCIIHex, ASCII85 and RunLength streams as Flate.
imagePredictorstrueTries PNG predictors on uncompressed and Flate images; keeps the result only if it is smaller.
compressionLevel9The Flate level, 1–9, used by the options above.

Duplicates

MemberDefaultWhat it does
dedupeStreamstrueStores byte-identical streams with identical dictionaries once: images, embedded fonts, form XObjects, ICC profiles, page content. (Acrobat, for example, writes an identical watermark form on every page.)
dedupeObjectstrueStores identical non-stream objects (font dictionaries, color spaces, graphics states) once.
mergeFontSubsetsfalseReserved for merging several subsets of the same font into one. Not implemented in this release; when set, it is reported in skipped.

Unused content

MemberDefaultWhat it does
removeUnreferencedtrueDrops objects not reachable from the document catalog.
removeUnusedResourcestrueDrops entries in page and form resource dictionaries that the content never uses.
removeUnusedLayerstrueDrops optional-content layers that no content refers to, such as the empty "Watermark" layer Acrobat leaves behind after removing a watermark.

Page content

MemberDefaultWhat it does
coalesceContenttrueMerges a page's multiple content streams into one, so that they compress together.
tidyContenttrueRemoves redundant white space, empty q/Q pairs and graphics-state operators that change nothing. Numbers are never rounded; marked-content tags are never dropped.

Fonts

MemberDefaultWhat it does
subsetFontstrueReduces fully embedded TrueType/OpenType fonts (CIDFontType2 with a FontFile2 program) to the glyphs used. Fonts that form fields need for newly typed text are left whole, as are Type 1, CFF and already-subset fonts.
compactType1FontsfalseReserved for converting embedded Type 1 fonts to CFF. Not implemented in this release; when set, it is reported in skipped.

File structure

MemberValues (default in bold)What it does
objectStreamsgenerate, preserve, disablegenerate packs small objects into compressed object streams with a cross-reference stream (PDF 1.5; the version is raised to 1.5 if lower, never lowered). preserve generates them only if the file already had them. disable writes classic objects and a classic cross-reference table, for consumers that cannot read object streams.
dropOldRevisionstrue, falseSaves a single revision, discarding the superseded content kept by earlier incremental updates. Also a privacy gain: old revisions can hold deleted text.

Example: override individual switches

jsonIn: for an old consumer that cannot read object streams

{
  "objectStreams": "disable",
  "dropOldRevisions": false
}

jsonOut

{
  "bytesSaved": 1181810,
  "bytesSavedIsEstimate": false,
  "actions": { "subsetFonts": { "count": 7, "bytesSaved": 942000 },
               "dedupeStreams": { "count": 39, "bytesSaved": 211800 }, ... },
  "skipped": []
}

jsonIn: only the first chapter's pages, faster compression

{
  "pages": "1-24",
  "compressionLevel": 6,
  "subsetFonts": false
}

jsonOut

{
  "bytesSaved": 318540,
  "bytesSavedIsEstimate": true,
  "actions": { "dedupeStreams": { "count": 12, "bytesSaved": 201300 },
               "objectStreams": { "bytesSaved": 54300, "estimate": true }, ... },
  "skipped": []
}

The audit report (op = audit)

jsonIn

{ }

jsonOut

{
  "fileSize": 4831022,
  "pdfVersion": "1.4",
  "pdfa": "none",
  "usage": {
    "images":        { "bytes": 3120455, "count": 41 },
    "fonts":         { "bytes": 1204310, "count": 12 },
    "pageContent":   { "bytes": 288112,  "count": 60 },
    "formXObjects":  { "bytes": 61200,   "count": 9 },
    "annotations":   { "bytes": 8400,    "count": 22 },
    "structureTree": { "bytes": 51020 },
    "metadata":      { "bytes": 6400 },
    "embeddedFiles": { "bytes": 0,       "count": 0 },
    "oldRevisions":  { "bytes": 18210 },
    "overhead":      { "bytes": 72915 }
  },
  "estimatedSavings": {
    "total": 1290410,
    "subsetFonts": 942000,
    "dedupeStreams": 211800,
    "objectStreams": 54300,
    "recompressFlate": 41210,
    "dropOldRevisions": 18210,
    "removeUnreferenced": 12890,
    "coalesceContent": 10000
  },
  "skipped": []
}
MemberMeaning
fileSizeThe file size in bytes.
pdfVersionThe version in the file header.
pdfaThe PDF/A part and conformance the file claims in its XMP metadata, such as 2b, or none.
usageWhere the bytes go, as stored (compressed) in the file. The categories cover the whole file, including those the lossless options cannot touch (images, metadata), so that callers see where a lossy pass would help later. overhead is cross-reference data, object headers and everything not counted elsewhere.
estimatedSavingstotal, and the estimate for each option by its name. Options that would save nothing are omitted.
skippedOptions that could not be applied (see below).

The optimize report (op = optimize)

jsonIn

{ }

jsonOut

{
  "bytesSaved": 1236110,
  "bytesSavedIsEstimate": true,
  "actions": {
    "subsetFonts":        { "count": 7,  "bytesSaved": 942000 },
    "dedupeStreams":      { "count": 39, "bytesSaved": 211800 },
    "objectStreams":      { "bytesSaved": 54300, "estimate": true },
    "recompressFlate":    { "count": 88, "bytesSaved": 41210 },
    "dropOldRevisions":   { "bytesSaved": 18210, "estimate": true },
    "removeUnreferenced": { "count": 17, "bytesSaved": 12890 },
    "coalesceContent":    { "count": 12, "bytesSaved": 10000 }
  },
  "skipped": [
    { "option": "tidyContent", "pages": "12",
      "reason": "content could not be parsed; left unchanged" }
  ]
}
MemberMeaning
bytesSavedThe total bytes saved.
bytesSavedIsEstimateTrue while a save-time option (objectStreams, dropOldRevisions) contributes; the exact figure is the difference in file size after WriteFile.
actionsPer option name: count (the objects affected, where that makes sense), bytesSaved, and estimate true for the save-time options.
skipped[i]Options that were requested but not applied: option, pages (when page-specific) and reason — page content that could not be parsed, a reserved option, objectStreams on a PDF/A-1 file, or a font the content analysis could not account for.

Signed, encrypted, PDF/A and tagged PDFs

Optimizing always rewrites the whole file, so it follows these rules; audit changes nothing and is always allowed.

  • Signed PDFs. With the PreserveSignatures property true (the default), optimize fails on a signed PDF. With it false, the PDF is optimized and its signatures are invalidated.
  • Encrypted PDFs. The PDF is saved with its original security unchanged; object streams are compatible with encryption. Because Chilkat does not ignore owner restrictions, optimize on an encrypted PDF requires owner rights or the modify permission (see Protection).
  • PDF/A. Chilkat reads the PDF/A claim from the XMP metadata and stays within that part's rules: for PDF/A-1, objectStreams is skipped and reported; for every part, the output intent and XMP metadata are kept and fonts stay embedded (subsetting is allowed). replaceLegacyFilters also removes LZW, which PDF/A forbids.
  • Forms. Fonts that form fields need to display newly typed text are not subset.
  • Tagged PDF. The structure tree is never touched; tidyContent keeps every marked-content tag, and dedupeStreams never shares a page content stream that carries marked-content IDs between two pages.

Option reference

{
  "preset": "lossless",
  "pages": "all",
  "compressUncompressed": true,     "recompressFlate": true,
  "replaceLegacyFilters": true,     "imagePredictors": true,
  "compressionLevel": 9,
  "dedupeStreams": true,            "dedupeObjects": true,
  "mergeFontSubsets": false,
  "removeUnreferenced": true,       "removeUnusedResources": true,
  "removeUnusedLayers": true,
  "coalesceContent": true,          "tidyContent": true,
  "subsetFonts": true,              "compactType1Fonts": false,
  "objectStreams": "generate",      // generate | preserve | disable
  "dropOldRevisions": true
}

Reference documentation: Pdf.Optimize, Pdf.WriteFile. Related: ExtractText, TextReplace, Watermark, Protection, Pdf class overview.

New in Chilkat v11.7.0.