Skip to main content

pdfluent_forms/
actions.rs

1//! Field validation, calculation, and format script hooks (B.7).
2
3use crate::tree::*;
4
5/// Action trigger types from the /AA dictionary.
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7pub enum ActionTrigger {
8    Keystroke,
9    Validate,
10    Format,
11    Calculate,
12    CursorEnter,
13    CursorExit,
14    Focus,
15    Blur,
16    PageOpen,
17    PageClose,
18}
19
20/// A field action extracted from the /AA dictionary.
21#[derive(Debug, Clone)]
22pub struct FieldAction {
23    /// Which trigger fires this action.
24    pub trigger: ActionTrigger,
25    /// JavaScript source code, if it's a JavaScript action.
26    pub javascript: Option<String>,
27}
28
29/// Callback interface for an external JavaScript engine.
30pub trait JsActionHandler {
31    /// Called on keystroke events (/K). Returns `true` if accepted.
32    fn on_keystroke(
33        &mut self,
34        tree: &mut FieldTree,
35        field_id: FieldId,
36        change: &str,
37        js: &str,
38    ) -> bool;
39    /// Called on validate events (/V). Returns `true` if valid.
40    fn on_validate(&mut self, tree: &mut FieldTree, field_id: FieldId, js: &str) -> bool;
41    /// Called on format events (/F). Returns formatted display string.
42    fn on_format(&mut self, tree: &FieldTree, field_id: FieldId, js: &str) -> Option<String>;
43    /// Called on calculate events (/C). Returns calculated value.
44    fn on_calculate(&mut self, tree: &mut FieldTree, field_id: FieldId, js: &str)
45        -> Option<String>;
46}
47
48/// Run calculation scripts for all fields in the calculation order (/CO).
49pub fn run_calculations(tree: &mut FieldTree, handler: &mut dyn JsActionHandler) {
50    let order: Vec<FieldId> = tree.calculation_order.clone();
51    for field_id in order {
52        if !tree.get(field_id).has_actions {
53            continue;
54        }
55        // Placeholder: actual JS execution requires wiring up the handler
56        let _ = (field_id, &mut *handler);
57    }
58}
59
60/// Extract action triggers present on a field.
61pub fn field_action_triggers(tree: &FieldTree, id: FieldId) -> Vec<ActionTrigger> {
62    if !tree.get(id).has_actions {
63        return vec![];
64    }
65    vec![
66        ActionTrigger::Keystroke,
67        ActionTrigger::Validate,
68        ActionTrigger::Format,
69        ActionTrigger::Calculate,
70        ActionTrigger::CursorEnter,
71        ActionTrigger::CursorExit,
72        ActionTrigger::Focus,
73        ActionTrigger::Blur,
74    ]
75}
76
77#[cfg(test)]
78mod tests {
79    use super::*;
80    use crate::flags::FieldFlags;
81    fn make_field(has_actions: bool) -> (FieldTree, FieldId) {
82        let mut tree = FieldTree::new();
83        let id = tree.alloc(FieldNode {
84            partial_name: "f".into(),
85            alternate_name: None,
86            mapping_name: None,
87            field_type: Some(FieldType::Text),
88            flags: FieldFlags::empty(),
89            value: None,
90            default_value: None,
91            default_appearance: None,
92            quadding: None,
93            max_len: None,
94            options: vec![],
95            top_index: None,
96            rect: None,
97            appearance_state: None,
98            page_index: None,
99            parent: None,
100            children: vec![],
101            object_id: None,
102            has_actions,
103            mk: None,
104            border_style: None,
105        });
106        (tree, id)
107    }
108    #[test]
109    fn triggers_empty() {
110        let (tree, id) = make_field(false);
111        assert!(field_action_triggers(&tree, id).is_empty());
112    }
113    #[test]
114    fn triggers_present() {
115        let (tree, id) = make_field(true);
116        assert!(!field_action_triggers(&tree, id).is_empty());
117    }
118}