1use 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
11const MAX_FIELD_DEPTH: usize = 64;
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
16pub struct SignatureFlags {
17 pub signatures_exist: bool,
20 pub append_only: bool,
23}
24
25impl SignatureFlags {
26 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#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
40pub enum Quadding {
41 #[default]
43 Left,
44 Centered,
46 Right,
48}
49
50impl Quadding {
51 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#[derive(Debug, Clone, PartialEq, Default)]
66pub struct DefaultAppearance {
67 pub font: Option<String>,
70 pub font_size: Option<f32>,
72 pub fill_color: Option<Vec<f32>>,
75}
76
77impl DefaultAppearance {
78 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#[derive(Debug, Clone, PartialEq)]
105pub struct InteractiveForm {
106 pub fields: Vec<ObjRef>,
109 pub need_appearances: bool,
112 pub signature_flags: SignatureFlags,
114 pub calculation_order: Vec<ObjRef>,
117 pub default_resources: Option<Dict>,
119 pub default_appearance: Option<String>,
122 pub quadding: Option<Quadding>,
125 pub xfa: bool,
128}
129
130pub 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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
183pub enum FieldType {
184 Button,
186 Text,
188 Choice,
190 Signature,
192}
193
194impl FieldType {
195 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#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
214pub struct FieldFlags(pub u32);
215
216impl FieldFlags {
217 pub fn read_only(self) -> bool {
219 self.0 & 1 != 0
220 }
221
222 pub fn required(self) -> bool {
225 self.0 & 2 != 0
226 }
227
228 pub fn no_export(self) -> bool {
230 self.0 & 4 != 0
231 }
232
233 pub fn multiline(self) -> bool {
236 self.bit(13)
237 }
238
239 pub fn password(self) -> bool {
242 self.bit(14)
243 }
244
245 pub fn file_select(self) -> bool {
248 self.bit(21)
249 }
250
251 pub fn do_not_spell_check(self) -> bool {
254 self.bit(23)
255 }
256
257 pub fn do_not_scroll(self) -> bool {
260 self.bit(24)
261 }
262
263 pub fn comb(self) -> bool {
266 self.bit(25)
267 }
268
269 pub fn rich_text(self) -> bool {
272 self.bit(26)
273 }
274
275 pub fn combo(self) -> bool {
278 self.bit(18)
279 }
280
281 pub fn edit(self) -> bool {
284 self.bit(19)
285 }
286
287 pub fn sort(self) -> bool {
290 self.bit(20)
291 }
292
293 pub fn multi_select(self) -> bool {
296 self.bit(22)
297 }
298
299 pub fn commit_on_sel_change(self) -> bool {
302 self.bit(27)
303 }
304
305 pub fn no_toggle_to_off(self) -> bool {
308 self.bit(15)
309 }
310
311 pub fn radio(self) -> bool {
314 self.bit(16)
315 }
316
317 pub fn pushbutton(self) -> bool {
320 self.bit(17)
321 }
322
323 pub fn radios_in_unison(self) -> bool {
326 self.bit(26)
327 }
328
329 fn bit(self, position: u32) -> bool {
332 self.0 & (1 << (position - 1)) != 0
333 }
334}
335
336#[derive(Debug, Clone, PartialEq, Eq)]
339pub struct Widget {
340 pub object: ObjRef,
343 pub appearance_state: Option<String>,
345 pub on_state: Option<String>,
349 pub characteristics: Option<AppearanceCharacteristics>,
352}
353
354#[derive(Debug, Clone, PartialEq, Eq, Default)]
359pub struct AppearanceCharacteristics {
360 pub caption: Option<String>,
362 pub rollover_caption: Option<String>,
365 pub alternate_caption: Option<String>,
367 pub icon: Option<ObjRef>,
369 pub rollover_icon: Option<ObjRef>,
371 pub alternate_icon: Option<ObjRef>,
373 pub caption_position: CaptionPosition,
376}
377
378#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
381pub enum CaptionPosition {
382 #[default]
384 CaptionOnly,
385 IconOnly,
387 Below,
389 Above,
391 Right,
393 Left,
395 Overlaid,
397}
398
399impl CaptionPosition {
400 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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
418pub enum ButtonKind {
419 PushButton,
421 CheckBox,
424 RadioButtons,
427}
428
429#[derive(Debug, Clone, PartialEq, Eq, Default)]
434pub struct Signature {
435 pub filter: Option<String>,
437 pub sub_filter: Option<String>,
440 pub byte_range: Vec<(u64, u64)>,
443 pub contents: Vec<u8>,
445 pub name: Option<String>,
447 pub signing_time: Option<String>,
449 pub location: Option<String>,
451 pub reason: Option<String>,
453 pub contact_info: Option<String>,
455}
456
457impl Signature {
458 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#[derive(Debug, Clone, PartialEq, Eq)]
491pub struct ChoiceOption {
492 pub export_value: String,
494 pub name: String,
497}
498
499#[derive(Debug, Clone, PartialEq)]
502pub struct FormField {
503 pub object: ObjRef,
505 pub parent: Option<ObjRef>,
508 pub kids: Vec<ObjRef>,
510 pub widgets: Vec<Widget>,
514 pub field_type: Option<FieldType>,
517 pub partial_name: Option<String>,
519 pub name: String,
523 pub alternate_name: Option<String>,
525 pub mapping_name: Option<String>,
527 pub flags: FieldFlags,
529 pub value: Option<Object>,
532 pub default_value: Option<Object>,
535 pub default_appearance: Option<String>,
540 pub quadding: Quadding,
545 pub default_style: Option<String>,
548 pub rich_text: Option<String>,
551 pub max_len: Option<u32>,
554 pub options: Vec<ChoiceOption>,
559 pub top_index: u32,
562 pub selected_indices: Vec<u32>,
565 pub additional_actions: Option<Dict>,
567 pub lock: Option<ObjRef>,
570 pub seed_value: Option<ObjRef>,
573}
574
575impl FormField {
576 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 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 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 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 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 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 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
672pub 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 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#[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
733struct Pending {
735 object: ObjRef,
736 dict: Dict,
737 parent: Option<ObjRef>,
738 inherited: Inherited,
739 depth: usize,
740}
741
742async 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 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 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
900fn 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
914async 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
955fn 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
975async 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
983async 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
994async 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
1021async 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
1037fn is_widget(dict: &Dict) -> bool {
1039 dict.get_name("Subtype")
1040 .is_some_and(|subtype| subtype.0 == "Widget")
1041}
1042
1043fn 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 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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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}