Skip to main content

oxidize_pdf/actions/
form_actions.rs

1//! Form-related actions (Submit-Form, Reset-Form, Import-Data) per ISO 32000-1 ยง12.7.5
2
3use crate::objects::{Dictionary, Object};
4
5/// Submit-form action flags
6#[derive(Debug, Clone, Copy)]
7pub struct SubmitFormFlags {
8    /// Include/exclude fields
9    pub include: bool,
10    /// Submit as HTML form instead of FDF
11    pub html: bool,
12    /// Include coordinates of mouse click
13    pub coordinates: bool,
14    /// Submit as XML instead of FDF
15    pub xml: bool,
16    /// Include annotations
17    pub include_annotations: bool,
18    /// Submit PDF file instead of FDF
19    pub pdf: bool,
20    /// Convert dates to standard format
21    pub canonical_dates: bool,
22    /// Include only user-entered data
23    pub exclude_non_user: bool,
24    /// Include field names without values
25    pub include_no_value_fields: bool,
26    /// Export as Windows code page
27    pub export_format: bool,
28}
29
30impl Default for SubmitFormFlags {
31    fn default() -> Self {
32        Self {
33            include: true,
34            html: false,
35            coordinates: false,
36            xml: false,
37            include_annotations: false,
38            pdf: false,
39            canonical_dates: false,
40            exclude_non_user: false,
41            include_no_value_fields: false,
42            export_format: false,
43        }
44    }
45}
46
47impl SubmitFormFlags {
48    /// Convert to integer flags value
49    pub fn to_flags(&self) -> i32 {
50        let mut flags = 0;
51        if !self.include {
52            flags |= 1 << 0;
53        }
54        if self.html {
55            flags |= 1 << 2;
56        }
57        if self.coordinates {
58            flags |= 1 << 4;
59        }
60        if self.xml {
61            flags |= 1 << 5;
62        }
63        if self.include_annotations {
64            flags |= 1 << 7;
65        }
66        if self.pdf {
67            flags |= 1 << 8;
68        }
69        if self.canonical_dates {
70            flags |= 1 << 9;
71        }
72        if self.exclude_non_user {
73            flags |= 1 << 10;
74        }
75        if self.include_no_value_fields {
76            flags |= 1 << 11;
77        }
78        if self.export_format {
79            flags |= 1 << 12;
80        }
81        flags
82    }
83}
84
85/// Submit-form action - submit form data to a URL
86#[derive(Debug, Clone)]
87pub struct SubmitFormAction {
88    /// URL to submit to
89    pub url: String,
90    /// Fields to include/exclude (empty means all)
91    pub fields: Vec<String>,
92    /// Submission flags
93    pub flags: SubmitFormFlags,
94    /// Character set for submission
95    pub charset: Option<String>,
96}
97
98impl SubmitFormAction {
99    /// Create new submit-form action
100    pub fn new(url: impl Into<String>) -> Self {
101        Self {
102            url: url.into(),
103            fields: Vec::new(),
104            flags: SubmitFormFlags::default(),
105            charset: None,
106        }
107    }
108
109    /// Submit as HTML form
110    pub fn as_html(mut self) -> Self {
111        self.flags.html = true;
112        self
113    }
114
115    /// Submit as XML
116    pub fn as_xml(mut self) -> Self {
117        self.flags.xml = true;
118        self
119    }
120
121    /// Submit entire PDF
122    pub fn as_pdf(mut self) -> Self {
123        self.flags.pdf = true;
124        self
125    }
126
127    /// Include specific fields only
128    pub fn with_fields(mut self, fields: Vec<String>) -> Self {
129        self.fields = fields;
130        self.flags.include = true;
131        self
132    }
133
134    /// Exclude specific fields
135    pub fn excluding_fields(mut self, fields: Vec<String>) -> Self {
136        self.fields = fields;
137        self.flags.include = false;
138        self
139    }
140
141    /// Set character set
142    pub fn with_charset(mut self, charset: impl Into<String>) -> Self {
143        self.charset = Some(charset.into());
144        self
145    }
146
147    /// Include mouse click coordinates
148    pub fn with_coordinates(mut self) -> Self {
149        self.flags.coordinates = true;
150        self
151    }
152
153    /// Include annotations
154    pub fn with_annotations(mut self) -> Self {
155        self.flags.include_annotations = true;
156        self
157    }
158
159    /// Convert to dictionary
160    pub fn to_dict(&self) -> Dictionary {
161        let mut dict = Dictionary::new();
162        dict.set("Type", Object::Name("Action".to_string()));
163        dict.set("S", Object::Name("SubmitForm".to_string()));
164        dict.set("F", Object::String(self.url.clone()));
165
166        // Add fields array if specified
167        if !self.fields.is_empty() {
168            let fields_array: Vec<Object> = self
169                .fields
170                .iter()
171                .map(|f| Object::String(f.clone()))
172                .collect();
173            dict.set("Fields", Object::Array(fields_array));
174        }
175
176        // Add flags
177        dict.set("Flags", Object::Integer(self.flags.to_flags() as i64));
178
179        // Add charset if specified
180        if let Some(charset) = &self.charset {
181            dict.set("CharSet", Object::String(charset.clone()));
182        }
183
184        dict
185    }
186}
187
188/// Reset-form action - reset form fields to default values
189#[derive(Debug, Clone)]
190pub struct ResetFormAction {
191    /// Fields to reset (empty means all)
192    pub fields: Vec<String>,
193    /// Whether to include (true) or exclude (false) the specified fields
194    pub include: bool,
195}
196
197impl ResetFormAction {
198    /// Create new reset-form action (resets all fields)
199    pub fn new() -> Self {
200        Self {
201            fields: Vec::new(),
202            include: true,
203        }
204    }
205
206    /// Reset specific fields only
207    pub fn with_fields(mut self, fields: Vec<String>) -> Self {
208        self.fields = fields;
209        self.include = true;
210        self
211    }
212
213    /// Reset all fields except specified ones
214    pub fn excluding_fields(mut self, fields: Vec<String>) -> Self {
215        self.fields = fields;
216        self.include = false;
217        self
218    }
219
220    /// Convert to dictionary
221    pub fn to_dict(&self) -> Dictionary {
222        let mut dict = Dictionary::new();
223        dict.set("Type", Object::Name("Action".to_string()));
224        dict.set("S", Object::Name("ResetForm".to_string()));
225
226        // Add fields array if specified
227        if !self.fields.is_empty() {
228            let fields_array: Vec<Object> = self
229                .fields
230                .iter()
231                .map(|f| Object::String(f.clone()))
232                .collect();
233            dict.set("Fields", Object::Array(fields_array));
234        }
235
236        // Add flags (bit 0: 0=include, 1=exclude)
237        let flags = if self.include { 0 } else { 1 };
238        dict.set("Flags", Object::Integer(flags));
239
240        dict
241    }
242}
243
244impl Default for ResetFormAction {
245    fn default() -> Self {
246        Self::new()
247    }
248}
249
250/// Import-data action - import FDF or XFDF data
251#[derive(Debug, Clone)]
252pub struct ImportDataAction {
253    /// File specification for the data file
254    pub file: String,
255}
256
257impl ImportDataAction {
258    /// Create new import-data action
259    pub fn new(file: impl Into<String>) -> Self {
260        Self { file: file.into() }
261    }
262
263    /// Convert to dictionary
264    pub fn to_dict(&self) -> Dictionary {
265        let mut dict = Dictionary::new();
266        dict.set("Type", Object::Name("Action".to_string()));
267        dict.set("S", Object::Name("ImportData".to_string()));
268        dict.set("F", Object::String(self.file.clone()));
269        dict
270    }
271}
272
273/// Hide action - show or hide form fields and annotations
274#[derive(Debug, Clone)]
275pub struct HideAction {
276    /// Target annotations/fields to hide
277    pub targets: Vec<String>,
278    /// Whether to hide (true) or show (false)
279    pub hide: bool,
280}
281
282impl HideAction {
283    /// Create new hide action
284    pub fn new(targets: Vec<String>) -> Self {
285        Self {
286            targets,
287            hide: true,
288        }
289    }
290
291    /// Hide the targets
292    pub fn hide(mut self) -> Self {
293        self.hide = true;
294        self
295    }
296
297    /// Show the targets
298    pub fn show(mut self) -> Self {
299        self.hide = false;
300        self
301    }
302
303    /// Convert to dictionary
304    pub fn to_dict(&self) -> Dictionary {
305        let mut dict = Dictionary::new();
306        dict.set("Type", Object::Name("Action".to_string()));
307        dict.set("S", Object::Name("Hide".to_string()));
308
309        // Add targets
310        if self.targets.len() == 1 {
311            dict.set("T", Object::String(self.targets[0].clone()));
312        } else {
313            let targets_array: Vec<Object> = self
314                .targets
315                .iter()
316                .map(|t| Object::String(t.clone()))
317                .collect();
318            dict.set("T", Object::Array(targets_array));
319        }
320
321        dict.set("H", Object::Boolean(self.hide));
322
323        dict
324    }
325}
326
327/// Set-OCG-State action - set the state of optional content groups
328#[derive(Debug, Clone)]
329pub struct SetOCGStateAction {
330    /// State changes to apply
331    pub state_changes: Vec<OCGStateChange>,
332    /// Whether to preserve radio button relationships
333    pub preserve_rb: bool,
334}
335
336/// OCG state change specification
337#[derive(Debug, Clone)]
338pub enum OCGStateChange {
339    /// Turn on OCGs
340    On(Vec<String>),
341    /// Turn off OCGs
342    Off(Vec<String>),
343    /// Toggle OCGs
344    Toggle(Vec<String>),
345}
346
347impl SetOCGStateAction {
348    /// Create new set-OCG-state action
349    pub fn new() -> Self {
350        Self {
351            state_changes: Vec::new(),
352            preserve_rb: true,
353        }
354    }
355
356    /// Turn on specified OCGs
357    pub fn turn_on(mut self, ocgs: Vec<String>) -> Self {
358        self.state_changes.push(OCGStateChange::On(ocgs));
359        self
360    }
361
362    /// Turn off specified OCGs
363    pub fn turn_off(mut self, ocgs: Vec<String>) -> Self {
364        self.state_changes.push(OCGStateChange::Off(ocgs));
365        self
366    }
367
368    /// Toggle specified OCGs
369    pub fn toggle(mut self, ocgs: Vec<String>) -> Self {
370        self.state_changes.push(OCGStateChange::Toggle(ocgs));
371        self
372    }
373
374    /// Set whether to preserve radio button relationships
375    pub fn preserve_radio_buttons(mut self, preserve: bool) -> Self {
376        self.preserve_rb = preserve;
377        self
378    }
379
380    /// Convert to dictionary
381    pub fn to_dict(&self) -> Dictionary {
382        let mut dict = Dictionary::new();
383        dict.set("Type", Object::Name("Action".to_string()));
384        dict.set("S", Object::Name("SetOCGState".to_string()));
385
386        // Build state array
387        let mut state_array = Vec::new();
388        for change in &self.state_changes {
389            match change {
390                OCGStateChange::On(ocgs) => {
391                    state_array.push(Object::Name("ON".to_string()));
392                    for ocg in ocgs {
393                        state_array.push(Object::String(ocg.clone()));
394                    }
395                }
396                OCGStateChange::Off(ocgs) => {
397                    state_array.push(Object::Name("OFF".to_string()));
398                    for ocg in ocgs {
399                        state_array.push(Object::String(ocg.clone()));
400                    }
401                }
402                OCGStateChange::Toggle(ocgs) => {
403                    state_array.push(Object::Name("Toggle".to_string()));
404                    for ocg in ocgs {
405                        state_array.push(Object::String(ocg.clone()));
406                    }
407                }
408            }
409        }
410
411        dict.set("State", Object::Array(state_array));
412        dict.set("PreserveRB", Object::Boolean(self.preserve_rb));
413
414        dict
415    }
416}
417
418impl Default for SetOCGStateAction {
419    fn default() -> Self {
420        Self::new()
421    }
422}
423
424/// JavaScript action - execute JavaScript code
425#[derive(Debug, Clone)]
426pub struct JavaScriptAction {
427    /// JavaScript code to execute
428    pub script: String,
429}
430
431impl JavaScriptAction {
432    /// Create new JavaScript action
433    pub fn new(script: impl Into<String>) -> Self {
434        Self {
435            script: script.into(),
436        }
437    }
438
439    /// Convert to dictionary
440    pub fn to_dict(&self) -> Dictionary {
441        let mut dict = Dictionary::new();
442        dict.set("Type", Object::Name("Action".to_string()));
443        dict.set("S", Object::Name("JavaScript".to_string()));
444        dict.set("JS", Object::String(self.script.clone()));
445        dict
446    }
447}
448
449/// Sound action - play a sound
450#[derive(Debug, Clone)]
451pub struct SoundAction {
452    /// Sound object name
453    pub sound: String,
454    /// Volume (0.0 to 1.0)
455    pub volume: f64,
456    /// Whether to play synchronously
457    pub synchronous: bool,
458    /// Whether to repeat
459    pub repeat: bool,
460    /// Whether to mix with other sounds
461    pub mix: bool,
462}
463
464impl SoundAction {
465    /// Create new sound action
466    pub fn new(sound: impl Into<String>) -> Self {
467        Self {
468            sound: sound.into(),
469            volume: 1.0,
470            synchronous: false,
471            repeat: false,
472            mix: false,
473        }
474    }
475
476    /// Set volume (0.0 to 1.0)
477    pub fn with_volume(mut self, volume: f64) -> Self {
478        self.volume = volume.clamp(0.0, 1.0);
479        self
480    }
481
482    /// Play synchronously
483    pub fn synchronous(mut self) -> Self {
484        self.synchronous = true;
485        self
486    }
487
488    /// Repeat the sound
489    pub fn repeat(mut self) -> Self {
490        self.repeat = true;
491        self
492    }
493
494    /// Mix with other sounds
495    pub fn mix(mut self) -> Self {
496        self.mix = true;
497        self
498    }
499
500    /// Convert to dictionary
501    pub fn to_dict(&self) -> Dictionary {
502        let mut dict = Dictionary::new();
503        dict.set("Type", Object::Name("Action".to_string()));
504        dict.set("S", Object::Name("Sound".to_string()));
505        dict.set("Sound", Object::String(self.sound.clone()));
506        dict.set("Volume", Object::Real(self.volume));
507        dict.set("Synchronous", Object::Boolean(self.synchronous));
508        dict.set("Repeat", Object::Boolean(self.repeat));
509        dict.set("Mix", Object::Boolean(self.mix));
510        dict
511    }
512}