1use rpdfium_core::{Name, PdfSource};
13use rpdfium_parser::{Object, ObjectId, ObjectStore};
14
15use crate::aaction::{AActionType, AdditionalActions, parse_additional_actions};
16use crate::action::{Action, parse_action};
17use crate::annotation_appearance::{AnnotationAppearance, extract_appearance};
18use crate::ap_settings::{MkDict, parse_mk_dict};
19use crate::default_appearance::parse_default_appearance;
20use crate::destination::{Destination, parse_destination};
21use crate::error::{DocError, DocResult};
22use crate::file_spec::{FileSpec, parse_file_spec};
23use crate::form_field::{ChoiceOption, FormFieldFlags, FormFieldType};
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30pub enum PdfValueType {
31 Unknown = 0,
33 Boolean = 1,
35 Number = 2,
37 String = 3,
39 Name = 4,
41 Array = 5,
43 Dictionary = 6,
45 Stream = 7,
47 Null = 8,
49 Reference = 9,
51}
52
53#[derive(Debug, Clone, Copy, PartialEq, Eq)]
58pub enum AppearanceMode {
59 Normal,
61 Rollover,
63 Down,
65}
66
67#[derive(Debug, Clone, Default)]
72pub struct AnnotationSubtypeData {
73 pub quad_points: Option<Vec<f32>>,
76 pub line_points: Option<[f32; 4]>,
78 pub leader_line_length: Option<f32>,
80 pub vertices: Option<Vec<f32>>,
82 pub ink_list: Option<Vec<Vec<f32>>>,
85 pub default_appearance: Option<String>,
87 pub stamp_name: Option<String>,
89}
90
91#[derive(Debug, Clone)]
93pub struct Annotation {
94 pub subtype: AnnotationType,
96 pub rect: [f32; 4],
98 pub contents: Option<String>,
100 pub flags: AnnotationFlags,
102 pub name: Option<String>,
104 pub appearance: Option<AnnotationAppearance>,
106 pub color: Option<Vec<f32>>,
108 pub border: Option<AnnotationBorder>,
110 pub action: Option<Action>,
112 pub destination: Option<Destination>,
114 pub subtype_data: AnnotationSubtypeData,
116 pub mk: Option<MkDict>,
118 pub file_spec: Option<FileSpec>,
120 pub parent_ref: Option<ObjectId>,
122 pub object_id: Option<ObjectId>,
124 pub open: Option<bool>,
126 pub ap_n_bytes: Option<Vec<u8>>,
131 pub ap_r_bytes: Option<Vec<u8>>,
136 pub ap_d_bytes: Option<Vec<u8>>,
141 pub irt_ref: Option<ObjectId>,
146 pub field_name: Option<String>,
152 pub alternate_name: Option<String>,
157 pub field_value: Option<String>,
164 pub form_field_flags: Option<FormFieldFlags>,
171 pub additional_actions: Option<AdditionalActions>,
177 pub form_field_type: Option<FormFieldType>,
185 pub options: Option<Vec<ChoiceOption>>,
192}
193
194#[derive(Debug, Clone, Copy, PartialEq, Eq)]
196pub enum AnnotationType {
197 Text,
199 Link,
201 FreeText,
203 Line,
205 Square,
207 Circle,
209 Polygon,
211 PolyLine,
213 Highlight,
215 Underline,
217 Squiggly,
219 StrikeOut,
221 Stamp,
223 Caret,
225 Ink,
227 Popup,
229 FileAttachment,
231 Sound,
233 Widget,
235 Movie,
237 Screen,
239 PrinterMark,
241 TrapNet,
243 Watermark,
245 ThreeD,
247 RichMedia,
249 XFAWidget,
251 Redact,
253 Other,
255}
256
257impl AnnotationType {
258 pub fn supports_ap_objects(&self) -> bool {
266 matches!(
267 self,
268 AnnotationType::Stamp
269 | AnnotationType::FreeText
270 | AnnotationType::Ink
271 | AnnotationType::Square
272 | AnnotationType::Circle
273 | AnnotationType::Polygon
274 | AnnotationType::PolyLine
275 | AnnotationType::Line
276 | AnnotationType::Highlight
277 | AnnotationType::Underline
278 | AnnotationType::Squiggly
279 | AnnotationType::StrikeOut
280 )
281 }
282
283 #[inline]
287 pub fn annot_is_object_supported_subtype(&self) -> bool {
288 self.supports_ap_objects()
289 }
290
291 #[deprecated(
293 since = "0.1.0",
294 note = "use annot_is_object_supported_subtype() instead"
295 )]
296 #[inline]
297 pub fn is_object_supported_subtype(&self) -> bool {
298 self.supports_ap_objects()
299 }
300
301 #[deprecated(
303 since = "0.1.0",
304 note = "use annot_is_object_supported_subtype() instead"
305 )]
306 #[inline]
307 pub fn is_ap_object_supported(&self) -> bool {
308 self.supports_ap_objects()
309 }
310
311 pub fn is_supported_for_creation(&self) -> bool {
318 matches!(
319 self,
320 AnnotationType::Circle
321 | AnnotationType::FileAttachment
322 | AnnotationType::FreeText
323 | AnnotationType::Highlight
324 | AnnotationType::Ink
325 | AnnotationType::Link
326 | AnnotationType::Popup
327 | AnnotationType::Square
328 | AnnotationType::Squiggly
329 | AnnotationType::Stamp
330 | AnnotationType::StrikeOut
331 | AnnotationType::Text
332 | AnnotationType::Underline
333 )
334 }
335
336 #[inline]
340 pub fn annot_is_supported_subtype(&self) -> bool {
341 self.is_supported_for_creation()
342 }
343
344 #[deprecated(since = "0.1.0", note = "use annot_is_supported_subtype() instead")]
346 #[inline]
347 pub fn is_supported_subtype(&self) -> bool {
348 self.is_supported_for_creation()
349 }
350
351 pub fn from_name(name: &str) -> Self {
353 match name {
354 "Text" => Self::Text,
355 "Link" => Self::Link,
356 "FreeText" => Self::FreeText,
357 "Line" => Self::Line,
358 "Square" => Self::Square,
359 "Circle" => Self::Circle,
360 "Polygon" => Self::Polygon,
361 "PolyLine" => Self::PolyLine,
362 "Highlight" => Self::Highlight,
363 "Underline" => Self::Underline,
364 "Squiggly" => Self::Squiggly,
365 "StrikeOut" => Self::StrikeOut,
366 "Stamp" => Self::Stamp,
367 "Caret" => Self::Caret,
368 "Ink" => Self::Ink,
369 "Popup" => Self::Popup,
370 "FileAttachment" => Self::FileAttachment,
371 "Sound" => Self::Sound,
372 "Widget" => Self::Widget,
373 "Movie" => Self::Movie,
374 "Screen" => Self::Screen,
375 "PrinterMark" => Self::PrinterMark,
376 "TrapNet" => Self::TrapNet,
377 "Watermark" => Self::Watermark,
378 "3D" => Self::ThreeD,
379 "RichMedia" => Self::RichMedia,
380 "XFAWidget" => Self::XFAWidget,
381 "Redact" => Self::Redact,
382 _ => Self::Other,
383 }
384 }
385
386 pub fn to_name(&self) -> &'static str {
389 match self {
390 Self::Text => "Text",
391 Self::Link => "Link",
392 Self::FreeText => "FreeText",
393 Self::Line => "Line",
394 Self::Square => "Square",
395 Self::Circle => "Circle",
396 Self::Polygon => "Polygon",
397 Self::PolyLine => "PolyLine",
398 Self::Highlight => "Highlight",
399 Self::Underline => "Underline",
400 Self::Squiggly => "Squiggly",
401 Self::StrikeOut => "StrikeOut",
402 Self::Stamp => "Stamp",
403 Self::Caret => "Caret",
404 Self::Ink => "Ink",
405 Self::Popup => "Popup",
406 Self::FileAttachment => "FileAttachment",
407 Self::Sound => "Sound",
408 Self::Widget => "Widget",
409 Self::Movie => "Movie",
410 Self::Screen => "Screen",
411 Self::PrinterMark => "PrinterMark",
412 Self::TrapNet => "TrapNet",
413 Self::Watermark => "Watermark",
414 Self::ThreeD => "3D",
415 Self::RichMedia => "RichMedia",
416 Self::XFAWidget => "XFAWidget",
417 Self::Redact => "Redact",
418 Self::Other => "Unknown",
419 }
420 }
421}
422
423#[derive(Debug, Clone, Copy, Default)]
428pub struct AnnotationFlags(u32);
429
430impl AnnotationFlags {
431 pub fn from_bits(bits: u32) -> Self {
433 Self(bits)
434 }
435
436 pub fn bits(&self) -> u32 {
438 self.0
439 }
440
441 pub fn invisible(&self) -> bool {
444 self.0 & 1 != 0
445 }
446
447 pub fn hidden(&self) -> bool {
449 self.0 & 2 != 0
450 }
451
452 pub fn print(&self) -> bool {
454 self.0 & 4 != 0
455 }
456
457 pub fn no_zoom(&self) -> bool {
460 self.0 & 8 != 0
461 }
462
463 pub fn no_rotate(&self) -> bool {
466 self.0 & 16 != 0
467 }
468
469 pub fn no_view(&self) -> bool {
471 self.0 & 32 != 0
472 }
473
474 pub fn read_only(&self) -> bool {
476 self.0 & 64 != 0
477 }
478
479 pub fn locked(&self) -> bool {
481 self.0 & 128 != 0
482 }
483
484 pub fn toggle_no_view(&self) -> bool {
487 self.0 & 256 != 0
488 }
489
490 pub fn locked_contents(&self) -> bool {
493 self.0 & 512 != 0
494 }
495
496 pub fn is_visible(&self) -> bool {
498 !self.hidden() && !self.no_view()
499 }
500}
501
502#[derive(Debug, Clone)]
504pub struct AnnotationBorder {
505 pub width: f32,
507 pub style: BorderStyle,
509}
510
511impl Default for AnnotationBorder {
512 fn default() -> Self {
513 Self {
514 width: 1.0,
515 style: BorderStyle::Solid,
516 }
517 }
518}
519
520#[derive(Debug, Clone, Copy, PartialEq, Eq)]
522pub enum BorderStyle {
523 Solid,
525 Dashed,
527 Beveled,
529 Inset,
531 Underline,
533}
534
535impl BorderStyle {
536 pub fn from_name(name: &str) -> Self {
538 match name {
539 "S" => Self::Solid,
540 "D" => Self::Dashed,
541 "B" => Self::Beveled,
542 "I" => Self::Inset,
543 "U" => Self::Underline,
544 _ => Self::Solid,
545 }
546 }
547}
548
549pub(crate) fn parse_single_annotation<S: PdfSource>(
551 obj: &Object,
552 store: &ObjectStore<S>,
553 object_id: Option<ObjectId>,
554) -> DocResult<Annotation> {
555 let dict = obj.as_dict().ok_or(DocError::UnexpectedType)?;
556
557 let subtype = dict
559 .get(&Name::subtype())
560 .and_then(|o| {
561 store
562 .deep_resolve(o)
563 .ok()
564 .and_then(|r| r.as_name().map(|n| n.as_str().into_owned()))
565 })
566 .map(|s| AnnotationType::from_name(&s))
567 .unwrap_or(AnnotationType::Other);
568
569 let rect = parse_rect(dict, store)?;
571
572 let contents = dict.get(&Name::contents()).and_then(|o| {
574 store
575 .deep_resolve(o)
576 .ok()
577 .and_then(|r| r.as_string().map(|s| s.to_string_lossy()))
578 });
579
580 let flags = dict
582 .get(&Name::f())
583 .and_then(|o| store.deep_resolve(o).ok().and_then(|r| r.as_i64()))
584 .map(|v| AnnotationFlags::from_bits(v as u32))
585 .unwrap_or_default();
586
587 let name = dict.get(&Name::nm()).and_then(|o| {
589 store
590 .deep_resolve(o)
591 .ok()
592 .and_then(|r| r.as_string().map(|s| s.to_string_lossy()))
593 });
594
595 let appearance = dict
597 .get(&Name::ap())
598 .and_then(|o| extract_appearance(o, store).ok());
599
600 let color = dict
602 .get(&Name::c())
603 .and_then(|o| store.deep_resolve(o).ok())
604 .and_then(parse_color_array);
605
606 let border = parse_border(dict, store);
608
609 let action = dict
611 .get(&Name::a())
612 .and_then(|o| parse_action(o, store).ok());
613
614 let destination = dict
616 .get(&Name::dest())
617 .and_then(|o| parse_destination(o, store).ok());
618
619 let subtype_data = parse_subtype_data(dict, store, subtype);
621
622 let mk = parse_mk_dict(dict, store);
624
625 let file_spec = if subtype == AnnotationType::FileAttachment {
627 dict.get(&Name::fs())
628 .and_then(|o| parse_file_spec(o, store))
629 } else {
630 None
631 };
632
633 let parent_ref = if subtype == AnnotationType::Popup {
635 dict.get(&Name::parent()).and_then(|o| o.as_reference())
636 } else {
637 None
638 };
639
640 let open = dict.get(&Name::open()).and_then(|o| o.as_bool());
642
643 let (ap_n_bytes, ap_r_bytes, ap_d_bytes) = extract_ap_stream_bytes(dict, store, object_id);
645
646 let irt_ref = dict.get(&Name::irt()).and_then(|o| o.as_reference());
648
649 let field_name = dict.get(&Name::t()).and_then(|o| {
651 store
652 .deep_resolve(o)
653 .ok()
654 .and_then(|r| r.as_string().map(|s| s.to_string_lossy()))
655 });
656
657 let alternate_name = dict.get(&Name::tu()).and_then(|o| {
659 store
660 .deep_resolve(o)
661 .ok()
662 .and_then(|r| r.as_string().map(|s| s.to_string_lossy()))
663 });
664
665 let field_value = dict.get(&Name::v()).and_then(|o| {
667 store.deep_resolve(o).ok().and_then(|r| {
668 if let Some(s) = r.as_string() {
669 Some(s.to_string_lossy())
670 } else {
671 r.as_name().map(|n| n.as_str().into_owned())
672 }
673 })
674 });
675
676 let form_field_flags = dict
678 .get(&Name::ff())
679 .and_then(|o| store.deep_resolve(o).ok().and_then(|r| r.as_i64()))
680 .map(|v| FormFieldFlags::from_bits(v as u32));
681
682 let additional_actions = if subtype == AnnotationType::Widget {
684 dict.get(&Name::aa())
685 .and_then(|o| parse_additional_actions(o, store).ok())
686 } else {
687 None
688 };
689
690 let form_field_type = dict.get(&Name::ft()).and_then(|o| {
692 store.deep_resolve(o).ok().and_then(|r| {
693 r.as_name().map(|n| match n.as_str().as_ref() {
694 "Tx" => FormFieldType::Text,
695 "Btn" => FormFieldType::Button,
696 "Ch" => FormFieldType::Choice,
697 "Sig" => FormFieldType::Signature,
698 _ => FormFieldType::Text,
699 })
700 })
701 });
702
703 let options = if subtype == AnnotationType::Widget {
705 let opts = parse_choice_options(dict, store);
706 if opts.is_empty() { None } else { Some(opts) }
707 } else {
708 None
709 };
710
711 Ok(Annotation {
712 subtype,
713 rect,
714 contents,
715 flags,
716 name,
717 appearance,
718 color,
719 border,
720 action,
721 destination,
722 subtype_data,
723 mk,
724 file_spec,
725 parent_ref,
726 object_id,
727 open,
728 ap_n_bytes,
729 ap_r_bytes,
730 ap_d_bytes,
731 irt_ref,
732 field_name,
733 alternate_name,
734 field_value,
735 form_field_flags,
736 additional_actions,
737 form_field_type,
738 options,
739 })
740}
741
742impl Annotation {
743 pub fn set_rect(&mut self, rect: [f32; 4]) -> DocResult<()> {
748 self.rect = rect;
749 Ok(())
750 }
751
752 #[inline]
756 pub fn annot_set_rect(&mut self, rect: [f32; 4]) -> DocResult<()> {
757 self.set_rect(rect)
758 }
759
760 pub fn set_open_state(&mut self, open: bool) -> DocResult<()> {
765 self.open = Some(open);
766 Ok(())
767 }
768
769 pub fn quad_points(&self) -> Option<&[f32]> {
776 self.subtype_data.quad_points.as_deref()
777 }
778
779 #[inline]
783 pub fn annot_get_quad_points(&self) -> Option<&[f32]> {
784 self.quad_points()
785 }
786
787 #[deprecated(since = "0.1.0", note = "use annot_get_quad_points() instead")]
789 #[inline]
790 pub fn get_quad_points(&self) -> Option<&[f32]> {
791 self.quad_points()
792 }
793
794 pub fn subtype(&self) -> AnnotationType {
803 self.subtype
804 }
805
806 #[inline]
810 pub fn annot_get_subtype(&self) -> AnnotationType {
811 self.subtype()
812 }
813
814 #[deprecated(since = "0.1.0", note = "use annot_get_subtype() instead")]
816 #[inline]
817 pub fn get_subtype(&self) -> AnnotationType {
818 self.subtype()
819 }
820
821 pub fn rect(&self) -> [f32; 4] {
825 self.rect
826 }
827
828 #[inline]
832 pub fn annot_get_rect(&self) -> [f32; 4] {
833 self.rect()
834 }
835
836 #[deprecated(since = "0.1.0", note = "use annot_get_rect() instead")]
838 #[inline]
839 pub fn get_rect(&self) -> [f32; 4] {
840 self.rect()
841 }
842
843 pub fn flags(&self) -> AnnotationFlags {
847 self.flags
848 }
849
850 #[inline]
854 pub fn annot_get_flags(&self) -> AnnotationFlags {
855 self.flags()
856 }
857
858 #[deprecated(since = "0.1.0", note = "use annot_get_flags() instead")]
860 #[inline]
861 pub fn get_flags(&self) -> AnnotationFlags {
862 self.flags()
863 }
864
865 pub fn has_attachment_points(&self) -> bool {
872 matches!(
873 self.subtype,
874 AnnotationType::Highlight
875 | AnnotationType::Underline
876 | AnnotationType::Squiggly
877 | AnnotationType::StrikeOut
878 | AnnotationType::Link
879 )
880 }
881
882 #[inline]
886 pub fn annot_has_attachment_points(&self) -> bool {
887 self.has_attachment_points()
888 }
889
890 pub fn attachment_point_count(&self) -> usize {
896 self.subtype_data
897 .quad_points
898 .as_ref()
899 .map_or(0, |v| v.len() / 8)
900 }
901
902 #[inline]
906 pub fn annot_count_attachment_points(&self) -> usize {
907 self.attachment_point_count()
908 }
909
910 #[deprecated(since = "0.1.0", note = "use annot_count_attachment_points() instead")]
912 #[inline]
913 pub fn count_attachment_points(&self) -> usize {
914 self.attachment_point_count()
915 }
916
917 pub fn attachment_points(&self, index: usize) -> Option<[f32; 8]> {
922 let flat = self.subtype_data.quad_points.as_ref()?;
923 let start = index * 8;
924 if start + 8 > flat.len() {
925 return None;
926 }
927 let s = &flat[start..start + 8];
928 Some([s[0], s[1], s[2], s[3], s[4], s[5], s[6], s[7]])
929 }
930
931 #[inline]
935 pub fn annot_get_attachment_points(&self, index: usize) -> Option<[f32; 8]> {
936 self.attachment_points(index)
937 }
938
939 #[deprecated(since = "0.1.0", note = "use annot_get_attachment_points() instead")]
941 #[inline]
942 pub fn get_attachment_points(&self, index: usize) -> Option<[f32; 8]> {
943 self.attachment_points(index)
944 }
945
946 pub fn vertices(&self) -> Option<&[f32]> {
951 self.subtype_data.vertices.as_deref()
952 }
953
954 #[inline]
958 pub fn annot_get_vertices(&self) -> Option<&[f32]> {
959 self.vertices()
960 }
961
962 #[deprecated(since = "0.1.0", note = "use annot_get_vertices() instead")]
964 #[inline]
965 pub fn get_vertices(&self) -> Option<&[f32]> {
966 self.vertices()
967 }
968
969 pub fn ink_list_count(&self) -> usize {
973 self.subtype_data.ink_list.as_ref().map_or(0, |v| v.len())
974 }
975
976 #[inline]
980 pub fn annot_get_ink_list_count(&self) -> usize {
981 self.ink_list_count()
982 }
983
984 #[deprecated(since = "0.1.0", note = "use annot_get_ink_list_count() instead")]
986 #[inline]
987 pub fn get_ink_list_count(&self) -> usize {
988 self.ink_list_count()
989 }
990
991 pub fn ink_list_path(&self, index: usize) -> Option<&[f32]> {
995 self.subtype_data
996 .ink_list
997 .as_ref()?
998 .get(index)
999 .map(|v| v.as_slice())
1000 }
1001
1002 #[inline]
1006 pub fn annot_get_ink_list_path(&self, index: usize) -> Option<&[f32]> {
1007 self.ink_list_path(index)
1008 }
1009
1010 #[deprecated(since = "0.1.0", note = "use annot_get_ink_list_path() instead")]
1012 #[inline]
1013 pub fn get_ink_list_path(&self, index: usize) -> Option<&[f32]> {
1014 self.ink_list_path(index)
1015 }
1016
1017 pub fn line_endpoints(&self) -> Option<([f32; 2], [f32; 2])> {
1022 let pts = self.subtype_data.line_points?;
1023 Some(([pts[0], pts[1]], [pts[2], pts[3]]))
1024 }
1025
1026 #[inline]
1030 pub fn annot_get_line(&self) -> Option<([f32; 2], [f32; 2])> {
1031 self.line_endpoints()
1032 }
1033
1034 #[deprecated(since = "0.1.0", note = "use annot_get_line() instead")]
1036 #[inline]
1037 pub fn get_line(&self) -> Option<([f32; 2], [f32; 2])> {
1038 self.line_endpoints()
1039 }
1040
1041 #[deprecated(since = "0.1.0", note = "use annot_get_line() instead")]
1043 #[inline]
1044 pub fn get_line_endpoints(&self) -> Option<([f32; 2], [f32; 2])> {
1045 self.line_endpoints()
1046 }
1047
1048 pub fn border_style(&self) -> Option<(f32, f32, f32)> {
1055 self.border.as_ref().map(|b| (0.0_f32, 0.0_f32, b.width))
1056 }
1057
1058 #[inline]
1062 pub fn annot_get_border(&self) -> Option<(f32, f32, f32)> {
1063 self.border_style()
1064 }
1065
1066 #[deprecated(since = "0.1.0", note = "use annot_get_border() instead")]
1068 #[inline]
1069 pub fn get_border(&self) -> Option<(f32, f32, f32)> {
1070 self.border_style()
1071 }
1072
1073 #[deprecated(since = "0.1.0", note = "use annot_get_border() instead")]
1075 #[inline]
1076 pub fn get_border_style(&self) -> Option<(f32, f32, f32)> {
1077 self.border_style()
1078 }
1079
1080 pub fn color_rgba(&self) -> Option<(u8, u8, u8, u8)> {
1090 let c = self.color.as_ref()?;
1091 let to_u8 = |f: f32| (f.clamp(0.0, 1.0) * 255.0).round() as u8;
1092 match c.len() {
1093 1 => {
1094 let gray = to_u8(c[0]);
1095 Some((gray, gray, gray, 255))
1096 }
1097 3 => Some((to_u8(c[0]), to_u8(c[1]), to_u8(c[2]), 255)),
1098 4 => {
1099 let k = c[3].clamp(0.0, 1.0);
1101 let r = to_u8((1.0 - c[0].clamp(0.0, 1.0)) * (1.0 - k));
1102 let g = to_u8((1.0 - c[1].clamp(0.0, 1.0)) * (1.0 - k));
1103 let b = to_u8((1.0 - c[2].clamp(0.0, 1.0)) * (1.0 - k));
1104 Some((r, g, b, 255))
1105 }
1106 _ => None,
1107 }
1108 }
1109
1110 #[inline]
1114 pub fn annot_get_color(&self) -> Option<(u8, u8, u8, u8)> {
1115 self.color_rgba()
1116 }
1117
1118 #[deprecated(since = "0.1.0", note = "use annot_get_color() instead")]
1120 #[inline]
1121 pub fn get_color(&self) -> Option<(u8, u8, u8, u8)> {
1122 self.color_rgba()
1123 }
1124
1125 #[deprecated(since = "0.1.0", note = "use annot_get_color() instead")]
1127 #[inline]
1128 pub fn get_color_rgba(&self) -> Option<(u8, u8, u8, u8)> {
1129 self.color_rgba()
1130 }
1131
1132 pub fn string_value(&self, key: &str) -> Option<&str> {
1140 match key {
1141 "Contents" => self.contents.as_deref(),
1142 "NM" => self.name.as_deref(),
1143 "T" => self.field_name.as_deref(),
1144 "TU" => self.alternate_name.as_deref(),
1145 "V" => self.field_value.as_deref(),
1146 _ => None,
1147 }
1148 }
1149
1150 #[inline]
1154 pub fn annot_get_string_value(&self, key: &str) -> Option<&str> {
1155 self.string_value(key)
1156 }
1157
1158 #[deprecated(since = "0.1.0", note = "use annot_get_string_value() instead")]
1160 #[inline]
1161 pub fn get_string_value(&self, key: &str) -> Option<&str> {
1162 self.string_value(key)
1163 }
1164
1165 pub fn field_name(&self) -> Option<&str> {
1172 self.field_name.as_deref()
1173 }
1174
1175 #[inline]
1179 pub fn annot_get_form_field_name(&self) -> Option<&str> {
1180 self.field_name()
1181 }
1182
1183 #[deprecated(since = "0.1.0", note = "use annot_get_form_field_name() instead")]
1185 #[inline]
1186 pub fn get_form_field_name(&self) -> Option<&str> {
1187 self.field_name()
1188 }
1189
1190 #[deprecated(since = "0.1.0", note = "use annot_get_form_field_name() instead")]
1192 #[inline]
1193 pub fn get_field_name(&self) -> Option<&str> {
1194 self.field_name()
1195 }
1196
1197 pub fn alternate_field_name(&self) -> Option<&str> {
1203 self.alternate_name.as_deref()
1204 }
1205
1206 #[inline]
1210 pub fn annot_get_form_field_alternate_name(&self) -> Option<&str> {
1211 self.alternate_field_name()
1212 }
1213
1214 #[deprecated(
1216 since = "0.1.0",
1217 note = "use annot_get_form_field_alternate_name() instead"
1218 )]
1219 #[inline]
1220 pub fn get_form_field_alternate_name(&self) -> Option<&str> {
1221 self.alternate_field_name()
1222 }
1223
1224 #[deprecated(
1226 since = "0.1.0",
1227 note = "use annot_get_form_field_alternate_name() instead"
1228 )]
1229 #[inline]
1230 pub fn get_alternate_field_name(&self) -> Option<&str> {
1231 self.alternate_field_name()
1232 }
1233
1234 pub fn field_value_str(&self) -> Option<&str> {
1242 self.field_value.as_deref()
1243 }
1244
1245 #[inline]
1249 pub fn annot_get_form_field_value(&self) -> Option<&str> {
1250 self.field_value_str()
1251 }
1252
1253 #[deprecated(since = "0.1.0", note = "use annot_get_form_field_value() instead")]
1255 #[inline]
1256 pub fn get_form_field_value(&self) -> Option<&str> {
1257 self.field_value_str()
1258 }
1259
1260 #[deprecated(since = "0.1.0", note = "use annot_get_form_field_value() instead")]
1262 #[inline]
1263 pub fn get_field_value(&self) -> Option<&str> {
1264 self.field_value_str()
1265 }
1266
1267 pub fn has_key(&self, key: &str) -> bool {
1271 self.string_value(key).is_some()
1272 }
1273
1274 #[inline]
1278 pub fn annot_has_key(&self, key: &str) -> bool {
1279 self.has_key(key)
1280 }
1281
1282 pub fn number_value(&self, key: &str) -> Option<f32> {
1288 match key {
1289 "F" => Some(self.flags.bits() as f32),
1290 "Border-Width" => self.border.as_ref().map(|b| b.width),
1291 _ => None,
1292 }
1293 }
1294
1295 #[inline]
1299 pub fn annot_get_number_value(&self, key: &str) -> Option<f32> {
1300 self.number_value(key)
1301 }
1302
1303 #[deprecated(since = "0.1.0", note = "use annot_get_number_value() instead")]
1305 #[inline]
1306 pub fn get_number_value(&self, key: &str) -> Option<f32> {
1307 self.number_value(key)
1308 }
1309
1310 pub fn value_type(&self, key: &str) -> PdfValueType {
1327 match key {
1328 "Contents" => {
1329 if self.contents.is_some() {
1330 PdfValueType::String
1331 } else {
1332 PdfValueType::Unknown
1333 }
1334 }
1335 "NM" => {
1336 if self.name.is_some() {
1337 PdfValueType::String
1338 } else {
1339 PdfValueType::Unknown
1340 }
1341 }
1342 "F" => PdfValueType::Number,
1343 "Subtype" => PdfValueType::Name,
1344 "AP" => {
1345 if self.appearance.is_some() {
1346 PdfValueType::Dictionary
1347 } else {
1348 PdfValueType::Unknown
1349 }
1350 }
1351 "Rect" => PdfValueType::Array,
1352 "C" => {
1353 if self.color.is_some() {
1354 PdfValueType::Array
1355 } else {
1356 PdfValueType::Unknown
1357 }
1358 }
1359 "Vertices" => {
1360 if self.subtype_data.vertices.is_some() {
1361 PdfValueType::Array
1362 } else {
1363 PdfValueType::Unknown
1364 }
1365 }
1366 "QuadPoints" => {
1367 if self.subtype_data.quad_points.is_some() {
1368 PdfValueType::Array
1369 } else {
1370 PdfValueType::Unknown
1371 }
1372 }
1373 "InkList" => {
1374 if self.subtype_data.ink_list.is_some() {
1375 PdfValueType::Array
1376 } else {
1377 PdfValueType::Unknown
1378 }
1379 }
1380 "IRT" => {
1381 if self.irt_ref.is_some() {
1382 PdfValueType::Reference
1383 } else {
1384 PdfValueType::Unknown
1385 }
1386 }
1387 _ => PdfValueType::Unknown,
1388 }
1389 }
1390
1391 #[inline]
1395 pub fn annot_get_value_type(&self, key: &str) -> PdfValueType {
1396 self.value_type(key)
1397 }
1398
1399 #[deprecated(since = "0.1.0", note = "use annot_get_value_type() instead")]
1401 #[inline]
1402 pub fn get_value_type(&self, key: &str) -> PdfValueType {
1403 self.value_type(key)
1404 }
1405
1406 pub fn appearance_stream_bytes(&self, mode: AppearanceMode) -> Option<&[u8]> {
1416 match mode {
1417 AppearanceMode::Normal => self.ap_n_bytes.as_deref(),
1418 AppearanceMode::Rollover => self.ap_r_bytes.as_deref(),
1419 AppearanceMode::Down => self.ap_d_bytes.as_deref(),
1420 }
1421 }
1422
1423 #[inline]
1427 pub fn annot_get_ap(&self, mode: AppearanceMode) -> Option<&[u8]> {
1428 self.appearance_stream_bytes(mode)
1429 }
1430
1431 #[deprecated(since = "0.1.0", note = "use annot_get_ap() instead")]
1433 #[inline]
1434 pub fn get_ap(&self, mode: AppearanceMode) -> Option<&[u8]> {
1435 self.appearance_stream_bytes(mode)
1436 }
1437
1438 #[deprecated(since = "0.1.0", note = "use annot_get_ap() instead")]
1440 #[inline]
1441 pub fn get_appearance_stream_bytes(&self, mode: AppearanceMode) -> Option<&[u8]> {
1442 self.appearance_stream_bytes(mode)
1443 }
1444
1445 pub fn set_appearance_stream_bytes(
1454 &mut self,
1455 _mode: AppearanceMode,
1456 _data: &[u8],
1457 ) -> DocResult<()> {
1458 Err(DocError::NotSupported(
1459 "set_appearance_stream_bytes: AP stream mutation not yet supported".into(),
1460 ))
1461 }
1462
1463 #[inline]
1467 pub fn annot_set_ap(&mut self, mode: AppearanceMode, data: &[u8]) -> DocResult<()> {
1468 self.set_appearance_stream_bytes(mode, data)
1469 }
1470
1471 #[deprecated(since = "0.1.0", note = "use annot_set_ap() instead")]
1473 #[inline]
1474 pub fn set_ap(&mut self, mode: AppearanceMode, data: &[u8]) -> DocResult<()> {
1475 self.set_appearance_stream_bytes(mode, data)
1476 }
1477
1478 pub fn linked_annot_ref(&self) -> Option<ObjectId> {
1487 self.irt_ref
1488 }
1489
1490 #[inline]
1494 pub fn annot_get_linked_annot(&self) -> Option<ObjectId> {
1495 self.linked_annot_ref()
1496 }
1497
1498 #[deprecated(since = "0.1.0", note = "use annot_get_linked_annot() instead")]
1500 #[inline]
1501 pub fn get_linked_annot(&self) -> Option<ObjectId> {
1502 self.linked_annot_ref()
1503 }
1504
1505 pub fn form_field_flags(&self) -> Option<FormFieldFlags> {
1521 self.form_field_flags
1522 }
1523
1524 #[inline]
1528 pub fn annot_get_form_field_flags(&self) -> Option<FormFieldFlags> {
1529 self.form_field_flags()
1530 }
1531
1532 #[deprecated(since = "0.1.0", note = "use annot_get_form_field_flags() instead")]
1534 #[inline]
1535 pub fn get_form_field_flags(&self) -> Option<FormFieldFlags> {
1536 self.form_field_flags()
1537 }
1538
1539 pub fn font_size(&self) -> Option<f32> {
1552 let da = self.subtype_data.default_appearance.as_deref()?;
1553 let parsed = parse_default_appearance(da);
1554 let size = parsed.font_size as f32;
1555 if size == 0.0 { None } else { Some(size) }
1556 }
1557
1558 #[inline]
1562 pub fn annot_get_font_size(&self) -> Option<f32> {
1563 self.font_size()
1564 }
1565
1566 pub fn font_color(&self) -> Option<(u8, u8, u8)> {
1580 let da = self.subtype_data.default_appearance.as_deref()?;
1581 let parsed = parse_default_appearance(da);
1582 let color = parsed.color?;
1583 match color.len() {
1584 1 => {
1586 let g = (color[0].clamp(0.0, 1.0) * 255.0).round() as u8;
1587 Some((g, g, g))
1588 }
1589 3 => {
1591 let r = (color[0].clamp(0.0, 1.0) * 255.0).round() as u8;
1592 let g = (color[1].clamp(0.0, 1.0) * 255.0).round() as u8;
1593 let b = (color[2].clamp(0.0, 1.0) * 255.0).round() as u8;
1594 Some((r, g, b))
1595 }
1596 4 => {
1598 let c = color[0].clamp(0.0, 1.0);
1599 let m = color[1].clamp(0.0, 1.0);
1600 let y = color[2].clamp(0.0, 1.0);
1601 let k = color[3].clamp(0.0, 1.0);
1602 let r = ((1.0 - c) * (1.0 - k) * 255.0).round() as u8;
1603 let g = ((1.0 - m) * (1.0 - k) * 255.0).round() as u8;
1604 let b = ((1.0 - y) * (1.0 - k) * 255.0).round() as u8;
1605 Some((r, g, b))
1606 }
1607 _ => None,
1608 }
1609 }
1610
1611 #[inline]
1615 pub fn annot_get_font_color(&self) -> Option<(u8, u8, u8)> {
1616 self.font_color()
1617 }
1618
1619 pub fn set_font_color(&mut self, r: u8, g: u8, b: u8) -> DocResult<()> {
1630 match self.subtype {
1631 AnnotationType::FreeText | AnnotationType::Widget => {}
1632 _ => {
1633 return Err(DocError::NotSupported(
1634 "set_font_color: only FreeText and Widget annotations support /DA".into(),
1635 ));
1636 }
1637 }
1638 let rf = r as f64 / 255.0;
1639 let gf = g as f64 / 255.0;
1640 let bf = b as f64 / 255.0;
1641
1642 let color_token = format!("{rf:.4} {gf:.4} {bf:.4} rg");
1643 let current = self
1644 .subtype_data
1645 .default_appearance
1646 .get_or_insert_with(String::new);
1647 let stripped = strip_da_color(current);
1649 *current = format!("{stripped} {color_token}").trim().to_owned();
1650 Ok(())
1651 }
1652
1653 #[inline]
1657 pub fn annot_set_font_color(&mut self, r: u8, g: u8, b: u8) -> DocResult<()> {
1658 self.set_font_color(r, g, b)
1659 }
1660
1661 pub fn form_additional_action_javascript(&self, event_type: AActionType) -> String {
1677 self.additional_actions
1678 .as_ref()
1679 .and_then(|aa| aa.action(event_type))
1680 .map(|a| a.javascript())
1681 .unwrap_or_default()
1682 }
1683
1684 #[inline]
1688 pub fn annot_get_form_additional_action_javascript(&self, event_type: AActionType) -> String {
1689 self.form_additional_action_javascript(event_type)
1690 }
1691
1692 pub fn link_ref(&self) -> (Option<&Action>, Option<&Destination>) {
1704 (self.action.as_ref(), self.destination.as_ref())
1705 }
1706
1707 #[inline]
1711 pub fn annot_get_link(&self) -> (Option<&Action>, Option<&Destination>) {
1712 self.link_ref()
1713 }
1714
1715 #[deprecated(since = "0.1.0", note = "use annot_get_link() instead")]
1717 #[inline]
1718 pub fn get_link(&self) -> (Option<&Action>, Option<&Destination>) {
1719 self.link_ref()
1720 }
1721
1722 pub fn file_attachment(&self) -> Option<&FileSpec> {
1727 self.file_spec.as_ref()
1728 }
1729
1730 #[inline]
1734 pub fn annot_get_file_attachment(&self) -> Option<&FileSpec> {
1735 self.file_attachment()
1736 }
1737
1738 #[deprecated(since = "0.1.0", note = "use annot_get_file_attachment() instead")]
1740 #[inline]
1741 pub fn get_file_attachment(&self) -> Option<&FileSpec> {
1742 self.file_attachment()
1743 }
1744
1745 pub fn form_field_type(&self) -> Option<FormFieldType> {
1756 self.form_field_type.clone()
1757 }
1758
1759 #[inline]
1763 pub fn annot_get_form_field_type(&self) -> Option<FormFieldType> {
1764 self.form_field_type()
1765 }
1766
1767 pub fn form_control_count(&self) -> DocResult<usize> {
1780 Err(DocError::NotSupported(
1781 "form_control_count: requires document-level AcroForm traversal (ADR-002)".into(),
1782 ))
1783 }
1784
1785 #[inline]
1789 pub fn annot_get_form_control_count(&self) -> DocResult<usize> {
1790 self.form_control_count()
1791 }
1792
1793 pub fn form_control_index(&self) -> DocResult<usize> {
1802 Err(DocError::NotSupported(
1803 "form_control_index: requires document-level AcroForm traversal (ADR-002)".into(),
1804 ))
1805 }
1806
1807 #[inline]
1811 pub fn annot_get_form_control_index(&self) -> DocResult<usize> {
1812 self.form_control_index()
1813 }
1814
1815 pub fn form_field_export_value(&self) -> DocResult<String> {
1824 Err(DocError::NotSupported(
1825 "form_field_export_value: requires document-level AcroForm traversal (ADR-002)".into(),
1826 ))
1827 }
1828
1829 #[inline]
1833 pub fn annot_get_form_field_export_value(&self) -> DocResult<String> {
1834 self.form_field_export_value()
1835 }
1836
1837 pub fn is_checked(&self) -> bool {
1848 match self.field_value.as_deref() {
1849 None | Some("Off") => false,
1850 Some(_) => true,
1851 }
1852 }
1853
1854 #[inline]
1858 pub fn annot_is_checked(&self) -> bool {
1859 self.is_checked()
1860 }
1861
1862 pub fn option_count(&self) -> usize {
1868 self.options.as_ref().map_or(0, |v| v.len())
1869 }
1870
1871 #[inline]
1875 pub fn annot_get_option_count(&self) -> usize {
1876 self.option_count()
1877 }
1878
1879 pub fn option_label(&self, index: usize) -> Option<&str> {
1884 self.options
1885 .as_ref()?
1886 .get(index)
1887 .map(|o| o.display_value.as_str())
1888 }
1889
1890 #[inline]
1894 pub fn annot_get_option_label(&self, index: usize) -> Option<&str> {
1895 self.option_label(index)
1896 }
1897
1898 pub fn is_option_selected(&self, index: usize) -> bool {
1905 let Some(opts) = self.options.as_ref() else {
1906 return false;
1907 };
1908 let Some(opt) = opts.get(index) else {
1909 return false;
1910 };
1911 match self.field_value.as_deref() {
1912 Some(v) => v == opt.export_value,
1913 None => false,
1914 }
1915 }
1916
1917 #[inline]
1921 pub fn annot_is_option_selected(&self, index: usize) -> bool {
1922 self.is_option_selected(index)
1923 }
1924
1925 pub fn focusable_subtype_count(&self) -> DocResult<usize> {
1940 Err(DocError::NotSupported(
1941 "focusable_subtype_count: requires FPDF_FORMHANDLE viewer session (ADR-002)".into(),
1942 ))
1943 }
1944
1945 #[inline]
1949 pub fn annot_get_focusable_subtypes_count(&self) -> DocResult<usize> {
1950 self.focusable_subtype_count()
1951 }
1952
1953 pub fn set_focusable_subtypes(&mut self, _subtypes: &[AnnotationType]) -> DocResult<()> {
1963 Err(DocError::NotSupported(
1964 "set_focusable_subtypes: requires FPDF_FORMHANDLE viewer session (ADR-002)".into(),
1965 ))
1966 }
1967
1968 #[inline]
1972 pub fn annot_set_focusable_subtypes(&mut self, subtypes: &[AnnotationType]) -> DocResult<()> {
1973 self.set_focusable_subtypes(subtypes)
1974 }
1975
1976 pub fn set_flags(&mut self, flags: AnnotationFlags) -> DocResult<()> {
1987 self.flags = flags;
1988 Ok(())
1989 }
1990
1991 #[inline]
1995 pub fn annot_set_flags(&mut self, flags: AnnotationFlags) -> DocResult<()> {
1996 self.set_flags(flags)
1997 }
1998
1999 pub fn set_form_field_flags(&mut self, flags: FormFieldFlags) -> DocResult<()> {
2006 self.form_field_flags = Some(flags);
2007 Ok(())
2008 }
2009
2010 #[inline]
2014 pub fn annot_set_form_field_flags(&mut self, flags: FormFieldFlags) -> DocResult<()> {
2015 self.set_form_field_flags(flags)
2016 }
2017}
2018
2019#[allow(clippy::type_complexity)]
2026fn extract_ap_stream_bytes<S: PdfSource>(
2027 dict: &std::collections::HashMap<Name, Object>,
2028 store: &ObjectStore<S>,
2029 object_id: Option<ObjectId>,
2030) -> (Option<Vec<u8>>, Option<Vec<u8>>, Option<Vec<u8>>) {
2031 let ap_entry = match dict.get(&Name::ap()) {
2032 None => return (None, None, None),
2033 Some(o) => o,
2034 };
2035 let resolved_ap = match store.deep_resolve(ap_entry) {
2036 Ok(o) => o,
2037 Err(_) => return (None, None, None),
2038 };
2039 let ap_dict = match resolved_ap.as_dict() {
2040 None => return (None, None, None),
2041 Some(d) => d,
2042 };
2043
2044 let decode_sub = |key: &Name| -> Option<Vec<u8>> {
2045 let sub = ap_dict.get(key)?;
2046 let stream_obj = if let Some(ref_id) = sub.as_reference() {
2048 match store.resolve(ref_id) {
2049 Ok(o) => o,
2050 Err(_) => return None,
2051 }
2052 } else {
2053 match store.deep_resolve(sub) {
2055 Ok(o) => o,
2056 Err(_) => return None,
2057 }
2058 };
2059 let actual_stream =
2061 if stream_obj.as_dict().is_some() && stream_obj.as_stream_dict().is_none() {
2062 let state_dict = stream_obj.as_dict()?;
2064 let mut found = None;
2065 for val in state_dict.values() {
2066 if let Some(ref_id) = val.as_reference() {
2067 if let Ok(s) = store.resolve(ref_id) {
2068 found = Some(s);
2069 break;
2070 }
2071 }
2072 }
2073 found?
2074 } else {
2075 stream_obj
2076 };
2077 let dummy_id = ObjectId::new(0, 0);
2079 let oid = object_id.unwrap_or(dummy_id);
2080 store.decode_stream_for_object(actual_stream, oid).ok()
2081 };
2082
2083 let n = decode_sub(&Name::n());
2084 let r = decode_sub(&Name::r());
2085 let d = decode_sub(&Name::d());
2086 (n, r, d)
2087}
2088
2089fn parse_rect<S: PdfSource>(
2091 dict: &std::collections::HashMap<Name, Object>,
2092 store: &ObjectStore<S>,
2093) -> DocResult<[f32; 4]> {
2094 let rect_obj = dict
2095 .get(&Name::rect())
2096 .ok_or_else(|| DocError::MissingKey("/Rect".into()))?;
2097 let resolved = store
2098 .deep_resolve(rect_obj)
2099 .map_err(|e| DocError::Parser(e.to_string()))?;
2100 let arr = resolved.as_array().ok_or(DocError::UnexpectedType)?;
2101 if arr.len() < 4 {
2102 return Err(DocError::UnexpectedType);
2103 }
2104
2105 let get = |idx: usize| -> f32 {
2106 arr.get(idx)
2107 .and_then(|o| o.as_f64())
2108 .map(|f| f as f32)
2109 .unwrap_or(0.0)
2110 };
2111
2112 Ok([get(0), get(1), get(2), get(3)])
2113}
2114
2115fn parse_color_array(obj: &Object) -> Option<Vec<f32>> {
2117 let arr = obj.as_array()?;
2118 let colors: Vec<f32> = arr
2119 .iter()
2120 .filter_map(|o| o.as_f64().map(|f| f as f32))
2121 .collect();
2122 if colors.is_empty() && !arr.is_empty() {
2123 return None;
2124 }
2125 Some(colors)
2126}
2127
2128fn parse_border<S: PdfSource>(
2130 dict: &std::collections::HashMap<Name, Object>,
2131 store: &ObjectStore<S>,
2132) -> Option<AnnotationBorder> {
2133 if let Some(bs_obj) = dict.get(&Name::bs()) {
2135 if let Ok(resolved) = store.deep_resolve(bs_obj) {
2136 if let Some(bs_dict) = resolved.as_dict() {
2137 let width = bs_dict
2138 .get(&Name::w())
2139 .and_then(|o| o.as_f64())
2140 .map(|f| f as f32)
2141 .unwrap_or(1.0);
2142 let style = bs_dict
2143 .get(&Name::s())
2144 .and_then(|o| o.as_name())
2145 .map(|n| BorderStyle::from_name(&n.as_str()))
2146 .unwrap_or(BorderStyle::Solid);
2147 return Some(AnnotationBorder { width, style });
2148 }
2149 }
2150 }
2151
2152 if let Some(border_obj) = dict.get(&Name::border()) {
2154 if let Ok(resolved) = store.deep_resolve(border_obj) {
2155 if let Some(arr) = resolved.as_array() {
2156 let width = arr
2158 .get(2)
2159 .and_then(|o| o.as_f64())
2160 .map(|f| f as f32)
2161 .unwrap_or(1.0);
2162 return Some(AnnotationBorder {
2163 width,
2164 style: BorderStyle::Solid,
2165 });
2166 }
2167 }
2168 }
2169
2170 None
2171}
2172
2173fn parse_subtype_data<S: PdfSource>(
2175 dict: &std::collections::HashMap<Name, Object>,
2176 store: &ObjectStore<S>,
2177 subtype: AnnotationType,
2178) -> AnnotationSubtypeData {
2179 let mut data = AnnotationSubtypeData::default();
2180
2181 match subtype {
2182 AnnotationType::Highlight
2183 | AnnotationType::Underline
2184 | AnnotationType::Squiggly
2185 | AnnotationType::StrikeOut
2186 | AnnotationType::Link => {
2187 data.quad_points = parse_f32_array_field(dict, store, &Name::quad_points());
2188 }
2189 AnnotationType::Line => {
2190 data.line_points = parse_line_points(dict, store);
2191 data.leader_line_length = parse_f32_field(dict, store, &Name::ll());
2192 }
2193 AnnotationType::Polygon | AnnotationType::PolyLine => {
2194 data.vertices = parse_f32_array_field(dict, store, &Name::vertices());
2195 }
2196 AnnotationType::Ink => {
2197 data.ink_list = parse_ink_list(dict, store);
2198 }
2199 AnnotationType::FreeText | AnnotationType::Widget => {
2200 data.default_appearance = parse_string_field(dict, store, &Name::da());
2201 }
2202 AnnotationType::Stamp => {
2203 data.stamp_name = dict
2204 .get(&Name::name_key())
2205 .and_then(|o| store.deep_resolve(o).ok())
2206 .and_then(|r| r.as_name().map(|n| n.as_str().into_owned()));
2207 }
2208 _ => {}
2209 }
2210
2211 data
2212}
2213
2214fn parse_f32_array_field<S: PdfSource>(
2215 dict: &std::collections::HashMap<Name, Object>,
2216 store: &ObjectStore<S>,
2217 key: &Name,
2218) -> Option<Vec<f32>> {
2219 let obj = dict.get(key)?;
2220 let resolved = store.deep_resolve(obj).ok()?;
2221 let arr = resolved.as_array()?;
2222 let values: Vec<f32> = arr
2223 .iter()
2224 .filter_map(|o| o.as_f64().map(|f| f as f32))
2225 .collect();
2226 if values.is_empty() {
2227 None
2228 } else {
2229 Some(values)
2230 }
2231}
2232
2233fn parse_f32_field<S: PdfSource>(
2234 dict: &std::collections::HashMap<Name, Object>,
2235 store: &ObjectStore<S>,
2236 key: &Name,
2237) -> Option<f32> {
2238 let obj = dict.get(key)?;
2239 store.deep_resolve(obj).ok()?.as_f64().map(|f| f as f32)
2240}
2241
2242fn parse_string_field<S: PdfSource>(
2243 dict: &std::collections::HashMap<Name, Object>,
2244 store: &ObjectStore<S>,
2245 key: &Name,
2246) -> Option<String> {
2247 let obj = dict.get(key)?;
2248 let resolved = store.deep_resolve(obj).ok()?;
2249 resolved.as_string().map(|s| s.to_string_lossy())
2250}
2251
2252fn parse_line_points<S: PdfSource>(
2253 dict: &std::collections::HashMap<Name, Object>,
2254 store: &ObjectStore<S>,
2255) -> Option<[f32; 4]> {
2256 let values = parse_f32_array_field(dict, store, &Name::l())?;
2257 if values.len() >= 4 {
2258 Some([values[0], values[1], values[2], values[3]])
2259 } else {
2260 None
2261 }
2262}
2263
2264fn strip_da_color(da: &str) -> String {
2269 let mut result: Vec<&str> = Vec::new();
2278 for token in da.split_whitespace() {
2279 match token {
2280 "g" => {
2281 if !result.is_empty() {
2282 result.pop();
2283 }
2284 }
2285 "rg" => {
2286 let len = result.len();
2287 if len >= 3 {
2288 result.truncate(len - 3);
2289 }
2290 }
2291 "k" => {
2292 let len = result.len();
2293 if len >= 4 {
2294 result.truncate(len - 4);
2295 }
2296 }
2297 _ => {
2298 result.push(token);
2299 }
2300 }
2301 }
2302 result.join(" ")
2303}
2304
2305fn parse_choice_options<S: PdfSource>(
2310 dict: &std::collections::HashMap<Name, Object>,
2311 store: &ObjectStore<S>,
2312) -> Vec<ChoiceOption> {
2313 let opt_obj = match dict.get(&Name::opt()) {
2314 Some(o) => o,
2315 None => return Vec::new(),
2316 };
2317 let resolved = match store.deep_resolve(opt_obj).ok() {
2318 Some(o) => o,
2319 None => return Vec::new(),
2320 };
2321 let arr = match resolved.as_array() {
2322 Some(a) => a,
2323 None => return Vec::new(),
2324 };
2325
2326 let mut options = Vec::with_capacity(arr.len());
2327 for item in arr {
2328 let ri = match store.deep_resolve(item).ok() {
2329 Some(o) => o,
2330 None => continue,
2331 };
2332 if let Some(sub) = ri.as_array() {
2333 if sub.len() >= 2 {
2334 let export = store
2335 .deep_resolve(&sub[0])
2336 .ok()
2337 .and_then(|o| o.as_string().map(|s| s.to_string_lossy()))
2338 .unwrap_or_default();
2339 let display = store
2340 .deep_resolve(&sub[1])
2341 .ok()
2342 .and_then(|o| o.as_string().map(|s| s.to_string_lossy()))
2343 .unwrap_or_default();
2344 options.push(ChoiceOption {
2345 export_value: export,
2346 display_value: display,
2347 });
2348 }
2349 } else if let Some(s) = ri.as_string() {
2350 let val = s.to_string_lossy();
2351 options.push(ChoiceOption {
2352 export_value: val.clone(),
2353 display_value: val,
2354 });
2355 }
2356 }
2357 options
2358}
2359
2360fn parse_ink_list<S: PdfSource>(
2361 dict: &std::collections::HashMap<Name, Object>,
2362 store: &ObjectStore<S>,
2363) -> Option<Vec<Vec<f32>>> {
2364 let obj = dict.get(&Name::ink_list())?;
2365 let resolved = store.deep_resolve(obj).ok()?;
2366 let outer = resolved.as_array()?;
2367 let mut strokes = Vec::new();
2368 for item in outer {
2369 if let Ok(inner) = store.deep_resolve(item) {
2370 if let Some(arr) = inner.as_array() {
2371 let points: Vec<f32> = arr
2372 .iter()
2373 .filter_map(|o| o.as_f64().map(|f| f as f32))
2374 .collect();
2375 if !points.is_empty() {
2376 strokes.push(points);
2377 }
2378 }
2379 }
2380 }
2381 if strokes.is_empty() {
2382 None
2383 } else {
2384 Some(strokes)
2385 }
2386}
2387
2388#[cfg(test)]
2389mod tests {
2390 use super::*;
2391 use rpdfium_parser::Object;
2392
2393 #[test]
2396 fn test_annotation_flags_parsing() {
2397 let flags = AnnotationFlags::from_bits(6);
2399 assert!(!flags.invisible());
2400 assert!(flags.hidden());
2401 assert!(flags.print());
2402 assert!(!flags.no_zoom());
2403 assert!(!flags.read_only());
2404 }
2405
2406 #[test]
2407 fn test_annotation_flags_invisible_hidden_print() {
2408 let invisible = AnnotationFlags::from_bits(1);
2409 assert!(invisible.invisible());
2410 assert!(!invisible.hidden());
2411
2412 let hidden = AnnotationFlags::from_bits(2);
2413 assert!(hidden.hidden());
2414 assert!(!hidden.is_visible());
2415
2416 let print = AnnotationFlags::from_bits(4);
2417 assert!(print.print());
2418 assert!(print.is_visible());
2419
2420 let no_view = AnnotationFlags::from_bits(32);
2421 assert!(no_view.no_view());
2422 assert!(!no_view.is_visible());
2423 }
2424
2425 #[test]
2426 fn test_annotation_flags_toggle_no_view_locked_contents() {
2427 let toggle = AnnotationFlags::from_bits(256);
2428 assert!(toggle.toggle_no_view());
2429 assert!(!toggle.locked_contents());
2430
2431 let lc = AnnotationFlags::from_bits(512);
2432 assert!(!lc.toggle_no_view());
2433 assert!(lc.locked_contents());
2434
2435 let both = AnnotationFlags::from_bits(256 | 512);
2436 assert!(both.toggle_no_view());
2437 assert!(both.locked_contents());
2438 }
2439
2440 #[test]
2441 fn test_annotation_flags_locked_read_only() {
2442 let locked = AnnotationFlags::from_bits(128);
2443 assert!(locked.locked());
2444 assert!(!locked.read_only());
2445
2446 let read_only = AnnotationFlags::from_bits(64);
2447 assert!(read_only.read_only());
2448 assert!(!read_only.locked());
2449 }
2450
2451 #[test]
2452 fn test_annotation_type_from_name() {
2453 assert_eq!(AnnotationType::from_name("Text"), AnnotationType::Text);
2454 assert_eq!(AnnotationType::from_name("Link"), AnnotationType::Link);
2455 assert_eq!(
2456 AnnotationType::from_name("FreeText"),
2457 AnnotationType::FreeText
2458 );
2459 assert_eq!(
2460 AnnotationType::from_name("Highlight"),
2461 AnnotationType::Highlight
2462 );
2463 assert_eq!(
2464 AnnotationType::from_name("StrikeOut"),
2465 AnnotationType::StrikeOut
2466 );
2467 assert_eq!(AnnotationType::from_name("Widget"), AnnotationType::Widget);
2468 assert_eq!(AnnotationType::from_name("Movie"), AnnotationType::Movie);
2469 assert_eq!(AnnotationType::from_name("Screen"), AnnotationType::Screen);
2470 assert_eq!(
2471 AnnotationType::from_name("PrinterMark"),
2472 AnnotationType::PrinterMark
2473 );
2474 assert_eq!(
2475 AnnotationType::from_name("TrapNet"),
2476 AnnotationType::TrapNet
2477 );
2478 assert_eq!(
2479 AnnotationType::from_name("Watermark"),
2480 AnnotationType::Watermark
2481 );
2482 assert_eq!(AnnotationType::from_name("3D"), AnnotationType::ThreeD);
2483 assert_eq!(
2484 AnnotationType::from_name("RichMedia"),
2485 AnnotationType::RichMedia
2486 );
2487 assert_eq!(
2488 AnnotationType::from_name("XFAWidget"),
2489 AnnotationType::XFAWidget
2490 );
2491 assert_eq!(AnnotationType::from_name("Redact"), AnnotationType::Redact);
2492 assert_eq!(AnnotationType::from_name("Unknown"), AnnotationType::Other);
2493 }
2494
2495 #[test]
2496 fn test_border_style_from_name() {
2497 assert_eq!(BorderStyle::from_name("S"), BorderStyle::Solid);
2498 assert_eq!(BorderStyle::from_name("D"), BorderStyle::Dashed);
2499 assert_eq!(BorderStyle::from_name("B"), BorderStyle::Beveled);
2500 assert_eq!(BorderStyle::from_name("I"), BorderStyle::Inset);
2501 assert_eq!(BorderStyle::from_name("U"), BorderStyle::Underline);
2502 assert_eq!(BorderStyle::from_name("X"), BorderStyle::Solid); }
2504
2505 #[test]
2506 fn test_annotation_border_default() {
2507 let border = AnnotationBorder::default();
2508 assert_eq!(border.width, 1.0);
2509 assert_eq!(border.style, BorderStyle::Solid);
2510 }
2511
2512 #[test]
2513 fn test_color_array_parsing() {
2514 let empty = parse_color_array(&Object::Array(vec![]));
2516 assert_eq!(empty, Some(vec![]));
2517
2518 let gray = parse_color_array(&Object::Array(vec![Object::Real(0.5)]));
2520 assert_eq!(gray, Some(vec![0.5]));
2521
2522 let rgb = parse_color_array(&Object::Array(vec![
2524 Object::Real(1.0),
2525 Object::Real(0.0),
2526 Object::Real(0.0),
2527 ]));
2528 assert_eq!(rgb, Some(vec![1.0, 0.0, 0.0]));
2529 }
2530
2531 #[test]
2532 fn test_set_rect_updates_in_memory() {
2533 let mut annot = Annotation {
2534 subtype: AnnotationType::Text,
2535 rect: [0.0, 0.0, 50.0, 50.0],
2536 contents: None,
2537 flags: AnnotationFlags::from_bits(0),
2538 name: None,
2539 appearance: None,
2540 color: None,
2541 border: None,
2542 action: None,
2543 destination: None,
2544 subtype_data: AnnotationSubtypeData::default(),
2545 mk: None,
2546 file_spec: None,
2547 parent_ref: None,
2548 object_id: None,
2549 open: None,
2550 ap_n_bytes: None,
2551 ap_r_bytes: None,
2552 ap_d_bytes: None,
2553 irt_ref: None,
2554 field_name: None,
2555 alternate_name: None,
2556 field_value: None,
2557 form_field_flags: None,
2558 additional_actions: None,
2559 form_field_type: None,
2560 options: None,
2561 };
2562 annot.set_rect([10.0, 20.0, 100.0, 50.0]).unwrap();
2563 assert_eq!(annot.rect, [10.0, 20.0, 100.0, 50.0]);
2564 }
2565
2566 #[test]
2567 fn test_set_open_state_updates_in_memory() {
2568 let mut annot = Annotation {
2569 subtype: AnnotationType::Text,
2570 rect: [0.0, 0.0, 50.0, 50.0],
2571 contents: None,
2572 flags: AnnotationFlags::from_bits(0),
2573 name: None,
2574 appearance: None,
2575 color: None,
2576 border: None,
2577 action: None,
2578 destination: None,
2579 subtype_data: AnnotationSubtypeData::default(),
2580 mk: None,
2581 file_spec: None,
2582 parent_ref: None,
2583 object_id: None,
2584 open: None,
2585 ap_n_bytes: None,
2586 ap_r_bytes: None,
2587 ap_d_bytes: None,
2588 irt_ref: None,
2589 field_name: None,
2590 alternate_name: None,
2591 field_value: None,
2592 form_field_flags: None,
2593 additional_actions: None,
2594 form_field_type: None,
2595 options: None,
2596 };
2597 annot.set_open_state(true).unwrap();
2598 assert_eq!(annot.open, Some(true));
2599 }
2600
2601 #[test]
2602 fn test_get_quad_points_returns_none_when_absent() {
2603 let annot = Annotation {
2604 subtype: AnnotationType::Text,
2605 rect: [0.0, 0.0, 50.0, 50.0],
2606 contents: None,
2607 flags: AnnotationFlags::from_bits(0),
2608 name: None,
2609 appearance: None,
2610 color: None,
2611 border: None,
2612 action: None,
2613 destination: None,
2614 subtype_data: AnnotationSubtypeData::default(),
2615 mk: None,
2616 file_spec: None,
2617 parent_ref: None,
2618 object_id: None,
2619 open: None,
2620 ap_n_bytes: None,
2621 ap_r_bytes: None,
2622 ap_d_bytes: None,
2623 irt_ref: None,
2624 field_name: None,
2625 alternate_name: None,
2626 field_value: None,
2627 form_field_flags: None,
2628 additional_actions: None,
2629 form_field_type: None,
2630 options: None,
2631 };
2632 assert!(annot.annot_get_quad_points().is_none());
2633 }
2634
2635 #[test]
2636 fn test_annotation_type_to_name_round_trip() {
2637 let variants = [
2639 AnnotationType::Text,
2640 AnnotationType::Link,
2641 AnnotationType::FreeText,
2642 AnnotationType::Line,
2643 AnnotationType::Square,
2644 AnnotationType::Circle,
2645 AnnotationType::Polygon,
2646 AnnotationType::PolyLine,
2647 AnnotationType::Highlight,
2648 AnnotationType::Underline,
2649 AnnotationType::Squiggly,
2650 AnnotationType::StrikeOut,
2651 AnnotationType::Stamp,
2652 AnnotationType::Caret,
2653 AnnotationType::Ink,
2654 AnnotationType::Popup,
2655 AnnotationType::FileAttachment,
2656 AnnotationType::Sound,
2657 AnnotationType::Widget,
2658 AnnotationType::Movie,
2659 AnnotationType::Screen,
2660 AnnotationType::PrinterMark,
2661 AnnotationType::TrapNet,
2662 AnnotationType::Watermark,
2663 AnnotationType::ThreeD,
2664 AnnotationType::RichMedia,
2665 AnnotationType::XFAWidget,
2666 AnnotationType::Redact,
2667 ];
2668 for variant in &variants {
2669 let name = variant.to_name();
2670 let parsed = AnnotationType::from_name(name);
2671 assert_eq!(
2672 *variant, parsed,
2673 "Round-trip failed for {name}: to_name produced {name:?}, from_name produced {parsed:?}"
2674 );
2675 }
2676 }
2677
2678 #[test]
2679 fn test_annotation_type_other_to_name() {
2680 assert_eq!(AnnotationType::Other.to_name(), "Unknown");
2681 }
2682
2683 #[test]
2684 fn test_get_quad_points_returns_values_when_present() {
2685 let quad = vec![10.0_f32, 20.0, 100.0, 20.0, 100.0, 30.0, 10.0, 30.0];
2686 let annot = Annotation {
2687 subtype: AnnotationType::Highlight,
2688 rect: [0.0, 0.0, 200.0, 50.0],
2689 contents: None,
2690 flags: AnnotationFlags::from_bits(0),
2691 name: None,
2692 appearance: None,
2693 color: None,
2694 border: None,
2695 action: None,
2696 destination: None,
2697 subtype_data: AnnotationSubtypeData {
2698 quad_points: Some(quad.clone()),
2699 ..Default::default()
2700 },
2701 mk: None,
2702 file_spec: None,
2703 parent_ref: None,
2704 object_id: None,
2705 open: None,
2706 ap_n_bytes: None,
2707 ap_r_bytes: None,
2708 ap_d_bytes: None,
2709 irt_ref: None,
2710 field_name: None,
2711 alternate_name: None,
2712 field_value: None,
2713 form_field_flags: None,
2714 additional_actions: None,
2715 form_field_type: None,
2716 options: None,
2717 };
2718 let qp = annot.annot_get_quad_points().unwrap();
2719 assert_eq!(qp.len(), 8);
2720 assert_eq!(qp[0], 10.0);
2721 assert_eq!(qp[4], 100.0);
2722 }
2723
2724 fn make_annot(subtype: AnnotationType) -> Annotation {
2726 Annotation {
2727 subtype,
2728 rect: [10.0, 20.0, 100.0, 50.0],
2729 contents: Some("Test content".into()),
2730 flags: AnnotationFlags::from_bits(4), name: Some("annot-001".into()),
2732 appearance: None,
2733 color: None,
2734 border: None,
2735 action: None,
2736 destination: None,
2737 subtype_data: AnnotationSubtypeData::default(),
2738 mk: None,
2739 file_spec: None,
2740 parent_ref: None,
2741 object_id: None,
2742 open: None,
2743 ap_n_bytes: None,
2744 ap_r_bytes: None,
2745 ap_d_bytes: None,
2746 irt_ref: None,
2747 field_name: None,
2748 alternate_name: None,
2749 field_value: None,
2750 form_field_flags: None,
2751 additional_actions: None,
2752 form_field_type: None,
2753 options: None,
2754 }
2755 }
2756
2757 #[test]
2761 fn test_subtype_getter_returns_correct_variant() {
2762 let a = make_annot(AnnotationType::Highlight);
2763 assert_eq!(a.subtype(), AnnotationType::Highlight);
2764 assert_eq!(a.annot_get_subtype(), AnnotationType::Highlight);
2765 }
2766
2767 #[test]
2771 fn test_rect_getter_returns_rect() {
2772 let a = make_annot(AnnotationType::Text);
2773 assert_eq!(a.rect(), [10.0, 20.0, 100.0, 50.0]);
2774 assert_eq!(a.annot_get_rect(), [10.0, 20.0, 100.0, 50.0]);
2775 }
2776
2777 #[test]
2781 fn test_flags_getter_returns_flags() {
2782 let a = make_annot(AnnotationType::Text);
2783 assert!(a.flags().print());
2784 assert_eq!(a.annot_get_flags().bits(), 4);
2785 }
2786
2787 #[test]
2791 fn test_has_attachment_points_true_for_markup_subtypes() {
2792 for subtype in [
2793 AnnotationType::Highlight,
2794 AnnotationType::Underline,
2795 AnnotationType::Squiggly,
2796 AnnotationType::StrikeOut,
2797 AnnotationType::Link,
2798 ] {
2799 let a = make_annot(subtype);
2800 assert!(
2801 a.has_attachment_points(),
2802 "{subtype:?} should support attachment points"
2803 );
2804 }
2805 }
2806
2807 #[test]
2808 fn test_has_attachment_points_false_for_other_subtypes() {
2809 for subtype in [
2810 AnnotationType::Text,
2811 AnnotationType::Ink,
2812 AnnotationType::Line,
2813 ] {
2814 let a = make_annot(subtype);
2815 assert!(
2816 !a.has_attachment_points(),
2817 "{subtype:?} should not support attachment points"
2818 );
2819 }
2820 }
2821
2822 #[test]
2827 fn test_attachment_point_count_no_quad_points() {
2828 let a = make_annot(AnnotationType::Highlight);
2829 assert_eq!(a.attachment_point_count(), 0);
2830 assert_eq!(a.annot_count_attachment_points(), 0);
2831 }
2832
2833 #[test]
2834 fn test_attachment_point_count_with_two_quads() {
2835 let mut a = make_annot(AnnotationType::Highlight);
2836 a.subtype_data.quad_points = Some(vec![
2838 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, ]);
2841 assert_eq!(a.attachment_point_count(), 2);
2842 let q0 = a.attachment_points(0).unwrap();
2843 assert_eq!(q0, [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0]);
2844 let q1 = a.annot_get_attachment_points(1).unwrap();
2845 assert_eq!(q1[0], 9.0);
2846 assert!(a.attachment_points(2).is_none());
2848 }
2849
2850 #[test]
2854 fn test_vertices_none_when_absent() {
2855 let a = make_annot(AnnotationType::Polygon);
2856 assert!(a.vertices().is_none());
2857 assert!(a.annot_get_vertices().is_none());
2858 }
2859
2860 #[test]
2861 fn test_vertices_returns_flat_slice() {
2862 let mut a = make_annot(AnnotationType::Polygon);
2863 a.subtype_data.vertices = Some(vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0]);
2864 let v = a.vertices().unwrap();
2865 assert_eq!(v.len(), 6);
2866 assert_eq!(v[0], 1.0);
2867 assert_eq!(a.annot_get_vertices().unwrap()[2], 3.0);
2868 }
2869
2870 #[test]
2875 fn test_ink_list_count_zero_when_absent() {
2876 let a = make_annot(AnnotationType::Ink);
2877 assert_eq!(a.ink_list_count(), 0);
2878 assert_eq!(a.annot_get_ink_list_count(), 0);
2879 assert!(a.ink_list_path(0).is_none());
2880 }
2881
2882 #[test]
2883 fn test_ink_list_returns_strokes() {
2884 let mut a = make_annot(AnnotationType::Ink);
2885 a.subtype_data.ink_list = Some(vec![
2886 vec![1.0, 2.0, 3.0, 4.0], vec![5.0, 6.0], ]);
2889 assert_eq!(a.ink_list_count(), 2);
2890 assert_eq!(a.annot_get_ink_list_count(), 2);
2891
2892 let s0 = a.ink_list_path(0).unwrap();
2893 assert_eq!(s0, &[1.0, 2.0, 3.0, 4.0]);
2894
2895 let s1 = a.annot_get_ink_list_path(1).unwrap();
2896 assert_eq!(s1, &[5.0, 6.0]);
2897
2898 assert!(a.ink_list_path(2).is_none());
2899 }
2900
2901 #[test]
2905 fn test_line_endpoints_none_when_absent() {
2906 let a = make_annot(AnnotationType::Line);
2907 assert!(a.line_endpoints().is_none());
2908 assert!(a.annot_get_line().is_none());
2909 }
2910
2911 #[test]
2912 fn test_line_endpoints_returns_two_points() {
2913 let mut a = make_annot(AnnotationType::Line);
2914 a.subtype_data.line_points = Some([10.0, 20.0, 300.0, 400.0]);
2915 let (p1, p2) = a.line_endpoints().unwrap();
2916 assert_eq!(p1, [10.0, 20.0]);
2917 assert_eq!(p2, [300.0, 400.0]);
2918
2919 let (p1b, p2b) = a.annot_get_line().unwrap();
2920 assert_eq!(p1b, [10.0, 20.0]);
2921 assert_eq!(p2b, [300.0, 400.0]);
2922 }
2923
2924 #[test]
2928 fn test_border_style_none_when_absent() {
2929 let a = make_annot(AnnotationType::Text);
2930 assert!(a.border_style().is_none());
2931 assert!(a.annot_get_border().is_none());
2932 }
2933
2934 #[test]
2935 fn test_border_style_returns_width_with_zero_radii() {
2936 let mut a = make_annot(AnnotationType::Text);
2937 a.border = Some(AnnotationBorder {
2938 width: 2.5,
2939 style: BorderStyle::Dashed,
2940 });
2941 let (h, v, w) = a.border_style().unwrap();
2942 assert_eq!(h, 0.0);
2943 assert_eq!(v, 0.0);
2944 assert_eq!(w, 2.5);
2945 assert_eq!(a.annot_get_border().unwrap().2, 2.5);
2946 }
2947
2948 #[test]
2952 fn test_color_rgba_none_when_absent() {
2953 let a = make_annot(AnnotationType::Text);
2954 assert!(a.color_rgba().is_none());
2955 assert!(a.annot_get_color().is_none());
2956 }
2957
2958 #[test]
2959 fn test_color_rgba_gray_one_component() {
2960 let mut a = make_annot(AnnotationType::Text);
2961 a.color = Some(vec![0.5]);
2962 let (r, g, b, al) = a.color_rgba().unwrap();
2963 assert_eq!(r, 128);
2964 assert_eq!(g, 128);
2965 assert_eq!(b, 128);
2966 assert_eq!(al, 255);
2967 }
2968
2969 #[test]
2970 fn test_color_rgba_rgb_three_components() {
2971 let mut a = make_annot(AnnotationType::Highlight);
2972 a.color = Some(vec![1.0, 0.0, 0.0]); let (r, g, b, al) = a.color_rgba().unwrap();
2974 assert_eq!(r, 255);
2975 assert_eq!(g, 0);
2976 assert_eq!(b, 0);
2977 assert_eq!(al, 255);
2978 }
2979
2980 #[test]
2981 fn test_color_rgba_cmyk_four_components() {
2982 let mut a = make_annot(AnnotationType::Text);
2983 a.color = Some(vec![0.0, 0.0, 0.0, 1.0]);
2985 let (r, g, b, al) = a.color_rgba().unwrap();
2986 assert_eq!(r, 0);
2987 assert_eq!(g, 0);
2988 assert_eq!(b, 0);
2989 assert_eq!(al, 255);
2990 }
2991
2992 #[test]
2993 fn test_color_rgba_none_for_zero_components() {
2994 let mut a = make_annot(AnnotationType::Text);
2995 a.color = Some(vec![]); assert!(a.color_rgba().is_none());
2999 }
3000
3001 #[test]
3005 fn test_string_value_contents_key() {
3006 let a = make_annot(AnnotationType::Text);
3007 assert_eq!(a.string_value("Contents"), Some("Test content"));
3008 assert_eq!(a.annot_get_string_value("Contents"), Some("Test content"));
3009 assert!(a.has_key("Contents"));
3010 }
3011
3012 #[test]
3013 fn test_string_value_nm_key() {
3014 let a = make_annot(AnnotationType::Text);
3015 assert_eq!(a.string_value("NM"), Some("annot-001"));
3016 assert_eq!(a.annot_get_string_value("NM"), Some("annot-001"));
3017 assert!(a.has_key("NM"));
3018 }
3019
3020 #[test]
3021 fn test_string_value_unknown_key_returns_none() {
3022 let a = make_annot(AnnotationType::Text);
3023 assert!(a.string_value("UnknownKey").is_none());
3024 assert!(!a.has_key("UnknownKey"));
3025 }
3026
3027 #[test]
3031 fn test_number_value_f_key_returns_flags() {
3032 let a = make_annot(AnnotationType::Text); assert_eq!(a.number_value("F"), Some(4.0));
3034 assert_eq!(a.annot_get_number_value("F"), Some(4.0));
3035 }
3036
3037 #[test]
3038 fn test_number_value_border_width_key() {
3039 let mut a = make_annot(AnnotationType::Text);
3040 a.border = Some(AnnotationBorder {
3041 width: 1.5,
3042 style: BorderStyle::Solid,
3043 });
3044 assert_eq!(a.number_value("Border-Width"), Some(1.5));
3045 assert_eq!(a.annot_get_number_value("Border-Width"), Some(1.5));
3046 }
3047
3048 #[test]
3049 fn test_number_value_unknown_key_returns_none() {
3050 let a = make_annot(AnnotationType::Text);
3051 assert!(a.number_value("UnknownKey").is_none());
3052 }
3053
3054 #[test]
3059 fn test_value_type_string_keys_return_string() {
3060 let a = make_annot(AnnotationType::Text); assert_eq!(a.value_type("Contents"), PdfValueType::String);
3062 assert_eq!(a.value_type("NM"), PdfValueType::String);
3063 assert_eq!(a.annot_get_value_type("Contents"), PdfValueType::String);
3064 }
3065
3066 #[test]
3067 fn test_value_type_fixed_type_keys() {
3068 let a = make_annot(AnnotationType::Text);
3069 assert_eq!(a.value_type("F"), PdfValueType::Number);
3071 assert_eq!(a.value_type("Subtype"), PdfValueType::Name);
3073 assert_eq!(a.value_type("Rect"), PdfValueType::Array);
3075 }
3076
3077 #[test]
3078 fn test_value_type_absent_optional_fields_return_unknown() {
3079 let a = make_annot(AnnotationType::Text); assert_eq!(a.value_type("C"), PdfValueType::Unknown);
3081 assert_eq!(a.value_type("AP"), PdfValueType::Unknown);
3082 assert_eq!(a.value_type("IRT"), PdfValueType::Unknown);
3083 assert_eq!(a.value_type("NonExistentKey"), PdfValueType::Unknown);
3084 }
3085
3086 #[test]
3087 fn test_value_type_present_optional_fields() {
3088 let mut a = make_annot(AnnotationType::Text);
3089 a.color = Some(vec![1.0, 0.0, 0.0]);
3090 assert_eq!(a.value_type("C"), PdfValueType::Array);
3091 }
3092
3093 #[test]
3094 fn test_value_type_irt_reference_when_set() {
3095 use rpdfium_parser::ObjectId;
3096 let mut a = make_annot(AnnotationType::Text);
3097 a.irt_ref = Some(ObjectId::new(7, 0));
3098 assert_eq!(a.value_type("IRT"), PdfValueType::Reference);
3099 assert_eq!(a.annot_get_value_type("IRT"), PdfValueType::Reference);
3100 }
3101
3102 #[test]
3107 fn test_appearance_stream_bytes_none_when_absent() {
3108 let a = make_annot(AnnotationType::Text);
3109 assert!(a.appearance_stream_bytes(AppearanceMode::Normal).is_none());
3110 assert!(a.annot_get_ap(AppearanceMode::Rollover).is_none());
3111 assert!(a.annot_get_ap(AppearanceMode::Down).is_none());
3112 }
3113
3114 #[test]
3115 fn test_appearance_stream_bytes_returns_stored_bytes() {
3116 let mut a = make_annot(AnnotationType::Text);
3117 a.ap_n_bytes = Some(b"q BT /F1 12 Tf ET Q".to_vec());
3118 let bytes = a.appearance_stream_bytes(AppearanceMode::Normal).unwrap();
3119 assert_eq!(bytes, b"q BT /F1 12 Tf ET Q");
3120 }
3121
3122 #[test]
3123 fn test_appearance_stream_bytes_mode_isolation() {
3124 let mut a = make_annot(AnnotationType::Text);
3125 a.ap_n_bytes = Some(b"normal".to_vec());
3126 a.ap_r_bytes = Some(b"rollover".to_vec());
3127 assert_eq!(
3129 a.appearance_stream_bytes(AppearanceMode::Normal).unwrap(),
3130 b"normal"
3131 );
3132 assert_eq!(
3133 a.annot_get_ap(AppearanceMode::Rollover).unwrap(),
3134 b"rollover"
3135 );
3136 assert!(a.appearance_stream_bytes(AppearanceMode::Down).is_none());
3137 }
3138
3139 #[test]
3140 fn test_set_appearance_stream_bytes_returns_not_supported() {
3141 let mut a = make_annot(AnnotationType::Text);
3142 let result = a.set_appearance_stream_bytes(AppearanceMode::Normal, b"data");
3143 assert!(matches!(result, Err(DocError::NotSupported(_))));
3144 }
3145
3146 #[test]
3151 fn test_linked_annot_ref_none_when_absent() {
3152 let a = make_annot(AnnotationType::Text);
3153 assert!(a.linked_annot_ref().is_none());
3154 assert!(a.annot_get_linked_annot().is_none());
3155 }
3156
3157 #[test]
3158 fn test_linked_annot_ref_returns_object_id() {
3159 use rpdfium_parser::ObjectId;
3160 let mut a = make_annot(AnnotationType::Text);
3161 a.irt_ref = Some(ObjectId::new(42, 0));
3162 let oid = a.linked_annot_ref().unwrap();
3163 assert_eq!(oid.number, 42);
3164 assert_eq!(oid.generation, 0);
3165 assert_eq!(a.annot_get_linked_annot().unwrap().number, 42);
3167 }
3168
3169 #[test]
3170 fn test_linked_annot_ref_alias_equals_primary() {
3171 use rpdfium_parser::ObjectId;
3172 let mut a = make_annot(AnnotationType::Text);
3173 a.irt_ref = Some(ObjectId::new(10, 1));
3174 assert_eq!(a.linked_annot_ref(), a.annot_get_linked_annot());
3175 }
3176
3177 #[test]
3184 fn test_field_name_none_when_absent() {
3185 let a = make_annot(AnnotationType::Widget);
3186 assert!(a.field_name().is_none());
3187 assert!(a.annot_get_form_field_name().is_none());
3188 }
3189
3190 #[test]
3191 fn test_field_name_returns_t_value() {
3192 let mut a = make_annot(AnnotationType::Widget);
3193 a.field_name = Some("FirstName".into());
3194 assert_eq!(a.field_name(), Some("FirstName"));
3195 assert_eq!(a.annot_get_form_field_name(), Some("FirstName"));
3196 assert_eq!(a.string_value("T"), Some("FirstName"));
3198 }
3199
3200 #[test]
3201 fn test_alternate_field_name_and_field_value() {
3202 let mut a = make_annot(AnnotationType::Widget);
3203 a.alternate_name = Some("Your first name".into());
3204 a.field_value = Some("Alice".into());
3205 assert_eq!(a.alternate_field_name(), Some("Your first name"));
3206 assert_eq!(
3207 a.annot_get_form_field_alternate_name(),
3208 Some("Your first name")
3209 );
3210 assert_eq!(a.field_value_str(), Some("Alice"));
3211 assert_eq!(a.annot_get_form_field_value(), Some("Alice"));
3212 assert_eq!(a.string_value("TU"), Some("Your first name"));
3214 assert_eq!(a.string_value("V"), Some("Alice"));
3215 }
3216
3217 #[test]
3218 fn test_field_value_none_for_non_widget() {
3219 let a = make_annot(AnnotationType::Text);
3221 assert!(a.field_value_str().is_none());
3222 assert!(a.annot_get_form_field_value().is_none());
3223 }
3224
3225 #[test]
3231 fn test_supports_ap_objects_true_for_supported_subtypes() {
3232 let supported = [
3233 AnnotationType::Stamp,
3234 AnnotationType::FreeText,
3235 AnnotationType::Ink,
3236 AnnotationType::Square,
3237 AnnotationType::Circle,
3238 AnnotationType::Polygon,
3239 AnnotationType::PolyLine,
3240 AnnotationType::Line,
3241 AnnotationType::Highlight,
3242 AnnotationType::Underline,
3243 AnnotationType::Squiggly,
3244 AnnotationType::StrikeOut,
3245 ];
3246 for subtype in &supported {
3247 assert!(
3248 subtype.supports_ap_objects(),
3249 "{subtype:?} should support AP objects"
3250 );
3251 assert!(
3252 subtype.annot_is_object_supported_subtype(),
3253 "{subtype:?} alias should also return true"
3254 );
3255 }
3256 }
3257
3258 #[test]
3259 fn test_supports_ap_objects_false_for_text_annotation() {
3260 let a = AnnotationType::Text;
3261 assert!(!a.supports_ap_objects());
3262 assert!(!a.annot_is_object_supported_subtype());
3263 }
3264
3265 #[test]
3266 fn test_supports_ap_objects_false_for_widget() {
3267 let w = AnnotationType::Widget;
3268 assert!(!w.supports_ap_objects());
3269 assert!(!w.annot_is_object_supported_subtype());
3270 }
3271
3272 #[test]
3277 fn test_form_field_flags_none_when_absent() {
3278 let a = make_annot(AnnotationType::Widget);
3279 assert!(a.form_field_flags().is_none());
3280 assert!(a.annot_get_form_field_flags().is_none());
3281 }
3282
3283 #[test]
3284 fn test_form_field_flags_returns_value_when_present() {
3285 use crate::form_field::FormFieldFlags;
3286 let mut a = make_annot(AnnotationType::Widget);
3287 a.form_field_flags = Some(FormFieldFlags::from_bits(1));
3289 let flags = a.form_field_flags().unwrap();
3290 assert!(flags.is_read_only());
3291 let flags2 = a.annot_get_form_field_flags().unwrap();
3293 assert!(flags2.is_read_only());
3294 }
3295
3296 #[test]
3297 fn test_form_field_flags_alias_equals_primary() {
3298 use crate::form_field::FormFieldFlags;
3299 let mut a = make_annot(AnnotationType::Widget);
3300 a.form_field_flags = Some(FormFieldFlags::from_bits(2)); assert_eq!(a.form_field_flags(), a.annot_get_form_field_flags());
3302 }
3303
3304 #[test]
3309 fn test_link_ref_none_when_no_action_or_dest() {
3310 let a = make_annot(AnnotationType::Link);
3311 let (action, dest) = a.link_ref();
3312 assert!(action.is_none());
3313 assert!(dest.is_none());
3314 let (action2, dest2) = a.annot_get_link();
3316 assert!(action2.is_none());
3317 assert!(dest2.is_none());
3318 }
3319
3320 #[test]
3321 fn test_link_ref_returns_destination_when_present() {
3322 use crate::destination::{Destination, PageFit};
3323 let mut a = make_annot(AnnotationType::Link);
3324 a.destination = Some(Destination::Page {
3325 page_index: 3,
3326 page_ref: None,
3327 fit: PageFit::Fit,
3328 });
3329 let (action, dest) = a.link_ref();
3330 assert!(action.is_none());
3331 let dest = dest.unwrap();
3332 assert_eq!(dest.dest_page_index(), Some(3));
3333 }
3334
3335 #[test]
3340 fn test_file_attachment_none_when_absent() {
3341 let a = make_annot(AnnotationType::FileAttachment);
3342 assert!(a.file_attachment().is_none());
3343 assert!(a.annot_get_file_attachment().is_none());
3344 }
3345
3346 #[test]
3347 fn test_file_attachment_returns_file_spec_when_present() {
3348 use crate::file_spec::FileSpec;
3349 let mut a = make_annot(AnnotationType::FileAttachment);
3350 a.file_spec = Some(FileSpec {
3351 file_system: None,
3352 filename: Some("attachment.pdf".to_string()),
3353 unicode_filename: None,
3354 dos_filename: None,
3355 unix_filename: None,
3356 embedded_file: None,
3357 description: None,
3358 data: None,
3359 });
3360 let fs = a.file_attachment().unwrap();
3361 assert_eq!(fs.filename, Some("attachment.pdf".to_string()));
3362 let fs2 = a.annot_get_file_attachment().unwrap();
3364 assert_eq!(fs2.filename, Some("attachment.pdf".to_string()));
3365 }
3366
3367 #[test]
3372 fn test_is_supported_for_creation_true_for_highlight() {
3373 assert!(AnnotationType::Highlight.is_supported_for_creation());
3374 }
3375
3376 #[test]
3377 fn test_is_supported_for_creation_false_for_widget() {
3378 assert!(!AnnotationType::Widget.is_supported_for_creation());
3379 }
3380
3381 fn rect_from_quad_points_array(points: &[f32], quad_index: usize) -> [f32; 4] {
3392 let base = quad_index * 8;
3393 if base + 7 >= points.len() {
3394 return [0.0, 0.0, 0.0, 0.0];
3395 }
3396 let x2 = points[base + 2];
3399 let y2 = points[base + 3];
3400 let x3 = points[base + 4];
3401 let y3 = points[base + 5];
3402 [x3, y3, x2, y2] }
3404
3405 fn quad_point_count(points: &[f32]) -> usize {
3408 points.len() / 8
3409 }
3410
3411 fn bounding_rect_from_quads(points: &[f32]) -> [f32; 4] {
3413 let count = quad_point_count(points);
3414 if count == 0 {
3415 return [0.0, 0.0, 0.0, 0.0];
3416 }
3417 let mut result = rect_from_quad_points_array(points, 0);
3418 for i in 1..count {
3419 let r = rect_from_quad_points_array(points, i);
3420 result[0] = result[0].min(r[0]); result[1] = result[1].min(r[1]); result[2] = result[2].max(r[2]); result[3] = result[3].max(r[3]); }
3425 result
3426 }
3427
3428 #[test]
3430 fn test_cpdf_annot_rect_from_quad_points_array() {
3431 let points: Vec<f32> = vec![
3432 0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 7.0, 6.0, 5.0, 4.0, 3.0, 2.0, 1.0, ];
3435
3436 let rect0 = rect_from_quad_points_array(&points, 0);
3437 assert_eq!(rect0, [4.0, 5.0, 2.0, 3.0]);
3438
3439 let rect1 = rect_from_quad_points_array(&points, 1);
3440 assert_eq!(rect1, [4.0, 3.0, 6.0, 5.0]);
3441 }
3442
3443 #[test]
3445 fn test_cpdf_annot_bounding_rect_from_quad_points() {
3446 let empty: Vec<f32> = vec![];
3448 assert_eq!(bounding_rect_from_quads(&empty), [0.0, 0.0, 0.0, 0.0]);
3449
3450 let few = vec![0.0, 1.0, 2.0];
3452 assert_eq!(bounding_rect_from_quads(&few), [0.0, 0.0, 0.0, 0.0]);
3453
3454 let one_quad = vec![0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0];
3456 assert_eq!(bounding_rect_from_quads(&one_quad), [4.0, 5.0, 2.0, 3.0]);
3457
3458 let three_quads = vec![
3460 0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 7.0, 6.0, 5.0, 4.0, 3.0, 2.0, 1.0, 9.0, 2.0, 5.0, 7.0, 3.0, 6.0, 4.0, 1.0, ];
3464 let rect = bounding_rect_from_quads(&three_quads);
3465 assert_eq!(rect[0], 3.0); assert_eq!(rect[1], 3.0); assert_eq!(rect[2], 6.0); assert_eq!(rect[3], 7.0); }
3470
3471 #[test]
3476 fn test_cpdf_annot_rect_from_quad_points() {
3477 let empty: Vec<f32> = vec![];
3479 assert_eq!(rect_from_quad_points_array(&empty, 0), [0.0, 0.0, 0.0, 0.0]);
3480 assert_eq!(rect_from_quad_points_array(&empty, 5), [0.0, 0.0, 0.0, 0.0]);
3481
3482 let one = vec![0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0];
3484 assert_eq!(rect_from_quad_points_array(&one, 0), [4.0, 5.0, 2.0, 3.0]);
3485 assert_eq!(rect_from_quad_points_array(&one, 5), [0.0, 0.0, 0.0, 0.0]);
3486
3487 let three = vec![
3489 0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 7.0, 6.0, 5.0, 4.0, 3.0, 2.0, 1.0, 9.0,
3490 2.0, 5.0, 7.0, 3.0, 6.0, 4.0, 1.0,
3491 ];
3492 assert_eq!(rect_from_quad_points_array(&three, 0), [4.0, 5.0, 2.0, 3.0]);
3493 assert_eq!(rect_from_quad_points_array(&three, 1), [4.0, 3.0, 6.0, 5.0]);
3494 assert_eq!(rect_from_quad_points_array(&three, 2), [3.0, 6.0, 5.0, 7.0]);
3495 }
3496
3497 #[test]
3499 fn test_cpdf_annot_quad_point_count() {
3500 let empty: Vec<f32> = vec![];
3501 assert_eq!(quad_point_count(&empty), 0);
3502
3503 for n in 0..8 {
3505 let arr: Vec<f32> = vec![0.0; n];
3506 assert_eq!(quad_point_count(&arr), 0);
3507 }
3508 for n in 8..16 {
3510 let arr: Vec<f32> = vec![0.0; n];
3511 assert_eq!(quad_point_count(&arr), 1);
3512 }
3513 let arr: Vec<f32> = vec![0.0; 65];
3515 assert_eq!(quad_point_count(&arr), 8);
3516 }
3517
3518 #[test]
3523 fn test_font_size_returns_size_from_da() {
3524 let mut a = make_annot(AnnotationType::FreeText);
3525 a.subtype_data.default_appearance = Some("/Helv 12 Tf 0 g".into());
3526 assert_eq!(a.font_size(), Some(12.0));
3527 assert_eq!(a.annot_get_font_size(), Some(12.0));
3528 }
3529
3530 #[test]
3531 fn test_font_size_returns_none_when_no_da() {
3532 let a = make_annot(AnnotationType::FreeText);
3533 assert!(a.font_size().is_none());
3534 }
3535
3536 #[test]
3537 fn test_font_size_returns_none_for_zero_size() {
3538 let mut a = make_annot(AnnotationType::FreeText);
3539 a.subtype_data.default_appearance = Some("/Helv 0 Tf".into());
3540 assert!(a.font_size().is_none());
3541 }
3542
3543 #[test]
3548 fn test_font_color_rgb_from_da() {
3549 let mut a = make_annot(AnnotationType::FreeText);
3550 a.subtype_data.default_appearance = Some("/Helv 12 Tf 1 0 0 rg".into());
3551 assert_eq!(a.font_color(), Some((255, 0, 0)));
3552 assert_eq!(a.annot_get_font_color(), Some((255, 0, 0)));
3553 }
3554
3555 #[test]
3556 fn test_font_color_grayscale_from_da() {
3557 let mut a = make_annot(AnnotationType::FreeText);
3558 a.subtype_data.default_appearance = Some("0.5 g /Helv 12 Tf".into());
3559 let (r, g, b) = a.font_color().unwrap();
3560 assert_eq!(r, g);
3561 assert_eq!(g, b);
3562 assert!(r >= 127 && r <= 128);
3563 }
3564
3565 #[test]
3566 fn test_font_color_returns_none_when_no_da() {
3567 let a = make_annot(AnnotationType::FreeText);
3568 assert!(a.font_color().is_none());
3569 }
3570
3571 #[test]
3572 fn test_set_font_color_updates_da() {
3573 let mut a = make_annot(AnnotationType::FreeText);
3574 a.subtype_data.default_appearance = Some("/Helv 12 Tf 0 g".into());
3575 a.set_font_color(255, 0, 0).unwrap();
3576 let da = a.subtype_data.default_appearance.as_deref().unwrap();
3577 assert!(da.contains("rg"), "DA should contain rg operator: {da}");
3578 assert_eq!(a.font_color(), Some((255, 0, 0)));
3579 }
3580
3581 #[test]
3582 fn test_set_font_color_on_widget() {
3583 let mut a = make_annot(AnnotationType::Widget);
3584 a.set_font_color(0, 128, 0).unwrap();
3585 let (r, g, b) = a.font_color().unwrap();
3586 assert_eq!(r, 0);
3587 assert!(g >= 127 && g <= 128);
3588 assert_eq!(b, 0);
3589 }
3590
3591 #[test]
3592 fn test_set_font_color_errors_on_non_widget() {
3593 let mut a = make_annot(AnnotationType::Text);
3594 assert!(a.set_font_color(0, 0, 0).is_err());
3595 }
3596
3597 #[test]
3606 fn test_strip_da_color_removes_grayscale() {
3607 assert_eq!(strip_da_color("/Helv 12 Tf 0 g"), "/Helv 12 Tf");
3608 }
3609
3610 #[test]
3611 fn test_strip_da_color_removes_rgb() {
3612 assert_eq!(strip_da_color("/Helv 12 Tf 1 0 0 rg"), "/Helv 12 Tf");
3613 }
3614
3615 #[test]
3616 fn test_strip_da_color_removes_cmyk() {
3617 assert_eq!(strip_da_color("/Helv 12 Tf 0 0 0 1 k"), "/Helv 12 Tf");
3618 }
3619
3620 #[test]
3621 fn test_strip_da_color_color_before_font() {
3622 assert_eq!(strip_da_color("0 g /Helv 12 Tf"), "/Helv 12 Tf");
3623 }
3624
3625 #[test]
3626 fn test_strip_da_color_no_color_unchanged() {
3627 assert_eq!(strip_da_color("/Helv 12 Tf"), "/Helv 12 Tf");
3628 }
3629
3630 #[test]
3631 fn test_strip_da_color_empty() {
3632 assert_eq!(strip_da_color(""), "");
3633 }
3634
3635 #[test]
3636 fn test_form_additional_action_javascript_none_when_no_aa() {
3637 let a = make_annot(AnnotationType::Widget);
3638 use crate::aaction::AActionType;
3639 assert_eq!(
3640 a.form_additional_action_javascript(AActionType::KeyStroke),
3641 ""
3642 );
3643 }
3644
3645 #[test]
3646 fn test_form_additional_action_javascript_returns_code() {
3647 use crate::aaction::{AActionType, AdditionalActions};
3648 use crate::action::Action;
3649 use std::collections::HashMap;
3650
3651 let mut entries = HashMap::new();
3652 entries.insert(
3653 "K".to_string(),
3654 Action::JavaScript {
3655 code: "alert(1)".into(),
3656 },
3657 );
3658 let aa = AdditionalActions { entries };
3659 let mut a = make_annot(AnnotationType::Widget);
3660 a.additional_actions = Some(aa);
3661
3662 assert_eq!(
3663 a.form_additional_action_javascript(AActionType::KeyStroke),
3664 "alert(1)"
3665 );
3666 assert_eq!(
3667 a.annot_get_form_additional_action_javascript(AActionType::KeyStroke),
3668 "alert(1)"
3669 );
3670 }
3671
3672 #[test]
3677 fn test_form_field_type_none_for_non_widget() {
3678 let a = make_annot(AnnotationType::Text);
3679 assert!(a.form_field_type().is_none());
3680 assert!(a.annot_get_form_field_type().is_none());
3681 }
3682
3683 #[test]
3684 fn test_form_field_type_returns_set_value() {
3685 let mut a = make_annot(AnnotationType::Widget);
3686 a.form_field_type = Some(FormFieldType::Text);
3687 assert_eq!(a.form_field_type(), Some(FormFieldType::Text));
3688 a.form_field_type = Some(FormFieldType::Choice);
3689 assert_eq!(a.annot_get_form_field_type(), Some(FormFieldType::Choice));
3690 }
3691
3692 #[test]
3693 fn test_form_control_count_returns_not_supported() {
3694 let a = make_annot(AnnotationType::Widget);
3695 assert!(a.form_control_count().is_err());
3696 assert!(a.annot_get_form_control_count().is_err());
3697 }
3698
3699 #[test]
3700 fn test_form_control_index_returns_not_supported() {
3701 let a = make_annot(AnnotationType::Widget);
3702 assert!(a.form_control_index().is_err());
3703 assert!(a.annot_get_form_control_index().is_err());
3704 }
3705
3706 #[test]
3707 fn test_form_field_export_value_returns_not_supported() {
3708 let a = make_annot(AnnotationType::Widget);
3709 assert!(a.form_field_export_value().is_err());
3710 assert!(a.annot_get_form_field_export_value().is_err());
3711 }
3712
3713 #[test]
3718 fn test_is_checked_off_is_unchecked() {
3719 let mut a = make_annot(AnnotationType::Widget);
3720 a.field_value = Some("Off".into());
3721 assert!(!a.is_checked());
3722 assert!(!a.annot_is_checked());
3723 }
3724
3725 #[test]
3726 fn test_is_checked_none_is_unchecked() {
3727 let mut a = make_annot(AnnotationType::Widget);
3728 a.field_value = None;
3729 assert!(!a.is_checked());
3730 }
3731
3732 #[test]
3733 fn test_is_checked_yes_is_checked() {
3734 let mut a = make_annot(AnnotationType::Widget);
3735 a.field_value = Some("Yes".into());
3736 assert!(a.is_checked());
3737 assert!(a.annot_is_checked());
3738 }
3739
3740 #[test]
3741 fn test_option_count_none_when_no_options() {
3742 let a = make_annot(AnnotationType::Widget);
3743 assert_eq!(a.option_count(), 0);
3744 assert_eq!(a.annot_get_option_count(), 0);
3745 }
3746
3747 #[test]
3748 fn test_option_count_and_label() {
3749 let mut a = make_annot(AnnotationType::Widget);
3750 a.options = Some(vec![
3751 ChoiceOption {
3752 export_value: "a".into(),
3753 display_value: "Apple".into(),
3754 },
3755 ChoiceOption {
3756 export_value: "b".into(),
3757 display_value: "Banana".into(),
3758 },
3759 ]);
3760 assert_eq!(a.option_count(), 2);
3761 assert_eq!(a.option_label(0), Some("Apple"));
3762 assert_eq!(a.annot_get_option_label(1), Some("Banana"));
3763 assert!(a.option_label(2).is_none());
3764 }
3765
3766 #[test]
3767 fn test_is_option_selected() {
3768 let mut a = make_annot(AnnotationType::Widget);
3769 a.options = Some(vec![
3770 ChoiceOption {
3771 export_value: "a".into(),
3772 display_value: "Apple".into(),
3773 },
3774 ChoiceOption {
3775 export_value: "b".into(),
3776 display_value: "Banana".into(),
3777 },
3778 ]);
3779 a.field_value = Some("b".into());
3780 assert!(!a.is_option_selected(0));
3781 assert!(a.is_option_selected(1));
3782 assert!(!a.annot_is_option_selected(2)); }
3784
3785 #[test]
3790 fn test_focusable_subtype_count_returns_not_supported() {
3791 let a = make_annot(AnnotationType::Widget);
3792 assert!(a.focusable_subtype_count().is_err());
3793 assert!(a.annot_get_focusable_subtypes_count().is_err());
3794 }
3795
3796 #[test]
3797 fn test_set_focusable_subtypes_returns_not_supported() {
3798 let mut a = make_annot(AnnotationType::Widget);
3799 assert!(a.set_focusable_subtypes(&[]).is_err());
3800 assert!(a.annot_set_focusable_subtypes(&[]).is_err());
3801 }
3802
3803 #[test]
3808 fn test_set_flags_updates_in_memory() {
3809 let mut a = make_annot(AnnotationType::Text);
3810 assert!(a.flags().print()); a.set_flags(AnnotationFlags::from_bits(0)).unwrap();
3812 assert!(!a.flags().print());
3813 a.annot_set_flags(AnnotationFlags::from_bits(2)).unwrap();
3814 assert!(a.flags().hidden());
3815 }
3816
3817 #[test]
3818 fn test_set_form_field_flags_updates_in_memory() {
3819 use crate::form_field::FormFieldFlags;
3820 let mut a = make_annot(AnnotationType::Widget);
3821 assert!(a.form_field_flags().is_none());
3822 a.set_form_field_flags(FormFieldFlags::from_bits(1))
3823 .unwrap();
3824 assert_eq!(a.form_field_flags().map(|f| f.bits()), Some(1));
3825 a.annot_set_form_field_flags(FormFieldFlags::from_bits(3))
3826 .unwrap();
3827 assert_eq!(a.form_field_flags().map(|f| f.bits()), Some(3));
3828 }
3829}