rust_pdf_box/interactive/
form.rs1use crate::cos::object::{COSDictionary, COSObject, COSString, ObjectId};
2use crate::error::{PdfError, PdfResult};
3use crate::pd::document::PDDocument;
4
5#[derive(Debug, Clone, PartialEq, Eq)]
6pub enum FieldType {
7 Text,
8 Button, Choice,
10 Signature,
11 Unknown(String),
12}
13
14#[derive(Debug, Clone)]
16pub struct PDFormField {
17 pub name: String,
18 pub field_type: FieldType,
19 pub value: Option<String>,
20 pub field_id: ObjectId,
21}
22
23#[derive(Debug, Clone)]
25pub struct PDAcroForm {
26 pub acro_form_dict: COSDictionary,
27}
28
29impl PDAcroForm {
30 pub fn from_doc(doc: &PDDocument) -> Option<Self> {
32 let catalog = doc.cos_doc.get_catalog()?;
33 let acro_form = catalog.get_dict("AcroForm")?.clone();
34 Some(Self {
35 acro_form_dict: acro_form,
36 })
37 }
38
39 pub fn fields(&self, doc: &PDDocument) -> Vec<PDFormField> {
41 let mut result = Vec::new();
42 if let Some(fields_arr) = self.acro_form_dict.get_array("Fields") {
43 for item in fields_arr.iter() {
44 if let Some(id) = item.as_reference() {
45 if let Some(COSObject::Dictionary(d)) = doc.cos_doc.get_object(id) {
46 let name = d
47 .get_string("T")
48 .map(|s| s.to_string_lossy())
49 .unwrap_or_default();
50
51 let ft_str = d.get_name("FT").unwrap_or("");
52 let field_type = match ft_str {
53 "Tx" => FieldType::Text,
54 "Btn" => FieldType::Button,
55 "Ch" => FieldType::Choice,
56 "Sig" => FieldType::Signature,
57 other => FieldType::Unknown(other.to_string()),
58 };
59
60 let value = d.get("V").map(|v| match v {
61 COSObject::String(s) => s.to_string_lossy(),
62 COSObject::Name(n) => n.as_str().to_string(),
63 _ => String::new(),
64 });
65
66 result.push(PDFormField {
67 name,
68 field_type,
69 value,
70 field_id: id,
71 });
72 }
73 }
74 }
75 }
76 result
77 }
78
79 pub fn set_field_value(
81 &mut self,
82 doc: &mut PDDocument,
83 name: &str,
84 value: &str,
85 ) -> PdfResult<()> {
86 let fields = self.fields(doc);
87 for f in fields {
88 if f.name == name {
89 if let Some(obj) = doc.cos_doc.get_object_mut(f.field_id) {
90 if let Some(dict) = obj.as_dict_mut() {
91 dict.set("V", COSString::literal(value.as_bytes().to_vec()));
92 return Ok(());
93 }
94 }
95 }
96 }
97 Err(PdfError::MissingEntry(format!(
98 "Form field '{name}' not found"
99 )))
100 }
101
102 pub fn flatten(&mut self, doc: &mut PDDocument) -> PdfResult<()> {
104 let total_pages = doc.page_count();
105 for i in 0..total_pages {
106 let mut widget_appearance_streams = Vec::new();
107 if let Ok(page) = doc.get_page(i) {
108 if let Some(annots) = page.dict.get_array("Annots") {
109 for item in annots.iter() {
110 if let Some(id) = item.as_reference() {
111 if let Some(COSObject::Dictionary(d)) = doc.cos_doc.get_object(id) {
112 if d.get_name("Subtype") == Some("Widget") {
113 if let Some(ap_dict) = d.get_dict("AP") {
114 if let Some(n_id) = ap_dict.get_reference("N") {
115 widget_appearance_streams.push(n_id);
116 }
117 }
118 }
119 }
120 }
121 }
122 }
123 }
124
125 if !widget_appearance_streams.is_empty() {
127 let page = doc.get_page_mut(i)?;
128 let mut current_ids = page.content_stream_ids();
129 current_ids.extend(widget_appearance_streams);
130 page.set_contents_array(current_ids);
131 }
132
133 let page = doc.get_page_mut(i)?;
135 page.dict.remove("Annots");
136 }
137
138 if let Some(cat) = doc.cos_doc.get_catalog_mut() {
140 cat.remove("AcroForm");
141 }
142
143 doc.sync();
144 Ok(())
145 }
146}