1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
//! Defines the [PdfForm] struct, exposing functionality related to a form
//! embedded in a `PdfDocument`.

use crate::bindgen::{
    FORMTYPE_ACRO_FORM, FORMTYPE_NONE, FORMTYPE_XFA_FOREGROUND, FORMTYPE_XFA_FULL, FPDF_DOCUMENT,
    FPDF_FORMFILLINFO, FPDF_FORMHANDLE,
};
use crate::bindings::PdfiumLibraryBindings;
use crate::error::PdfiumError;
use crate::form_field::PdfFormFieldCommon;
use crate::form_field::PdfFormFieldType;
use crate::pages::PdfPages;
use std::collections::HashMap;
use std::ops::DerefMut;
use std::pin::Pin;
use std::ptr::null_mut;

/// The internal definition type of a [PdfForm] embedded in a `PdfDocument`.
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum PdfFormType {
    // The FORMTYPE_COUNT constant simply specifies the number of form types supported
    // by Pdfium; we do not need to expose it.
    None = FORMTYPE_NONE as isize,
    Acrobat = FORMTYPE_ACRO_FORM as isize,
    XfaFull = FORMTYPE_XFA_FULL as isize,
    XfaForeground = FORMTYPE_XFA_FOREGROUND as isize,
}

impl PdfFormType {
    #[inline]
    pub(crate) fn from_pdfium(form_type: u32) -> Result<PdfFormType, PdfiumError> {
        match form_type {
            FORMTYPE_NONE => Ok(PdfFormType::None),
            FORMTYPE_ACRO_FORM => Ok(PdfFormType::Acrobat),
            FORMTYPE_XFA_FULL => Ok(PdfFormType::XfaFull),
            FORMTYPE_XFA_FOREGROUND => Ok(PdfFormType::XfaForeground),
            _ => Err(PdfiumError::UnknownFormType),
        }
    }

    #[inline]
    #[allow(dead_code)]
    // The as_pdfium() function is not currently used, but we expect it to be in future
    pub(crate) fn as_pdfium(&self) -> u32 {
        match self {
            PdfFormType::None => FORMTYPE_NONE,
            PdfFormType::Acrobat => FORMTYPE_ACRO_FORM,
            PdfFormType::XfaFull => FORMTYPE_XFA_FULL,
            PdfFormType::XfaForeground => FORMTYPE_XFA_FOREGROUND,
        }
    }
}

/// The [PdfForm] embedded inside a `PdfDocument`.
///
/// Form fields in Pdfium are exposed as page annotations of type `PdfPageAnnotationType::Widget`
/// or `PdfPageAnnotationType::XfaWidget`, depending on the type of form embedded inside the
/// document. To retrieve the user-specified form field values, iterate over each annotation
/// on each page in the document, filtering out annotations that do not contain a valid form field:
///
/// ```
/// for page in document.pages.iter() {
///     for annotation in page.annotations.iter() {
///         if let Some(field) = annotation.as_form_field() {
///             // We can now unwrap the specific type of form field
///             // and access its properties, including any user-specified value.
///         }
///     }
/// }
/// ```
///
/// Alternatively, use the [PdfForm::field_values()] function to eagerly retrieve the values of all
/// fields in the document as a map of (field name, field value) pairs.
pub struct PdfForm<'a> {
    form_handle: FPDF_FORMHANDLE,
    document_handle: FPDF_DOCUMENT,

    #[allow(dead_code)]
    // The form_fill_info field is not currently used, but we expect it to be in future
    form_fill_info: Pin<Box<FPDF_FORMFILLINFO>>,
    bindings: &'a dyn PdfiumLibraryBindings,
}

impl<'a> PdfForm<'a> {
    /// Attempts to bind to an embedded form, if any, inside the document with the given
    /// document handle.
    #[inline]
    pub(crate) fn from_pdfium(
        document_handle: FPDF_DOCUMENT,
        bindings: &'a dyn PdfiumLibraryBindings,
    ) -> Option<Self> {
        // Pdfium does not load form field data or widgets (and therefore will not
        // render them) until a call has been made to the
        // FPDFDOC_InitFormFillEnvironment() function. This function takes a large
        // struct, FPDF_FORMFILLINFO, which Pdfium uses to store a variety of form
        // configuration information - mostly callback functions that should be called
        // when the user interacts with a form field widget. Since pdfium-render has
        // no concept of interactivity, we can leave all these set to None.

        // We allocate the FPDF_FORMFILLINFO struct on the heap and pin its pointer location
        // so Rust will not move it around. Pdfium retains the pointer location
        // when we call FPDFDOC_InitFormFillEnvironment() and expects the pointer
        // location to still be valid when we later call FPDFDOC_ExitFormFillEnvironment()
        // during drop(); if we don't pin the struct's location it may move, and the
        // call to FPDFDOC_ExitFormFillEnvironment() will segfault.

        let mut form_fill_info = Box::pin(FPDF_FORMFILLINFO {
            version: 2,
            Release: None,
            FFI_Invalidate: None,
            FFI_OutputSelectedRect: None,
            FFI_SetCursor: None,
            FFI_SetTimer: None,
            FFI_KillTimer: None,
            FFI_GetLocalTime: None,
            FFI_OnChange: None,
            FFI_GetPage: None,
            FFI_GetCurrentPage: None,
            FFI_GetRotation: None,
            FFI_ExecuteNamedAction: None,
            FFI_SetTextFieldFocus: None,
            FFI_DoURIAction: None,
            FFI_DoGoToAction: None,
            m_pJsPlatform: null_mut(),
            xfa_disabled: 0,
            FFI_DisplayCaret: None,
            FFI_GetCurrentPageIndex: None,
            FFI_SetCurrentPage: None,
            FFI_GotoURL: None,
            FFI_GetPageViewRect: None,
            FFI_PageEvent: None,
            FFI_PopupMenu: None,
            FFI_OpenFile: None,
            FFI_EmailTo: None,
            FFI_UploadTo: None,
            FFI_GetPlatform: None,
            FFI_GetLanguage: None,
            FFI_DownloadFromURL: None,
            FFI_PostRequestURL: None,
            FFI_PutRequestURL: None,
            FFI_OnFocusChange: None,
            FFI_DoURIActionWithKeyboardModifier: None,
        });

        let form_handle =
            bindings.FPDFDOC_InitFormFillEnvironment(document_handle, form_fill_info.deref_mut());

        if !form_handle.is_null() {
            // There is a form embedded in this document, and we retrieved a valid handle to it.

            let form = PdfForm {
                form_handle,
                document_handle,
                form_fill_info,
                bindings,
            };

            if form.form_type() != PdfFormType::None {
                // The form is valid.

                Some(form)
            } else {
                // The form is valid, but empty. No point returning it.

                None
            }
        } else {
            // There is no form embedded in this document.

            None
        }
    }

    /// Returns the internal `FPDF_FORMHANDLE` handle for this [PdfForm].
    #[inline]
    pub(crate) fn handle(&self) -> FPDF_FORMHANDLE {
        self.form_handle
    }

    /// Returns the [PdfiumLibraryBindings] used by this [PdfForm].
    #[inline]
    pub fn bindings(&self) -> &'a dyn PdfiumLibraryBindings {
        self.bindings
    }

    /// Returns the [PdfFormType] of this [PdfForm].
    #[inline]
    pub fn form_type(&self) -> PdfFormType {
        PdfFormType::from_pdfium(self.bindings.FPDF_GetFormType(self.document_handle) as u32)
            .unwrap()
    }

    /// Captures a string representation of the value of every form field on every page of
    /// the given [PdfPages] collection, returning a map of (field name, field value) pairs.
    ///
    /// This function assumes that all form fields in the document have unique field names.
    pub fn field_values(&self, pages: &'a PdfPages<'a>) -> HashMap<String, Option<String>> {
        let mut result = HashMap::new();

        for page in pages.iter() {
            for annotation in page.annotations().iter() {
                if let Some(field) = annotation.as_form_field() {
                    let value = match field.field_type() {
                        PdfFormFieldType::Checkbox => {
                            if field
                                .as_checkbox_field()
                                .unwrap()
                                .is_checked()
                                .unwrap_or(false)
                            {
                                Some("true".to_string())
                            } else {
                                Some("false".to_string())
                            }
                        }
                        PdfFormFieldType::ComboBox => field.as_combo_box_field().unwrap().value(),
                        PdfFormFieldType::ListBox => field.as_list_box_field().unwrap().value(),
                        PdfFormFieldType::RadioButton => {
                            if field
                                .as_radio_button_field()
                                .unwrap()
                                .is_checked()
                                .unwrap_or(false)
                            {
                                Some("true".to_string())
                            } else {
                                Some("false".to_string())
                            }
                        }
                        PdfFormFieldType::Text => field.as_text_field().unwrap().value(),
                        PdfFormFieldType::PushButton
                        | PdfFormFieldType::Signature
                        | PdfFormFieldType::Unknown => None,
                    };

                    result.insert(field.name().unwrap_or_default(), value);
                }
            }
        }

        result
    }
}

impl<'a> Drop for PdfForm<'a> {
    /// Closes this [PdfForm], releasing held memory.
    #[inline]
    fn drop(&mut self) {
        self.bindings
            .FPDFDOC_ExitFormFillEnvironment(self.form_handle);
    }
}