1use folio_cos::PdfObject;
4use indexmap::IndexMap;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub enum ActionType {
9 GoTo,
10 GoToR,
11 GoToE,
12 Launch,
13 Thread,
14 URI,
15 Sound,
16 Movie,
17 Hide,
18 Named,
19 SubmitForm,
20 ResetForm,
21 ImportData,
22 JavaScript,
23 SetOCGState,
24 Rendition,
25 Trans,
26 GoTo3DView,
27 RichMediaExecute,
28 Unknown,
29}
30
31impl ActionType {
32 pub fn from_name(name: &[u8]) -> Self {
33 match name {
34 b"GoTo" => Self::GoTo,
35 b"GoToR" => Self::GoToR,
36 b"GoToE" => Self::GoToE,
37 b"Launch" => Self::Launch,
38 b"Thread" => Self::Thread,
39 b"URI" => Self::URI,
40 b"Sound" => Self::Sound,
41 b"Movie" => Self::Movie,
42 b"Hide" => Self::Hide,
43 b"Named" => Self::Named,
44 b"SubmitForm" => Self::SubmitForm,
45 b"ResetForm" => Self::ResetForm,
46 b"ImportData" => Self::ImportData,
47 b"JavaScript" => Self::JavaScript,
48 b"SetOCGState" => Self::SetOCGState,
49 b"Rendition" => Self::Rendition,
50 b"Trans" => Self::Trans,
51 b"GoTo3DView" => Self::GoTo3DView,
52 b"RichMediaExecute" => Self::RichMediaExecute,
53 _ => Self::Unknown,
54 }
55 }
56}
57
58#[derive(Debug, Clone)]
60pub struct Action {
61 pub action_type: ActionType,
63 pub dict: IndexMap<Vec<u8>, PdfObject>,
65}
66
67impl Action {
68 pub fn from_dict(dict: &IndexMap<Vec<u8>, PdfObject>) -> Self {
70 let action_type = dict
71 .get(b"S".as_slice())
72 .and_then(|o| o.as_name())
73 .map(ActionType::from_name)
74 .unwrap_or(ActionType::Unknown);
75
76 Self {
77 action_type,
78 dict: dict.clone(),
79 }
80 }
81
82 pub fn from_object(obj: &PdfObject) -> Option<Self> {
84 let dict = obj.as_dict()?;
85 Some(Self::from_dict(dict))
86 }
87
88 pub fn uri(&self) -> Option<String> {
90 if self.action_type != ActionType::URI {
91 return None;
92 }
93 self.dict
94 .get(b"URI".as_slice())?
95 .as_str()
96 .map(|s| String::from_utf8_lossy(s).into_owned())
97 }
98
99 pub fn javascript(&self) -> Option<String> {
101 if self.action_type != ActionType::JavaScript {
102 return None;
103 }
104 match self.dict.get(b"JS".as_slice())? {
105 PdfObject::Str(s) => Some(String::from_utf8_lossy(&s).into_owned()),
106 _ => None,
107 }
108 }
109
110 pub fn destination(&self) -> Option<&PdfObject> {
112 if self.action_type != ActionType::GoTo {
113 return None;
114 }
115 self.dict.get(b"D".as_slice())
116 }
117
118 pub fn named_action(&self) -> Option<String> {
120 if self.action_type != ActionType::Named {
121 return None;
122 }
123 self.dict
124 .get(b"N".as_slice())?
125 .as_name()
126 .map(|n| String::from_utf8_lossy(n).into_owned())
127 }
128
129 pub fn next(&self) -> Option<&PdfObject> {
131 self.dict.get(b"Next".as_slice())
132 }
133}
134
135#[cfg(test)]
136mod tests {
137 use super::*;
138
139 #[test]
140 fn test_action_type_parsing() {
141 assert_eq!(ActionType::from_name(b"GoTo"), ActionType::GoTo);
142 assert_eq!(ActionType::from_name(b"URI"), ActionType::URI);
143 assert_eq!(ActionType::from_name(b"JavaScript"), ActionType::JavaScript);
144 assert_eq!(ActionType::from_name(b"Unknown"), ActionType::Unknown);
145 }
146}