PDF Optimization

Audit, Classify & Optimize PDFs

Drop in any PDF. The demo runs a Space Audit (where every byte lives), detects whether the document is a scan or born-digital, then recommends and applies the optimization that fits — MRC compression for scans, general optimization for everything else. Powered by the Foxit PDF SDK Optimization Add-On.

Machine
Foxit PDF SDK version

How it works

1 — Space Audit. Optimizer::ComputeAuditSpace() walks the file and attributes every byte to a category — images, fonts, content streams, bookmarks, attachments, cross-reference tables and so on. That tells you where the weight actually is before touching anything (the same audit Foxit PDF Editor shows in its Space Audit dialog).

2 — Classification. The demo samples pages and inspects their graphics objects. A page that is one big image with no visible text (invisible OCR text is ignored) reads as a scan; pages of real text objects read as born-digital. This drives the recommendation — and you can override it.

3a — MRC compression (Optimizer::OptimizeScannedPDF()) for scans: each page image is segmented into a razor-sharp 1-bit JBIG2 foreground mask (text, line art) over a heavily-downsampled JPEG/JPEG 2000 background. Scans typically shrink 5–20×. JBIG2 defaults to lossless here — lossy JBIG2 can silently swap similar-looking glyphs (the 2013 Xerox scanner incident).

3b — General optimization (Optimizer::Optimize()) for born-digital files: downsample and re-encode oversized images, subset embedded fonts to the glyphs actually used (StartSubsetEmbedFont()), re-encode legacy streams with Flate, and optionally discard dead weight — thumbnails, invalid bookmarks/links, metadata, attachments. Feeding a born-digital file to the MRC path is refused with a hint, not an error page.

Pick the machine and the SDK version. The identical source is compiled four ways — Kramer (Windows) and Jerry (Linux), each on Foxit PDF SDK 11.0 and 11.1 — a controlled 2×2 for separating SDK-release differences from OS/environment differences.

Sample implementation code

The full pipeline this demo runs — audit, classify, then the right optimizer — in each SDK language binding. Condensed for clarity.

#include "common/fs_common.h"
#include "pdf/fs_pdfdoc.h"
#include "addon/optimization/fs_optimization.h"

using namespace foxit;
using namespace foxit::common;
using namespace foxit::pdf;
using namespace foxit::addon::optimization;

int main() {
  Library::Initialize(sn, key);              // key must include "Optimizer"
  PDFDoc doc(L"input.pdf");
  if (doc.Load() != e_ErrSuccess) return 1;

  // --- 1. Space Audit: where do the bytes live? ---
  AuditSpaceSize audit = Optimizer::ComputeAuditSpace(doc);
  printf("images=%u fonts=%u content=%u xref=%u\n",
         audit.images, audit.fonts, audit.content_streams, audit.cross_ref_tables);

  // --- 2. Classify: is this a scan? (image covers page, no visible text) ---
  bool looks_scanned = ClassifyByPageObjects(doc);   // see the demo source

  if (looks_scanned) {
    // --- 3a. MRC compression for scanned documents ---
    OptimizeScannedPDFSettings s;
    s.SetColorGrayImageCompressionMode(
        OptimizeScannedPDFSettings::e_ScannedImageCompressHigh);   // MRC
    s.SetColorGrayImageCompressionQuality(33);
    s.SetMonoImageCompressionMode(
        OptimizeScannedPDFSettings::e_ScannedMonoImageCompressjbig2LossLess);
    // Callback supplies temp streams for the JBIG2/JP2K encoders.
    Progressive p = Optimizer::OptimizeScannedPDF(doc, s, new MyCompressionCallback(), NULL);
    while (p.Continue() == Progressive::e_ToBeContinued) {}
  } else {
    // --- 3b. General optimization for born-digital documents ---
    OptimizerSettings s;
    s.SetOptimizerOptions(OptimizerSettings::e_OptimizerCompressImages
                        | OptimizerSettings::e_OptimizerCleanUp
                        | OptimizerSettings::e_OptimizerDiscardObjects);
    ImageSettings img;
    img.SetCompressionMode(ImageSettings::e_ImageCompressjpeg);
    img.SetQuality(ImageSettings::e_ImageCompressQualityMedium);
    img.SetImageDPI(150);                        // downsample target
    s.SetColorImageSettings(img);
    s.SetGrayscaleImageSettings(img);
    MonoImageSettings mono;
    mono.SetCompressionMode(MonoImageSettings::e_ImageCompressjbig2);
    mono.SetImageDPI(250);
    s.SetMonoImageSettings(mono);
    s.SetCleanUpOptions(OptimizerSettings::e_CleanUpUseFlateForNonEncodedStream
                      | OptimizerSettings::e_CleanUpUseFlateInsteadOfLZW
                      | OptimizerSettings::e_CleanUpRemoveInvalidBookmarks
                      | OptimizerSettings::e_CleanUpRemoveInvalidLinks);
    s.SetDiscardObjectsOptions(OptimizerSettings::e_DiscardObjectsEmbeddedPageThumbnails);
    Progressive p = Optimizer::Optimize(doc, s, NULL);
    while (p.Continue() == Progressive::e_ToBeContinued) {}

    // Optional extra pass: subset embedded fonts to the glyphs in use.
    Progressive p2 = Optimizer::StartSubsetEmbedFont(doc, NULL);
    while (p2.Continue() == Progressive::e_ToBeContinued) {}
  }

  // RemoveRedundantObjects prunes the replaced streams — that's the size win.
  doc.SaveAs(L"optimized.pdf",
             PDFDoc::e_SaveFlagNoOriginal
           | PDFDoc::e_SaveFlagXRefStream
           | PDFDoc::e_SaveFlagRemoveRedundantObjects);
  Library::Release();
  return 0;
}
using foxit;
using foxit.common;
using foxit.pdf;
using foxit.addon.optimization;

Library.Initialize(sn, key);                 // key must include "Optimizer"
using var doc = new PDFDoc("input.pdf");
doc.Load(null);

// --- 1. Space Audit ---
var audit = Optimizer.ComputeAuditSpace(doc);
Console.WriteLine($"images={audit.images} fonts={audit.fonts}");

// --- 2. Classify (see the demo's C++ source for the page-object walk) ---
bool looksScanned = ClassifyByPageObjects(doc);

if (looksScanned) {
    // --- 3a. MRC compression ---
    using var s = new OptimizeScannedPDFSettings();
    s.SetColorGrayImageCompressionMode(
        OptimizeScannedPDFSettings.ScannedImageCompressMode.e_ScannedImageCompressHigh);
    s.SetColorGrayImageCompressionQuality(33);
    s.SetMonoImageCompressionMode(
        OptimizeScannedPDFSettings.ScannedMonoImageCompressMode.e_ScannedMonoImageCompressjbig2LossLess);
    var p = Optimizer.OptimizeScannedPDF(doc, s, new MyCompressionCallback(), null);
    while (p.Continue() == Progressive.State.e_ToBeContinued) { }
} else {
    // --- 3b. General optimization ---
    using var s = new OptimizerSettings();
    s.SetOptimizerOptions((int)(OptimizerSettings.OptimizerOptions.e_OptimizerCompressImages
                              | OptimizerSettings.OptimizerOptions.e_OptimizerCleanUp));
    using var img = new ImageSettings();
    img.SetCompressionMode(ImageSettings.ImageCompressMode.e_ImageCompressjpeg);
    img.SetQuality(ImageSettings.ImageCompressQuality.e_ImageCompressQualityMedium);
    img.SetImageDPI(150);
    s.SetColorImageSettings(img);
    s.SetGrayscaleImageSettings(img);
    s.SetCleanUpOptions((int)(OptimizerSettings.CleanUpOptions.e_CleanUpUseFlateForNonEncodedStream
                            | OptimizerSettings.CleanUpOptions.e_CleanUpUseFlateInsteadOfLZW));
    var p = Optimizer.Optimize(doc, s, null);
    while (p.Continue() == Progressive.State.e_ToBeContinued) { }

    var p2 = Optimizer.StartSubsetEmbedFont(doc, null);   // font subsetting
    while (p2.Continue() == Progressive.State.e_ToBeContinued) { }
}

doc.SaveAs("optimized.pdf",
    (int)(PDFDoc.SaveFlags.e_SaveFlagNoOriginal
        | PDFDoc.SaveFlags.e_SaveFlagXRefStream
        | PDFDoc.SaveFlags.e_SaveFlagRemoveRedundantObjects));
Library.Release();
import com.foxit.sdk.common.Library;
import com.foxit.sdk.common.Progressive;
import com.foxit.sdk.pdf.PDFDoc;
import com.foxit.sdk.addon.optimization.*;

Library.initialize(sn, key);                 // key must include "Optimizer"
PDFDoc doc = new PDFDoc("input.pdf");
doc.load(null);

// --- 1. Space Audit ---
AuditSpaceSize audit = Optimizer.computeAuditSpace(doc);
System.out.println("images=" + audit.getImages() + " fonts=" + audit.getFonts());

// --- 2. Classify (see the demo's C++ source for the page-object walk) ---
boolean looksScanned = classifyByPageObjects(doc);

if (looksScanned) {
    // --- 3a. MRC compression ---
    OptimizeScannedPDFSettings s = new OptimizeScannedPDFSettings();
    s.setColorGrayImageCompressionMode(
        OptimizeScannedPDFSettings.e_ScannedImageCompressHigh);      // MRC
    s.setColorGrayImageCompressionQuality(33);
    s.setMonoImageCompressionMode(
        OptimizeScannedPDFSettings.e_ScannedMonoImageCompressjbig2LossLess);
    Progressive p = Optimizer.optimizeScannedPDF(doc, s, new MyCompressionCallback(), null);
    while (p.resume() == Progressive.e_ToBeContinued) { }
} else {
    // --- 3b. General optimization ---
    OptimizerSettings s = new OptimizerSettings();
    s.setOptimizerOptions(OptimizerSettings.e_OptimizerCompressImages
                        | OptimizerSettings.e_OptimizerCleanUp);
    ImageSettings img = new ImageSettings();
    img.setCompressionMode(ImageSettings.e_ImageCompressjpeg);
    img.setQuality(ImageSettings.e_ImageCompressQualityMedium);
    img.setImageDPI(150);
    s.setColorImageSettings(img);
    s.setGrayscaleImageSettings(img);
    s.setCleanUpOptions(OptimizerSettings.e_CleanUpUseFlateForNonEncodedStream
                      | OptimizerSettings.e_CleanUpUseFlateInsteadOfLZW);
    Progressive p = Optimizer.optimize(doc, s, null);
    while (p.resume() == Progressive.e_ToBeContinued) { }

    Progressive p2 = Optimizer.startSubsetEmbedFont(doc, null);  // font subsetting
    while (p2.resume() == Progressive.e_ToBeContinued) { }
}

doc.saveAs("optimized.pdf",
    PDFDoc.e_SaveFlagNoOriginal | PDFDoc.e_SaveFlagXRefStream
  | PDFDoc.e_SaveFlagRemoveRedundantObjects);
Library.release();
from FoxitPDFSDKPython3 import *

Library.Initialize(sn, key)                  # key must include "Optimizer"
doc = PDFDoc("input.pdf")
assert doc.Load("") == e_ErrSuccess

# --- 1. Space Audit ---
audit = Optimizer.ComputeAuditSpace(doc)
print(f"images={audit.images} fonts={audit.fonts} content={audit.content_streams}")

# --- 2. Classify (see the demo's C++ source for the page-object walk) ---
looks_scanned = classify_by_page_objects(doc)

if looks_scanned:
    # --- 3a. MRC compression ---
    s = OptimizeScannedPDFSettings()
    s.SetColorGrayImageCompressionMode(
        OptimizeScannedPDFSettings.e_ScannedImageCompressHigh)   # MRC
    s.SetColorGrayImageCompressionQuality(33)
    s.SetMonoImageCompressionMode(
        OptimizeScannedPDFSettings.e_ScannedMonoImageCompressjbig2LossLess)
    prog = Optimizer.OptimizeScannedPDF(doc, s, MyCompressionCallback(), None)
    while prog.Continue() == Progressive.e_ToBeContinued:
        pass
else:
    # --- 3b. General optimization ---
    s = OptimizerSettings()
    s.SetOptimizerOptions(OptimizerSettings.e_OptimizerCompressImages
                        | OptimizerSettings.e_OptimizerCleanUp)
    img = ImageSettings()
    img.SetCompressionMode(ImageSettings.e_ImageCompressjpeg)
    img.SetQuality(ImageSettings.e_ImageCompressQualityMedium)
    img.SetImageDPI(150)
    s.SetColorImageSettings(img)
    s.SetGrayscaleImageSettings(img)
    s.SetCleanUpOptions(OptimizerSettings.e_CleanUpUseFlateForNonEncodedStream
                      | OptimizerSettings.e_CleanUpUseFlateInsteadOfLZW)
    prog = Optimizer.Optimize(doc, s, None)
    while prog.Continue() == Progressive.e_ToBeContinued:
        pass

    prog2 = Optimizer.StartSubsetEmbedFont(doc, None)   # font subsetting
    while prog2.Continue() == Progressive.e_ToBeContinued:
        pass

doc.SaveAs("optimized.pdf",
           PDFDoc.e_SaveFlagNoOriginal | PDFDoc.e_SaveFlagXRefStream
         | PDFDoc.e_SaveFlagRemoveRedundantObjects)
Library.Release()
// Foxit PDF SDK for Node.js (naming follows the Node binding conventions)
const {
  Library, PDFDoc, Progressive, Optimizer,
  OptimizerSettings, OptimizeScannedPDFSettings, ImageSettings,
} = require('@foxitsoftware/foxit-pdf-sdk-node');

Library.initialize(sn, key);                 // key must include "Optimizer"
const doc = new PDFDoc('input.pdf');
doc.load(null);

// --- 1. Space Audit ---
const audit = Optimizer.computeAuditSpace(doc);
console.log(`images=${audit.images} fonts=${audit.fonts}`);

// --- 2. Classify (see the demo's C++ source for the page-object walk) ---
const looksScanned = classifyByPageObjects(doc);

if (looksScanned) {
  // --- 3a. MRC compression ---
  const s = new OptimizeScannedPDFSettings();
  s.setColorGrayImageCompressionMode(
    OptimizeScannedPDFSettings.e_ScannedImageCompressHigh);      // MRC
  s.setColorGrayImageCompressionQuality(33);
  s.setMonoImageCompressionMode(
    OptimizeScannedPDFSettings.e_ScannedMonoImageCompressjbig2LossLess);
  const p = Optimizer.optimizeScannedPDF(doc, s, new MyCompressionCallback(), null);
  while (p.resume() === Progressive.e_ToBeContinued) { /* keep going */ }
} else {
  // --- 3b. General optimization ---
  const s = new OptimizerSettings();
  s.setOptimizerOptions(OptimizerSettings.e_OptimizerCompressImages
                      | OptimizerSettings.e_OptimizerCleanUp);
  const img = new ImageSettings();
  img.setCompressionMode(ImageSettings.e_ImageCompressjpeg);
  img.setQuality(ImageSettings.e_ImageCompressQualityMedium);
  img.setImageDPI(150);
  s.setColorImageSettings(img);
  s.setGrayscaleImageSettings(img);
  s.setCleanUpOptions(OptimizerSettings.e_CleanUpUseFlateForNonEncodedStream
                    | OptimizerSettings.e_CleanUpUseFlateInsteadOfLZW);
  const p = Optimizer.optimize(doc, s, null);
  while (p.resume() === Progressive.e_ToBeContinued) { /* keep going */ }

  const p2 = Optimizer.startSubsetEmbedFont(doc, null);   // font subsetting
  while (p2.resume() === Progressive.e_ToBeContinued) { /* keep going */ }
}

doc.saveAs('optimized.pdf',
    PDFDoc.e_SaveFlagNoOriginal
  | PDFDoc.e_SaveFlagXRefStream
  | PDFDoc.e_SaveFlagRemoveRedundantObjects);
Library.release();

Samples are condensed for clarity — production code should check every return value and wrap SDK calls in the binding’s exception handling. Exact class/module names can differ slightly between SDK releases; the C++ tab matches the real source this demo runs (kept alongside the binary as foxit_pdf_optimize.cpp).