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.
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).