1#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
7pub enum AnnotationSubtype {
8 Text,
10 Link,
12 FreeText,
14 Line,
16 Square,
18 Circle,
20 Polygon,
22 PolyLine,
24 Highlight,
26 Underline,
28 Squiggly,
30 StrikeOut,
32 Stamp,
34 Caret,
36 Ink,
38 Popup,
40 FileAttachment,
42 Sound,
44 Movie,
46 Widget,
48 Screen,
50 PrinterMark,
52 TrapNet,
54 Watermark,
56 ThreeD,
58 Redact,
60 RichMedia,
62 Unknown,
64}
65
66impl AnnotationSubtype {
67 pub fn pdf_name(&self) -> &'static str {
69 match self {
70 Self::Text => "Text",
71 Self::Link => "Link",
72 Self::FreeText => "FreeText",
73 Self::Line => "Line",
74 Self::Square => "Square",
75 Self::Circle => "Circle",
76 Self::Polygon => "Polygon",
77 Self::PolyLine => "PolyLine",
78 Self::Highlight => "Highlight",
79 Self::Underline => "Underline",
80 Self::Squiggly => "Squiggly",
81 Self::StrikeOut => "StrikeOut",
82 Self::Stamp => "Stamp",
83 Self::Caret => "Caret",
84 Self::Ink => "Ink",
85 Self::Popup => "Popup",
86 Self::FileAttachment => "FileAttachment",
87 Self::Sound => "Sound",
88 Self::Movie => "Movie",
89 Self::Widget => "Widget",
90 Self::Screen => "Screen",
91 Self::PrinterMark => "PrinterMark",
92 Self::TrapNet => "TrapNet",
93 Self::Watermark => "Watermark",
94 Self::ThreeD => "3D",
95 Self::Redact => "Redact",
96 Self::RichMedia => "RichMedia",
97 Self::Unknown => "Unknown",
98 }
99 }
100
101 pub fn from_pdf_name(name: &str) -> Self {
103 match name {
104 "Text" => Self::Text,
105 "Link" => Self::Link,
106 "FreeText" => Self::FreeText,
107 "Line" => Self::Line,
108 "Square" => Self::Square,
109 "Circle" => Self::Circle,
110 "Polygon" => Self::Polygon,
111 "PolyLine" => Self::PolyLine,
112 "Highlight" => Self::Highlight,
113 "Underline" => Self::Underline,
114 "Squiggly" => Self::Squiggly,
115 "StrikeOut" => Self::StrikeOut,
116 "Stamp" => Self::Stamp,
117 "Caret" => Self::Caret,
118 "Ink" => Self::Ink,
119 "Popup" => Self::Popup,
120 "FileAttachment" => Self::FileAttachment,
121 "Sound" => Self::Sound,
122 "Movie" => Self::Movie,
123 "Widget" => Self::Widget,
124 "Screen" => Self::Screen,
125 "PrinterMark" => Self::PrinterMark,
126 "TrapNet" => Self::TrapNet,
127 "Watermark" => Self::Watermark,
128 "3D" => Self::ThreeD,
129 "Redact" => Self::Redact,
130 "RichMedia" => Self::RichMedia,
131 _ => Self::Unknown,
132 }
133 }
134
135 pub fn is_markup(&self) -> bool {
137 matches!(
138 self,
139 Self::Text
140 | Self::FreeText
141 | Self::Line
142 | Self::Square
143 | Self::Circle
144 | Self::Polygon
145 | Self::PolyLine
146 | Self::Highlight
147 | Self::Underline
148 | Self::Squiggly
149 | Self::StrikeOut
150 | Self::Stamp
151 | Self::Caret
152 | Self::Ink
153 | Self::FileAttachment
154 | Self::Sound
155 | Self::Redact
156 )
157 }
158
159 pub fn is_text_markup(&self) -> bool {
161 matches!(
162 self,
163 Self::Highlight | Self::Underline | Self::Squiggly | Self::StrikeOut
164 )
165 }
166}
167
168#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
172pub struct AnnotationFlags(u32);
173
174impl AnnotationFlags {
175 pub const INVISIBLE: u32 = 1 << 0;
177 pub const HIDDEN: u32 = 1 << 1;
179 pub const PRINT: u32 = 1 << 2;
181 pub const NO_ZOOM: u32 = 1 << 3;
183 pub const NO_ROTATE: u32 = 1 << 4;
185 pub const NO_VIEW: u32 = 1 << 5;
187 pub const READ_ONLY: u32 = 1 << 6;
189 pub const LOCKED: u32 = 1 << 7;
191 pub const TOGGLE_NO_VIEW: u32 = 1 << 8;
193 pub const LOCKED_CONTENTS: u32 = 1 << 9;
195
196 pub fn new(value: u32) -> Self {
198 Self(value)
199 }
200
201 pub fn empty() -> Self {
203 Self(0)
204 }
205
206 pub fn printable() -> Self {
208 Self(Self::PRINT)
209 }
210
211 pub fn bits(&self) -> u32 {
213 self.0
214 }
215
216 pub fn contains(&self, flag: u32) -> bool {
218 (self.0 & flag) != 0
219 }
220
221 pub fn set(&mut self, flag: u32) {
223 self.0 |= flag;
224 }
225
226 pub fn clear(&mut self, flag: u32) {
228 self.0 &= !flag;
229 }
230
231 pub fn is_invisible(&self) -> bool {
233 self.contains(Self::INVISIBLE)
234 }
235
236 pub fn is_hidden(&self) -> bool {
238 self.contains(Self::HIDDEN)
239 }
240
241 pub fn is_printable(&self) -> bool {
243 self.contains(Self::PRINT)
244 }
245
246 pub fn is_no_zoom(&self) -> bool {
248 self.contains(Self::NO_ZOOM)
249 }
250
251 pub fn is_no_rotate(&self) -> bool {
253 self.contains(Self::NO_ROTATE)
254 }
255
256 pub fn is_read_only(&self) -> bool {
258 self.contains(Self::READ_ONLY)
259 }
260
261 pub fn is_locked(&self) -> bool {
263 self.contains(Self::LOCKED)
264 }
265}
266
267#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
269pub enum BorderStyleType {
270 #[default]
272 Solid,
273 Dashed,
275 Beveled,
277 Inset,
279 Underline,
281}
282
283impl BorderStyleType {
284 pub fn pdf_name(&self) -> &'static str {
286 match self {
287 Self::Solid => "S",
288 Self::Dashed => "D",
289 Self::Beveled => "B",
290 Self::Inset => "I",
291 Self::Underline => "U",
292 }
293 }
294
295 pub fn from_pdf_name(name: &str) -> Self {
297 match name {
298 "S" => Self::Solid,
299 "D" => Self::Dashed,
300 "B" => Self::Beveled,
301 "I" => Self::Inset,
302 "U" => Self::Underline,
303 _ => Self::Solid,
304 }
305 }
306}
307
308#[derive(Debug, Clone, Default)]
310pub struct AnnotationBorderStyle {
311 pub width: f32,
313 pub style: BorderStyleType,
315 pub dash_pattern: Option<Vec<f32>>,
317}
318
319impl AnnotationBorderStyle {
320 pub fn solid(width: f32) -> Self {
322 Self {
323 width,
324 style: BorderStyleType::Solid,
325 dash_pattern: None,
326 }
327 }
328
329 pub fn dashed(width: f32, dash: f32, gap: f32) -> Self {
331 Self {
332 width,
333 style: BorderStyleType::Dashed,
334 dash_pattern: Some(vec![dash, gap]),
335 }
336 }
337
338 pub fn none() -> Self {
340 Self {
341 width: 0.0,
342 style: BorderStyleType::Solid,
343 dash_pattern: None,
344 }
345 }
346}
347
348#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
350pub enum BorderEffectStyle {
351 #[default]
353 None,
354 Cloudy,
356}
357
358impl BorderEffectStyle {
359 pub fn pdf_name(&self) -> &'static str {
361 match self {
362 Self::None => "S",
363 Self::Cloudy => "C",
364 }
365 }
366
367 pub fn from_pdf_name(name: &str) -> Self {
369 match name {
370 "C" => Self::Cloudy,
371 _ => Self::None,
372 }
373 }
374}
375
376#[derive(Debug, Clone, Default)]
378pub struct BorderEffect {
379 pub style: BorderEffectStyle,
381 pub intensity: f32,
383}
384
385#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
387pub enum LineEndingStyle {
388 #[default]
390 None,
391 Square,
393 Circle,
395 Diamond,
397 OpenArrow,
399 ClosedArrow,
401 Butt,
403 ROpenArrow,
405 RClosedArrow,
407 Slash,
409}
410
411impl LineEndingStyle {
412 pub fn pdf_name(&self) -> &'static str {
414 match self {
415 Self::None => "None",
416 Self::Square => "Square",
417 Self::Circle => "Circle",
418 Self::Diamond => "Diamond",
419 Self::OpenArrow => "OpenArrow",
420 Self::ClosedArrow => "ClosedArrow",
421 Self::Butt => "Butt",
422 Self::ROpenArrow => "ROpenArrow",
423 Self::RClosedArrow => "RClosedArrow",
424 Self::Slash => "Slash",
425 }
426 }
427
428 pub fn from_pdf_name(name: &str) -> Self {
430 match name {
431 "None" => Self::None,
432 "Square" => Self::Square,
433 "Circle" => Self::Circle,
434 "Diamond" => Self::Diamond,
435 "OpenArrow" => Self::OpenArrow,
436 "ClosedArrow" => Self::ClosedArrow,
437 "Butt" => Self::Butt,
438 "ROpenArrow" => Self::ROpenArrow,
439 "RClosedArrow" => Self::RClosedArrow,
440 "Slash" => Self::Slash,
441 _ => Self::None,
442 }
443 }
444}
445
446#[derive(Debug, Clone, PartialEq, Default)]
450pub enum AnnotationColor {
451 #[default]
453 None,
454 Gray(f32),
456 Rgb(f32, f32, f32),
458 Cmyk(f32, f32, f32, f32),
460}
461
462impl AnnotationColor {
463 pub fn yellow() -> Self {
465 Self::Rgb(1.0, 1.0, 0.0)
466 }
467
468 pub fn red() -> Self {
470 Self::Rgb(1.0, 0.0, 0.0)
471 }
472
473 pub fn green() -> Self {
475 Self::Rgb(0.0, 1.0, 0.0)
476 }
477
478 pub fn blue() -> Self {
480 Self::Rgb(0.0, 0.0, 1.0)
481 }
482
483 pub fn black() -> Self {
485 Self::Gray(0.0)
486 }
487
488 pub fn white() -> Self {
490 Self::Gray(1.0)
491 }
492
493 pub fn to_array(&self) -> Option<Vec<f32>> {
495 match self {
496 Self::None => None,
497 Self::Gray(g) => Some(vec![*g]),
498 Self::Rgb(r, g, b) => Some(vec![*r, *g, *b]),
499 Self::Cmyk(c, m, y, k) => Some(vec![*c, *m, *y, *k]),
500 }
501 }
502
503 pub fn from_array(arr: &[f32]) -> Self {
505 match arr.len() {
506 0 => Self::None,
507 1 => Self::Gray(arr[0]),
508 3 => Self::Rgb(arr[0], arr[1], arr[2]),
509 4 => Self::Cmyk(arr[0], arr[1], arr[2], arr[3]),
510 _ => Self::None,
511 }
512 }
513}
514
515#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
517pub enum TextAnnotationIcon {
518 Comment,
520 Key,
522 #[default]
524 Note,
525 Help,
527 NewParagraph,
529 Paragraph,
531 Insert,
533}
534
535impl TextAnnotationIcon {
536 pub fn pdf_name(&self) -> &'static str {
538 match self {
539 Self::Comment => "Comment",
540 Self::Key => "Key",
541 Self::Note => "Note",
542 Self::Help => "Help",
543 Self::NewParagraph => "NewParagraph",
544 Self::Paragraph => "Paragraph",
545 Self::Insert => "Insert",
546 }
547 }
548
549 pub fn from_pdf_name(name: &str) -> Self {
551 match name {
552 "Comment" => Self::Comment,
553 "Key" => Self::Key,
554 "Note" => Self::Note,
555 "Help" => Self::Help,
556 "NewParagraph" => Self::NewParagraph,
557 "Paragraph" => Self::Paragraph,
558 "Insert" => Self::Insert,
559 _ => Self::Note,
560 }
561 }
562}
563
564#[derive(Debug, Clone, Copy, PartialEq, Eq)]
566pub enum TextMarkupType {
567 Highlight,
569 Underline,
571 Squiggly,
573 StrikeOut,
575}
576
577impl TextMarkupType {
578 pub fn subtype(&self) -> AnnotationSubtype {
580 match self {
581 Self::Highlight => AnnotationSubtype::Highlight,
582 Self::Underline => AnnotationSubtype::Underline,
583 Self::Squiggly => AnnotationSubtype::Squiggly,
584 Self::StrikeOut => AnnotationSubtype::StrikeOut,
585 }
586 }
587
588 pub fn default_color(&self) -> AnnotationColor {
590 match self {
591 Self::Highlight => AnnotationColor::yellow(),
592 Self::Underline => AnnotationColor::green(),
593 Self::Squiggly => AnnotationColor::Rgb(1.0, 0.5, 0.0),
594 Self::StrikeOut => AnnotationColor::red(),
595 }
596 }
597}
598
599#[derive(Debug, Clone, PartialEq, Eq, Default)]
601pub enum StampType {
602 Approved,
604 Experimental,
606 NotApproved,
608 AsIs,
610 Expired,
612 NotForPublicRelease,
614 Confidential,
616 Final,
618 Sold,
620 Departmental,
622 ForComment,
624 TopSecret,
626 #[default]
628 Draft,
629 ForPublicRelease,
631 Custom(String),
633}
634
635impl StampType {
636 pub fn pdf_name(&self) -> String {
638 match self {
639 Self::Approved => "Approved".to_string(),
640 Self::Experimental => "Experimental".to_string(),
641 Self::NotApproved => "NotApproved".to_string(),
642 Self::AsIs => "AsIs".to_string(),
643 Self::Expired => "Expired".to_string(),
644 Self::NotForPublicRelease => "NotForPublicRelease".to_string(),
645 Self::Confidential => "Confidential".to_string(),
646 Self::Final => "Final".to_string(),
647 Self::Sold => "Sold".to_string(),
648 Self::Departmental => "Departmental".to_string(),
649 Self::ForComment => "ForComment".to_string(),
650 Self::TopSecret => "TopSecret".to_string(),
651 Self::Draft => "Draft".to_string(),
652 Self::ForPublicRelease => "ForPublicRelease".to_string(),
653 Self::Custom(name) => name.clone(),
654 }
655 }
656
657 pub fn from_pdf_name(name: &str) -> Self {
659 match name {
660 "Approved" => Self::Approved,
661 "Experimental" => Self::Experimental,
662 "NotApproved" => Self::NotApproved,
663 "AsIs" => Self::AsIs,
664 "Expired" => Self::Expired,
665 "NotForPublicRelease" => Self::NotForPublicRelease,
666 "Confidential" => Self::Confidential,
667 "Final" => Self::Final,
668 "Sold" => Self::Sold,
669 "Departmental" => Self::Departmental,
670 "ForComment" => Self::ForComment,
671 "TopSecret" => Self::TopSecret,
672 "Draft" => Self::Draft,
673 "ForPublicRelease" => Self::ForPublicRelease,
674 other => Self::Custom(other.to_string()),
675 }
676 }
677}
678
679#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
681pub enum FreeTextIntent {
682 #[default]
684 FreeText,
685 FreeTextCallout,
687 FreeTextTypeWriter,
689}
690
691impl FreeTextIntent {
692 pub fn pdf_name(&self) -> &'static str {
694 match self {
695 Self::FreeText => "FreeText",
696 Self::FreeTextCallout => "FreeTextCallout",
697 Self::FreeTextTypeWriter => "FreeTextTypeWriter",
698 }
699 }
700
701 pub fn from_pdf_name(name: &str) -> Self {
703 match name {
704 "FreeTextCallout" => Self::FreeTextCallout,
705 "FreeTextTypeWriter" => Self::FreeTextTypeWriter,
706 _ => Self::FreeText,
707 }
708 }
709}
710
711#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
713pub enum TextAlignment {
714 #[default]
716 Left,
717 Center,
719 Right,
721}
722
723impl TextAlignment {
724 pub fn to_pdf_int(&self) -> i32 {
726 match self {
727 Self::Left => 0,
728 Self::Center => 1,
729 Self::Right => 2,
730 }
731 }
732
733 pub fn from_pdf_int(value: i32) -> Self {
735 match value {
736 1 => Self::Center,
737 2 => Self::Right,
738 _ => Self::Left,
739 }
740 }
741}
742
743#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
745pub enum CaretSymbol {
746 #[default]
748 None,
749 Paragraph,
751}
752
753impl CaretSymbol {
754 pub fn pdf_name(&self) -> &'static str {
756 match self {
757 Self::None => "None",
758 Self::Paragraph => "P",
759 }
760 }
761
762 pub fn from_pdf_name(name: &str) -> Self {
764 match name {
765 "P" => Self::Paragraph,
766 _ => Self::None,
767 }
768 }
769}
770
771#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
773pub enum FileAttachmentIcon {
774 GraphPushPin,
776 #[default]
778 PaperclipTag,
779 PushPin,
781}
782
783impl FileAttachmentIcon {
784 pub fn pdf_name(&self) -> &'static str {
786 match self {
787 Self::GraphPushPin => "GraphPushPin",
788 Self::PaperclipTag => "PaperclipTag",
789 Self::PushPin => "PushPin",
790 }
791 }
792
793 pub fn from_pdf_name(name: &str) -> Self {
795 match name {
796 "GraphPushPin" => Self::GraphPushPin,
797 "PushPin" => Self::PushPin,
798 _ => Self::PaperclipTag,
799 }
800 }
801}
802
803#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
805pub enum ReplyType {
806 #[default]
808 Reply,
809 Group,
811}
812
813impl ReplyType {
814 pub fn pdf_name(&self) -> &'static str {
816 match self {
817 Self::Reply => "R",
818 Self::Group => "Group",
819 }
820 }
821
822 pub fn from_pdf_name(name: &str) -> Self {
824 match name {
825 "Group" => Self::Group,
826 _ => Self::Reply,
827 }
828 }
829}
830
831#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
833pub enum HighlightMode {
834 None,
836 #[default]
838 Invert,
839 Outline,
841 Push,
843}
844
845impl HighlightMode {
846 pub fn pdf_name(&self) -> &'static str {
848 match self {
849 Self::None => "N",
850 Self::Invert => "I",
851 Self::Outline => "O",
852 Self::Push => "P",
853 }
854 }
855
856 pub fn from_pdf_name(name: &str) -> Self {
858 match name {
859 "N" => Self::None,
860 "O" => Self::Outline,
861 "P" => Self::Push,
862 _ => Self::Invert,
863 }
864 }
865}
866
867#[derive(Debug, Clone, PartialEq, Default)]
869pub enum WidgetFieldType {
870 #[default]
872 Text,
873 Checkbox {
875 checked: bool,
877 },
878 Radio {
880 selected: Option<String>,
882 },
883 Button,
885 Choice {
887 options: Vec<String>,
889 selected: Option<String>,
891 },
892 Signature,
894 Unknown,
896}
897
898pub type QuadPoint = [f64; 8];
903
904pub mod quad_points {
906 use super::QuadPoint;
907 use crate::geometry::Rect;
908
909 pub fn from_rect(rect: &Rect) -> QuadPoint {
911 let x1 = rect.x as f64;
912 let y1 = rect.y as f64;
913 let x2 = (rect.x + rect.width) as f64;
914 let y2 = rect.y as f64;
915 let x3 = (rect.x + rect.width) as f64;
916 let y3 = (rect.y + rect.height) as f64;
917 let x4 = rect.x as f64;
918 let y4 = (rect.y + rect.height) as f64;
919
920 [x1, y1, x2, y2, x3, y3, x4, y4]
921 }
922
923 pub fn bounding_rect(quad: &QuadPoint) -> Rect {
925 let min_x = quad[0].min(quad[2]).min(quad[4]).min(quad[6]) as f32;
926 let max_x = quad[0].max(quad[2]).max(quad[4]).max(quad[6]) as f32;
927 let min_y = quad[1].min(quad[3]).min(quad[5]).min(quad[7]) as f32;
928 let max_y = quad[1].max(quad[3]).max(quad[5]).max(quad[7]) as f32;
929
930 Rect::new(min_x, min_y, max_x - min_x, max_y - min_y)
931 }
932
933 pub fn parse(arr: &[f64]) -> Vec<QuadPoint> {
935 arr.as_chunks::<8>()
936 .0
937 .iter()
938 .map(|chunk| {
939 [
940 chunk[0], chunk[1], chunk[2], chunk[3], chunk[4], chunk[5], chunk[6], chunk[7],
941 ]
942 })
943 .collect()
944 }
945
946 pub fn flatten(quads: &[QuadPoint]) -> Vec<f64> {
948 quads.iter().flat_map(|q| q.iter().copied()).collect()
949 }
950}
951
952#[cfg(test)]
953mod tests;