1use std::collections::HashMap;
12
13use rpdfium_core::{Name, PdfSource};
14use rpdfium_parser::{Object, ObjectId, ObjectStore};
15
16use crate::aaction::{AdditionalActions, parse_additional_actions};
17use crate::error::DocError;
18use crate::form_control::{FormControl, HighlightingMode, TextPosition};
19use crate::icon_fit::{IconFit, parse_icon_fit};
20
21#[derive(Debug, Clone, PartialEq, Eq)]
23pub enum FormFieldType {
24 Text,
26 Button,
28 Choice,
30 Signature,
32}
33
34#[derive(Debug, Clone, PartialEq, Eq)]
36pub struct ChoiceOption {
37 pub export_value: String,
39 pub display_value: String,
41}
42
43#[derive(Debug, Clone, PartialEq)]
45pub enum FieldValue {
46 String(String),
48 Bool(bool),
50 Choice(usize),
52 Indices(Vec<usize>),
54}
55
56#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
63pub struct FormFieldFlags(u32);
64
65impl FormFieldFlags {
66 pub fn from_bits(bits: u32) -> Self {
68 Self(bits)
69 }
70
71 pub fn bits(self) -> u32 {
73 self.0
74 }
75
76 pub fn is_read_only(self) -> bool {
80 self.0 & (1 << 0) != 0
81 }
82
83 pub fn is_required(self) -> bool {
85 self.0 & (1 << 1) != 0
86 }
87
88 pub fn is_no_export(self) -> bool {
90 self.0 & (1 << 2) != 0
91 }
92
93 pub fn is_no_toggle_to_off(self) -> bool {
97 self.0 & (1 << 14) != 0
98 }
99
100 pub fn is_radio(self) -> bool {
102 self.0 & (1 << 15) != 0
103 }
104
105 pub fn is_push_button(self) -> bool {
107 self.0 & (1 << 16) != 0
108 }
109
110 pub fn is_radios_in_unison(self) -> bool {
113 self.0 & (1 << 25) != 0
114 }
115
116 pub fn is_multiline(self) -> bool {
120 self.0 & (1 << 12) != 0
121 }
122
123 pub fn is_password(self) -> bool {
125 self.0 & (1 << 13) != 0
126 }
127
128 pub fn is_file_select(self) -> bool {
130 self.0 & (1 << 20) != 0
131 }
132
133 pub fn is_do_not_spell_check(self) -> bool {
135 self.0 & (1 << 22) != 0
136 }
137
138 pub fn is_do_not_scroll(self) -> bool {
140 self.0 & (1 << 23) != 0
141 }
142
143 pub fn is_comb(self) -> bool {
145 self.0 & (1 << 24) != 0
146 }
147
148 pub fn is_rich_text(self) -> bool {
151 self.0 & (1 << 25) != 0
152 }
153
154 pub fn is_combo(self) -> bool {
158 self.0 & (1 << 17) != 0
159 }
160
161 pub fn is_combo_editable(self) -> bool {
163 self.0 & (1 << 18) != 0
164 }
165
166 pub fn is_sort(self) -> bool {
168 self.0 & (1 << 19) != 0
169 }
170
171 pub fn is_multi_select(self) -> bool {
173 self.0 & (1 << 21) != 0
174 }
175
176 pub fn is_commit_on_sel_change(self) -> bool {
178 self.0 & (1 << 26) != 0
179 }
180
181 pub fn set_read_only(&mut self, v: bool) {
183 if v {
184 self.0 |= 1 << 0;
185 } else {
186 self.0 &= !(1 << 0);
187 }
188 }
189
190 pub fn set_required(&mut self, v: bool) {
192 if v {
193 self.0 |= 1 << 1;
194 } else {
195 self.0 &= !(1 << 1);
196 }
197 }
198
199 pub fn set_no_export(&mut self, v: bool) {
201 if v {
202 self.0 |= 1 << 2;
203 } else {
204 self.0 &= !(1 << 2);
205 }
206 }
207}
208
209#[derive(Debug, Clone)]
211pub struct FormField {
212 pub name: String,
214 pub field_type: FormFieldType,
216 pub value: Option<String>,
218 pub default_value: Option<String>,
220 pub flags: FormFieldFlags,
222 pub tooltip: Option<String>,
224 pub alternate_name: Option<String>,
226 pub mapping_name: Option<String>,
228 pub max_len: Option<u32>,
230 pub options: Vec<ChoiceOption>,
232 pub appearance_state: Option<String>,
234 pub children: Vec<FormField>,
236 pub controls: Vec<FormControl>,
238 pub dirty: bool,
240 pub selected_indices: Vec<usize>,
242 pub additional_actions: Option<AdditionalActions>,
244}
245
246impl FormField {
247 pub(crate) fn from_dict<S: PdfSource>(
252 dict: &HashMap<Name, Object>,
253 store: &ObjectStore<S>,
254 parent_ft: Option<&str>,
255 parent_name: &str,
256 ) -> Option<Self> {
257 let partial_name = dict
259 .get(&Name::t())
260 .and_then(|o| store.deep_resolve(o).ok())
261 .and_then(|o| o.as_string().map(|s| s.to_string_lossy()));
262
263 let name = if let Some(store_name) = resolve_full_field_name_from_store(dict, store) {
269 store_name
271 } else {
272 match (&partial_name, parent_name.is_empty()) {
274 (Some(pn), true) => pn.clone(),
275 (Some(pn), false) => format!("{parent_name}.{pn}"),
276 (None, _) => parent_name.to_string(),
277 }
278 };
279
280 let ft_obj = get_field_attr_inherited(dict, &Name::ft(), store);
286 let ft_str = ft_obj
287 .as_ref()
288 .and_then(|o| store.deep_resolve(o).ok())
289 .and_then(|o| o.as_name().map(|n| n.as_str().into_owned()));
290 let ft_ref = ft_str.as_deref().or(parent_ft);
291
292 let field_type = match ft_ref {
293 Some("Tx") => FormFieldType::Text,
294 Some("Btn") => FormFieldType::Button,
295 Some("Ch") => FormFieldType::Choice,
296 Some("Sig") => FormFieldType::Signature,
297 _ => {
298 return None;
301 }
302 };
303
304 let value = extract_inherited_string_or_name(dict, &Name::v(), store);
306
307 let default_value = extract_inherited_string_or_name(dict, &Name::dv(), store);
309
310 let ff_obj = get_field_attr_inherited(dict, &Name::ff(), store);
312 let flags = FormFieldFlags::from_bits(
313 ff_obj
314 .as_ref()
315 .and_then(|o| store.deep_resolve(o).ok())
316 .and_then(|o| o.as_i64())
317 .unwrap_or(0) as u32,
318 );
319
320 let tooltip = dict
322 .get(&Name::tu())
323 .and_then(|o| store.deep_resolve(o).ok())
324 .and_then(|o| o.as_string().map(|s| s.to_string_lossy()));
325
326 let alternate_name = tooltip.clone();
328
329 let mapping_name = dict
331 .get(&Name::tm())
332 .and_then(|o| store.deep_resolve(o).ok())
333 .and_then(|o| o.as_string().map(|s| s.to_string_lossy()));
334
335 let max_len = dict
337 .get(&Name::max_len())
338 .and_then(|o| store.deep_resolve(o).ok())
339 .and_then(|o| o.as_i64())
340 .map(|v| v as u32);
341
342 let options = parse_options(dict, store);
344
345 let appearance_state = dict
347 .get(&Name::as_name())
348 .and_then(|o| store.deep_resolve(o).ok())
349 .and_then(|o| o.as_name().map(|n| n.as_str().into_owned()));
350
351 let selected_indices = parse_selected_indices(dict, store);
353
354 let controls = parse_widget_controls(dict, store, &name);
356
357 let additional_actions = dict
359 .get(&Name::aa())
360 .and_then(|o| parse_additional_actions(o, store).ok());
361
362 Some(FormField {
363 name,
364 field_type,
365 value,
366 default_value,
367 flags,
368 tooltip,
369 alternate_name,
370 mapping_name,
371 max_len,
372 options,
373 appearance_state,
374 children: Vec::new(),
375 controls,
376 dirty: false,
377 selected_indices,
378 additional_actions,
379 })
380 }
381
382 pub fn set_value(&mut self, value: FieldValue) -> Result<(), DocError> {
389 match (&self.field_type, &value) {
390 (FormFieldType::Text, FieldValue::String(s)) => {
391 if let Some(max) = self.max_len {
392 if s.len() > max as usize {
393 return Err(DocError::ValueTooLong { len: s.len(), max });
394 }
395 }
396 self.value = Some(s.clone());
397 }
398 (FormFieldType::Button, FieldValue::Bool(checked)) => {
399 self.appearance_state = Some(if *checked {
400 "Yes".to_string()
401 } else {
402 "Off".to_string()
403 });
404 self.value = Some(self.appearance_state.as_ref().unwrap().clone());
405 }
406 (FormFieldType::Choice, FieldValue::Choice(idx)) => {
407 if *idx >= self.options.len() {
408 return Err(DocError::InvalidChoiceIndex {
409 index: *idx,
410 count: self.options.len(),
411 });
412 }
413 self.value = Some(self.options[*idx].export_value.clone());
414 }
415 (FormFieldType::Choice, FieldValue::String(s)) => {
416 self.value = Some(s.clone());
418 }
419 (FormFieldType::Choice, FieldValue::Indices(indices)) => {
420 for &idx in indices {
421 if idx >= self.options.len() {
422 return Err(DocError::InvalidChoiceIndex {
423 index: idx,
424 count: self.options.len(),
425 });
426 }
427 }
428 let vals: Vec<&str> = indices
430 .iter()
431 .map(|&i| self.options[i].export_value.as_str())
432 .collect();
433 self.value = Some(vals.join(","));
434 }
435 (field_type, val) => {
436 let expected = match field_type {
437 FormFieldType::Text => "String",
438 FormFieldType::Button => "Bool",
439 FormFieldType::Choice => "Choice or Indices",
440 FormFieldType::Signature => "Signature (read-only)",
441 };
442 let got = match val {
443 FieldValue::String(_) => "String",
444 FieldValue::Bool(_) => "Bool",
445 FieldValue::Choice(_) => "Choice",
446 FieldValue::Indices(_) => "Indices",
447 };
448 return Err(DocError::TypeMismatch {
449 expected: expected.to_string(),
450 got: got.to_string(),
451 });
452 }
453 }
454 self.dirty = true;
455 Ok(())
456 }
457
458 pub fn controls(&self) -> &[FormControl] {
460 &self.controls
461 }
462
463 pub fn needs_appearance(&self) -> bool {
466 self.dirty
467 }
468
469 pub fn reset_to_default(&mut self) {
471 self.value = self.default_value.clone();
472 self.dirty = true;
473 }
474
475 pub fn selected_item_count(&self) -> usize {
481 self.selected_indices.len()
482 }
483
484 #[inline]
488 pub fn count_selected_items(&self) -> usize {
489 self.selected_item_count()
490 }
491
492 pub fn is_item_selected(&self, index: usize) -> bool {
494 self.selected_indices.contains(&index)
495 }
496
497 pub fn selected_index(&self, sel_index: usize) -> Option<usize> {
501 self.selected_indices.get(sel_index).copied()
502 }
503
504 #[inline]
508 pub fn get_selected_index(&self, sel_index: usize) -> Option<usize> {
509 self.selected_index(sel_index)
510 }
511
512 pub fn clear_selection(&mut self) -> Result<(), DocError> {
517 self.selected_indices.clear();
518 Ok(())
519 }
520
521 pub fn set_read_only(&mut self, v: bool) {
526 self.flags.set_read_only(v);
527 }
528
529 pub fn set_required(&mut self, v: bool) {
534 self.flags.set_required(v);
535 }
536
537 pub fn set_no_export(&mut self, v: bool) {
542 self.flags.set_no_export(v);
543 }
544
545 pub fn additional_actions(&self) -> Option<&AdditionalActions> {
547 self.additional_actions.as_ref()
548 }
549
550 #[inline]
554 pub fn get_additional_actions(&self) -> Option<&AdditionalActions> {
555 self.additional_actions()
556 }
557
558 pub fn option_count(&self) -> usize {
564 self.options.len()
565 }
566
567 #[inline]
571 pub fn count_options(&self) -> usize {
572 self.option_count()
573 }
574
575 pub fn option_label(&self, index: usize) -> Option<&str> {
579 self.options.get(index).map(|o| o.display_value.as_str())
580 }
581
582 #[inline]
586 pub fn get_option_label(&self, index: usize) -> Option<&str> {
587 self.option_label(index)
588 }
589
590 pub fn option_value(&self, index: usize) -> Option<&str> {
594 self.options.get(index).map(|o| o.export_value.as_str())
595 }
596
597 #[inline]
601 pub fn get_option_value(&self, index: usize) -> Option<&str> {
602 self.option_value(index)
603 }
604
605 pub fn control_count(&self) -> usize {
611 self.controls.len()
612 }
613
614 #[inline]
618 pub fn count_controls(&self) -> usize {
619 self.control_count()
620 }
621
622 pub fn control_at(&self, index: usize) -> Option<&FormControl> {
626 self.controls.get(index)
627 }
628
629 #[inline]
633 pub fn get_control(&self, index: usize) -> Option<&FormControl> {
634 self.control_at(index)
635 }
636}
637
638const MAX_FIELD_ATTR_DEPTH: usize = 32;
640
641fn get_field_attr_inherited<S: PdfSource>(
650 start_dict: &HashMap<Name, Object>,
651 name: &Name,
652 store: &ObjectStore<S>,
653) -> Option<Object> {
654 if let Some(val) = start_dict.get(name) {
656 return Some(val.clone());
657 }
658
659 let mut current_id: Option<ObjectId> = None;
661 let mut parent_ref = start_dict
662 .get(&Name::parent())
663 .and_then(|v| v.as_reference());
664
665 for _ in 0..MAX_FIELD_ATTR_DEPTH {
666 let parent_id = parent_ref?;
667
668 if current_id == Some(parent_id) {
670 break;
671 }
672 current_id = Some(parent_id);
673
674 let parent_obj = store.resolve(parent_id).ok()?;
675 let parent_dict = parent_obj.as_dict()?;
676
677 if let Some(val) = parent_dict.get(name) {
678 return Some(val.clone());
679 }
680
681 parent_ref = parent_dict
682 .get(&Name::parent())
683 .and_then(|v| v.as_reference());
684 }
685
686 None
687}
688
689fn resolve_full_field_name_from_store<S: PdfSource>(
699 start_dict: &HashMap<Name, Object>,
700 store: &ObjectStore<S>,
701) -> Option<String> {
702 start_dict.get(&Name::parent())?.as_reference()?;
704
705 let mut parts: Vec<String> = Vec::new();
706
707 if let Some(t_obj) = start_dict.get(&Name::t()) {
709 if let Ok(resolved) = store.deep_resolve(t_obj) {
710 if let Some(s) = resolved.as_string() {
711 parts.push(s.to_string_lossy());
712 }
713 }
714 }
715
716 let mut parent_ref = start_dict
718 .get(&Name::parent())
719 .and_then(|v| v.as_reference());
720 let mut current_id: Option<ObjectId> = None;
721
722 for _ in 0..MAX_FIELD_ATTR_DEPTH {
723 let pid = match parent_ref {
724 Some(id) => id,
725 None => break,
726 };
727 if current_id == Some(pid) {
728 break;
729 }
730 current_id = Some(pid);
731
732 let pobj = match store.resolve(pid).ok() {
733 Some(o) => o,
734 None => break,
735 };
736 let pdict = match pobj.as_dict() {
737 Some(d) => d,
738 None => break,
739 };
740
741 if let Some(t_obj) = pdict.get(&Name::t()) {
742 if let Ok(resolved) = store.deep_resolve(t_obj) {
743 if let Some(s) = resolved.as_string() {
744 parts.push(s.to_string_lossy());
745 }
746 }
747 }
748
749 parent_ref = pdict.get(&Name::parent()).and_then(|v| v.as_reference());
750 }
751
752 if parts.is_empty() {
753 return None;
754 }
755
756 parts.reverse();
757 Some(parts.join("."))
758}
759
760fn extract_inherited_string_or_name<S: PdfSource>(
765 dict: &HashMap<Name, Object>,
766 key: &Name,
767 store: &ObjectStore<S>,
768) -> Option<String> {
769 let obj = get_field_attr_inherited(dict, key, store)?;
770 let resolved = store.deep_resolve(&obj).ok()?;
771 if let Some(s) = resolved.as_string() {
772 Some(s.to_string_lossy())
773 } else {
774 resolved.as_name().map(|n| n.as_str().into_owned())
775 }
776}
777
778fn parse_options<S: PdfSource>(
784 dict: &HashMap<Name, Object>,
785 store: &ObjectStore<S>,
786) -> Vec<ChoiceOption> {
787 let opt_obj = match dict.get(&Name::opt()) {
788 Some(o) => o,
789 None => return Vec::new(),
790 };
791 let resolved = match store.deep_resolve(opt_obj).ok() {
792 Some(o) => o,
793 None => return Vec::new(),
794 };
795 let arr = match resolved.as_array() {
796 Some(a) => a,
797 None => return Vec::new(),
798 };
799
800 let mut options = Vec::with_capacity(arr.len());
801 for item in arr {
802 let resolved_item = match store.deep_resolve(item).ok() {
803 Some(o) => o,
804 None => continue,
805 };
806
807 if let Some(sub_arr) = resolved_item.as_array() {
808 if sub_arr.len() >= 2 {
810 let export = store
811 .deep_resolve(&sub_arr[0])
812 .ok()
813 .and_then(|o| o.as_string().map(|s| s.to_string_lossy()))
814 .unwrap_or_default();
815 let display = store
816 .deep_resolve(&sub_arr[1])
817 .ok()
818 .and_then(|o| o.as_string().map(|s| s.to_string_lossy()))
819 .unwrap_or_default();
820 options.push(ChoiceOption {
821 export_value: export,
822 display_value: display,
823 });
824 }
825 } else if let Some(s) = resolved_item.as_string() {
826 let val = s.to_string_lossy();
827 options.push(ChoiceOption {
828 export_value: val.clone(),
829 display_value: val,
830 });
831 }
832 }
833 options
834}
835
836fn parse_selected_indices<S: PdfSource>(
838 dict: &HashMap<Name, Object>,
839 store: &ObjectStore<S>,
840) -> Vec<usize> {
841 let i_obj = match dict.get(&Name::i()) {
842 Some(o) => o,
843 None => return Vec::new(),
844 };
845 let resolved = match store.deep_resolve(i_obj).ok() {
846 Some(o) => o,
847 None => return Vec::new(),
848 };
849 let arr = match resolved.as_array() {
850 Some(a) => a,
851 None => return Vec::new(),
852 };
853
854 arr.iter()
855 .filter_map(|item| item.as_i64().map(|n| n as usize))
856 .collect()
857}
858
859fn parse_widget_controls<S: PdfSource>(
862 dict: &HashMap<Name, Object>,
863 store: &ObjectStore<S>,
864 field_name: &str,
865) -> Vec<FormControl> {
866 let kids_obj = match dict.get(&Name::kids()) {
867 Some(o) => o,
868 None => return Vec::new(),
869 };
870 let kids_resolved = match store.deep_resolve(kids_obj).ok() {
871 Some(o) => o,
872 None => return Vec::new(),
873 };
874 let kids_arr = match kids_resolved.as_array() {
875 Some(a) => a,
876 None => return Vec::new(),
877 };
878
879 let mut controls = Vec::new();
880 for kid in kids_arr {
881 let resolved = match store.deep_resolve(kid).ok() {
882 Some(o) => o,
883 None => continue,
884 };
885 let kid_dict = match resolved.as_dict() {
886 Some(d) => d,
887 None => continue,
888 };
889
890 let is_widget = kid_dict
892 .get(&Name::subtype())
893 .and_then(|o| store.deep_resolve(o).ok())
894 .and_then(|o| o.as_name().map(|n| n.as_str().into_owned()))
895 .is_some_and(|s| s == "Widget");
896 let has_ft = kid_dict.get(&Name::ft()).is_some();
897
898 if is_widget && !has_ft {
899 let rect = parse_rect(kid_dict, store);
900 let appearance_state = kid_dict
901 .get(&Name::as_name())
902 .and_then(|o| store.deep_resolve(o).ok())
903 .and_then(|o| o.as_name().map(|n| n.as_str().into_owned()));
904
905 let highlighting_mode = kid_dict
907 .get(&Name::h())
908 .and_then(|o| store.deep_resolve(o).ok())
909 .and_then(|o| {
910 o.as_name()
911 .map(|n| HighlightingMode::from_name(&n.as_str()))
912 })
913 .unwrap_or_default();
914
915 let default_appearance = kid_dict
917 .get(&Name::da())
918 .and_then(|o| store.deep_resolve(o).ok())
919 .and_then(|o| o.as_string().map(|s| s.to_string_lossy()));
920
921 let mk = parse_mk_fields(kid_dict, store);
923
924 controls.push(FormControl {
925 field_name: field_name.to_string(),
926 rect,
927 appearance_state,
928 page_index: None,
929 highlighting_mode,
930 rotation: mk.rotation,
931 border_color: mk.border_color,
932 background_color: mk.background_color,
933 caption: mk.caption,
934 rollover_caption: mk.rollover_caption,
935 alt_caption: mk.alt_caption,
936 default_appearance,
937 normal_icon: mk.normal_icon,
938 rollover_icon: mk.rollover_icon,
939 down_icon: mk.down_icon,
940 icon_fit: mk.icon_fit,
941 text_position: mk.text_position,
942 });
943 }
944 }
945 controls
946}
947
948#[derive(Default)]
950struct MkFields {
951 rotation: u32,
952 border_color: Option<Vec<f32>>,
953 background_color: Option<Vec<f32>>,
954 caption: Option<String>,
955 rollover_caption: Option<String>,
956 alt_caption: Option<String>,
957 normal_icon: Option<ObjectId>,
958 rollover_icon: Option<ObjectId>,
959 down_icon: Option<ObjectId>,
960 icon_fit: Option<IconFit>,
961 text_position: TextPosition,
962}
963
964fn parse_mk_fields<S: PdfSource>(dict: &HashMap<Name, Object>, store: &ObjectStore<S>) -> MkFields {
966 let mk_obj = match dict.get(&Name::mk()) {
967 Some(o) => o,
968 None => return MkFields::default(),
969 };
970 let resolved = match store.deep_resolve(mk_obj).ok() {
971 Some(o) => o,
972 None => return MkFields::default(),
973 };
974 let mk_dict = match resolved.as_dict() {
975 Some(d) => d,
976 None => return MkFields::default(),
977 };
978
979 let rotation = mk_dict
980 .get(&Name::r())
981 .and_then(|o| o.as_i64())
982 .unwrap_or(0) as u32;
983
984 let border_color = parse_color_array(mk_dict, &Name::bc(), store);
985 let background_color = parse_color_array(mk_dict, &Name::bg_color(), store);
986
987 let caption = mk_dict
988 .get(&Name::ca_display())
989 .and_then(|o| store.deep_resolve(o).ok())
990 .and_then(|o| o.as_string().map(|s| s.to_string_lossy()));
991
992 let rollover_caption = mk_dict
993 .get(&Name::rc())
994 .and_then(|o| store.deep_resolve(o).ok())
995 .and_then(|o| o.as_string().map(|s| s.to_string_lossy()));
996
997 let alt_caption = mk_dict
998 .get(&Name::ac())
999 .and_then(|o| store.deep_resolve(o).ok())
1000 .and_then(|o| o.as_string().map(|s| s.to_string_lossy()));
1001
1002 let normal_icon = mk_dict.get(&Name::i()).and_then(|o| o.as_reference());
1003 let rollover_icon = mk_dict.get(&Name::ri()).and_then(|o| o.as_reference());
1004 let down_icon = mk_dict.get(&Name::ix()).and_then(|o| o.as_reference());
1005
1006 let text_position = mk_dict
1007 .get(&Name::tp())
1008 .and_then(|o| o.as_i64())
1009 .map(|v| TextPosition::from_value(v as u32))
1010 .unwrap_or_default();
1011
1012 let icon_fit = parse_icon_fit(mk_dict, store);
1013
1014 MkFields {
1015 rotation,
1016 border_color,
1017 background_color,
1018 caption,
1019 rollover_caption,
1020 alt_caption,
1021 normal_icon,
1022 rollover_icon,
1023 down_icon,
1024 icon_fit,
1025 text_position,
1026 }
1027}
1028
1029fn parse_color_array<S: PdfSource>(
1031 dict: &HashMap<Name, Object>,
1032 key: &Name,
1033 store: &ObjectStore<S>,
1034) -> Option<Vec<f32>> {
1035 let obj = dict.get(key)?;
1036 let resolved = store.deep_resolve(obj).ok()?;
1037 let arr = resolved.as_array()?;
1038 let colors: Vec<f32> = arr
1039 .iter()
1040 .filter_map(|o| o.as_f64().map(|v| v as f32))
1041 .collect();
1042 if colors.is_empty() {
1043 None
1044 } else {
1045 Some(colors)
1046 }
1047}
1048
1049fn parse_rect<S: PdfSource>(
1051 dict: &HashMap<Name, Object>,
1052 store: &ObjectStore<S>,
1053) -> rpdfium_core::Rect {
1054 let default = rpdfium_core::Rect {
1055 left: 0.0,
1056 bottom: 0.0,
1057 right: 0.0,
1058 top: 0.0,
1059 };
1060 let rect_obj = match dict.get(&Name::rect()) {
1061 Some(o) => o,
1062 None => return default,
1063 };
1064 let resolved = match store.deep_resolve(rect_obj).ok() {
1065 Some(o) => o,
1066 None => return default,
1067 };
1068 let arr = match resolved.as_array() {
1069 Some(a) if a.len() >= 4 => a,
1070 _ => return default,
1071 };
1072
1073 let get_f64 = |idx: usize| -> f64 { arr[idx].as_f64().unwrap_or(0.0) };
1074
1075 rpdfium_core::Rect {
1076 left: get_f64(0),
1077 bottom: get_f64(1),
1078 right: get_f64(2),
1079 top: get_f64(3),
1080 }
1081}
1082
1083#[cfg(test)]
1084mod tests {
1085 use super::*;
1086 use rpdfium_core::PdfString;
1087
1088 fn build_store() -> ObjectStore<Vec<u8>> {
1089 let pdf = build_minimal_pdf();
1090 ObjectStore::open(pdf, rpdfium_core::ParsingMode::Lenient).unwrap()
1091 }
1092
1093 fn build_minimal_pdf() -> Vec<u8> {
1094 let mut pdf = Vec::new();
1095 pdf.extend_from_slice(b"%PDF-1.4\n");
1096 let obj1_offset = pdf.len();
1097 pdf.extend_from_slice(b"1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n");
1098 let obj2_offset = pdf.len();
1099 pdf.extend_from_slice(b"2 0 obj\n<< /Type /Pages /Kids [] /Count 0 >>\nendobj\n");
1100 let xref_offset = pdf.len();
1101 pdf.extend_from_slice(b"xref\n0 3\n");
1102 pdf.extend_from_slice(b"0000000000 65535 f \r\n");
1103 pdf.extend_from_slice(format!("{:010} 00000 n \r\n", obj1_offset).as_bytes());
1104 pdf.extend_from_slice(format!("{:010} 00000 n \r\n", obj2_offset).as_bytes());
1105 pdf.extend_from_slice(b"trailer\n<< /Size 3 /Root 1 0 R >>\n");
1106 pdf.extend_from_slice(format!("startxref\n{}\n%%EOF", xref_offset).as_bytes());
1107 pdf
1108 }
1109
1110 fn str_obj(s: &str) -> Object {
1111 Object::String(PdfString::from_bytes(s.as_bytes().to_vec()))
1112 }
1113
1114 #[test]
1115 fn test_parse_text_field() {
1116 let store = build_store();
1117 let mut dict = HashMap::new();
1118 dict.insert(Name::ft(), Object::Name(Name::from("Tx")));
1119 dict.insert(Name::t(), str_obj("username"));
1120 dict.insert(Name::v(), str_obj("john"));
1121 dict.insert(Name::max_len(), Object::Integer(50));
1122 dict.insert(Name::tu(), str_obj("Enter your name"));
1123
1124 let field = FormField::from_dict(&dict, &store, None, "").unwrap();
1125 assert_eq!(field.field_type, FormFieldType::Text);
1126 assert_eq!(field.name, "username");
1127 assert_eq!(field.value.as_deref(), Some("john"));
1128 assert_eq!(field.max_len, Some(50));
1129 assert_eq!(field.tooltip.as_deref(), Some("Enter your name"));
1130 }
1131
1132 #[test]
1133 fn test_parse_button_field_checkbox() {
1134 let store = build_store();
1135 let mut dict = HashMap::new();
1136 dict.insert(Name::ft(), Object::Name(Name::from("Btn")));
1137 dict.insert(Name::t(), str_obj("agree"));
1138 dict.insert(Name::as_name(), Object::Name(Name::from("Yes")));
1139 dict.insert(Name::ff(), Object::Integer(0));
1140
1141 let field = FormField::from_dict(&dict, &store, None, "").unwrap();
1142 assert_eq!(field.field_type, FormFieldType::Button);
1143 assert_eq!(field.name, "agree");
1144 assert_eq!(field.appearance_state.as_deref(), Some("Yes"));
1145 assert_eq!(field.flags.bits(), 0);
1146 }
1147
1148 #[test]
1149 fn test_parse_choice_field_with_options() {
1150 let store = build_store();
1151 let mut dict = HashMap::new();
1152 dict.insert(Name::ft(), Object::Name(Name::from("Ch")));
1153 dict.insert(Name::t(), str_obj("color"));
1154
1155 let opts = Object::Array(vec![str_obj("Red"), str_obj("Green"), str_obj("Blue")]);
1157 dict.insert(Name::opt(), opts);
1158
1159 let field = FormField::from_dict(&dict, &store, None, "").unwrap();
1160 assert_eq!(field.field_type, FormFieldType::Choice);
1161 assert_eq!(field.options.len(), 3);
1162 assert_eq!(field.options[0].export_value, "Red");
1163 assert_eq!(field.options[0].display_value, "Red");
1164 assert_eq!(field.options[2].export_value, "Blue");
1165 }
1166
1167 #[test]
1168 fn test_parse_choice_field_with_export_display_pairs() {
1169 let store = build_store();
1170 let mut dict = HashMap::new();
1171 dict.insert(Name::ft(), Object::Name(Name::from("Ch")));
1172 dict.insert(Name::t(), str_obj("country"));
1173
1174 let opts = Object::Array(vec![Object::Array(vec![
1176 str_obj("US"),
1177 str_obj("United States"),
1178 ])]);
1179 dict.insert(Name::opt(), opts);
1180
1181 let field = FormField::from_dict(&dict, &store, None, "").unwrap();
1182 assert_eq!(field.options.len(), 1);
1183 assert_eq!(field.options[0].export_value, "US");
1184 assert_eq!(field.options[0].display_value, "United States");
1185 }
1186
1187 #[test]
1188 fn test_hierarchical_field_name() {
1189 let store = build_store();
1190 let mut dict = HashMap::new();
1191 dict.insert(Name::ft(), Object::Name(Name::from("Tx")));
1192 dict.insert(Name::t(), str_obj("city"));
1193
1194 let field = FormField::from_dict(&dict, &store, None, "address").unwrap();
1195 assert_eq!(field.name, "address.city");
1196 }
1197
1198 #[test]
1199 fn test_inherit_ft_from_parent() {
1200 let store = build_store();
1201 let mut dict = HashMap::new();
1202 dict.insert(Name::t(), str_obj("line1"));
1204 dict.insert(Name::v(), str_obj("123 Main St"));
1205
1206 let field = FormField::from_dict(&dict, &store, Some("Tx"), "address").unwrap();
1207 assert_eq!(field.field_type, FormFieldType::Text);
1208 assert_eq!(field.name, "address.line1");
1209 assert_eq!(field.value.as_deref(), Some("123 Main St"));
1210 }
1211
1212 #[test]
1213 fn test_field_flags_parsing() {
1214 let store = build_store();
1215 let mut dict = HashMap::new();
1216 dict.insert(Name::ft(), Object::Name(Name::from("Tx")));
1217 dict.insert(Name::t(), str_obj("notes"));
1218 dict.insert(Name::ff(), Object::Integer(4096)); let field = FormField::from_dict(&dict, &store, None, "").unwrap();
1221 assert_eq!(field.flags.bits(), 4096);
1222 }
1223
1224 #[test]
1225 fn test_no_ft_returns_none() {
1226 let store = build_store();
1227 let mut dict = HashMap::new();
1228 dict.insert(Name::t(), str_obj("orphan"));
1229
1230 let result = FormField::from_dict(&dict, &store, None, "");
1231 assert!(result.is_none());
1232 }
1233
1234 fn make_field(ft: FormFieldType) -> FormField {
1237 FormField {
1238 name: "test".to_string(),
1239 field_type: ft,
1240 value: None,
1241 default_value: Some("default_val".to_string()),
1242 flags: FormFieldFlags::from_bits(0),
1243 tooltip: None,
1244 alternate_name: None,
1245 mapping_name: None,
1246 max_len: None,
1247 options: Vec::new(),
1248 appearance_state: None,
1249 children: Vec::new(),
1250 controls: Vec::new(),
1251 dirty: false,
1252 selected_indices: Vec::new(),
1253 additional_actions: None,
1254 }
1255 }
1256
1257 #[test]
1258 fn test_set_value_text_field() {
1259 let mut field = make_field(FormFieldType::Text);
1260 field.max_len = Some(10);
1261 field
1262 .set_value(FieldValue::String("hello".to_string()))
1263 .unwrap();
1264 assert_eq!(field.value.as_deref(), Some("hello"));
1265 assert!(field.dirty);
1266 }
1267
1268 #[test]
1269 fn test_set_value_text_field_too_long() {
1270 let mut field = make_field(FormFieldType::Text);
1271 field.max_len = Some(3);
1272 let result = field.set_value(FieldValue::String("toolong".to_string()));
1273 assert!(result.is_err());
1274 assert!(!field.dirty);
1275 }
1276
1277 #[test]
1278 fn test_set_value_button_checked() {
1279 let mut field = make_field(FormFieldType::Button);
1280 field.set_value(FieldValue::Bool(true)).unwrap();
1281 assert_eq!(field.appearance_state.as_deref(), Some("Yes"));
1282 assert_eq!(field.value.as_deref(), Some("Yes"));
1283 assert!(field.dirty);
1284 }
1285
1286 #[test]
1287 fn test_set_value_button_unchecked() {
1288 let mut field = make_field(FormFieldType::Button);
1289 field.set_value(FieldValue::Bool(false)).unwrap();
1290 assert_eq!(field.appearance_state.as_deref(), Some("Off"));
1291 }
1292
1293 #[test]
1294 fn test_set_value_choice_by_index() {
1295 let mut field = make_field(FormFieldType::Choice);
1296 field.options = vec![
1297 ChoiceOption {
1298 export_value: "R".into(),
1299 display_value: "Red".into(),
1300 },
1301 ChoiceOption {
1302 export_value: "G".into(),
1303 display_value: "Green".into(),
1304 },
1305 ChoiceOption {
1306 export_value: "B".into(),
1307 display_value: "Blue".into(),
1308 },
1309 ];
1310 field.set_value(FieldValue::Choice(1)).unwrap();
1311 assert_eq!(field.value.as_deref(), Some("G"));
1312 }
1313
1314 #[test]
1315 fn test_set_value_choice_out_of_bounds() {
1316 let mut field = make_field(FormFieldType::Choice);
1317 field.options = vec![ChoiceOption {
1318 export_value: "R".into(),
1319 display_value: "Red".into(),
1320 }];
1321 let result = field.set_value(FieldValue::Choice(5));
1322 assert!(result.is_err());
1323 }
1324
1325 #[test]
1326 fn test_set_value_type_mismatch() {
1327 let mut field = make_field(FormFieldType::Text);
1328 let result = field.set_value(FieldValue::Bool(true));
1329 assert!(result.is_err());
1330 assert!(!field.dirty);
1331 }
1332
1333 #[test]
1334 fn test_reset_to_default() {
1335 let mut field = make_field(FormFieldType::Text);
1336 field.value = Some("modified".to_string());
1337 field.reset_to_default();
1338 assert_eq!(field.value.as_deref(), Some("default_val"));
1339 assert!(field.dirty);
1340 }
1341
1342 #[test]
1343 fn test_needs_appearance_after_set() {
1344 let mut field = make_field(FormFieldType::Text);
1345 assert!(!field.needs_appearance());
1346 field
1347 .set_value(FieldValue::String("x".to_string()))
1348 .unwrap();
1349 assert!(field.needs_appearance());
1350 }
1351
1352 #[test]
1355 fn test_controls_accessor_empty() {
1356 let field = make_field(FormFieldType::Text);
1357 assert!(field.controls().is_empty());
1358 }
1359
1360 #[test]
1361 fn test_widget_kids_parsed_as_controls() {
1362 let store = build_store();
1363
1364 let mut widget_dict = HashMap::new();
1366 widget_dict.insert(Name::subtype(), Object::Name(Name::from("Widget")));
1367 widget_dict.insert(
1368 Name::rect(),
1369 Object::Array(vec![
1370 Object::Real(10.0),
1371 Object::Real(20.0),
1372 Object::Real(110.0),
1373 Object::Real(40.0),
1374 ]),
1375 );
1376 widget_dict.insert(Name::as_name(), Object::Name(Name::from("Yes")));
1377
1378 let mut field_dict = HashMap::new();
1379 field_dict.insert(Name::ft(), Object::Name(Name::from("Btn")));
1380 field_dict.insert(Name::t(), str_obj("checkbox"));
1381 field_dict.insert(
1382 Name::kids(),
1383 Object::Array(vec![Object::Dictionary(widget_dict)]),
1384 );
1385
1386 let field = FormField::from_dict(&field_dict, &store, None, "").unwrap();
1387 assert_eq!(field.controls().len(), 1);
1388 assert_eq!(field.controls()[0].field_name, "checkbox");
1389 assert_eq!(field.controls()[0].rect.left, 10.0);
1390 assert_eq!(field.controls()[0].rect.right, 110.0);
1391 assert_eq!(field.controls()[0].appearance_state.as_deref(), Some("Yes"));
1392 }
1393
1394 #[test]
1397 fn test_flag_helpers_default() {
1398 let field = make_field(FormFieldType::Text);
1399 assert!(!field.flags.is_read_only());
1400 assert!(!field.flags.is_required());
1401 assert!(!field.flags.is_no_export());
1402 assert!(!field.flags.is_multiline());
1403 assert!(!field.flags.is_password());
1404 }
1405
1406 #[test]
1407 fn test_flag_read_only() {
1408 let mut field = make_field(FormFieldType::Text);
1409 field.flags = FormFieldFlags::from_bits(1 << 0);
1410 assert!(field.flags.is_read_only());
1411 }
1412
1413 #[test]
1414 fn test_flag_required() {
1415 let mut field = make_field(FormFieldType::Text);
1416 field.flags = FormFieldFlags::from_bits(1 << 1);
1417 assert!(field.flags.is_required());
1418 }
1419
1420 #[test]
1421 fn test_flag_no_export() {
1422 let mut field = make_field(FormFieldType::Text);
1423 field.flags = FormFieldFlags::from_bits(1 << 2);
1424 assert!(field.flags.is_no_export());
1425 }
1426
1427 #[test]
1428 fn test_flag_multiline() {
1429 let mut field = make_field(FormFieldType::Text);
1430 field.flags = FormFieldFlags::from_bits(1 << 12);
1431 assert!(field.flags.is_multiline());
1432 }
1433
1434 #[test]
1435 fn test_flag_password() {
1436 let mut field = make_field(FormFieldType::Text);
1437 field.flags = FormFieldFlags::from_bits(1 << 13);
1438 assert!(field.flags.is_password());
1439 }
1440
1441 #[test]
1442 fn test_flag_no_toggle_to_off() {
1443 let mut field = make_field(FormFieldType::Button);
1444 field.flags = FormFieldFlags::from_bits(1 << 14);
1445 assert!(field.flags.is_no_toggle_to_off());
1446 }
1447
1448 #[test]
1449 fn test_flag_radio() {
1450 let mut field = make_field(FormFieldType::Button);
1451 field.flags = FormFieldFlags::from_bits(1 << 15);
1452 assert!(field.flags.is_radio());
1453 }
1454
1455 #[test]
1456 fn test_flag_push_button() {
1457 let mut field = make_field(FormFieldType::Button);
1458 field.flags = FormFieldFlags::from_bits(1 << 16);
1459 assert!(field.flags.is_push_button());
1460 }
1461
1462 #[test]
1463 fn test_flag_combo() {
1464 let mut field = make_field(FormFieldType::Choice);
1465 field.flags = FormFieldFlags::from_bits(1 << 17);
1466 assert!(field.flags.is_combo());
1467 }
1468
1469 #[test]
1470 fn test_flag_combo_editable() {
1471 let mut field = make_field(FormFieldType::Choice);
1472 field.flags = FormFieldFlags::from_bits(1 << 18);
1473 assert!(field.flags.is_combo_editable());
1474 }
1475
1476 #[test]
1477 fn test_flag_sort() {
1478 let mut field = make_field(FormFieldType::Choice);
1479 field.flags = FormFieldFlags::from_bits(1 << 19);
1480 assert!(field.flags.is_sort());
1481 }
1482
1483 #[test]
1484 fn test_flag_file_select() {
1485 let mut field = make_field(FormFieldType::Text);
1486 field.flags = FormFieldFlags::from_bits(1 << 20);
1487 assert!(field.flags.is_file_select());
1488 }
1489
1490 #[test]
1491 fn test_flag_multi_select() {
1492 let mut field = make_field(FormFieldType::Choice);
1493 field.flags = FormFieldFlags::from_bits(1 << 21);
1494 assert!(field.flags.is_multi_select());
1495 }
1496
1497 #[test]
1498 fn test_flag_do_not_spell_check() {
1499 let mut field = make_field(FormFieldType::Text);
1500 field.flags = FormFieldFlags::from_bits(1 << 22);
1501 assert!(field.flags.is_do_not_spell_check());
1502 }
1503
1504 #[test]
1505 fn test_flag_do_not_scroll() {
1506 let mut field = make_field(FormFieldType::Text);
1507 field.flags = FormFieldFlags::from_bits(1 << 23);
1508 assert!(field.flags.is_do_not_scroll());
1509 }
1510
1511 #[test]
1512 fn test_flag_comb() {
1513 let mut field = make_field(FormFieldType::Text);
1514 field.flags = FormFieldFlags::from_bits(1 << 24);
1515 assert!(field.flags.is_comb());
1516 }
1517
1518 #[test]
1519 fn test_flag_rich_text_and_radios_in_unison() {
1520 let mut field = make_field(FormFieldType::Text);
1522 field.flags = FormFieldFlags::from_bits(1 << 25);
1523 assert!(field.flags.is_rich_text());
1524 assert!(field.flags.is_radios_in_unison());
1525 }
1526
1527 #[test]
1528 fn test_flag_commit_on_sel_change() {
1529 let mut field = make_field(FormFieldType::Choice);
1530 field.flags = FormFieldFlags::from_bits(1 << 26);
1531 assert!(field.flags.is_commit_on_sel_change());
1532 }
1533
1534 #[test]
1537 fn test_selected_indices_empty() {
1538 let field = make_field(FormFieldType::Choice);
1539 assert_eq!(field.selected_item_count(), 0);
1540 assert!(!field.is_item_selected(0));
1541 assert!(field.selected_index(0).is_none());
1542 }
1543
1544 #[test]
1545 fn test_selected_indices_populated() {
1546 let mut field = make_field(FormFieldType::Choice);
1547 field.selected_indices = vec![1, 3];
1548 assert_eq!(field.selected_item_count(), 2);
1549 assert!(!field.is_item_selected(0));
1550 assert!(field.is_item_selected(1));
1551 assert!(!field.is_item_selected(2));
1552 assert!(field.is_item_selected(3));
1553 assert_eq!(field.selected_index(0), Some(1));
1554 assert_eq!(field.selected_index(1), Some(3));
1555 assert!(field.selected_index(2).is_none());
1556 }
1557
1558 #[test]
1561 fn test_alternate_name_from_tooltip() {
1562 let store = build_store();
1563 let mut dict = HashMap::new();
1564 dict.insert(Name::ft(), Object::Name(Name::from("Tx")));
1565 dict.insert(Name::t(), str_obj("field1"));
1566 dict.insert(Name::tu(), str_obj("Enter your name"));
1567 let field = FormField::from_dict(&dict, &store, None, "").unwrap();
1568 assert_eq!(field.alternate_name.as_deref(), Some("Enter your name"));
1569 assert_eq!(field.tooltip.as_deref(), Some("Enter your name"));
1570 }
1571
1572 #[test]
1573 fn test_mapping_name_parsed() {
1574 let store = build_store();
1575 let mut dict = HashMap::new();
1576 dict.insert(Name::ft(), Object::Name(Name::from("Tx")));
1577 dict.insert(Name::t(), str_obj("field1"));
1578 dict.insert(Name::tm(), str_obj("export_field1"));
1579 let field = FormField::from_dict(&dict, &store, None, "").unwrap();
1580 assert_eq!(field.mapping_name.as_deref(), Some("export_field1"));
1581 }
1582
1583 #[test]
1586 fn test_selected_indices_parsed_from_dict() {
1587 let store = build_store();
1588 let mut dict = HashMap::new();
1589 dict.insert(Name::ft(), Object::Name(Name::from("Ch")));
1590 dict.insert(Name::t(), str_obj("colors"));
1591 let opts = Object::Array(vec![str_obj("Red"), str_obj("Green"), str_obj("Blue")]);
1592 dict.insert(Name::opt(), opts);
1593 dict.insert(
1594 Name::i(),
1595 Object::Array(vec![Object::Integer(0), Object::Integer(2)]),
1596 );
1597 let field = FormField::from_dict(&dict, &store, None, "").unwrap();
1598 assert_eq!(field.selected_indices, vec![0, 2]);
1599 assert!(field.is_item_selected(0));
1600 assert!(!field.is_item_selected(1));
1601 assert!(field.is_item_selected(2));
1602 }
1603
1604 #[test]
1605 fn test_kids_with_ft_not_treated_as_controls() {
1606 let store = build_store();
1607
1608 let mut child_dict = HashMap::new();
1610 child_dict.insert(Name::subtype(), Object::Name(Name::from("Widget")));
1611 child_dict.insert(Name::ft(), Object::Name(Name::from("Tx")));
1612 child_dict.insert(Name::t(), str_obj("child"));
1613
1614 let mut field_dict = HashMap::new();
1615 field_dict.insert(Name::ft(), Object::Name(Name::from("Tx")));
1616 field_dict.insert(Name::t(), str_obj("parent"));
1617 field_dict.insert(
1618 Name::kids(),
1619 Object::Array(vec![Object::Dictionary(child_dict)]),
1620 );
1621
1622 let field = FormField::from_dict(&field_dict, &store, None, "").unwrap();
1623 assert!(field.controls().is_empty());
1625 }
1626
1627 #[test]
1630 fn test_clear_selection_clears_indices() {
1631 let mut field = make_field(FormFieldType::Choice);
1632 field.selected_indices = vec![0, 2];
1633 let result = field.clear_selection();
1634 assert!(result.is_ok());
1635 assert!(field.selected_indices.is_empty());
1637 }
1638
1639 #[test]
1642 fn test_additional_actions_none_by_default() {
1643 let field = make_field(FormFieldType::Text);
1644 assert!(field.additional_actions().is_none());
1645 }
1646
1647 #[test]
1650 fn test_option_count_returns_options_length() {
1651 let store = build_store();
1652 let mut dict = HashMap::new();
1653 dict.insert(Name::ft(), Object::Name(Name::from("Ch")));
1654 dict.insert(Name::t(), str_obj("colors"));
1655 dict.insert(
1656 Name::opt(),
1657 Object::Array(vec![str_obj("Red"), str_obj("Green"), str_obj("Blue")]),
1658 );
1659 let field = FormField::from_dict(&dict, &store, None, "").unwrap();
1660 assert_eq!(field.option_count(), 3);
1661 }
1662
1663 #[test]
1664 fn test_get_option_label_returns_display_value() {
1665 let store = build_store();
1666 let mut dict = HashMap::new();
1667 dict.insert(Name::ft(), Object::Name(Name::from("Ch")));
1668 dict.insert(Name::t(), str_obj("sizes"));
1669 dict.insert(
1671 Name::opt(),
1672 Object::Array(vec![
1673 Object::Array(vec![str_obj("sm"), str_obj("Small")]),
1674 Object::Array(vec![str_obj("lg"), str_obj("Large")]),
1675 ]),
1676 );
1677 let field = FormField::from_dict(&dict, &store, None, "").unwrap();
1678 assert_eq!(field.get_option_label(0), Some("Small"));
1679 assert_eq!(field.get_option_label(1), Some("Large"));
1680 assert_eq!(field.get_option_label(2), None);
1681 }
1682
1683 #[test]
1684 fn test_get_option_value_returns_export_value() {
1685 let store = build_store();
1686 let mut dict = HashMap::new();
1687 dict.insert(Name::ft(), Object::Name(Name::from("Ch")));
1688 dict.insert(Name::t(), str_obj("sizes"));
1689 dict.insert(
1690 Name::opt(),
1691 Object::Array(vec![
1692 Object::Array(vec![str_obj("sm"), str_obj("Small")]),
1693 Object::Array(vec![str_obj("lg"), str_obj("Large")]),
1694 ]),
1695 );
1696 let field = FormField::from_dict(&dict, &store, None, "").unwrap();
1697 assert_eq!(field.get_option_value(0), Some("sm"));
1698 assert_eq!(field.get_option_value(1), Some("lg"));
1699 assert_eq!(field.get_option_value(99), None);
1700 }
1701
1702 #[test]
1705 fn test_control_count_returns_controls_length() {
1706 let mut field = make_field(FormFieldType::Choice);
1707 assert_eq!(field.control_count(), 0);
1708 use crate::form_control::{FormControl, HighlightingMode, TextPosition};
1709 use rpdfium_core::Rect;
1710 field.controls.push(FormControl {
1711 field_name: "test".to_string(),
1712 rect: Rect::new(0.0, 0.0, 100.0, 20.0),
1713 appearance_state: None,
1714 page_index: None,
1715 highlighting_mode: HighlightingMode::Invert,
1716 rotation: 0,
1717 border_color: None,
1718 background_color: None,
1719 caption: None,
1720 rollover_caption: None,
1721 alt_caption: None,
1722 default_appearance: None,
1723 normal_icon: None,
1724 rollover_icon: None,
1725 down_icon: None,
1726 icon_fit: None,
1727 text_position: TextPosition::CaptionOnly,
1728 });
1729 assert_eq!(field.control_count(), 1);
1730 }
1731
1732 #[test]
1733 fn test_get_control_returns_correct_control() {
1734 let mut field = make_field(FormFieldType::Text);
1735 use crate::form_control::{FormControl, HighlightingMode, TextPosition};
1736 use rpdfium_core::Rect;
1737 let ctrl = FormControl {
1738 field_name: "f".to_string(),
1739 rect: Rect::new(10.0, 10.0, 110.0, 30.0),
1740 appearance_state: None,
1741 page_index: Some(0),
1742 highlighting_mode: HighlightingMode::None,
1743 rotation: 0,
1744 border_color: None,
1745 background_color: None,
1746 caption: None,
1747 rollover_caption: None,
1748 alt_caption: None,
1749 default_appearance: None,
1750 normal_icon: None,
1751 rollover_icon: None,
1752 down_icon: None,
1753 icon_fit: None,
1754 text_position: TextPosition::CaptionOnly,
1755 };
1756 field.controls.push(ctrl);
1757 assert!(field.get_control(0).is_some());
1758 assert_eq!(field.get_control(0).unwrap().page_index, Some(0));
1759 assert!(field.get_control(1).is_none());
1760 }
1761
1762 #[test]
1779 fn test_cpdf_form_field_is_item_selected() {
1780 let options = vec![
1782 ChoiceOption {
1783 export_value: "Alpha".into(),
1784 display_value: "Alpha".into(),
1785 },
1786 ChoiceOption {
1787 export_value: "Beta".into(),
1788 display_value: "Beta".into(),
1789 },
1790 ChoiceOption {
1791 export_value: "Gamma".into(),
1792 display_value: "Gamma".into(),
1793 },
1794 ChoiceOption {
1795 export_value: "Delta".into(),
1796 display_value: "Delta".into(),
1797 },
1798 ChoiceOption {
1799 export_value: "Epsilon".into(),
1800 display_value: "Epsilon".into(),
1801 },
1802 ];
1803
1804 let make_choice_field = |selected: Vec<usize>| -> FormField {
1805 FormField {
1806 name: "multi".to_string(),
1807 field_type: FormFieldType::Choice,
1808 value: None,
1809 default_value: None,
1810 flags: FormFieldFlags::from_bits(1 << 21), tooltip: None,
1812 alternate_name: None,
1813 mapping_name: None,
1814 max_len: None,
1815 options: options.clone(),
1816 appearance_state: None,
1817 children: Vec::new(),
1818 controls: Vec::new(),
1819 dirty: false,
1820 selected_indices: selected,
1821 additional_actions: None,
1822 }
1823 };
1824
1825 {
1827 let field = make_choice_field(vec![]);
1828 for i in 0..5 {
1829 assert!(!field.is_item_selected(i));
1830 }
1831 assert!(!field.is_item_selected(5));
1833 }
1834
1835 {
1837 let field = make_choice_field(vec![2]); assert!(!field.is_item_selected(0));
1839 assert!(!field.is_item_selected(1));
1840 assert!(field.is_item_selected(2));
1841 assert!(!field.is_item_selected(3));
1842 assert!(!field.is_item_selected(4));
1843 }
1844
1845 {
1847 let field = make_choice_field(vec![0, 2, 3]);
1848 assert!(field.is_item_selected(0));
1849 assert!(!field.is_item_selected(1));
1850 assert!(field.is_item_selected(2));
1851 assert!(field.is_item_selected(3));
1852 assert!(!field.is_item_selected(4));
1853 }
1854
1855 {
1857 let field = make_choice_field(vec![0, 2, 3, 12, 42]);
1858 assert!(field.is_item_selected(0));
1859 assert!(!field.is_item_selected(1));
1860 assert!(field.is_item_selected(2));
1861 assert!(field.is_item_selected(3));
1862 assert!(!field.is_item_selected(4));
1863 assert!(!field.is_item_selected(5));
1865 assert!(field.is_item_selected(12));
1867 assert!(field.is_item_selected(42));
1868 }
1869
1870 {
1872 let field = make_choice_field(vec![1, 4]);
1873 assert_eq!(field.selected_item_count(), 2);
1874 assert_eq!(field.count_selected_items(), 2);
1875 }
1876
1877 {
1879 let field = make_choice_field(vec![1, 4]);
1880 assert_eq!(field.selected_index(0), Some(1));
1881 assert_eq!(field.selected_index(1), Some(4));
1882 assert_eq!(field.selected_index(2), None);
1883 }
1884 }
1885}