pdf_signing icon indicating copy to clipboard operation
pdf_signing copied to clipboard

how to sign a pdf without acroform

Open sunnycamel opened this issue 1 year ago • 1 comments

After reading code of the example, I notice that there must be some acroform in the pdf。Can you show some code that will be able to add such kind of acroform to the pdf ?

sunnycamel avatar Dec 13 '24 09:12 sunnycamel

Hi @sunnycamel,

I've come across the same issue as you. Here's the code I used.

use lopdf::{Document, Object, ObjectId, Dictionary};
use lopdf::Object::{Array, Name};
use lopdf::dictionary;

fn main() -> lopdf::Result<()> {
    // 1) Load the input PDF
    let mut doc = Document::load("input.pdf")?;

    // 2) Choose a page (here the first one)
    let first_page_id = *doc.get_pages().values().next().expect("PDF without pages?");

    // 3) Create IDs for the new objects (field + widget)
    let sig_field_id: ObjectId = doc.new_object_id();
    let widget_id: ObjectId    = doc.new_object_id();

    // 4) Form field dictionary (type Sig)
    let sig_field = dictionary! {
        "FT" => Name(b"Sig".to_vec()),                         // Field type: Signature
        "T"  => Object::string_literal("Signature1"),          // Field name
        // "V" missing => empty field (to be signed later)
        // "Ff" => 0,                                // optional flags
        "Kids" => Array(vec![widget_id.into()])      // links the widget to this field
    };

    // 5) Widget annotation dictionary (clickable rectangle on the page)
    // Rect = [llx, lly, urx, ury] in points (origin at bottom-left)
    let rect = Array(vec![100.into(), 100.into(), 300.into(), 150.into()]);
    let widget = dictionary! {
        "Type"    => Name(b"Annot".to_vec()),
        "Subtype" => Name(b"Widget".to_vec()),
        "Rect"    => rect,
        "P"       => Object::Reference(first_page_id), // parent page
        "Parent"  => Object::Reference(sig_field_id),  // links to the field
        "F"       => Object::Integer(4),               // printable
        // Optional: "FT" => Name("Sig"), "T" => Object::string_literal("Signature1")
    };

    // 6) Insert the objects into the document
    doc.objects.insert(sig_field_id, Object::Dictionary(sig_field));
    doc.objects.insert(widget_id,    Object::Dictionary(widget));

    // 7) Add the widget to the page's /Annots list
    {
        let page_obj = doc.get_object_mut(first_page_id)?.as_dict_mut()?;
        match page_obj.get_mut(b"Annots") {
            Ok(annots_obj) => {
                match annots_obj {
                    Array(items) => items.push(widget_id.into()),
                    _ => {
                        *annots_obj = Array(vec![widget_id.into()]);
                    }
                }
            }
            Err(_) => {
                page_obj.set("Annots", Array(vec![widget_id.into()]));
            }
        }
    }

    // 8) Create/update the /AcroForm in the catalog
    //    /Fields must contain the field; /SigFlags (1 or 3) indicates the presence of signatures
    let acro_id = doc.new_object_id();
    let acro_form = dictionary! {
        "Fields"   => Array(vec![sig_field_id.into()]),
        "SigFlags" => Object::Integer(3) // 1: signable, 3: signable + append-only recommended
    };
    doc.objects.insert(acro_id, Object::Dictionary(acro_form));

    // Inject /AcroForm into the catalog
    let catalog_id = doc.trailer.get(b"Root").and_then(Object::as_reference)
        .expect("Missing catalog");
    let catalog = doc.get_object_mut(catalog_id)?.as_dict_mut()?;
    catalog.set("AcroForm", acro_id);

    // 9) Save
    doc.compress(); // optional
    doc.save("output-with-signature-field.pdf")?;
    Ok(())
}

mstjr avatar Oct 02 '25 23:10 mstjr