Flatten PDFs
Bake signatures, annotations, and form widgets into the page content stream — so they survive an eSign or any other tool that strips form objects. Text and vectors stay as text and vectors; nothing gets rasterized. Powered by the Foxit PDF SDK (PDFPage::Flatten()).
How it works
The Foxit PDF SDK runs PDFPage::Flatten() on every page, which rewrites the page content stream to include the rendered appearance of any signature fields, form widgets, and annotations — and then removes those objects from the page. The result looks identical but no longer carries form/widget structure that downstream tools might strip.
Output is saved with a compressed xref stream and unreferenced objects pruned, so size typically stays the same or shrinks. Far better than a TIFF round-trip: text remains searchable, vector graphics stay vector, and signatures keep their original visual fidelity.
Pick the machine and the SDK version. The identical flattener source (foxit_flatten.cpp) 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. Same code, same input; only the OS or the SDK version changes.
Sample implementation code
The full pipeline this demo runs — parse each page, flatten annotations and form widgets into the content stream, then compact-save — in each SDK language binding. Condensed for clarity.
#include "common/fs_common.h"
#include "pdf/fs_pdfdoc.h"
#include "pdf/fs_pdfpage.h"
using namespace foxit;
using namespace foxit::common;
using namespace foxit::pdf;
int main() {
Library::Initialize(sn, key);
PDFDoc doc(L"input.pdf");
if (doc.Load() != e_ErrSuccess) return 1;
// --- Flatten every page ---
for (int i = 0; i < doc.GetPageCount(); ++i) {
PDFPage page = doc.GetPage(i);
page.StartParse(PDFPage::e_ParsePageNormal, NULL, false);
// for_display=true bakes the displayed appearance (what the user
// sees, including signed-signature appearances) into the content
// stream. e_FlattenAll = annotations + form widgets.
page.Flatten(true, PDFPage::e_FlattenAll);
}
// e_SaveFlagNoOriginal: rewrite from current in-memory state
// e_SaveFlagXRefStream: compressed xref stream
// e_SaveFlagRemoveRedundantObjects: drop unreferenced objects
// (the now-orphaned annotation/widget objects -- that's the size win)
doc.SaveAs(L"flattened.pdf",
PDFDoc::e_SaveFlagNoOriginal
| PDFDoc::e_SaveFlagXRefStream
| PDFDoc::e_SaveFlagRemoveRedundantObjects);
Library::Release();
return 0;
}
using foxit;
using foxit.common;
using foxit.pdf;
Library.Initialize(sn, key);
using var doc = new PDFDoc("input.pdf");
doc.Load(null);
// --- Flatten every page ---
for (int i = 0; i < doc.GetPageCount(); i++) {
var page = doc.GetPage(i);
page.StartParse(PDFPage.ParseFlags.e_ParsePageNormal, null, false);
// forDisplay=true bakes the displayed appearance (what the user
// sees, including signed-signature appearances) into the content
// stream. e_FlattenAll = annotations + form widgets.
page.Flatten(true, PDFPage.FlattenOptions.e_FlattenAll);
}
// NoOriginal: rewrite from in-memory state; XRefStream: compressed xref;
// RemoveRedundantObjects prunes the now-orphaned annotation/widget objects.
doc.SaveAs("flattened.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.pdf.PDFDoc;
import com.foxit.sdk.pdf.PDFPage;
Library.initialize(sn, key);
PDFDoc doc = new PDFDoc("input.pdf");
doc.load(null);
// --- Flatten every page ---
for (int i = 0; i < doc.getPageCount(); i++) {
PDFPage page = doc.getPage(i);
page.startParse(PDFPage.e_ParsePageNormal, null, false);
// forDisplay=true bakes the displayed appearance (what the user
// sees, including signed-signature appearances) into the content
// stream. e_FlattenAll = annotations + form widgets.
page.flatten(true, PDFPage.e_FlattenAll);
}
// NoOriginal: rewrite from in-memory state; XRefStream: compressed xref;
// RemoveRedundantObjects prunes the now-orphaned annotation/widget objects.
doc.saveAs("flattened.pdf",
PDFDoc.e_SaveFlagNoOriginal | PDFDoc.e_SaveFlagXRefStream
| PDFDoc.e_SaveFlagRemoveRedundantObjects);
Library.release();
from FoxitPDFSDKPython3 import *
Library.Initialize(sn, key)
doc = PDFDoc("input.pdf")
assert doc.Load("") == e_ErrSuccess
# --- Flatten every page ---
for i in range(doc.GetPageCount()):
page = doc.GetPage(i)
page.StartParse(PDFPage.e_ParsePageNormal, None, False)
# for_display=True bakes the displayed appearance (what the user
# sees, including signed-signature appearances) into the content
# stream. e_FlattenAll = annotations + form widgets.
page.Flatten(True, PDFPage.e_FlattenAll)
# NoOriginal: rewrite from in-memory state; XRefStream: compressed xref;
# RemoveRedundantObjects prunes the now-orphaned annotation/widget objects.
doc.SaveAs("flattened.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, PDFPage } = require('@foxitsoftware/foxit-pdf-sdk-node');
Library.initialize(sn, key);
const doc = new PDFDoc('input.pdf');
doc.load(null);
// --- Flatten every page ---
for (let i = 0; i < doc.getPageCount(); i++) {
const page = doc.getPage(i);
page.startParse(PDFPage.e_ParsePageNormal, null, false);
// forDisplay=true bakes the displayed appearance (what the user
// sees, including signed-signature appearances) into the content
// stream. e_FlattenAll = annotations + form widgets.
page.flatten(true, PDFPage.e_FlattenAll);
}
// NoOriginal: rewrite from in-memory state; XRefStream: compressed xref;
// RemoveRedundantObjects prunes the now-orphaned annotation/widget objects.
doc.saveAs('flattened.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 each binary as foxit_flatten.cpp in server_11_0/ and server_11_1/).