XFA → AcroForm

Convert Static XFA to AcroForm

Drop a static XFA form (the hybrid PDFs LiveCycle Designer produces) and get back a plain, standards-compliant AcroForm — the XFA layer removed, but every field, value, and appearance kept fully fillable. It opens and fills correctly in any PDF viewer, with no “please wait” placeholder.

Machine
Foxit PDF SDK version
No XFA form handy?

How it works

A static XFA form is a hybrid file. Beneath the XFA XML template it already carries a complete, real AcroForm — actual field objects and widget annotations. XFA-aware readers render the XFA layer; everyone else already falls back to that AcroForm. So converting one is a subtraction, not a re-render: the Foxit PDF SDK removes the /AcroForm/XFA entry (and the /NeedsRendering hint), then re-saves. Fields, values, and appearances are untouched because they were real all along — fidelity is near-perfect and the form stays interactive.

The demo classifies your upload at the PDF-object level: it checks for an /AcroForm/XFA entry and a genuine field tree underneath. A plain PDF, or a dynamic XFA form (which has no real AcroForm — just a “please wait” placeholder that an XFA engine lays out at open time), is refused rather than turned into a broken file.

Need to handle a dynamic XFA form? There’s no lossless path to AcroForm for those (even Acrobat can’t do it). Render it to flat page content with the XFA Flatten demo instead.

Pick the machine and the SDK version. The identical foxit_xfa_to_acroform.cpp compiles four ways — Kramer (Windows) and Jerry (Linux), each on Foxit PDF SDK 11.0 and 11.1. Because the conversion is pure PDF-object subtraction — no rendering, no fonts — the output is byte-identical in all four cells, in contrast to XFA Flatten, where the OS’s font environment swings output size ~5×. That invariance is the point of the matrix here: it demonstrates which operations depend on the environment and which don’t.

Sample implementation code

The full pipeline this demo runs — detect the XFA layer, classify static vs dynamic, then strip the XFA by subtraction — in each SDK language binding. Condensed for clarity.

#include "common/fs_common.h"
#include "pdf/fs_pdfdoc.h"
#include "pdf/objects/fs_pdfobject.h"
#include "pdf/interform/fs_pdfform.h"

using namespace foxit;
using namespace foxit::common;
using namespace foxit::pdf;
using namespace foxit::pdf::objects;
using namespace foxit::pdf::interform;

int main() {
  Library::Initialize(sn, key);     // no XFA module needed — object model only
  PDFDoc doc(L"static_xfa.pdf");
  if (doc.Load() != e_ErrSuccess) return 1;

  // --- 1. Locate the AcroForm dictionary and its /XFA entry ---
  PDFDictionary* catalog = doc.GetCatalog();
  PDFObject* af = catalog ? catalog->GetElement("AcroForm") : NULL;
  PDFDictionary* acro_form = af ? af->GetDict() : NULL;
  if (!acro_form || !acro_form->GetElement("XFA")) return 5;   // not_xfa

  // --- 2. Classify: dynamic XFA has no real AcroForm underneath ---
  PDFObject* nr = catalog->GetElement("NeedsRendering");
  bool needs_rendering = nr && nr->GetType() == PDFObject::e_Boolean
                            && nr->GetBoolean();
  Form form(doc);
  if (needs_rendering || form.GetFieldCount() <= 0) return 5;  // dynamic_xfa — refuse

  // --- 3. Convert by subtraction: drop the XFA layer, keep the fields ---
  acro_form->RemoveAt("XFA");
  if (catalog->GetElement("NeedsRendering")) catalog->RemoveAt("NeedsRendering");
  // /Perms holds the Reader-Extensions usage-rights signature; the re-save
  // invalidates it anyway, so drop it to avoid the "document altered" warning.
  if (catalog->GetElement("Perms")) catalog->RemoveAt("Perms");

  // --- 4. Compact save prunes the orphaned XFA template/datasets ---
  doc.SaveAs(L"acroform.pdf",
             PDFDoc::e_SaveFlagNoOriginal
           | PDFDoc::e_SaveFlagXRefStream
           | PDFDoc::e_SaveFlagRemoveRedundantObjects);
  Library::Release();
  return 0;
}
using foxit;
using foxit.common;
using foxit.pdf;
using foxit.pdf.objects;
using foxit.pdf.interform;

Library.Initialize(sn, key);        // no XFA module needed — object model only
using var doc = new PDFDoc("static_xfa.pdf");
doc.Load(null);

// --- 1. Locate the AcroForm dictionary and its /XFA entry ---
var catalog = doc.GetCatalog();
var acroForm = catalog?.GetElement("AcroForm")?.GetDict();
if (acroForm?.GetElement("XFA") == null) return;          // not_xfa

// --- 2. Classify: dynamic XFA has no real AcroForm underneath ---
var nr = catalog.GetElement("NeedsRendering");
bool needsRendering = nr != null && nr.GetBoolean();
using var form = new Form(doc);
if (needsRendering || form.GetFieldCount() <= 0) return;  // dynamic_xfa — refuse

// --- 3. Convert by subtraction: drop the XFA layer, keep the fields ---
acroForm.RemoveAt("XFA");
if (catalog.GetElement("NeedsRendering") != null) catalog.RemoveAt("NeedsRendering");
// /Perms holds the Reader-Extensions usage-rights signature; the re-save
// invalidates it anyway, so drop it to avoid the "document altered" warning.
if (catalog.GetElement("Perms") != null) catalog.RemoveAt("Perms");

// --- 4. Compact save prunes the orphaned XFA template/datasets ---
doc.SaveAs("acroform.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.objects.PDFDictionary;
import com.foxit.sdk.pdf.objects.PDFObject;
import com.foxit.sdk.pdf.interform.Form;

Library.initialize(sn, key);        // no XFA module needed — object model only
PDFDoc doc = new PDFDoc("static_xfa.pdf");
doc.load(null);

// --- 1. Locate the AcroForm dictionary and its /XFA entry ---
PDFDictionary catalog = doc.getCatalog();
PDFObject af = catalog.getElement("AcroForm");
PDFDictionary acroForm = af != null ? af.getDict() : null;
if (acroForm == null || acroForm.getElement("XFA") == null) return;  // not_xfa

// --- 2. Classify: dynamic XFA has no real AcroForm underneath ---
PDFObject nr = catalog.getElement("NeedsRendering");
boolean needsRendering = nr != null && nr.getBoolean();
Form form = new Form(doc);
if (needsRendering || form.getFieldCount() <= 0) return;  // dynamic_xfa — refuse

// --- 3. Convert by subtraction: drop the XFA layer, keep the fields ---
acroForm.removeAt("XFA");
if (catalog.getElement("NeedsRendering") != null) catalog.removeAt("NeedsRendering");
if (catalog.getElement("Perms") != null) catalog.removeAt("Perms");  // dead UR3 sig

// --- 4. Compact save prunes the orphaned XFA template/datasets ---
doc.saveAs("acroform.pdf",
    PDFDoc.e_SaveFlagNoOriginal | PDFDoc.e_SaveFlagXRefStream
  | PDFDoc.e_SaveFlagRemoveRedundantObjects);
Library.release();
from FoxitPDFSDKPython3 import *

Library.Initialize(sn, key)         # no XFA module needed — object model only
doc = PDFDoc("static_xfa.pdf")
assert doc.Load("") == e_ErrSuccess

# --- 1. Locate the AcroForm dictionary and its /XFA entry ---
catalog = doc.GetCatalog()
af = catalog.GetElement("AcroForm")
acro_form = af.GetDict() if af else None
if not acro_form or not acro_form.GetElement("XFA"):
    raise SystemExit("not_xfa")

# --- 2. Classify: dynamic XFA has no real AcroForm underneath ---
nr = catalog.GetElement("NeedsRendering")
needs_rendering = bool(nr and nr.GetBoolean())
form = Form(doc)
if needs_rendering or form.GetFieldCount() <= 0:
    raise SystemExit("dynamic_xfa")  # no real fields — flatten it instead

# --- 3. Convert by subtraction: drop the XFA layer, keep the fields ---
acro_form.RemoveAt("XFA")
if catalog.GetElement("NeedsRendering"):
    catalog.RemoveAt("NeedsRendering")
if catalog.GetElement("Perms"):      # dead Reader-Extensions UR3 signature
    catalog.RemoveAt("Perms")

# --- 4. Compact save prunes the orphaned XFA template/datasets ---
doc.SaveAs("acroform.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, Form,
} = require('@foxitsoftware/foxit-pdf-sdk-node');

Library.initialize(sn, key);        // no XFA module needed — object model only
const doc = new PDFDoc('static_xfa.pdf');
doc.load(null);

// --- 1. Locate the AcroForm dictionary and its /XFA entry ---
const catalog = doc.getCatalog();
const acroForm = catalog?.getElement('AcroForm')?.getDict();
if (!acroForm || !acroForm.getElement('XFA')) throw new Error('not_xfa');

// --- 2. Classify: dynamic XFA has no real AcroForm underneath ---
const nr = catalog.getElement('NeedsRendering');
const needsRendering = !!(nr && nr.getBoolean());
const form = new Form(doc);
if (needsRendering || form.getFieldCount() <= 0) throw new Error('dynamic_xfa');

// --- 3. Convert by subtraction: drop the XFA layer, keep the fields ---
acroForm.removeAt('XFA');
if (catalog.getElement('NeedsRendering')) catalog.removeAt('NeedsRendering');
if (catalog.getElement('Perms')) catalog.removeAt('Perms');  // dead UR3 signature

// --- 4. Compact save prunes the orphaned XFA template/datasets ---
doc.saveAs('acroform.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_xfa_to_acroform.cpp in server_11_0/ and server_11_1/).