1use crate::objects::{Dictionary, Object};
4use crate::structure::{Destination, PageDestination};
5
6#[derive(Debug, Clone)]
8pub struct GoToAction {
9 pub destination: Destination,
11}
12
13impl GoToAction {
14 pub fn new(destination: Destination) -> Self {
16 Self { destination }
17 }
18
19 pub fn to_page(page_number: u32) -> Self {
21 Self {
22 destination: Destination::fit(PageDestination::PageNumber(page_number)),
23 }
24 }
25
26 pub fn to_page_xyz(page_number: u32, x: f64, y: f64, zoom: Option<f64>) -> Self {
28 Self {
29 destination: Destination::xyz(
30 PageDestination::PageNumber(page_number),
31 Some(x),
32 Some(y),
33 zoom,
34 ),
35 }
36 }
37
38 pub fn to_dict(&self) -> Dictionary {
40 let mut dict = Dictionary::new();
41 dict.set("Type", Object::Name("Action".to_string()));
42 dict.set("S", Object::Name("GoTo".to_string()));
43 dict.set("D", Object::Array(self.destination.to_array().into()));
44 dict
45 }
46}
47
48#[derive(Debug, Clone)]
50pub struct RemoteGoToAction {
51 pub file: String,
53 pub destination: Option<RemoteDestination>,
55 pub new_window: Option<bool>,
57}
58
59#[derive(Debug, Clone)]
61pub enum RemoteDestination {
62 PageNumber(u32),
64 Named(String),
66 Explicit(Destination),
68}
69
70impl RemoteGoToAction {
71 pub fn new(file: impl Into<String>) -> Self {
73 Self {
74 file: file.into(),
75 destination: None,
76 new_window: None,
77 }
78 }
79
80 pub fn to_page(mut self, page: u32) -> Self {
82 self.destination = Some(RemoteDestination::PageNumber(page));
83 self
84 }
85
86 pub fn to_named(mut self, name: impl Into<String>) -> Self {
88 self.destination = Some(RemoteDestination::Named(name.into()));
89 self
90 }
91
92 pub fn to_destination(mut self, dest: Destination) -> Self {
94 self.destination = Some(RemoteDestination::Explicit(dest));
95 self
96 }
97
98 pub fn in_new_window(mut self, new_window: bool) -> Self {
100 self.new_window = Some(new_window);
101 self
102 }
103
104 pub fn to_dict(&self) -> Dictionary {
106 let mut dict = Dictionary::new();
107 dict.set("Type", Object::Name("Action".to_string()));
108 dict.set("S", Object::Name("GoToR".to_string()));
109 dict.set("F", Object::String(self.file.clone()));
110
111 if let Some(dest) = &self.destination {
112 match dest {
113 RemoteDestination::PageNumber(page) => {
114 dict.set("D", Object::Integer(*page as i64));
115 }
116 RemoteDestination::Named(name) => {
117 dict.set("D", Object::String(name.clone()));
118 }
119 RemoteDestination::Explicit(destination) => {
120 dict.set("D", Object::Array(destination.to_array().into()));
121 }
122 }
123 }
124
125 if let Some(nw) = self.new_window {
126 dict.set("NewWindow", Object::Boolean(nw));
127 }
128
129 dict
130 }
131}
132
133#[cfg(test)]
134mod tests {
135 use super::*;
136
137 #[test]
138 fn test_goto_action_to_page() {
139 let action = GoToAction::to_page(5);
140 let dict = action.to_dict();
141
142 assert_eq!(dict.get("S"), Some(&Object::Name("GoTo".to_string())));
143 assert!(dict.get("D").is_some());
144 }
145
146 #[test]
147 fn test_goto_action_xyz() {
148 let action = GoToAction::to_page_xyz(2, 100.0, 200.0, Some(1.5));
149 let dict = action.to_dict();
150
151 assert_eq!(dict.get("S"), Some(&Object::Name("GoTo".to_string())));
152
153 if let Some(Object::Array(dest_array)) = dict.get("D") {
155 assert!(dest_array.len() >= 5); } else {
157 panic!("Expected destination array");
158 }
159 }
160
161 #[test]
162 fn test_remote_goto_action() {
163 let action = RemoteGoToAction::new("other.pdf")
164 .to_page(10)
165 .in_new_window(true);
166
167 let dict = action.to_dict();
168
169 assert_eq!(dict.get("S"), Some(&Object::Name("GoToR".to_string())));
170 assert_eq!(
171 dict.get("F"),
172 Some(&Object::String("other.pdf".to_string()))
173 );
174 assert_eq!(dict.get("D"), Some(&Object::Integer(10)));
175 assert_eq!(dict.get("NewWindow"), Some(&Object::Boolean(true)));
176 }
177
178 #[test]
179 fn test_remote_goto_named() {
180 let action = RemoteGoToAction::new("document.pdf").to_named("Chapter1");
181
182 let dict = action.to_dict();
183
184 assert_eq!(dict.get("D"), Some(&Object::String("Chapter1".to_string())));
185 }
186
187 #[test]
188 fn test_goto_action_debug() {
189 let action = GoToAction::to_page_xyz(0, 100.0, 200.0, Some(1.5));
190 let _ = format!("{:?}", action);
191 }
192
193 #[test]
194 fn test_goto_action_clone() {
195 let action = GoToAction::to_page_xyz(0, 100.0, 200.0, Some(1.5));
196 let cloned = action.clone();
197
198 let dict1 = action.to_dict();
199 let dict2 = cloned.to_dict();
200 assert_eq!(dict1.get("S"), dict2.get("S"));
201 assert_eq!(dict1.get("D"), dict2.get("D"));
202 }
203
204 #[test]
205 fn test_goto_action_from_destination() {
206 use crate::structure::{Destination, PageDestination};
207
208 let dest = Destination::fit(PageDestination::PageNumber(5));
210 let action = GoToAction::new(dest);
211 let dict = action.to_dict();
212
213 assert_eq!(dict.get("S"), Some(&Object::Name("GoTo".to_string())));
214 assert!(dict.get("D").is_some());
215 }
216
217 #[test]
218 fn test_goto_action_various_destinations() {
219 use crate::structure::{Destination, PageDestination};
220
221 let fit_dest = Destination::fit(PageDestination::PageNumber(3));
223 let action1 = GoToAction::new(fit_dest);
224 let dict1 = action1.to_dict();
225 assert!(dict1.get("D").is_some());
226
227 let action2 = GoToAction::to_page_xyz(1, 100.0, 200.0, Some(1.5));
229 let dict2 = action2.to_dict();
230 if let Some(Object::Array(dest)) = dict2.get("D") {
231 assert!(dest.len() >= 4); }
233
234 let action3 = GoToAction::to_page_xyz(2, 0.0, 0.0, None);
236 let dict3 = action3.to_dict();
237 assert!(dict3.get("D").is_some());
238 }
239
240 #[test]
241 fn test_goto_action_page_destinations() {
242 use crate::structure::{Destination, PageDestination};
243
244 let page_num_dest = Destination::fit(PageDestination::PageNumber(10));
246 let action = GoToAction::new(page_num_dest);
247 let dict = action.to_dict();
248
249 assert_eq!(dict.get("Type"), Some(&Object::Name("Action".to_string())));
250 assert_eq!(dict.get("S"), Some(&Object::Name("GoTo".to_string())));
251 assert!(dict.get("D").is_some());
252 }
253
254 #[test]
255 fn test_goto_action_coordinate_precision() {
256 let action = GoToAction::to_page_xyz(0, 123.456, 789.012, Some(1.25));
257 let dict = action.to_dict();
258
259 assert_eq!(dict.get("S"), Some(&Object::Name("GoTo".to_string())));
261 assert!(dict.get("D").is_some());
262
263 if let Some(Object::Array(dest)) = dict.get("D") {
265 assert!(!dest.is_empty());
266 }
267 }
268
269 #[test]
270 fn test_remote_goto_action_creation() {
271 let action = RemoteGoToAction::new("external.pdf");
272 let dict = action.to_dict();
273
274 assert_eq!(dict.get("S"), Some(&Object::Name("GoToR".to_string())));
275 assert_eq!(
276 dict.get("F"),
277 Some(&Object::String("external.pdf".to_string()))
278 );
279 assert!(dict.get("D").is_none()); assert!(dict.get("NewWindow").is_none()); }
282
283 #[test]
284 fn test_remote_goto_action_window_options() {
285 let action1 = RemoteGoToAction::new("doc1.pdf").in_new_window(true);
286 let dict1 = action1.to_dict();
287 assert_eq!(dict1.get("NewWindow"), Some(&Object::Boolean(true)));
288
289 let action2 = RemoteGoToAction::new("doc2.pdf").in_new_window(false);
290 let dict2 = action2.to_dict();
291 assert_eq!(dict2.get("NewWindow"), Some(&Object::Boolean(false)));
292
293 let action3 = RemoteGoToAction::new("doc3.pdf"); let dict3 = action3.to_dict();
295 assert!(dict3.get("NewWindow").is_none());
296 }
297
298 #[test]
299 fn test_remote_goto_action_destination_types() {
300 let action1 = RemoteGoToAction::new("target.pdf").to_page(5);
302 let dict1 = action1.to_dict();
303 assert_eq!(dict1.get("D"), Some(&Object::Integer(5)));
304
305 let action2 = RemoteGoToAction::new("target.pdf").to_named("Introduction");
307 let dict2 = action2.to_dict();
308 assert_eq!(
309 dict2.get("D"),
310 Some(&Object::String("Introduction".to_string()))
311 );
312 }
313
314 #[test]
315 fn test_remote_goto_action_clone_debug() {
316 let action = RemoteGoToAction::new("test.pdf")
317 .to_page(3)
318 .in_new_window(true);
319
320 let cloned = action.clone();
321 let dict1 = action.to_dict();
322 let dict2 = cloned.to_dict();
323
324 assert_eq!(dict1.get("F"), dict2.get("F"));
325 assert_eq!(dict1.get("D"), dict2.get("D"));
326 assert_eq!(dict1.get("NewWindow"), dict2.get("NewWindow"));
327
328 let _ = format!("{:?}", action);
330 }
331
332 #[test]
333 fn test_goto_action_edge_cases() {
334 use crate::structure::{Destination, PageDestination};
335
336 let action = GoToAction::to_page(0);
338 let dict = action.to_dict();
339 assert_eq!(dict.get("S"), Some(&Object::Name("GoTo".to_string())));
340 assert!(dict.get("D").is_some());
341
342 let action = GoToAction::to_page(9999);
344 let dict = action.to_dict();
345 assert_eq!(dict.get("S"), Some(&Object::Name("GoTo".to_string())));
346 assert!(dict.get("D").is_some());
347
348 let dest = Destination::fit(PageDestination::PageNumber(0));
350 let action = GoToAction::new(dest);
351 let dict = action.to_dict();
352 assert!(dict.get("D").is_some());
353 }
354
355 #[test]
356 fn test_goto_action_comprehensive() {
357 use crate::structure::{Destination, PageDestination};
358
359 let action1 = GoToAction::to_page(5);
361 let dict1 = action1.to_dict();
362 assert_eq!(dict1.get("S"), Some(&Object::Name("GoTo".to_string())));
363
364 let action2 = GoToAction::to_page_xyz(3, 100.0, 200.0, Some(1.5));
365 let dict2 = action2.to_dict();
366 assert_eq!(dict2.get("S"), Some(&Object::Name("GoTo".to_string())));
367
368 let xyz_dest = Destination::xyz(
370 PageDestination::PageNumber(1),
371 Some(50.0),
372 Some(75.0),
373 Some(2.0),
374 );
375 let action3 = GoToAction::new(xyz_dest);
376 let dict3 = action3.to_dict();
377 assert_eq!(dict3.get("S"), Some(&Object::Name("GoTo".to_string())));
378 }
379}