Class XfdfImporter


  • public final class XfdfImporter
    extends java.lang.Object
    Imports an XFDF document's (ISO 19444-1) annotations and form field values into a PDF -- built entirely on the pd4ml com.pd4ml.pdf.cos object model/parser (no PDFBox, no other PDF library), the same foundation com.pd4ml.pdf.sign, com.pd4ml.pdf.merge and com.pd4ml.pdf.optimizer are built on.

    Each <annots> entry is matched to an existing PDF annotation by its name attribute (the PDF /NM unique identifier), independently of XfdfImporter.Options.updateIfExists(boolean):

    • updateIfExists(true) (default false) -- an existing match is updated in place (its position, appearance-relevant properties, and content are overwritten from the XFDF; anything the XFDF doesn't mention is left as-is). No match means a new annotation is added, exactly as with the switch off.
    • updateIfExists(false) -- every <annots> entry becomes a new annotation, even if its name collides with one already present (a plain, literal "add everything" import, matching how a viewer's basic "Import Annotations" command usually behaves).
    A reply-to reference (inreplyto) is resolved against the combined set of annotations already in the PDF and everything else in this same XFDF batch, in a second pass once every annotation in the batch has been created or matched -- so it works regardless of which order the XFDF lists a thread's replies in.

    <fields> entries only ever update a field that already exists in the target PDF's /AcroForm (see FieldMapper's class documentation for why, and for the signature-field exclusion); a name with no match is reported back via XfdfImporter.Result.getFieldsNotFound() rather than silently dropped or fabricated. Since a field update only ever changes an existing widget's value in place -- never adding a new one -- it never touches the target's accessibility structure tree either way: a widget that was already tagged stays exactly as tagged as it was before.

    Tagged (accessibility-structured) target PDFs: a brand-new annotation added from an <annots> entry is additionally wired into the target's existing /StructTreeRoot, if it has one, per the Matterhorn Protocol / PDF/UA-1 rules -- see StructureTreeUpdater's class documentation for exactly how, and which annotations are (and aren't) tagged. A target with no existing /StructTreeRoot is left untagged, exactly as before -- this only ever extends structure that's already there.

    Selecting a subset of the XFDF to actually apply -- rather than every annotation and field it contains -- is what XfdfImporter.Options.types(String) and XfdfImporter.Options.ids(String) are for; see their own documentation for the exact matching rules. Both are optional and independent of each other, and of XfdfImporter.Options.updateIfExists(boolean).

    Encrypted input is supported the same way every other pd4ml PDF feature supports it: pass the password to XfdfImporter.Options.password(String). The imported output stays plain unless XfdfImporter.Options.encryptOutput(java.lang.String, java.lang.String) is also given. Optimizing the output -- collapsing incremental-update history and dropping unreferenced objects, see com.pd4ml.pdf.optimizer.PdfOptimizer -- is available via XfdfImporter.Options.optimizeOutput(boolean); when combined with XfdfImporter.Options.encryptOutput(java.lang.String, java.lang.String), optimization always runs first and encryption is applied to its result, since encrypting first would leave the optimizer trying to reachability-walk ciphertext.

    Thread-safety: XfdfImporter holds no state at all (every method is static); call importInto(...) freely and concurrently from any number of threads.

    Example

    
     XfdfImporter.Options options = new XfdfImporter.Options()
             .updateIfExists(true)
             .optimizeOutput(true)
             .encryptOutput("open123", "owner456");
     XfdfImporter.Result result = XfdfImporter.importInto(pdfBytes, xfdfBytes, options);
     Files.write(Paths.get("annotated.pdf"), result.getPdf());