Skip to main content

pdfboss_core/
form.rs

1//! The document's interactive form (ISO 32000-1 §12.7): the catalog's
2//! `/AcroForm` dictionary, which names the root fields and carries the
3//! defaults their widgets are drawn with.
4
5use crate::content::{parse_content, Op};
6use crate::hash::FastSet;
7use crate::object::{decode_text_string, Dict, ObjRef, Object};
8use crate::source::AsyncObjectSource;
9use crate::tree::{resolved_dict, Entries};
10
11/// Fields nested deeper than this below the root fields are not read.
12const MAX_FIELD_DEPTH: usize = 64;
13
14/// The document-level signature flags (`/SigFlags`, ISO 32000-1 §12.7.2).
15#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
16pub struct SignatureFlags {
17    /// Bit 1, `SignaturesExist`: the document has at least one signature
18    /// field.
19    pub signatures_exist: bool,
20    /// Bit 2, `AppendOnly`: the document's signatures would be invalidated
21    /// by anything but an incremental update.
22    pub append_only: bool,
23}
24
25impl SignatureFlags {
26    /// The flags of a `/SigFlags` word; the reserved bits are ignored.
27    ///
28    /// Covers ISO 32000-1 §12.7.2.
29    pub fn from_bits(bits: i64) -> SignatureFlags {
30        SignatureFlags {
31            signatures_exist: bits & 1 != 0,
32            append_only: bits & 2 != 0,
33        }
34    }
35}
36
37/// The quadding of variable text (`/Q`, §12.7.3.3): how the text of a field
38/// is justified within its widget.
39#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
40pub enum Quadding {
41    /// 0: left-justified, the default.
42    #[default]
43    Left,
44    /// 1: centred.
45    Centered,
46    /// 2: right-justified.
47    Right,
48}
49
50impl Quadding {
51    /// The quadding a `/Q` value names, `None` for anything but 0, 1 and 2.
52    pub fn from_int(value: i64) -> Option<Quadding> {
53        match value {
54            0 => Some(Quadding::Left),
55            1 => Some(Quadding::Centered),
56            2 => Some(Quadding::Right),
57            _ => None,
58        }
59    }
60}
61
62/// What a default appearance string (`/DA`, ISO 32000-1 §12.7.3.3) sets: the
63/// font resource name and size of its `Tf` operator and the fill colour of
64/// its last `g`, `rg` or `k` operator. Any other operator is ignored.
65#[derive(Debug, Clone, PartialEq, Default)]
66pub struct DefaultAppearance {
67    /// The `Tf` font resource name, a key of the form's `/DR` `/Font`
68    /// dictionary.
69    pub font: Option<String>,
70    /// The `Tf` size as written; 0 means the text is sized to fit its widget.
71    pub font_size: Option<f32>,
72    /// The fill colour components of the last `g`, `rg` or `k` operator:
73    /// one gray, three RGB or four CMYK components.
74    pub fill_color: Option<Vec<f32>>,
75}
76
77impl DefaultAppearance {
78    /// Reads a `/DA` string as content stream operators; a string the
79    /// content parser rejects sets nothing.
80    ///
81    /// Covers ISO 32000-1 §12.7.3.3.
82    pub fn parse(da: &str) -> DefaultAppearance {
83        let mut appearance = DefaultAppearance::default();
84        let Ok(ops) = parse_content(da.as_bytes()) else {
85            return appearance;
86        };
87        for op in ops {
88            match op {
89                Op::SetFont(name, size) => {
90                    appearance.font = Some(name.0);
91                    appearance.font_size = Some(size);
92                }
93                Op::SetFillGray(g) => appearance.fill_color = Some(vec![g]),
94                Op::SetFillRGB(r, g, b) => appearance.fill_color = Some(vec![r, g, b]),
95                Op::SetFillCMYK(c, m, y, k) => appearance.fill_color = Some(vec![c, m, y, k]),
96                _ => {}
97            }
98        }
99        appearance
100    }
101}
102
103/// The interactive form dictionary (ISO 32000-1 §12.7.2, Table 218).
104#[derive(Debug, Clone, PartialEq)]
105pub struct InteractiveForm {
106    /// `/Fields`: the root fields, those with no parent, as the references
107    /// the array holds.
108    pub fields: Vec<ObjRef>,
109    /// `/NeedAppearances`: whether a reader should build the widgets'
110    /// appearance streams itself; false by default.
111    pub need_appearances: bool,
112    /// `/SigFlags`, no flag set by default.
113    pub signature_flags: SignatureFlags,
114    /// `/CO`: the fields with calculation actions, in the order their values
115    /// are recalculated.
116    pub calculation_order: Vec<ObjRef>,
117    /// `/DR`: the default resources for the fields' appearance streams.
118    pub default_resources: Option<Dict>,
119    /// `/DA`: the document-wide default appearance string for variable
120    /// text, a content fragment such as `/Helv 0 Tf 0 g`, as written.
121    pub default_appearance: Option<String>,
122    /// `/Q`: the document-wide default quadding for variable text; `None`
123    /// when absent or not 0, 1 or 2.
124    pub quadding: Option<Quadding>,
125    /// `/XFA`: whether the form carries an XFA resource, as a stream or an
126    /// array of packets (§12.7.8).
127    pub xfa: bool,
128}
129
130/// The catalog's `/AcroForm` dictionary (ISO 32000-1 §12.7.2), `None` when
131/// the catalog has none. Indirect values are followed; an entry of the
132/// wrong type reads as absent.
133///
134/// Covers ISO 32000-1 §12.7.2.
135pub async fn interactive_form_with<S: AsyncObjectSource>(
136    src: &S,
137    trailer: &Dict,
138) -> Option<InteractiveForm> {
139    let catalog = resolved_dict(src, trailer.get("Root")?).await?;
140    let dict = resolved_dict(src, catalog.get("AcroForm")?).await?;
141    let entries = Entries { src, dict: &dict };
142    let fields = references(entries.value("Fields").await.as_ref());
143    let calculation_order = references(entries.value("CO").await.as_ref());
144    let need_appearances = entries.flag("NeedAppearances").await.unwrap_or(false);
145    let signature_flags = entries
146        .value("SigFlags")
147        .await
148        .and_then(|bits| bits.as_int())
149        .map_or_else(SignatureFlags::default, SignatureFlags::from_bits);
150    let default_resources = match dict.get("DR") {
151        Some(entry) => resolved_dict(src, entry).await,
152        None => None,
153    };
154    let default_appearance = entries
155        .value("DA")
156        .await
157        .and_then(|da| Some(String::from_utf8_lossy(da.as_str_bytes()?).into_owned()));
158    let quadding = entries
159        .value("Q")
160        .await
161        .and_then(|q| q.as_int())
162        .and_then(Quadding::from_int);
163    let xfa = matches!(
164        entries.value("XFA").await,
165        Some(Object::Stream(_)) | Some(Object::Array(_))
166    );
167    Some(InteractiveForm {
168        fields,
169        need_appearances,
170        signature_flags,
171        calculation_order,
172        default_resources,
173        default_appearance,
174        quadding,
175        xfa,
176    })
177}
178
179/// The type of a terminal field (`/FT`, ISO 32000-1 §12.7.3, Table 220).
180///
181/// Covers ISO 32000-1 §12.7.4.
182#[derive(Debug, Clone, Copy, PartialEq, Eq)]
183pub enum FieldType {
184    /// `Btn`: a push button, check box or radio button (§12.7.4.2).
185    Button,
186    /// `Tx`: a text field (§12.7.4.3).
187    Text,
188    /// `Ch`: a list box or combo box (§12.7.4.4).
189    Choice,
190    /// `Sig`: a signature field (§12.7.4.5).
191    Signature,
192}
193
194impl FieldType {
195    /// The type a `/FT` name stands for, `None` for a name Table 220 does
196    /// not list.
197    ///
198    /// Covers ISO 32000-1 §12.7.3.
199    pub fn from_name(name: &str) -> Option<FieldType> {
200        match name {
201            "Btn" => Some(FieldType::Button),
202            "Tx" => Some(FieldType::Text),
203            "Ch" => Some(FieldType::Choice),
204            "Sig" => Some(FieldType::Signature),
205            _ => None,
206        }
207    }
208}
209
210/// A field's `/Ff` word (ISO 32000-1 §12.7.3, Table 221). The bits every
211/// field type shares are read here; a type's own bits are read by that
212/// type's methods.
213#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
214pub struct FieldFlags(pub u32);
215
216impl FieldFlags {
217    /// Bit 1, `ReadOnly`: the user may not change the field's value.
218    pub fn read_only(self) -> bool {
219        self.0 & 1 != 0
220    }
221
222    /// Bit 2, `Required`: the field must have a value when the form is
223    /// submitted.
224    pub fn required(self) -> bool {
225        self.0 & 2 != 0
226    }
227
228    /// Bit 3, `NoExport`: a submit-form action leaves the field out.
229    pub fn no_export(self) -> bool {
230        self.0 & 4 != 0
231    }
232
233    /// Bit 13, `Multiline` (text fields, Table 228): the text may span
234    /// several lines.
235    pub fn multiline(self) -> bool {
236        self.bit(13)
237    }
238
239    /// Bit 14, `Password` (text fields): the text is echoed unreadably and
240    /// should not be stored in the file.
241    pub fn password(self) -> bool {
242        self.bit(14)
243    }
244
245    /// Bit 21, `FileSelect` (text fields): the text is the path of a file
246    /// whose contents are the field's value.
247    pub fn file_select(self) -> bool {
248        self.bit(21)
249    }
250
251    /// Bit 23, `DoNotSpellCheck` (text and choice fields): the text is not
252    /// spell-checked.
253    pub fn do_not_spell_check(self) -> bool {
254        self.bit(23)
255    }
256
257    /// Bit 24, `DoNotScroll` (text fields): the field takes no more text
258    /// than fits its rectangle.
259    pub fn do_not_scroll(self) -> bool {
260        self.bit(24)
261    }
262
263    /// Bit 25, `Comb` (text fields): the text is laid out in `MaxLen`
264    /// equally spaced cells.
265    pub fn comb(self) -> bool {
266        self.bit(25)
267    }
268
269    /// Bit 26, `RichText` (text fields): the value is a rich text string
270    /// held in `/RV`.
271    pub fn rich_text(self) -> bool {
272        self.bit(26)
273    }
274
275    /// Bit 18, `Combo` (choice fields, Table 230): a combo box rather than
276    /// a list box.
277    pub fn combo(self) -> bool {
278        self.bit(18)
279    }
280
281    /// Bit 19, `Edit` (choice fields): the combo box has an editable text
282    /// box next to its list.
283    pub fn edit(self) -> bool {
284        self.bit(19)
285    }
286
287    /// Bit 20, `Sort` (choice fields): a writer sorts the options; readers
288    /// show them in `/Opt` order regardless.
289    pub fn sort(self) -> bool {
290        self.bit(20)
291    }
292
293    /// Bit 22, `MultiSelect` (choice fields): more than one option may be
294    /// selected.
295    pub fn multi_select(self) -> bool {
296        self.bit(22)
297    }
298
299    /// Bit 27, `CommitOnSelChange` (choice fields): a new selection is
300    /// committed at once instead of when the field is left.
301    pub fn commit_on_sel_change(self) -> bool {
302        self.bit(27)
303    }
304
305    /// Bit 15, `NoToggleToOff` (radio buttons, Table 226): one button is
306    /// always on; clicking the selected one does nothing.
307    pub fn no_toggle_to_off(self) -> bool {
308        self.bit(15)
309    }
310
311    /// Bit 16, `Radio` (button fields): a set of radio buttons rather than
312    /// a check box.
313    pub fn radio(self) -> bool {
314        self.bit(16)
315    }
316
317    /// Bit 17, `Pushbutton` (button fields): a pushbutton, which keeps no
318    /// value.
319    pub fn pushbutton(self) -> bool {
320        self.bit(17)
321    }
322
323    /// Bit 26, `RadiosInUnison` (radio buttons): buttons sharing an on
324    /// state turn on and off together.
325    pub fn radios_in_unison(self) -> bool {
326        self.bit(26)
327    }
328
329    /// Whether the bit at `position`, numbered from 1 as the standard
330    /// does, is set.
331    fn bit(self, position: u32) -> bool {
332        self.0 & (1 << (position - 1)) != 0
333    }
334}
335
336/// A widget annotation that draws a field (ISO 32000-1 §12.5.6.19), as
337/// far as the field's state needs it.
338#[derive(Debug, Clone, PartialEq, Eq)]
339pub struct Widget {
340    /// The annotation dictionary's reference; the field's own for a merged
341    /// field.
342    pub object: ObjRef,
343    /// `/AS`: the appearance state the widget shows.
344    pub appearance_state: Option<String>,
345    /// The widget's on state (§12.7.4.2.3): the one key of its `/AP /N`
346    /// dictionary other than `Off`. `None` when the normal appearance is a
347    /// single stream or names no or several other states.
348    pub on_state: Option<String>,
349    /// `/MK`: the captions and icons a button is drawn with (§12.7.4.2.2,
350    /// Table 189); `None` without the dictionary.
351    pub characteristics: Option<AppearanceCharacteristics>,
352}
353
354/// The entries of a widget's appearance characteristics dictionary (`/MK`,
355/// ISO 32000-1 §12.5.6.19, Table 189) that give a button its captions and
356/// icons. The normal caption may sit on any button; the rest are for
357/// pushbuttons (§12.7.4.2.2).
358#[derive(Debug, Clone, PartialEq, Eq, Default)]
359pub struct AppearanceCharacteristics {
360    /// `/CA`: the normal caption, shown when the button is at rest.
361    pub caption: Option<String>,
362    /// `/RC`: the rollover caption, shown while the cursor is over the
363    /// button.
364    pub rollover_caption: Option<String>,
365    /// `/AC`: the alternate caption, shown while the mouse button is down.
366    pub alternate_caption: Option<String>,
367    /// `/I`: the normal icon, a form XObject by reference.
368    pub icon: Option<ObjRef>,
369    /// `/RI`: the rollover icon.
370    pub rollover_icon: Option<ObjRef>,
371    /// `/IX`: the alternate icon.
372    pub alternate_icon: Option<ObjRef>,
373    /// `/TP`: where the caption sits relative to the icon; caption only by
374    /// default.
375    pub caption_position: CaptionPosition,
376}
377
378/// Where a pushbutton's caption sits relative to its icon (`/TP`, ISO
379/// 32000-1 §12.5.6.19, Table 189).
380#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
381pub enum CaptionPosition {
382    /// 0: no icon, caption only.
383    #[default]
384    CaptionOnly,
385    /// 1: no caption, icon only.
386    IconOnly,
387    /// 2: caption below the icon.
388    Below,
389    /// 3: caption above the icon.
390    Above,
391    /// 4: caption to the right of the icon.
392    Right,
393    /// 5: caption to the left of the icon.
394    Left,
395    /// 6: caption overlaid on the icon.
396    Overlaid,
397}
398
399impl CaptionPosition {
400    /// The position a `/TP` code names, `None` for a code outside 0 to 6.
401    pub fn from_int(code: i64) -> Option<CaptionPosition> {
402        Some(match code {
403            0 => CaptionPosition::CaptionOnly,
404            1 => CaptionPosition::IconOnly,
405            2 => CaptionPosition::Below,
406            3 => CaptionPosition::Above,
407            4 => CaptionPosition::Right,
408            5 => CaptionPosition::Left,
409            6 => CaptionPosition::Overlaid,
410            _ => return None,
411        })
412    }
413}
414
415/// The three kinds of button field (ISO 32000-1 §12.7.4.2), told apart by
416/// the `Pushbutton` and `Radio` flags.
417#[derive(Debug, Clone, Copy, PartialEq, Eq)]
418pub enum ButtonKind {
419    /// `Pushbutton` set: a control that keeps no value (§12.7.4.2.2).
420    PushButton,
421    /// Both flags clear: one or more check boxes toggling between on and
422    /// off (§12.7.4.2.3).
423    CheckBox,
424    /// `Radio` set: a set of related buttons of which normally one is on
425    /// (§12.7.4.2.4).
426    RadioButtons,
427}
428
429/// A signature dictionary (ISO 32000-1 §12.8.1, Table 252) as the value of
430/// a signature field, read as data: nothing here is verified.
431///
432/// Covers ISO 32000-1 §12.8.1.
433#[derive(Debug, Clone, PartialEq, Eq, Default)]
434pub struct Signature {
435    /// `/Filter`: the preferred signature handler.
436    pub filter: Option<String>,
437    /// `/SubFilter`: the encoding of the signature value and key
438    /// information, such as `adbe.pkcs7.detached`.
439    pub sub_filter: Option<String>,
440    /// `/ByteRange`: the (offset, length) pairs of the bytes the digest
441    /// covers; an incomplete pair or a negative number ends the list.
442    pub byte_range: Vec<(u64, u64)>,
443    /// `/Contents`: the signature value as written; empty when absent.
444    pub contents: Vec<u8>,
445    /// `/Name`: the signer named in the dictionary.
446    pub name: Option<String>,
447    /// `/M`: the time of signing as written, a PDF date string.
448    pub signing_time: Option<String>,
449    /// `/Location`: where the signing took place.
450    pub location: Option<String>,
451    /// `/Reason`: why the document was signed.
452    pub reason: Option<String>,
453    /// `/ContactInfo`: how to reach the signer.
454    pub contact_info: Option<String>,
455}
456
457impl Signature {
458    /// The Table 252 entries of a signature dictionary as data: the filter
459    /// and sub-filter names, the byte range pairs, the contents bytes, and
460    /// the signer's name, time, location, reason and contact information as
461    /// text strings. Nothing is verified. The same reader serves a signature
462    /// field's `/V` (§12.7.4.5) and the catalog's permission handlers
463    /// (§12.8.4).
464    ///
465    /// Covers ISO 32000-1 §12.8.1.
466    pub fn from_dict(dict: &Dict) -> Signature {
467        let text = |key: &str| Some(decode_text_string(dict.get(key)?.as_str_bytes()?));
468        let name = |key: &str| Some(dict.get_name(key)?.0.clone());
469        Signature {
470            filter: name("Filter"),
471            sub_filter: name("SubFilter"),
472            byte_range: byte_range(dict.get("ByteRange")),
473            contents: dict
474                .get("Contents")
475                .and_then(Object::as_str_bytes)
476                .map(<[u8]>::to_vec)
477                .unwrap_or_default(),
478            name: text("Name"),
479            signing_time: text("M"),
480            location: text("Location"),
481            reason: text("Reason"),
482            contact_info: text("ContactInfo"),
483        }
484    }
485}
486
487/// One entry of a choice field's `/Opt` array (ISO 32000-1 §12.7.4.4,
488/// Table 231): the value exported for the option and the text shown for
489/// it. A lone text string in the array is both.
490#[derive(Debug, Clone, PartialEq, Eq)]
491pub struct ChoiceOption {
492    /// The export value.
493    pub export_value: String,
494    /// The text shown to the user; also what `/V` names when the option is
495    /// selected.
496    pub name: String,
497}
498
499/// One field dictionary (ISO 32000-1 §12.7.3, Table 220) with the
500/// inheritable entries taken from the nearest ancestor that has them.
501#[derive(Debug, Clone, PartialEq)]
502pub struct FormField {
503    /// The field dictionary's own reference.
504    pub object: ObjRef,
505    /// `/Parent`: the field whose `/Kids` holds this one, as walked; `None`
506    /// for a root field.
507    pub parent: Option<ObjRef>,
508    /// The `/Kids` that are fields themselves, in array order.
509    pub kids: Vec<ObjRef>,
510    /// The widget annotations that draw this field: the `/Kids` that are
511    /// `/Subtype /Widget` dictionaries without a `/T` of their own, or the
512    /// field itself when its single widget is merged into it.
513    pub widgets: Vec<Widget>,
514    /// `/FT`, inherited; `None` for a non-terminal field that names none
515    /// and for a name Table 220 does not list.
516    pub field_type: Option<FieldType>,
517    /// `/T`: the partial field name (§12.7.3.2).
518    pub partial_name: Option<String>,
519    /// The fully qualified field name (§12.7.3.2): the partial names from
520    /// the root field down, joined by a period; a field without a `/T`
521    /// shares its parent's name, and a root without one has the empty name.
522    pub name: String,
523    /// `/TU`: the name shown to the user in place of the field name.
524    pub alternate_name: Option<String>,
525    /// `/TM`: the name used when the field's data is exported.
526    pub mapping_name: Option<String>,
527    /// `/Ff`, inherited; no bit set by default.
528    pub flags: FieldFlags,
529    /// `/V`, inherited, resolved one level, in the format of the field's
530    /// type; a merged widget's own value beats its parent's.
531    pub value: Option<Object>,
532    /// `/DV`, inherited, resolved one level: the value a reset-form action
533    /// restores.
534    pub default_value: Option<Object>,
535    /// `/DA`, inherited (§12.7.3.3, Table 222): the default appearance
536    /// string of a variable text field, the interactive form's document-wide
537    /// `/DA` when neither the field nor an ancestor has one; what it sets
538    /// reads through [`DefaultAppearance::parse`].
539    pub default_appearance: Option<String>,
540    /// `/Q`, inherited (§12.7.3.3, Table 222): how a variable text field's
541    /// text is justified, the interactive form's document-wide `/Q` when
542    /// neither the field nor an ancestor has one, left-justified when none
543    /// does or the value is not 0, 1 or 2.
544    pub quadding: Quadding,
545    /// `/DS` (§12.7.3.3, Table 222): the default style string rich text is
546    /// laid out with (§12.7.3.4).
547    pub default_style: Option<String>,
548    /// `/RV` (§12.7.3.3, Table 222): the rich text value as text, a text
549    /// stream read into a string; its markup is not parsed (§12.7.3.4).
550    pub rich_text: Option<String>,
551    /// `/MaxLen`, inherited (§12.7.4.3, Table 229): the most characters a
552    /// text field's text may hold.
553    pub max_len: Option<u32>,
554    /// `/Opt`, inherited: the options of a choice field (§12.7.4.4, Table
555    /// 231) or the export values of a check box or radio button
556    /// (§12.7.4.2, Table 227); an entry that is neither a text string nor a
557    /// pair of them is skipped.
558    pub options: Vec<ChoiceOption>,
559    /// `/TI` (§12.7.4.4): the index into `options` of the first option a
560    /// scrollable list box shows; 0 by default.
561    pub top_index: u32,
562    /// `/I` (§12.7.4.4): the indices into `options` of the selected options
563    /// of a multi-select choice field, as written.
564    pub selected_indices: Vec<u32>,
565    /// `/AA`: the field's additional-actions dictionary, as written.
566    pub additional_actions: Option<Dict>,
567    /// `/Lock` (§12.7.4.5, Table 232): the signature field lock dictionary
568    /// naming the fields that lock once this field is signed, by reference.
569    pub lock: Option<ObjRef>,
570    /// `/SV` (§12.7.4.5, Table 232): the seed value dictionary constraining
571    /// a signature applied to this field, by reference.
572    pub seed_value: Option<ObjRef>,
573}
574
575impl FormField {
576    /// The text of a text field (ISO 32000-1 §12.7.4.3): `/V` decoded as a
577    /// text string. `None` for a field of another type or without a value.
578    pub fn text(&self) -> Option<String> {
579        if self.field_type != Some(FieldType::Text) {
580            return None;
581        }
582        Some(decode_text_string(self.value.as_ref()?.as_str_bytes()?))
583    }
584
585    /// Which kind of button a button field is (ISO 32000-1 §12.7.4.2,
586    /// Table 226): a pushbutton when the `Pushbutton` flag is set, radio
587    /// buttons when `Radio` is set, a check box otherwise. `None` for a
588    /// field of another type.
589    pub fn button_kind(&self) -> Option<ButtonKind> {
590        if self.field_type != Some(FieldType::Button) {
591            return None;
592        }
593        Some(if self.flags.pushbutton() {
594            ButtonKind::PushButton
595        } else if self.flags.radio() {
596            ButtonKind::RadioButtons
597        } else {
598            ButtonKind::CheckBox
599        })
600    }
601
602    /// The appearance state a check box or radio button field is in (ISO
603    /// 32000-1 §12.7.4.2): `/V` as a name, the one the widgets' `/AP /N`
604    /// dictionaries key their on and off appearances by, `Off` when there
605    /// is no value (§12.7.4.2.4's default). `None` for a pushbutton, which
606    /// keeps no value, for another field type, and for a value that is no
607    /// name.
608    pub fn state(&self) -> Option<&str> {
609        match self.button_kind()? {
610            ButtonKind::PushButton => None,
611            ButtonKind::CheckBox | ButtonKind::RadioButtons => match &self.value {
612                None => Some("Off"),
613                Some(value) => Some(value.as_name()?.0.as_str()),
614            },
615        }
616    }
617
618    /// Whether a check box is checked (ISO 32000-1 §12.7.4.2.3): its state
619    /// is a name other than `Off`; an absent value is the off state. `None`
620    /// for a field that is no check box.
621    pub fn checked(&self) -> Option<bool> {
622        (self.button_kind()? == ButtonKind::CheckBox)
623            .then(|| self.state().is_some_and(|state| state != "Off"))
624    }
625
626    /// The widgets of a check box or radio button field that are in the on
627    /// state (ISO 32000-1 §12.7.4.2.3, §12.7.4.2.4): those whose on state
628    /// is the field's state, as indices into `widgets`. The same indices
629    /// pick the Table 227 export values out of `options`. Empty for a field
630    /// in the off state, a pushbutton, or another field type.
631    pub fn on_widgets(&self) -> Vec<usize> {
632        let Some(state) = self.state().filter(|state| *state != "Off") else {
633            return Vec::new();
634        };
635        self.widgets
636            .iter()
637            .enumerate()
638            .filter(|(_, widget)| widget.on_state.as_deref() == Some(state))
639            .map(|(index, _)| index)
640            .collect()
641    }
642
643    /// The signature a signature field holds (ISO 32000-1 §12.7.4.5): its
644    /// `/V` read as a signature dictionary. `None` for another field type,
645    /// an unsigned field, or a value that is no dictionary.
646    pub fn signature(&self) -> Option<Signature> {
647        if self.field_type != Some(FieldType::Signature) {
648            return None;
649        }
650        Some(Signature::from_dict(self.value.as_ref()?.as_dict()?))
651    }
652
653    /// The names of a choice field's selected options (ISO 32000-1
654    /// §12.7.4.4): `/V` as one text string or, for a multi-select field,
655    /// an array of them. Empty for a field of another type, without a
656    /// value, or whose value is neither.
657    pub fn selected(&self) -> Vec<String> {
658        if self.field_type != Some(FieldType::Choice) {
659            return Vec::new();
660        }
661        match &self.value {
662            Some(Object::String(name)) => vec![decode_text_string(name)],
663            Some(Object::Array(names)) => names
664                .iter()
665                .filter_map(|name| Some(decode_text_string(name.as_str_bytes()?)))
666                .collect(),
667            _ => Vec::new(),
668        }
669    }
670}
671
672/// Every field dictionary reachable from the form's `/Fields`, depth first
673/// in array order, each with `/FT`, `/Ff`, `/V` and `/DV` filled from the
674/// nearest ancestor when its own dictionary lacks them. A `/Kids` entry
675/// that is a `/Subtype /Widget` dictionary without a `/T` is one of its
676/// field's widgets, not a field; a reference seen before, one that is not
677/// a dictionary, and anything deeper than the field depth cap is skipped.
678///
679/// Covers ISO 32000-1 §12.7.3.
680pub async fn form_fields_with<S: AsyncObjectSource>(src: &S, trailer: &Dict) -> Vec<FormField> {
681    let mut fields = Vec::new();
682    let Some(form) = interactive_form_with(src, trailer).await else {
683        return fields;
684    };
685    let mut visited: FastSet<ObjRef> = FastSet::default();
686    let mut stack = Vec::new();
687    // The form's /DA and /Q are the document-wide defaults of every
688    // variable text field (Table 218), so the root fields start from them.
689    // Covers ISO 32000-1 §12.7.3.3.
690    let defaults = Inherited {
691        default_appearance: form.default_appearance,
692        quadding: form.quadding,
693        ..Inherited::default()
694    };
695    for object in form.fields.into_iter().rev() {
696        if let Some(dict) = field_dict(src, object).await {
697            stack.push(Pending {
698                object,
699                dict,
700                parent: None,
701                inherited: defaults.clone(),
702                depth: 0,
703            });
704        }
705    }
706    while let Some(pending) = stack.pop() {
707        if !visited.insert(pending.object) {
708            continue;
709        }
710        let (field, children) = read_field(src, pending, &visited).await;
711        fields.push(field);
712        stack.extend(children.into_iter().rev());
713    }
714    fields
715}
716
717/// The entries a field takes from its parent when its own dictionary lacks
718/// them, the rows of Table 220 marked inheritable, and the parent's fully
719/// qualified name the field's own name is built on.
720#[derive(Clone, Default)]
721struct Inherited {
722    field_type: Option<FieldType>,
723    flags: Option<FieldFlags>,
724    value: Option<Object>,
725    default_value: Option<Object>,
726    default_appearance: Option<String>,
727    quadding: Option<Quadding>,
728    name: String,
729    max_len: Option<u32>,
730    options: Vec<ChoiceOption>,
731}
732
733/// A field dictionary waiting to be read, with what it inherits.
734struct Pending {
735    object: ObjRef,
736    dict: Dict,
737    parent: Option<ObjRef>,
738    inherited: Inherited,
739    depth: usize,
740}
741
742/// Reads one field and classifies its `/Kids` into widgets and child
743/// fields; the children come back ready to be read with what they inherit.
744/// A kid already visited, one that is not a dictionary, and every kid of a
745/// field at the depth cap is skipped.
746async fn read_field<S: AsyncObjectSource>(
747    src: &S,
748    pending: Pending,
749    visited: &FastSet<ObjRef>,
750) -> (FormField, Vec<Pending>) {
751    let Pending {
752        object,
753        dict,
754        parent,
755        inherited,
756        depth,
757    } = pending;
758    let entries = Entries { src, dict: &dict };
759    let field_type = entries
760        .value("FT")
761        .await
762        .and_then(|ft| FieldType::from_name(&ft.as_name()?.0))
763        .or(inherited.field_type);
764    let flags = entries
765        .value("Ff")
766        .await
767        .and_then(|ff| u32::try_from(ff.as_int()?).ok())
768        .map(FieldFlags)
769        .or(inherited.flags)
770        .unwrap_or_default();
771    let value = match entries.value("V").await.or(inherited.value) {
772        // A text field's value may be a stream since PDF 1.5 (§12.7.4.3);
773        // its data is the text.
774        Some(Object::Stream(stream)) if field_type == Some(FieldType::Text) => {
775            src.stream_data(&stream).await.ok().map(Object::String)
776        }
777        value => value,
778    };
779    let default_value = entries.value("DV").await.or(inherited.default_value);
780    let default_appearance = entries
781        .value("DA")
782        .await
783        .and_then(|da| Some(String::from_utf8_lossy(da.as_str_bytes()?).into_owned()))
784        .or(inherited.default_appearance);
785    let quadding = entries
786        .value("Q")
787        .await
788        .and_then(|q| Quadding::from_int(q.as_int()?))
789        .or(inherited.quadding);
790    let default_style = entries.text("DS").await;
791    let rich_text = match entries.value("RV").await {
792        // A rich text value may be a text stream since PDF 1.5 (§12.7.3.3).
793        Some(Object::Stream(stream)) => src
794            .stream_data(&stream)
795            .await
796            .ok()
797            .map(|data| decode_text_string(&data)),
798        Some(value) => value.as_str_bytes().map(decode_text_string),
799        None => None,
800    };
801    let max_len = entries
802        .value("MaxLen")
803        .await
804        .and_then(|len| u32::try_from(len.as_int()?).ok())
805        .or(inherited.max_len);
806    let options = match entries.value("Opt").await {
807        Some(opt) => choice_options(src, &opt).await,
808        None => inherited.options,
809    };
810    let top_index = entries
811        .value("TI")
812        .await
813        .and_then(|ti| u32::try_from(ti.as_int()?).ok())
814        .unwrap_or(0);
815    let selected_indices = entries
816        .value("I")
817        .await
818        .and_then(|indices| {
819            Some(
820                indices
821                    .as_array()?
822                    .iter()
823                    .filter_map(|index| u32::try_from(index.as_int()?).ok())
824                    .collect(),
825            )
826        })
827        .unwrap_or_default();
828    let partial_name = entries.text("T").await;
829    let name = qualified_name(&inherited.name, partial_name.as_deref());
830    let additional_actions = match dict.get("AA") {
831        Some(entry) => resolved_dict(src, entry).await,
832        None => None,
833    };
834    let mut widgets = Vec::new();
835    if is_widget(&dict) {
836        widgets.push(widget(src, object, &dict).await);
837    }
838    let mut kids = Vec::new();
839    let mut children = Vec::new();
840    if depth < MAX_FIELD_DEPTH {
841        for kid in references(entries.value("Kids").await.as_ref()) {
842            if visited.contains(&kid) {
843                continue;
844            }
845            let Some(kid_dict) = field_dict(src, kid).await else {
846                continue;
847            };
848            if is_widget(&kid_dict) && kid_dict.get("T").is_none() {
849                widgets.push(widget(src, kid, &kid_dict).await);
850                continue;
851            }
852            kids.push(kid);
853            children.push(Pending {
854                object: kid,
855                dict: kid_dict,
856                parent: Some(object),
857                inherited: Inherited {
858                    field_type,
859                    flags: Some(flags),
860                    value: value.clone(),
861                    default_value: default_value.clone(),
862                    default_appearance: default_appearance.clone(),
863                    quadding,
864                    name: name.clone(),
865                    max_len,
866                    options: options.clone(),
867                },
868                depth: depth + 1,
869            });
870        }
871    }
872    let field = FormField {
873        object,
874        parent,
875        kids,
876        widgets,
877        field_type,
878        partial_name,
879        name,
880        alternate_name: entries.text("TU").await,
881        mapping_name: entries.text("TM").await,
882        flags,
883        value,
884        default_value,
885        default_appearance,
886        quadding: quadding.unwrap_or_default(),
887        default_style,
888        rich_text,
889        max_len,
890        options,
891        top_index,
892        selected_indices,
893        additional_actions,
894        lock: dict.get("Lock").and_then(Object::as_ref),
895        seed_value: dict.get("SV").and_then(Object::as_ref),
896    };
897    (field, children)
898}
899
900/// The fully qualified name of a field with partial name `partial` under
901/// a parent named `parent`: the two joined by a period, the partial name
902/// alone under an unnamed root, and the parent's name for a field without
903/// a `/T`.
904///
905/// Covers ISO 32000-1 §12.7.3.2.
906fn qualified_name(parent: &str, partial: Option<&str>) -> String {
907    match partial {
908        Some(partial) if parent.is_empty() => partial.to_string(),
909        Some(partial) => format!("{parent}.{partial}"),
910        None => parent.to_string(),
911    }
912}
913
914/// The options an `/Opt` array holds (ISO 32000-1 §12.7.4.4, Table 231):
915/// a text string is an option whose export value is its name, a pair of
916/// text strings is an export value and a name; indirect entries are
917/// followed and anything else is skipped.
918async fn choice_options<S: AsyncObjectSource>(src: &S, opt: &Object) -> Vec<ChoiceOption> {
919    let Some(items) = opt.as_array() else {
920        return Vec::new();
921    };
922    let mut options = Vec::new();
923    for item in items {
924        let Ok(item) = src.resolve(item).await else {
925            continue;
926        };
927        match &item {
928            Object::String(name) => {
929                let name = decode_text_string(name);
930                options.push(ChoiceOption {
931                    export_value: name.clone(),
932                    name,
933                });
934            }
935            Object::Array(pair) => {
936                let [export_value, name] = pair.as_slice() else {
937                    continue;
938                };
939                let (Some(export_value), Some(name)) =
940                    (export_value.as_str_bytes(), name.as_str_bytes())
941                else {
942                    continue;
943                };
944                options.push(ChoiceOption {
945                    export_value: decode_text_string(export_value),
946                    name: decode_text_string(name),
947                });
948            }
949            _ => {}
950        }
951    }
952    options
953}
954
955/// The (offset, length) pairs of a `/ByteRange` array (ISO 32000-1
956/// §12.8.1): complete pairs of non-negative integers up to the first
957/// element that is not one.
958fn byte_range(value: Option<&Object>) -> Vec<(u64, u64)> {
959    let Some(items) = value.and_then(Object::as_array) else {
960        return Vec::new();
961    };
962    items
963        .as_chunks::<2>()
964        .0
965        .iter()
966        .map_while(|[offset, length]| {
967            Some((
968                u64::try_from(offset.as_int()?).ok()?,
969                u64::try_from(length.as_int()?).ok()?,
970            ))
971        })
972        .collect()
973}
974
975/// The dictionary a reference points at, `None` for anything else.
976async fn field_dict<S: AsyncObjectSource>(src: &S, object: ObjRef) -> Option<Dict> {
977    match src.get(object).await.ok()? {
978        Object::Dict(dict) => Some(dict),
979        _ => None,
980    }
981}
982
983/// The widget record of an annotation dictionary: its reference, its
984/// `/AS` and its on state.
985async fn widget<S: AsyncObjectSource>(src: &S, object: ObjRef, dict: &Dict) -> Widget {
986    Widget {
987        object,
988        appearance_state: dict.get_name("AS").map(|state| state.0.clone()),
989        on_state: on_state(src, dict).await,
990        characteristics: characteristics(src, dict).await,
991    }
992}
993
994/// The caption and icon entries of a widget's `/MK` dictionary (Table
995/// 189); `None` without the dictionary. A caption that is no string, an
996/// icon that is no reference and a `/TP` outside 0 to 6 read as absent.
997///
998/// Covers ISO 32000-1 §12.7.4.2.2.
999async fn characteristics<S: AsyncObjectSource>(
1000    src: &S,
1001    dict: &Dict,
1002) -> Option<AppearanceCharacteristics> {
1003    let mk = resolved_dict(src, dict.get("MK")?).await?;
1004    let entries = Entries { src, dict: &mk };
1005    let reference = |key: &str| mk.get(key).and_then(Object::as_ref);
1006    Some(AppearanceCharacteristics {
1007        caption: entries.text("CA").await,
1008        rollover_caption: entries.text("RC").await,
1009        alternate_caption: entries.text("AC").await,
1010        icon: reference("I"),
1011        rollover_icon: reference("RI"),
1012        alternate_icon: reference("IX"),
1013        caption_position: entries
1014            .value("TP")
1015            .await
1016            .and_then(|code| CaptionPosition::from_int(code.as_int()?))
1017            .unwrap_or_default(),
1018    })
1019}
1020
1021/// The on state of a widget (ISO 32000-1 §12.7.4.2.3): the one key of its
1022/// `/AP /N` dictionary other than `Off`; `None` when `/N` is a single
1023/// stream or names no or several other states.
1024async fn on_state<S: AsyncObjectSource>(src: &S, dict: &Dict) -> Option<String> {
1025    let appearance = resolved_dict(src, dict.get("AP")?).await?;
1026    let Object::Dict(states) = src.resolve(appearance.get("N")?).await.ok()? else {
1027        return None;
1028    };
1029    let mut on = states
1030        .iter()
1031        .map(|(state, _)| state)
1032        .filter(|state| state.0 != "Off");
1033    let state = on.next()?;
1034    on.next().is_none().then(|| state.0.clone())
1035}
1036
1037/// Whether a dictionary is a widget annotation (`/Subtype /Widget`).
1038fn is_widget(dict: &Dict) -> bool {
1039    dict.get_name("Subtype")
1040        .is_some_and(|subtype| subtype.0 == "Widget")
1041}
1042
1043/// The references an array holds, anything else in it skipped; empty for a
1044/// value that is no array.
1045fn references(value: Option<&Object>) -> Vec<ObjRef> {
1046    value
1047        .and_then(Object::as_array)
1048        .map(|items| items.iter().filter_map(Object::as_ref).collect())
1049        .unwrap_or_default()
1050}
1051
1052#[cfg(test)]
1053mod tests {
1054    use super::{
1055        AppearanceCharacteristics, ButtonKind, CaptionPosition, ChoiceOption, DefaultAppearance,
1056        FieldFlags, FieldType, FormField, InteractiveForm, Quadding, Signature, SignatureFlags,
1057        Widget,
1058    };
1059    use crate::object::{Name, ObjRef, Object};
1060    use crate::Document;
1061    use pdfboss_testkit::PdfBuilder;
1062
1063    fn doc(catalog_extra: &str, objects: &[(u32, &str)]) -> Document {
1064        let mut b = PdfBuilder::new();
1065        b.object(
1066            1,
1067            &format!("<< /Type /Catalog /Pages 2 0 R {catalog_extra} >>"),
1068        );
1069        b.object(2, "<< /Type /Pages /Kids [3 0 R] /Count 1 >>");
1070        b.object(3, "<< /Type /Page /Parent 2 0 R /MediaBox [0 0 100 100] >>");
1071        for (num, body) in objects {
1072            b.object(*num, body);
1073        }
1074        Document::load(b.build(1)).unwrap()
1075    }
1076
1077    fn r(num: u32) -> ObjRef {
1078        ObjRef { num, gen: 0 }
1079    }
1080
1081    fn widget_refs(field: &FormField) -> Vec<ObjRef> {
1082        field.widgets.iter().map(|widget| widget.object).collect()
1083    }
1084
1085    /// `doc` with one stream object added.
1086    fn doc_with_stream(
1087        catalog_extra: &str,
1088        objects: &[(u32, &str)],
1089        stream: (u32, &str, &[u8]),
1090    ) -> Document {
1091        let mut b = PdfBuilder::new();
1092        b.object(
1093            1,
1094            &format!("<< /Type /Catalog /Pages 2 0 R {catalog_extra} >>"),
1095        );
1096        b.object(2, "<< /Type /Pages /Kids [3 0 R] /Count 1 >>");
1097        b.object(3, "<< /Type /Page /Parent 2 0 R /MediaBox [0 0 100 100] >>");
1098        for (num, body) in objects {
1099            b.object(*num, body);
1100        }
1101        b.stream(stream.0, stream.1, stream.2);
1102        Document::load(b.build(1)).unwrap()
1103    }
1104
1105    /// A field's `/DA` and `/Q` come from its own dictionary, then the
1106    /// nearest ancestor, then the interactive form's document-wide defaults;
1107    /// the quadding is left when none of them says otherwise, as is a `/Q`
1108    /// outside 0 to 2. `/DS` and `/RV` are the field's own.
1109    // Covers ISO 32000-1 §12.7.3.3.
1110    #[test]
1111    fn variable_text_entries_inherit_and_fall_back_to_the_form() {
1112        let document = doc(
1113            "/AcroForm << /Fields [20 0 R 23 0 R] /DA (/Helv 0 Tf 0 g) /Q 1 >>",
1114            &[
1115                (
1116                    20,
1117                    "<< /T (parent) /FT /Tx /DA (/TiRo 12 Tf 1 0 0 rg) /Kids [21 0 R 22 0 R] >>",
1118                ),
1119                (21, "<< /T (plain) /Parent 20 0 R >>"),
1120                (
1121                    22,
1122                    "<< /T (styled) /Parent 20 0 R /Q 2 /DS (font: Arial) /RV (<p>Ada</p>) >>",
1123                ),
1124                (23, "<< /T (bare) /FT /Tx >>"),
1125            ],
1126        );
1127        let fields = document.form_fields();
1128        let by_name = |name: &str| fields.iter().find(|field| field.name == name).unwrap();
1129        let parent = by_name("parent");
1130        assert_eq!(
1131            parent.default_appearance.as_deref(),
1132            Some("/TiRo 12 Tf 1 0 0 rg")
1133        );
1134        assert_eq!(parent.quadding, Quadding::Centered);
1135        let plain = by_name("parent.plain");
1136        assert_eq!(
1137            plain.default_appearance.as_deref(),
1138            Some("/TiRo 12 Tf 1 0 0 rg")
1139        );
1140        assert_eq!(plain.quadding, Quadding::Centered);
1141        assert_eq!(plain.default_style, None);
1142        assert_eq!(plain.rich_text, None);
1143        let styled = by_name("parent.styled");
1144        assert_eq!(styled.quadding, Quadding::Right);
1145        assert_eq!(styled.default_style.as_deref(), Some("font: Arial"));
1146        assert_eq!(styled.rich_text.as_deref(), Some("<p>Ada</p>"));
1147        let bare = by_name("bare");
1148        assert_eq!(bare.default_appearance.as_deref(), Some("/Helv 0 Tf 0 g"));
1149        assert_eq!(bare.quadding, Quadding::Centered);
1150
1151        let unstyled = doc(
1152            "/AcroForm << /Fields [23 0 R 24 0 R] >>",
1153            &[
1154                (23, "<< /T (bare) /FT /Tx >>"),
1155                (24, "<< /T (odd) /FT /Tx /Q 7 >>"),
1156            ],
1157        );
1158        for field in unstyled.form_fields() {
1159            assert_eq!(field.default_appearance, None, "{}", field.name);
1160            assert_eq!(field.quadding, Quadding::Left, "{}", field.name);
1161        }
1162    }
1163
1164    /// A `/RV` text stream (PDF 1.5) reads as its decoded data.
1165    // Covers ISO 32000-1 §12.7.3.3.
1166    #[test]
1167    fn rich_text_stream_reads_as_text() {
1168        let document = doc_with_stream(
1169            "/AcroForm << /Fields [20 0 R] >>",
1170            &[(20, "<< /T (rich) /FT /Tx /RV 21 0 R >>")],
1171            (21, "", b"<p>Ada</p>"),
1172        );
1173        let fields = document.form_fields();
1174        assert_eq!(fields[0].rich_text.as_deref(), Some("<p>Ada</p>"));
1175    }
1176
1177    /// The `/DA` string's `Tf` names the font resource and size, 0 meaning
1178    /// auto-size, and its last `g`, `rg` or `k` sets the fill colour; a
1179    /// string without them, or a `Tf` short of an operand, sets nothing.
1180    // Covers ISO 32000-1 §12.7.3.3.
1181    #[test]
1182    fn default_appearance_reads_font_size_and_fill_colour() {
1183        let da = DefaultAppearance::parse("/Helv 0 Tf 0 g");
1184        assert_eq!(da.font.as_deref(), Some("Helv"));
1185        assert_eq!(da.font_size, Some(0.0));
1186        assert_eq!(da.fill_color, Some(vec![0.0]));
1187        let da = DefaultAppearance::parse("0 0 1 rg /TiRo 12 Tf 0 0 0 1 k");
1188        assert_eq!(da.font.as_deref(), Some("TiRo"));
1189        assert_eq!(da.font_size, Some(12.0));
1190        assert_eq!(da.fill_color, Some(vec![0.0, 0.0, 0.0, 1.0]));
1191        assert_eq!(DefaultAppearance::parse(""), DefaultAppearance::default());
1192        assert_eq!(
1193            DefaultAppearance::parse("/Helv Tf"),
1194            DefaultAppearance::default()
1195        );
1196    }
1197
1198    /// Every entry of Table 218 set, the dictionary itself and its flags
1199    /// indirect; a direct dictionary in `/Fields` is not a reference and is
1200    /// skipped.
1201    // Covers ISO 32000-1 §12.7.2.
1202    #[test]
1203    fn the_interactive_form_dictionary_is_read() {
1204        let found = doc(
1205            "/AcroForm 4 0 R",
1206            &[
1207                (
1208                    4,
1209                    "<< /Fields [5 0 R << /T (inline) >> 6 0 R] /NeedAppearances true \
1210                     /SigFlags 7 0 R /CO [6 0 R] /DR << /Font << /Helv 8 0 R >> >> \
1211                     /DA (/Helv 0 Tf 0 g) /Q 1 /XFA [(preamble) 9 0 R] >>",
1212                ),
1213                (5, "<< /T (a) /FT /Tx >>"),
1214                (6, "<< /T (b) /FT /Tx >>"),
1215                (7, "3"),
1216                (8, "<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>"),
1217            ],
1218        )
1219        .interactive_form()
1220        .unwrap();
1221        assert_eq!(found.fields, vec![r(5), r(6)]);
1222        assert!(found.need_appearances);
1223        assert_eq!(
1224            found.signature_flags,
1225            SignatureFlags {
1226                signatures_exist: true,
1227                append_only: true,
1228            }
1229        );
1230        assert_eq!(found.calculation_order, vec![r(6)]);
1231        assert!(found
1232            .default_resources
1233            .as_ref()
1234            .and_then(|dr| dr.get_dict("Font"))
1235            .is_some());
1236        assert_eq!(found.default_appearance.as_deref(), Some("/Helv 0 Tf 0 g"));
1237        assert_eq!(found.quadding, Some(Quadding::Centered));
1238        assert!(found.xfa);
1239    }
1240
1241    /// A catalog without the dictionary has no form; an empty dictionary
1242    /// holds the table's defaults; a quadding the table does not define and
1243    /// a `/Fields` that is no array read as absent.
1244    // Covers ISO 32000-1 §12.7.2.
1245    #[test]
1246    fn missing_and_malformed_form_entries_take_the_defaults() {
1247        assert_eq!(doc("", &[]).interactive_form(), None);
1248        assert_eq!(doc("/AcroForm 5", &[]).interactive_form(), None);
1249        assert_eq!(
1250            doc("/AcroForm << >>", &[]).interactive_form(),
1251            Some(InteractiveForm {
1252                fields: Vec::new(),
1253                need_appearances: false,
1254                signature_flags: SignatureFlags::default(),
1255                calculation_order: Vec::new(),
1256                default_resources: None,
1257                default_appearance: None,
1258                quadding: None,
1259                xfa: false,
1260            })
1261        );
1262        let odd = doc(
1263            "/AcroForm << /Fields 5 0 R /Q 7 /SigFlags 4 >>",
1264            &[(5, "(x)")],
1265        )
1266        .interactive_form()
1267        .unwrap();
1268        assert_eq!(odd.fields, Vec::new());
1269        assert_eq!(odd.quadding, None);
1270        assert_eq!(odd.signature_flags, SignatureFlags::default());
1271    }
1272
1273    /// A root field with its single widget merged into it (Table 220's
1274    /// `Kids` omitted): every non-inheritable entry read as written, the
1275    /// text strings decoded, the field its own widget.
1276    // Covers ISO 32000-1 §12.7.3.
1277    #[test]
1278    fn a_merged_field_is_read_with_itself_as_its_widget() {
1279        let fields = doc(
1280            "/AcroForm << /Fields [5 0 R] >>",
1281            &[
1282                (
1283                    5,
1284                    "<< /FT /Tx /T <FEFF004E0061006D0065> /TU (Your name) /TM (name_export) \
1285                 /Ff 7 /V (Ada) /DV 6 0 R /AA << /F 7 0 R >> /Subtype /Widget \
1286                 /Rect [0 0 10 10] >>",
1287                ),
1288                (6, "(nobody)"),
1289            ],
1290        )
1291        .form_fields();
1292        assert_eq!(fields.len(), 1);
1293        let field = &fields[0];
1294        assert_eq!(field.object, r(5));
1295        assert_eq!(field.parent, None);
1296        assert_eq!(field.kids, Vec::new());
1297        assert_eq!(widget_refs(field), vec![r(5)]);
1298        assert_eq!(field.field_type, Some(FieldType::Text));
1299        assert_eq!(field.partial_name.as_deref(), Some("Name"));
1300        assert_eq!(field.alternate_name.as_deref(), Some("Your name"));
1301        assert_eq!(field.mapping_name.as_deref(), Some("name_export"));
1302        assert!(field.flags.read_only() && field.flags.required() && field.flags.no_export());
1303        assert_eq!(field.value, Some(Object::String(b"Ada".to_vec())));
1304        assert_eq!(
1305            field.default_value,
1306            Some(Object::String(b"nobody".to_vec()))
1307        );
1308        assert!(field
1309            .additional_actions
1310            .as_ref()
1311            .is_some_and(|aa| aa.get("F").is_some()));
1312    }
1313
1314    /// A radio button field whose `/Kids` are two widget annotations: one
1315    /// field, two widgets, no child fields.
1316    // Covers ISO 32000-1 §12.7.3.
1317    #[test]
1318    fn widget_kids_belong_to_their_field_and_are_not_fields() {
1319        let fields = doc(
1320            "/AcroForm << /Fields [5 0 R] >>",
1321            &[
1322                (
1323                    5,
1324                    "<< /FT /Btn /Ff 32768 /T (card) /V /visa /Kids [6 0 R 7 0 R] >>",
1325                ),
1326                (
1327                    6,
1328                    "<< /Subtype /Widget /Parent 5 0 R /AS /visa /Rect [0 0 1 1] >>",
1329                ),
1330                (
1331                    7,
1332                    "<< /Subtype /Widget /Parent 5 0 R /AS /Off /Rect [0 0 1 1] >>",
1333                ),
1334            ],
1335        )
1336        .form_fields();
1337        assert_eq!(fields.len(), 1);
1338        assert_eq!(fields[0].kids, Vec::new());
1339        assert_eq!(widget_refs(&fields[0]), vec![r(6), r(7)]);
1340        assert_eq!(fields[0].value, Some(Object::Name(Name("visa".into()))));
1341    }
1342
1343    /// Fields are listed depth first in array order; a child takes `/FT`,
1344    /// `/Ff`, `/V` and `/DV` from its parent unless it has its own, keeps
1345    /// its own `/T`, and a kid with a `/T` is a field even when it is also
1346    /// a widget.
1347    // Covers ISO 32000-1 §12.7.3.
1348    #[test]
1349    fn children_inherit_the_inheritable_entries_from_their_parent() {
1350        let fields = doc(
1351            "/AcroForm << /Fields [5 0 R 9 0 R] >>",
1352            &[
1353                (5, "<< /T (personal) /FT /Tx /Ff 1 /V (from parent) /DV (reset) /Kids [6 0 R 7 0 R] >>"),
1354                (6, "<< /T (first) /Parent 5 0 R /Subtype /Widget /Rect [0 0 1 1] >>"),
1355                (7, "<< /T (last) /Parent 5 0 R /Ff 2 /V (own) /Kids [8 0 R] >>"),
1356                (8, "<< /Subtype /Widget /Parent 7 0 R /Rect [0 0 1 1] >>"),
1357                (9, "<< /T (other) /FT /Ch /Subtype /Widget /Rect [0 0 1 1] >>"),
1358            ],
1359        )
1360        .form_fields();
1361        let objects: Vec<ObjRef> = fields.iter().map(|f| f.object).collect();
1362        assert_eq!(objects, vec![r(5), r(6), r(7), r(9)]);
1363        let root = &fields[0];
1364        assert_eq!(root.kids, vec![r(6), r(7)]);
1365        assert_eq!(widget_refs(root), Vec::new());
1366        let first = &fields[1];
1367        assert_eq!(first.parent, Some(r(5)));
1368        assert_eq!(first.partial_name.as_deref(), Some("first"));
1369        assert_eq!(first.field_type, Some(FieldType::Text));
1370        assert_eq!(first.flags, FieldFlags(1));
1371        assert_eq!(first.value, Some(Object::String(b"from parent".to_vec())));
1372        assert_eq!(first.default_value, Some(Object::String(b"reset".to_vec())));
1373        assert_eq!(widget_refs(first), vec![r(6)]);
1374        let last = &fields[2];
1375        assert_eq!(last.flags, FieldFlags(2));
1376        assert_eq!(last.value, Some(Object::String(b"own".to_vec())));
1377        assert_eq!(last.default_value, Some(Object::String(b"reset".to_vec())));
1378        assert_eq!(widget_refs(last), vec![r(8)]);
1379        assert_eq!(fields[3].field_type, Some(FieldType::Choice));
1380        assert_eq!(fields[3].flags, FieldFlags::default());
1381    }
1382
1383    /// No form or no fields gives an empty list; a `/Fields` or `/Kids`
1384    /// entry that is not a dictionary, a kid that points back up the tree
1385    /// and an `/FT` name Table 220 does not list are skipped or read as
1386    /// absent.
1387    // Covers ISO 32000-1 §12.7.3.
1388    #[test]
1389    fn malformed_field_trees_are_walked_without_repeats() {
1390        assert_eq!(doc("", &[]).form_fields(), Vec::new());
1391        assert_eq!(doc("/AcroForm << >>", &[]).form_fields(), Vec::new());
1392        let fields = doc(
1393            "/AcroForm << /Fields [5 0 R 6 0 R 7 0 R] >>",
1394            &[
1395                (5, "(not a field)"),
1396                (6, "<< /T (loop) /FT /Bogus /Kids [6 0 R 8 0 R 7 0 R] >>"),
1397                (7, "<< /T (leaf) /FT /Sig >>"),
1398                (8, "42"),
1399            ],
1400        )
1401        .form_fields();
1402        let objects: Vec<ObjRef> = fields.iter().map(|f| f.object).collect();
1403        assert_eq!(objects, vec![r(6), r(7)]);
1404        assert_eq!(fields[0].field_type, None);
1405        assert_eq!(fields[0].kids, vec![r(7)]);
1406        assert_eq!(fields[1].parent, Some(r(6)));
1407        assert_eq!(fields[1].field_type, Some(FieldType::Signature));
1408    }
1409
1410    /// The standard's own example: PersonalData, Address and ZipCode nested
1411    /// three deep give the leaf the name PersonalData.Address.ZipCode, and
1412    /// each level's name is a prefix of its children's.
1413    // Covers ISO 32000-1 §12.7.3.2.
1414    #[test]
1415    fn fully_qualified_names_join_the_partial_names_with_periods() {
1416        let fields = doc(
1417            "/AcroForm << /Fields [5 0 R] >>",
1418            &[
1419                (5, "<< /T (PersonalData) /Kids [6 0 R] >>"),
1420                (6, "<< /T (Address) /Parent 5 0 R /Kids [7 0 R] >>"),
1421                (7, "<< /T (ZipCode) /FT /Tx /Parent 6 0 R >>"),
1422            ],
1423        )
1424        .form_fields();
1425        let names: Vec<&str> = fields.iter().map(|f| f.name.as_str()).collect();
1426        assert_eq!(
1427            names,
1428            [
1429                "PersonalData",
1430                "PersonalData.Address",
1431                "PersonalData.Address.ZipCode"
1432            ]
1433        );
1434    }
1435
1436    /// A kid without a `/T` is another representation of its parent's
1437    /// field and shares the fully qualified name; a root without a `/T`
1438    /// has the empty name and its named child carries no leading period.
1439    // Covers ISO 32000-1 §12.7.3.2.
1440    #[test]
1441    fn fields_without_a_partial_name_share_their_parent_s_name() {
1442        let fields = doc(
1443            "/AcroForm << /Fields [5 0 R 8 0 R] >>",
1444            &[
1445                (5, "<< /T (choice) /FT /Btn /Kids [6 0 R 7 0 R] >>"),
1446                (6, "<< /Parent 5 0 R /Kids [9 0 R] >>"),
1447                (7, "<< /Parent 5 0 R /Subtype /Widget /Rect [0 0 1 1] >>"),
1448                (8, "<< /FT /Tx /Kids [10 0 R] >>"),
1449                (9, "<< /Parent 6 0 R /Subtype /Widget /Rect [0 0 1 1] >>"),
1450                (10, "<< /T (lonely) /Parent 8 0 R >>"),
1451            ],
1452        )
1453        .form_fields();
1454        let names: Vec<(ObjRef, &str)> =
1455            fields.iter().map(|f| (f.object, f.name.as_str())).collect();
1456        assert_eq!(
1457            names,
1458            [
1459                (r(5), "choice"),
1460                (r(6), "choice"),
1461                (r(8), ""),
1462                (r(10), "lonely")
1463            ]
1464        );
1465        assert_eq!(fields[1].partial_name, None);
1466    }
1467
1468    /// A text field's value is a text string; `/MaxLen` is inheritable; the
1469    /// Table 228 bits are read by position, numbered from 1.
1470    // Covers ISO 32000-1 §12.7.4.3.
1471    #[test]
1472    fn text_fields_decode_their_value_and_read_max_len_and_their_flags() {
1473        let fields = doc(
1474            "/AcroForm << /Fields [5 0 R] >>",
1475            &[
1476                (
1477                    5,
1478                    "<< /T (box) /FT /Tx /MaxLen 8 /Ff 16781312 /Kids [6 0 R] >>",
1479                ),
1480                (6, "<< /T (line) /Parent 5 0 R /V <FEFF00480069> >>"),
1481            ],
1482        )
1483        .form_fields();
1484        let line = &fields[1];
1485        assert_eq!(line.text().as_deref(), Some("Hi"));
1486        assert_eq!(line.max_len, Some(8));
1487        assert!(line.flags.multiline() && line.flags.comb());
1488        assert!(!line.flags.password() && !line.flags.file_select());
1489        assert!(!line.flags.do_not_spell_check() && !line.flags.do_not_scroll());
1490        assert!(!line.flags.rich_text());
1491        assert_eq!(fields[0].text(), None);
1492        let all = FieldFlags((1 << 25) | (1 << 24) | (1 << 23) | (1 << 22) | (1 << 20) | (1 << 13));
1493        assert!(all.password() && all.file_select() && all.do_not_spell_check());
1494        assert!(all.do_not_scroll() && all.comb() && all.rich_text());
1495        assert!(!all.multiline());
1496    }
1497
1498    /// Since PDF 1.5 the value may be a stream; its data is the text.
1499    // Covers ISO 32000-1 §12.7.4.3.
1500    #[test]
1501    fn a_stream_value_of_a_text_field_is_read_as_its_text() {
1502        let fields = doc_with_stream(
1503            "/AcroForm << /Fields [5 0 R] >>",
1504            &[(5, "<< /T (essay) /FT /Tx /V 6 0 R >>")],
1505            (6, "", b"From a stream"),
1506        )
1507        .form_fields();
1508        assert_eq!(fields[0].text().as_deref(), Some("From a stream"));
1509        assert_eq!(
1510            fields[0].value,
1511            Some(Object::String(b"From a stream".to_vec()))
1512        );
1513    }
1514
1515    /// Only text fields have text: a button's name value and a text field
1516    /// without a value give none, and a value that is no string or stream
1517    /// gives none.
1518    // Covers ISO 32000-1 §12.7.4.3.
1519    #[test]
1520    fn other_field_types_and_odd_values_have_no_text() {
1521        let fields = doc(
1522            "/AcroForm << /Fields [5 0 R 6 0 R 7 0 R] >>",
1523            &[
1524                (5, "<< /T (check) /FT /Btn /V /Yes >>"),
1525                (6, "<< /T (empty) /FT /Tx >>"),
1526                (7, "<< /T (odd) /FT /Tx /V 42 /MaxLen -1 >>"),
1527            ],
1528        )
1529        .form_fields();
1530        assert_eq!(fields[0].text(), None);
1531        assert_eq!(fields[1].text(), None);
1532        assert_eq!(fields[2].text(), None);
1533        assert_eq!(fields[2].max_len, None);
1534    }
1535
1536    /// A combo box: `/Opt` mixes lone names with export/name pairs, `/TI`
1537    /// and `/I` are read, `/V` names the selected option, and the Table
1538    /// 230 bits are read by position.
1539    // Covers ISO 32000-1 §12.7.4.4.
1540    #[test]
1541    fn choice_fields_list_their_options_top_index_and_selection() {
1542        let fields = doc(
1543            "/AcroForm << /Fields [5 0 R] >>",
1544            &[(
1545                5,
1546                "<< /T (colour) /FT /Ch /Ff 393216 /Opt [(Red) [(b) (Blue)] <FEFF0047>] \
1547                 /TI 1 /I [1] /V (Blue) >>",
1548            )],
1549        )
1550        .form_fields();
1551        let field = &fields[0];
1552        assert_eq!(
1553            field.options,
1554            vec![
1555                ChoiceOption {
1556                    export_value: "Red".into(),
1557                    name: "Red".into(),
1558                },
1559                ChoiceOption {
1560                    export_value: "b".into(),
1561                    name: "Blue".into(),
1562                },
1563                ChoiceOption {
1564                    export_value: "G".into(),
1565                    name: "G".into(),
1566                },
1567            ]
1568        );
1569        assert_eq!(field.top_index, 1);
1570        assert_eq!(field.selected_indices, vec![1]);
1571        assert_eq!(field.selected(), vec!["Blue".to_string()]);
1572        assert!(field.flags.combo() && field.flags.edit());
1573        assert!(!field.flags.sort() && !field.flags.multi_select());
1574        assert!(!field.flags.commit_on_sel_change());
1575    }
1576
1577    /// A multi-select list box holds an array of names in `/V`; an indirect
1578    /// `/Opt` array with an indirect element is followed; `/Opt` is taken
1579    /// from the parent when the field has none.
1580    // Covers ISO 32000-1 §12.7.4.4.
1581    #[test]
1582    fn multi_select_choice_fields_hold_several_selected_names() {
1583        let fields = doc(
1584            "/AcroForm << /Fields [5 0 R] >>",
1585            &[
1586                (
1587                    5,
1588                    "<< /T (toppings) /FT /Ch /Ff 69730304 /Opt 6 0 R /Kids [8 0 R] >>",
1589                ),
1590                (6, "[(a) 7 0 R (c)]"),
1591                (7, "[(bee) (b)]"),
1592                (8, "<< /Parent 5 0 R /V [(a) (c)] /I [0 2] >>"),
1593            ],
1594        )
1595        .form_fields();
1596        let kid = &fields[1];
1597        assert_eq!(kid.options.len(), 3);
1598        assert_eq!(kid.options[1].export_value, "bee");
1599        assert_eq!(kid.selected(), vec!["a".to_string(), "c".to_string()]);
1600        assert_eq!(kid.selected_indices, vec![0, 2]);
1601        assert!(kid.flags.sort() && kid.flags.multi_select() && kid.flags.commit_on_sel_change());
1602        assert!(!kid.flags.combo());
1603        assert_eq!(fields[0].selected(), Vec::<String>::new());
1604    }
1605
1606    /// Other field types have no selection even with a string value; a
1607    /// choice field without `/Opt` has no options and the default top
1608    /// index; an `/Opt` entry that is a number or a three-element array, a
1609    /// negative `/TI` and a negative index in `/I` are skipped.
1610    // Covers ISO 32000-1 §12.7.4.4.
1611    #[test]
1612    fn choice_entries_default_for_other_types_and_skip_odd_values() {
1613        let fields = doc(
1614            "/AcroForm << /Fields [5 0 R 6 0 R 7 0 R] >>",
1615            &[
1616                (5, "<< /T (text) /FT /Tx /V (x) /Opt [(y)] >>"),
1617                (6, "<< /T (bare) /FT /Ch >>"),
1618                (
1619                    7,
1620                    "<< /T (odd) /FT /Ch /Opt [7 [(a) (b) (c)] (ok)] /TI -3 /I [-1 0] /V 5 >>",
1621                ),
1622            ],
1623        )
1624        .form_fields();
1625        assert_eq!(fields[0].selected(), Vec::<String>::new());
1626        assert_eq!(fields[0].options.len(), 1);
1627        assert_eq!(fields[1].options, Vec::new());
1628        assert_eq!(fields[1].top_index, 0);
1629        assert_eq!(fields[1].selected(), Vec::<String>::new());
1630        assert_eq!(fields[2].options.len(), 1);
1631        assert_eq!(fields[2].options[0].name, "ok");
1632        assert_eq!(fields[2].top_index, 0);
1633        assert_eq!(fields[2].selected_indices, vec![0]);
1634        assert_eq!(fields[2].selected(), Vec::<String>::new());
1635    }
1636
1637    /// The `Pushbutton` and `Radio` bits pick the kind; `NoToggleToOff` and
1638    /// `RadiosInUnison` are read alongside; a field of another type has no
1639    /// kind.
1640    // Covers ISO 32000-1 §12.7.4.2.
1641    #[test]
1642    fn button_fields_are_told_apart_by_their_flags() {
1643        let fields = doc(
1644            "/AcroForm << /Fields [5 0 R 6 0 R 7 0 R 8 0 R] >>",
1645            &[
1646                (5, "<< /T (go) /FT /Btn /Ff 65536 >>"),
1647                (6, "<< /T (card) /FT /Btn /Ff 33603584 /V /visa >>"),
1648                (7, "<< /T (urgent) /FT /Btn /V /Yes >>"),
1649                (8, "<< /T (name) /FT /Tx /V (x) >>"),
1650            ],
1651        )
1652        .form_fields();
1653        assert_eq!(fields[0].button_kind(), Some(ButtonKind::PushButton));
1654        assert!(fields[0].flags.pushbutton() && !fields[0].flags.radio());
1655        assert_eq!(fields[1].button_kind(), Some(ButtonKind::RadioButtons));
1656        assert!(fields[1].flags.radio() && fields[1].flags.no_toggle_to_off());
1657        assert!(fields[1].flags.radios_in_unison() && !fields[1].flags.pushbutton());
1658        assert_eq!(fields[2].button_kind(), Some(ButtonKind::CheckBox));
1659        assert!(!fields[2].flags.no_toggle_to_off() && !fields[2].flags.radios_in_unison());
1660        assert_eq!(fields[3].button_kind(), None);
1661    }
1662
1663    /// A check box's or radio button field's `/V` names its appearance
1664    /// state; a pushbutton keeps no value even when one is written; a
1665    /// missing value is the `Off` state and a value that is no name gives
1666    /// none.
1667    // Covers ISO 32000-1 §12.7.4.2, §12.7.4.2.4.
1668    #[test]
1669    fn a_button_field_s_state_is_its_value_name() {
1670        let fields = doc(
1671            "/AcroForm << /Fields [5 0 R 6 0 R 7 0 R 8 0 R 9 0 R] >>",
1672            &[
1673                (5, "<< /T (urgent) /FT /Btn /V /Yes >>"),
1674                (6, "<< /T (card) /FT /Btn /Ff 32768 /V /cardbrand1 >>"),
1675                (7, "<< /T (blank) /FT /Btn >>"),
1676                (8, "<< /T (go) /FT /Btn /Ff 65536 /V /Yes >>"),
1677                (9, "<< /T (odd) /FT /Btn /V (Yes) >>"),
1678            ],
1679        )
1680        .form_fields();
1681        assert_eq!(fields[0].state(), Some("Yes"));
1682        assert_eq!(fields[1].state(), Some("cardbrand1"));
1683        assert_eq!(fields[2].state(), Some("Off"));
1684        assert_eq!(fields[3].state(), None);
1685        assert_eq!(fields[4].state(), None);
1686    }
1687
1688    /// A merged check box: `/V /Yes` is checked, `/V /Off` and no value are
1689    /// not; its widget carries `/AS` and the on state read from `/AP /N`;
1690    /// a radio field and a text field have no checked state.
1691    // Covers ISO 32000-1 §12.7.4.2.3.
1692    #[test]
1693    fn a_check_box_is_checked_when_its_state_is_not_off() {
1694        let fields = doc(
1695            "/AcroForm << /Fields [5 0 R 6 0 R 7 0 R 8 0 R 9 0 R] >>",
1696            &[
1697                (
1698                    5,
1699                    "<< /T (urgent) /FT /Btn /V /Yes /AS /Yes /Subtype /Widget /Rect [0 0 1 1] \
1700                     /AP << /N << /Yes 10 0 R /Off 11 0 R >> >> >>",
1701                ),
1702                (
1703                    6,
1704                    "<< /T (done) /FT /Btn /V /Off /AS /Off /Subtype /Widget /Rect [0 0 1 1] >>",
1705                ),
1706                (7, "<< /T (blank) /FT /Btn >>"),
1707                (8, "<< /T (card) /FT /Btn /Ff 32768 /V /a >>"),
1708                (9, "<< /T (name) /FT /Tx /V (Yes) >>"),
1709            ],
1710        )
1711        .form_fields();
1712        assert_eq!(fields[0].checked(), Some(true));
1713        assert_eq!(
1714            fields[0].widgets,
1715            vec![Widget {
1716                object: r(5),
1717                appearance_state: Some("Yes".into()),
1718                on_state: Some("Yes".into()),
1719                characteristics: None,
1720            }]
1721        );
1722        assert_eq!(fields[0].on_widgets(), vec![0]);
1723        assert_eq!(fields[1].checked(), Some(false));
1724        assert_eq!(fields[1].widgets[0].on_state, None);
1725        assert_eq!(fields[1].on_widgets(), Vec::<usize>::new());
1726        assert_eq!(fields[2].checked(), Some(false));
1727        assert_eq!(fields[3].checked(), None);
1728        assert_eq!(fields[4].checked(), None);
1729    }
1730
1731    /// A check box field with two widgets and `/Opt` (Table 227): each
1732    /// widget's on state is its position in `/Kids` as a name, the field's
1733    /// state picks the widget that is on, and that index picks the export
1734    /// value; a widget whose `/N` is one stream or names two on states has
1735    /// no on state.
1736    // Covers ISO 32000-1 §12.7.4.2.3.
1737    #[test]
1738    fn check_box_widgets_are_matched_to_the_state_and_their_export_values() {
1739        let fields = doc_with_stream(
1740            "/AcroForm << /Fields [5 0 R 9 0 R] >>",
1741            &[
1742                (
1743                    5,
1744                    "<< /T (size) /FT /Btn /Opt [(small) (large)] /V /1 /Kids [6 0 R 7 0 R] >>",
1745                ),
1746                (
1747                    6,
1748                    "<< /Subtype /Widget /Parent 5 0 R /AS /Off /Rect [0 0 1 1] \
1749                     /AP << /N << /0 10 0 R /Off 10 0 R >> >> >>",
1750                ),
1751                (
1752                    7,
1753                    "<< /Subtype /Widget /Parent 5 0 R /AS /1 /Rect [0 0 1 1] \
1754                     /AP 8 0 R >>",
1755                ),
1756                (8, "<< /N << /1 10 0 R /Off 10 0 R >> >>"),
1757                (9, "<< /T (odd) /FT /Btn /V /Yes /Kids [11 0 R 12 0 R] >>"),
1758                (
1759                    11,
1760                    "<< /Subtype /Widget /Parent 9 0 R /AP << /N 10 0 R >> /Rect [0 0 1 1] >>",
1761                ),
1762                (
1763                    12,
1764                    "<< /Subtype /Widget /Parent 9 0 R /Rect [0 0 1 1] \
1765                     /AP << /N << /Yes 10 0 R /Also 10 0 R /Off 10 0 R >> >> >>",
1766                ),
1767            ],
1768            (10, "/Type /XObject /Subtype /Form /BBox [0 0 1 1]", b""),
1769        )
1770        .form_fields();
1771        let size = &fields[0];
1772        assert_eq!(size.checked(), Some(true));
1773        assert_eq!(size.on_widgets(), vec![1]);
1774        assert_eq!(size.widgets[0].on_state.as_deref(), Some("0"));
1775        assert_eq!(size.widgets[1].on_state.as_deref(), Some("1"));
1776        assert_eq!(size.widgets[1].appearance_state.as_deref(), Some("1"));
1777        assert_eq!(size.options[1].export_value, "large");
1778        let odd = &fields[1];
1779        assert_eq!(odd.widgets[0].on_state, None);
1780        assert_eq!(odd.widgets[1].on_state, None);
1781        assert_eq!(odd.on_widgets(), Vec::<usize>::new());
1782        assert_eq!(odd.checked(), Some(true));
1783    }
1784
1785    /// The standard's radio button example: the parent's `/V` names the
1786    /// on state of the button that is on, each kid widget's on state comes
1787    /// from its own `/AP /N`, and the on widget's index picks its Table
1788    /// 227 export value; a radio field is no check box.
1789    // Covers ISO 32000-1 §12.7.4.2.4.
1790    #[test]
1791    fn a_radio_button_field_names_the_widget_that_is_on() {
1792        let fields = doc_with_stream(
1793            "/AcroForm << /Fields [10 0 R] >>",
1794            &[
1795                (
1796                    10,
1797                    "<< /FT /Btn /Ff 32768 /T (Credit card) /V /cardbrand1 /Opt [(Visa) (Master)] \
1798                     /Kids [11 0 R 12 0 R] >>",
1799                ),
1800                (
1801                    11,
1802                    "<< /Parent 10 0 R /Subtype /Widget /AS /cardbrand1 /Rect [0 0 1 1] \
1803                     /AP << /N << /cardbrand1 8 0 R /Off 8 0 R >> >> >>",
1804                ),
1805                (
1806                    12,
1807                    "<< /Parent 10 0 R /Subtype /Widget /AS /Off /Rect [0 0 1 1] \
1808                     /AP << /N << /cardbrand2 8 0 R /Off 8 0 R >> >> >>",
1809                ),
1810            ],
1811            (8, "/Type /XObject /Subtype /Form /BBox [0 0 1 1]", b""),
1812        )
1813        .form_fields();
1814        let card = &fields[0];
1815        assert_eq!(card.button_kind(), Some(ButtonKind::RadioButtons));
1816        assert_eq!(card.state(), Some("cardbrand1"));
1817        assert_eq!(card.on_widgets(), vec![0]);
1818        assert_eq!(card.widgets[0].on_state.as_deref(), Some("cardbrand1"));
1819        assert_eq!(card.widgets[1].on_state.as_deref(), Some("cardbrand2"));
1820        assert_eq!(card.widgets[1].appearance_state.as_deref(), Some("Off"));
1821        assert_eq!(card.options[0].export_value, "Visa");
1822        assert_eq!(card.checked(), None);
1823    }
1824
1825    /// With `RadiosInUnison` two buttons sharing an on state are on
1826    /// together; a radio field without `/V` is in the `Off` state with no
1827    /// button on; `NoToggleToOff` is read.
1828    // Covers ISO 32000-1 §12.7.4.2.4.
1829    #[test]
1830    fn radios_in_unison_are_on_together_and_a_valueless_radio_field_is_off() {
1831        let fields = doc(
1832            "/AcroForm << /Fields [5 0 R 9 0 R] >>",
1833            &[
1834                (5, "<< /T (size) /FT /Btn /Ff 33587200 /V /a /Kids [6 0 R 7 0 R 8 0 R] >>"),
1835                (6, "<< /Parent 5 0 R /Subtype /Widget /AS /a /AP << /N << /a 6 0 R /Off 6 0 R >> >> >>"),
1836                (7, "<< /Parent 5 0 R /Subtype /Widget /AS /a /AP << /N << /a 6 0 R /Off 6 0 R >> >> >>"),
1837                (8, "<< /Parent 5 0 R /Subtype /Widget /AS /Off /AP << /N << /b 6 0 R /Off 6 0 R >> >> >>"),
1838                (9, "<< /T (none) /FT /Btn /Ff 49152 /Kids [10 0 R] >>"),
1839                (10, "<< /Parent 9 0 R /Subtype /Widget /AS /Off /AP << /N << /x 6 0 R /Off 6 0 R >> >> >>"),
1840            ],
1841        )
1842        .form_fields();
1843        let size = &fields[0];
1844        assert!(size.flags.radio() && size.flags.radios_in_unison());
1845        assert_eq!(size.on_widgets(), vec![0, 1]);
1846        let none = &fields[1];
1847        assert!(none.flags.radio() && none.flags.no_toggle_to_off());
1848        assert_eq!(none.state(), Some("Off"));
1849        assert_eq!(none.on_widgets(), Vec::<usize>::new());
1850    }
1851
1852    /// A merged pushbutton with every caption and icon entry of Table 189:
1853    /// the captions are text strings, the icons references, the caption
1854    /// position a code.
1855    // Covers ISO 32000-1 §12.7.4.2.2.
1856    #[test]
1857    fn a_pushbutton_s_widget_carries_its_captions_and_icons() {
1858        let fields = doc_with_stream(
1859            "/AcroForm << /Fields [5 0 R] >>",
1860            &[
1861                (
1862                    5,
1863                    "<< /T (go) /FT /Btn /Ff 65536 /V /Yes /Subtype /Widget /Rect [0 0 10 10] \
1864                     /MK 7 0 R >>",
1865                ),
1866                (
1867                    7,
1868                    "<< /CA <FEFF00530065006E0064> /RC (Go!) /AC (Sending) /I 6 0 R /RI 6 0 R \
1869                     /IX 6 0 R /TP 2 /BC [0] /BG [1 1 1] >>",
1870                ),
1871            ],
1872            (6, "/Type /XObject /Subtype /Form /BBox [0 0 1 1]", b""),
1873        )
1874        .form_fields();
1875        let go = &fields[0];
1876        assert_eq!(go.button_kind(), Some(ButtonKind::PushButton));
1877        assert_eq!(go.state(), None);
1878        assert_eq!(
1879            go.widgets[0].characteristics,
1880            Some(AppearanceCharacteristics {
1881                caption: Some("Send".into()),
1882                rollover_caption: Some("Go!".into()),
1883                alternate_caption: Some("Sending".into()),
1884                icon: Some(r(6)),
1885                rollover_icon: Some(r(6)),
1886                alternate_icon: Some(r(6)),
1887                caption_position: CaptionPosition::Below,
1888            })
1889        );
1890    }
1891
1892    /// A check box may carry the normal caption alone, with the caption
1893    /// position at its default; a widget without `/MK` has no
1894    /// characteristics; a caption that is a number, an icon written
1895    /// directly and a `/TP` of 9 read as absent.
1896    // Covers ISO 32000-1 §12.7.4.2.2.
1897    #[test]
1898    fn captions_default_and_odd_characteristics_read_as_absent() {
1899        let fields = doc(
1900            "/AcroForm << /Fields [5 0 R 6 0 R 7 0 R] >>",
1901            &[
1902                (5, "<< /T (urgent) /FT /Btn /V /Yes /Subtype /Widget /Rect [0 0 1 1] /MK << /CA (8) >> >>"),
1903                (6, "<< /T (plain) /FT /Btn /Ff 65536 /Subtype /Widget /Rect [0 0 1 1] >>"),
1904                (
1905                    7,
1906                    "<< /T (odd) /FT /Btn /Ff 65536 /Subtype /Widget /Rect [0 0 1 1] \
1907                     /MK << /CA 5 /I << /Type /XObject >> /TP 9 >> >>",
1908                ),
1909            ],
1910        )
1911        .form_fields();
1912        assert_eq!(
1913            fields[0].widgets[0].characteristics,
1914            Some(AppearanceCharacteristics {
1915                caption: Some("8".into()),
1916                ..AppearanceCharacteristics::default()
1917            })
1918        );
1919        assert_eq!(fields[1].widgets[0].characteristics, None);
1920        assert_eq!(
1921            fields[2].widgets[0].characteristics,
1922            Some(AppearanceCharacteristics::default())
1923        );
1924        assert_eq!(
1925            CaptionPosition::from_int(6),
1926            Some(CaptionPosition::Overlaid)
1927        );
1928        assert_eq!(CaptionPosition::from_int(7), None);
1929    }
1930
1931    /// A signed signature field: `/V` is a signature dictionary whose
1932    /// Table 252 entries are read as data, and the field's own `/Lock` and
1933    /// `/SV` are kept by reference.
1934    // Covers ISO 32000-1 §12.7.4.5.
1935    #[test]
1936    fn a_signed_signature_field_exposes_its_signature_dictionary() {
1937        let fields = doc(
1938            "/AcroForm << /Fields [5 0 R] /SigFlags 3 >>",
1939            &[
1940                (5, "<< /T (sig) /FT /Sig /V 6 0 R /Lock 7 0 R /SV 8 0 R /Subtype /Widget /Rect [0 0 0 0] >>"),
1941                (
1942                    6,
1943                    "<< /Type /Sig /Filter /Adobe.PPKLite /SubFilter /adbe.pkcs7.detached \
1944                     /ByteRange [0 100 200 50] /Contents <3031> /Name <FEFF00410064> \
1945                     /M (D:20260909100000Z) /Location (Berlin) /Reason (I agree) \
1946                     /ContactInfo (ada@example.org) >>",
1947                ),
1948                (7, "<< /Type /SigFieldLock /Action /All >>"),
1949                (8, "<< /Type /SV /Ff 1 >>"),
1950            ],
1951        )
1952        .form_fields();
1953        let sig = &fields[0];
1954        assert_eq!(sig.field_type, Some(FieldType::Signature));
1955        assert_eq!(
1956            sig.signature(),
1957            Some(Signature {
1958                filter: Some("Adobe.PPKLite".into()),
1959                sub_filter: Some("adbe.pkcs7.detached".into()),
1960                byte_range: vec![(0, 100), (200, 50)],
1961                contents: b"01".to_vec(),
1962                name: Some("Ad".into()),
1963                signing_time: Some("D:20260909100000Z".into()),
1964                location: Some("Berlin".into()),
1965                reason: Some("I agree".into()),
1966                contact_info: Some("ada@example.org".into()),
1967            })
1968        );
1969        assert_eq!(sig.lock, Some(r(7)));
1970        assert_eq!(sig.seed_value, Some(r(8)));
1971        assert_eq!(sig.text(), None);
1972        assert_eq!(sig.selected(), Vec::<String>::new());
1973    }
1974
1975    /// An unsigned signature field has no signature; a text field's
1976    /// dictionary value is none either; a signature dictionary with only
1977    /// `/Type`, an odd `/ByteRange` and a direct `/Lock` read as defaults or
1978    /// absent.
1979    // Covers ISO 32000-1 §12.7.4.5.
1980    #[test]
1981    fn unsigned_fields_and_odd_signature_entries_read_as_absent() {
1982        let fields = doc(
1983            "/AcroForm << /Fields [5 0 R 6 0 R 7 0 R] >>",
1984            &[
1985                (5, "<< /T (unsigned) /FT /Sig /Lock << /Action /All >> >>"),
1986                (6, "<< /T (name) /FT /Tx /V << /Filter /X >> >>"),
1987                (
1988                    7,
1989                    "<< /T (odd) /FT /Sig /V << /Type /Sig /ByteRange [0 10 20] /Contents 5 >> >>",
1990                ),
1991            ],
1992        )
1993        .form_fields();
1994        assert_eq!(fields[0].signature(), None);
1995        assert_eq!(fields[0].lock, None);
1996        assert_eq!(fields[0].seed_value, None);
1997        assert_eq!(fields[1].signature(), None);
1998        assert_eq!(
1999            fields[2].signature(),
2000            Some(Signature {
2001                byte_range: vec![(0, 10)],
2002                ..Signature::default()
2003            })
2004        );
2005    }
2006}