Skip to main content

pdfluent_forms/
tree.rs

1//! Arena-based field tree for AcroForm fields (B.1).
2
3use crate::flags::FieldFlags;
4
5/// Identifier for a node in the field tree.
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
7pub struct FieldId(pub(crate) usize);
8
9/// The type of a form field.
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum FieldType {
12    /// Text input field (/Tx).
13    Text,
14    /// Button field (/Btn) — checkbox, radio, or push button.
15    Button,
16    /// Choice field (/Ch) — combo box or list box.
17    Choice,
18    /// Digital signature field (/Sig).
19    Signature,
20}
21
22/// Text alignment (quadding).
23#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
24pub enum Quadding {
25    /// Left-justified (0).
26    #[default]
27    Left,
28    /// Centered (1).
29    Center,
30    /// Right-justified (2).
31    Right,
32}
33
34/// A field value.
35#[derive(Debug, Clone, PartialEq)]
36pub enum FieldValue {
37    /// Text or name value (string).
38    Text(String),
39    /// Array of selected values (choice fields).
40    StringArray(Vec<String>),
41}
42
43/// An option in a choice field (/Opt entry).
44#[derive(Debug, Clone, PartialEq)]
45pub struct ChoiceOption {
46    /// Export value (sent when form is submitted).
47    pub export: String,
48    /// Display value (shown in the field).
49    pub display: String,
50}
51
52/// Widget appearance characteristics (/MK dictionary).
53#[derive(Debug, Clone, Default)]
54pub struct MkDict {
55    /// Border color (/BC).
56    pub border_color: Option<Vec<f32>>,
57    /// Background color (/BG).
58    pub background_color: Option<Vec<f32>>,
59    /// Normal caption (/CA).
60    pub caption: Option<String>,
61    /// Rollover caption (/RC).
62    pub rollover_caption: Option<String>,
63    /// Alternate caption (/AC).
64    pub alternate_caption: Option<String>,
65    /// Icon/caption layout (/TP).
66    pub text_position: Option<u32>,
67    /// Rotation (/R).
68    pub rotation: Option<u32>,
69}
70
71/// Border style (/BS dictionary).
72#[derive(Debug, Clone)]
73pub struct BorderStyle {
74    /// Border width (/W).
75    pub width: f32,
76    /// Border style (/S): S=solid, D=dashed, B=beveled, I=inset, U=underline.
77    pub style: u8,
78}
79
80impl Default for BorderStyle {
81    fn default() -> Self {
82        Self {
83            width: 1.0,
84            style: b'S',
85        }
86    }
87}
88
89/// A single node in the AcroForm field tree.
90#[derive(Debug, Clone)]
91pub struct FieldNode {
92    /// Partial field name (/T).
93    pub partial_name: String,
94    /// Alternate field name (/TU).
95    pub alternate_name: Option<String>,
96    /// Mapping name (/TM).
97    pub mapping_name: Option<String>,
98    /// Field type (/FT): may be inherited from parent.
99    pub field_type: Option<FieldType>,
100    /// Field flags (/Ff).
101    pub flags: FieldFlags,
102    /// Current value (/V).
103    pub value: Option<FieldValue>,
104    /// Default value (/DV).
105    pub default_value: Option<FieldValue>,
106    /// Default appearance string (/DA).
107    pub default_appearance: Option<String>,
108    /// Quadding / text alignment (/Q).
109    pub quadding: Option<Quadding>,
110    /// Maximum length (/MaxLen) for text fields.
111    pub max_len: Option<u32>,
112    /// Options (/Opt) for choice fields.
113    pub options: Vec<ChoiceOption>,
114    /// Top index (/TI) for list box scroll position.
115    pub top_index: Option<u32>,
116    /// Widget rectangle (/Rect).
117    pub rect: Option<[f32; 4]>,
118    /// Current appearance state (/AS).
119    pub appearance_state: Option<String>,
120    /// On-state name for button widgets: the first non-`Off` key of this
121    /// widget's `/AP /N` sub-dictionary. `None` for non-button widgets or
122    /// when no appearance dictionary is present.
123    pub on_state: Option<String>,
124    /// Page index (0-based) this widget appears on.
125    pub page_index: Option<usize>,
126    /// Parent node in the field tree.
127    pub parent: Option<FieldId>,
128    /// Child nodes.
129    pub children: Vec<FieldId>,
130    /// PDF object identifier (obj_number, gen_number).
131    pub object_id: Option<(i32, i32)>,
132    /// Whether this field has /AA (additional actions).
133    pub has_actions: bool,
134    /// Widget appearance characteristics (/MK).
135    pub mk: Option<MkDict>,
136    /// Border style (/BS).
137    pub border_style: Option<BorderStyle>,
138}
139
140/// The complete AcroForm field tree.
141#[derive(Debug)]
142pub struct FieldTree {
143    nodes: Vec<FieldNode>,
144    /// Calculation order (/CO) — field IDs in evaluation order.
145    pub calculation_order: Vec<FieldId>,
146    /// Document-level default appearance (/DA).
147    pub document_da: Option<String>,
148    /// Document-level quadding (/Q).
149    pub document_quadding: Option<Quadding>,
150    /// Whether /NeedAppearances is set.
151    pub need_appearances: bool,
152    /// SigFlags value.
153    pub sig_flags: u32,
154}
155
156impl FieldTree {
157    /// Create an empty field tree.
158    pub fn new() -> Self {
159        Self {
160            nodes: Vec::new(),
161            calculation_order: Vec::new(),
162            document_da: None,
163            document_quadding: None,
164            need_appearances: false,
165            sig_flags: 0,
166        }
167    }
168
169    /// Allocate a new node, returning its ID.
170    pub fn alloc(&mut self, node: FieldNode) -> FieldId {
171        let id = FieldId(self.nodes.len());
172        self.nodes.push(node);
173        id
174    }
175
176    /// Get a node by ID.
177    pub fn get(&self, id: FieldId) -> &FieldNode {
178        &self.nodes[id.0]
179    }
180
181    /// Get a mutable node by ID.
182    pub fn get_mut(&mut self, id: FieldId) -> &mut FieldNode {
183        &mut self.nodes[id.0]
184    }
185
186    /// Number of nodes.
187    pub fn len(&self) -> usize {
188        self.nodes.len()
189    }
190
191    /// Whether the tree is empty.
192    pub fn is_empty(&self) -> bool {
193        self.nodes.is_empty()
194    }
195
196    /// Return IDs of all root nodes (no parent).
197    pub fn roots(&self) -> Vec<FieldId> {
198        self.nodes
199            .iter()
200            .enumerate()
201            .filter(|(_, n)| n.parent.is_none())
202            .map(|(i, _)| FieldId(i))
203            .collect()
204    }
205
206    /// Return IDs of all terminal (leaf/widget) fields.
207    pub fn terminal_fields(&self) -> Vec<FieldId> {
208        self.nodes
209            .iter()
210            .enumerate()
211            .filter(|(_, n)| n.children.is_empty())
212            .map(|(i, _)| FieldId(i))
213            .collect()
214    }
215
216    /// Compute the fully qualified field name by walking up the parent chain.
217    pub fn fully_qualified_name(&self, id: FieldId) -> String {
218        let mut parts = Vec::new();
219        let mut cur = Some(id);
220        while let Some(cid) = cur {
221            let node = self.get(cid);
222            if !node.partial_name.is_empty() {
223                parts.push(node.partial_name.as_str());
224            }
225            cur = node.parent;
226        }
227        parts.reverse();
228        parts.join(".")
229    }
230
231    /// Walk up the tree to find the effective field type.
232    pub fn effective_field_type(&self, id: FieldId) -> Option<FieldType> {
233        let mut cur = Some(id);
234        while let Some(cid) = cur {
235            if let Some(ft) = self.get(cid).field_type {
236                return Some(ft);
237            }
238            cur = self.get(cid).parent;
239        }
240        None
241    }
242
243    /// Walk up the tree to find the effective value.
244    pub fn effective_value(&self, id: FieldId) -> Option<&FieldValue> {
245        let mut cur = Some(id);
246        while let Some(cid) = cur {
247            let node = self.get(cid);
248            if node.value.is_some() {
249                return node.value.as_ref();
250            }
251            cur = node.parent;
252        }
253        None
254    }
255
256    /// Walk up the tree to find the effective DA string.
257    pub fn effective_da(&self, id: FieldId) -> Option<&str> {
258        let mut cur = Some(id);
259        while let Some(cid) = cur {
260            if let Some(ref da) = self.get(cid).default_appearance {
261                return Some(da.as_str());
262            }
263            cur = self.get(cid).parent;
264        }
265        self.document_da.as_deref()
266    }
267
268    /// Walk up the tree to find the effective quadding.
269    pub fn effective_quadding(&self, id: FieldId) -> Quadding {
270        let mut cur = Some(id);
271        while let Some(cid) = cur {
272            if let Some(q) = self.get(cid).quadding {
273                return q;
274            }
275            cur = self.get(cid).parent;
276        }
277        self.document_quadding.unwrap_or_default()
278    }
279
280    /// Walk up the tree to get effective flags.
281    pub fn effective_flags(&self, id: FieldId) -> FieldFlags {
282        self.get(id).flags
283    }
284
285    /// Walk up the tree to find the effective MaxLen.
286    ///
287    /// `/MaxLen` is treated as inheritable (like `/FT`, `/DA`, `/Q`): if a
288    /// widget does not carry it directly, the value propagates from the nearest
289    /// ancestor that does.
290    pub fn effective_max_len(&self, id: FieldId) -> Option<u32> {
291        let mut cur = Some(id);
292        while let Some(cid) = cur {
293            if let Some(ml) = self.get(cid).max_len {
294                return Some(ml);
295            }
296            cur = self.get(cid).parent;
297        }
298        None
299    }
300
301    /// Find a terminal field by fully qualified name.
302    pub fn find_by_name(&self, name: &str) -> Option<FieldId> {
303        self.terminal_fields()
304            .into_iter()
305            .find(|&id| self.fully_qualified_name(id) == name)
306    }
307
308    /// Return all node IDs.
309    pub fn all_ids(&self) -> impl Iterator<Item = FieldId> {
310        (0..self.nodes.len()).map(FieldId)
311    }
312}
313
314impl Default for FieldTree {
315    fn default() -> Self {
316        Self::new()
317    }
318}
319
320#[cfg(test)]
321mod tests {
322    use super::*;
323
324    fn make_node(name: &str) -> FieldNode {
325        FieldNode {
326            partial_name: name.into(),
327            alternate_name: None,
328            mapping_name: None,
329            field_type: None,
330            flags: FieldFlags::empty(),
331            value: None,
332            default_value: None,
333            default_appearance: None,
334            quadding: None,
335            max_len: None,
336            options: vec![],
337            top_index: None,
338            rect: None,
339            appearance_state: None,
340            on_state: None,
341            page_index: None,
342            parent: None,
343            children: vec![],
344            object_id: None,
345            has_actions: false,
346            mk: None,
347            border_style: None,
348        }
349    }
350
351    #[test]
352    fn fqn_simple() {
353        let mut tree = FieldTree::new();
354        let root = tree.alloc(make_node("form"));
355        let mut child = make_node("name");
356        child.parent = Some(root);
357        child.field_type = Some(FieldType::Text);
358        let child_id = tree.alloc(child);
359        tree.get_mut(root).children.push(child_id);
360        assert_eq!(tree.fully_qualified_name(child_id), "form.name");
361    }
362
363    #[test]
364    fn inherited_field_type() {
365        let mut tree = FieldTree::new();
366        let mut parent = make_node("group");
367        parent.field_type = Some(FieldType::Button);
368        let parent_id = tree.alloc(parent);
369        let mut child = make_node("opt1");
370        child.parent = Some(parent_id);
371        let child_id = tree.alloc(child);
372        tree.get_mut(parent_id).children.push(child_id);
373        assert_eq!(tree.effective_field_type(child_id), Some(FieldType::Button));
374    }
375
376    #[test]
377    fn inherited_da() {
378        let mut tree = FieldTree::new();
379        tree.document_da = Some("0 g /Helv 12 Tf".into());
380        let id = tree.alloc(make_node("field"));
381        assert_eq!(tree.effective_da(id), Some("0 g /Helv 12 Tf"));
382    }
383
384    #[test]
385    fn inherited_max_len() {
386        let mut tree = FieldTree::new();
387        // Parent carries MaxLen; child does not.
388        let mut parent = make_node("group");
389        parent.max_len = Some(10);
390        let parent_id = tree.alloc(parent);
391
392        let mut child = make_node("field");
393        child.parent = Some(parent_id);
394        let child_id = tree.alloc(child);
395        tree.get_mut(parent_id).children.push(child_id);
396
397        assert_eq!(tree.effective_max_len(child_id), Some(10));
398    }
399
400    #[test]
401    fn own_max_len_overrides_parent() {
402        let mut tree = FieldTree::new();
403        let mut parent = make_node("group");
404        parent.max_len = Some(10);
405        let parent_id = tree.alloc(parent);
406
407        let mut child = make_node("field");
408        child.parent = Some(parent_id);
409        child.max_len = Some(5);
410        let child_id = tree.alloc(child);
411        tree.get_mut(parent_id).children.push(child_id);
412
413        assert_eq!(tree.effective_max_len(child_id), Some(5));
414    }
415}