pdf_signing
pdf_signing copied to clipboard
how to sign a pdf without acroform
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 ?
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(())
}