1use crate::geometry::Rectangle;
4use crate::objects::{Array, Object, ObjectId};
5
6#[derive(Debug, Clone, PartialEq)]
8pub enum DestinationType {
9 XYZ {
11 left: Option<f64>,
12 top: Option<f64>,
13 zoom: Option<f64>,
14 },
15 Fit,
17 FitH { top: Option<f64> },
19 FitV { left: Option<f64> },
21 FitR { rect: Rectangle },
23 FitB,
25 FitBH { top: Option<f64> },
27 FitBV { left: Option<f64> },
29}
30
31#[derive(Debug, Clone)]
33pub enum PageDestination {
34 PageNumber(u32),
36 PageRef(ObjectId),
38}
39
40#[derive(Debug, Clone)]
42pub struct Destination {
43 pub page: PageDestination,
45 pub dest_type: DestinationType,
47}
48
49impl Destination {
50 pub fn xyz(
52 page: PageDestination,
53 left: Option<f64>,
54 top: Option<f64>,
55 zoom: Option<f64>,
56 ) -> Self {
57 Self {
58 page,
59 dest_type: DestinationType::XYZ { left, top, zoom },
60 }
61 }
62
63 pub fn fit(page: PageDestination) -> Self {
65 Self {
66 page,
67 dest_type: DestinationType::Fit,
68 }
69 }
70
71 pub fn fit_h(page: PageDestination, top: Option<f64>) -> Self {
73 Self {
74 page,
75 dest_type: DestinationType::FitH { top },
76 }
77 }
78
79 pub fn fit_v(page: PageDestination, left: Option<f64>) -> Self {
81 Self {
82 page,
83 dest_type: DestinationType::FitV { left },
84 }
85 }
86
87 pub fn fit_r(page: PageDestination, rect: Rectangle) -> Self {
89 Self {
90 page,
91 dest_type: DestinationType::FitR { rect },
92 }
93 }
94
95 pub fn fit_b(page: PageDestination) -> Self {
97 Self {
98 page,
99 dest_type: DestinationType::FitB,
100 }
101 }
102
103 pub fn fit_bh(page: PageDestination, top: Option<f64>) -> Self {
105 Self {
106 page,
107 dest_type: DestinationType::FitBH { top },
108 }
109 }
110
111 pub fn fit_bv(page: PageDestination, left: Option<f64>) -> Self {
113 Self {
114 page,
115 dest_type: DestinationType::FitBV { left },
116 }
117 }
118
119 pub fn from_array(arr: &Array) -> Result<Self, crate::error::PdfError> {
121 use crate::error::PdfError;
122
123 if arr.len() < 2 {
124 return Err(PdfError::InvalidStructure(
125 "Destination array too short".into(),
126 ));
127 }
128
129 let page = match arr.get(0) {
131 Some(Object::Integer(num)) => PageDestination::PageNumber(*num as u32),
132 Some(Object::Reference(id)) => PageDestination::PageRef(*id),
133 _ => {
134 return Err(PdfError::InvalidStructure(
135 "Invalid page reference in destination".into(),
136 ))
137 }
138 };
139
140 let type_name = match arr.get(1) {
142 Some(Object::Name(name)) => name,
143 _ => {
144 return Err(PdfError::InvalidStructure(
145 "Invalid destination type".into(),
146 ))
147 }
148 };
149
150 let dest_type = match type_name.as_str() {
151 "XYZ" => {
152 if arr.len() < 5 {
153 return Err(PdfError::InvalidStructure(
154 "XYZ destination missing parameters".into(),
155 ));
156 }
157 let left = match arr.get(2) {
158 Some(Object::Real(v)) => Some(*v),
159 Some(Object::Integer(v)) => Some(*v as f64),
160 Some(Object::Null) => None,
161 _ => {
162 return Err(PdfError::InvalidStructure(
163 "Invalid XYZ left parameter".into(),
164 ))
165 }
166 };
167 let top = match arr.get(3) {
168 Some(Object::Real(v)) => Some(*v),
169 Some(Object::Integer(v)) => Some(*v as f64),
170 Some(Object::Null) => None,
171 _ => {
172 return Err(PdfError::InvalidStructure(
173 "Invalid XYZ top parameter".into(),
174 ))
175 }
176 };
177 let zoom = match arr.get(4) {
178 Some(Object::Real(v)) => Some(*v),
179 Some(Object::Integer(v)) => Some(*v as f64),
180 Some(Object::Null) => None,
181 _ => {
182 return Err(PdfError::InvalidStructure(
183 "Invalid XYZ zoom parameter".into(),
184 ))
185 }
186 };
187 DestinationType::XYZ { left, top, zoom }
188 }
189 "Fit" => DestinationType::Fit,
190 "FitH" => {
191 if arr.len() < 3 {
192 return Err(PdfError::InvalidStructure(
193 "FitH destination missing parameter".into(),
194 ));
195 }
196 let top = match arr.get(2) {
197 Some(Object::Real(v)) => Some(*v),
198 Some(Object::Integer(v)) => Some(*v as f64),
199 Some(Object::Null) => None,
200 _ => {
201 return Err(PdfError::InvalidStructure(
202 "Invalid FitH top parameter".into(),
203 ))
204 }
205 };
206 DestinationType::FitH { top }
207 }
208 "FitV" => {
209 if arr.len() < 3 {
210 return Err(PdfError::InvalidStructure(
211 "FitV destination missing parameter".into(),
212 ));
213 }
214 let left = match arr.get(2) {
215 Some(Object::Real(v)) => Some(*v),
216 Some(Object::Integer(v)) => Some(*v as f64),
217 Some(Object::Null) => None,
218 _ => {
219 return Err(PdfError::InvalidStructure(
220 "Invalid FitV left parameter".into(),
221 ))
222 }
223 };
224 DestinationType::FitV { left }
225 }
226 "FitR" => {
227 if arr.len() < 6 {
228 return Err(PdfError::InvalidStructure(
229 "FitR destination missing parameters".into(),
230 ));
231 }
232 let left = match arr.get(2) {
233 Some(Object::Real(v)) => *v,
234 Some(Object::Integer(v)) => *v as f64,
235 _ => {
236 return Err(PdfError::InvalidStructure(
237 "Invalid FitR left parameter".into(),
238 ))
239 }
240 };
241 let bottom = match arr.get(3) {
242 Some(Object::Real(v)) => *v,
243 Some(Object::Integer(v)) => *v as f64,
244 _ => {
245 return Err(PdfError::InvalidStructure(
246 "Invalid FitR bottom parameter".into(),
247 ))
248 }
249 };
250 let right = match arr.get(4) {
251 Some(Object::Real(v)) => *v,
252 Some(Object::Integer(v)) => *v as f64,
253 _ => {
254 return Err(PdfError::InvalidStructure(
255 "Invalid FitR right parameter".into(),
256 ))
257 }
258 };
259 let top = match arr.get(5) {
260 Some(Object::Real(v)) => *v,
261 Some(Object::Integer(v)) => *v as f64,
262 _ => {
263 return Err(PdfError::InvalidStructure(
264 "Invalid FitR top parameter".into(),
265 ))
266 }
267 };
268 let rect = Rectangle::new(
269 crate::geometry::Point::new(left, bottom),
270 crate::geometry::Point::new(right, top),
271 );
272 DestinationType::FitR { rect }
273 }
274 "FitB" => DestinationType::FitB,
275 "FitBH" => {
276 if arr.len() < 3 {
277 return Err(PdfError::InvalidStructure(
278 "FitBH destination missing parameter".into(),
279 ));
280 }
281 let top = match arr.get(2) {
282 Some(Object::Real(v)) => Some(*v),
283 Some(Object::Integer(v)) => Some(*v as f64),
284 Some(Object::Null) => None,
285 _ => {
286 return Err(PdfError::InvalidStructure(
287 "Invalid FitBH top parameter".into(),
288 ))
289 }
290 };
291 DestinationType::FitBH { top }
292 }
293 "FitBV" => {
294 if arr.len() < 3 {
295 return Err(PdfError::InvalidStructure(
296 "FitBV destination missing parameter".into(),
297 ));
298 }
299 let left = match arr.get(2) {
300 Some(Object::Real(v)) => Some(*v),
301 Some(Object::Integer(v)) => Some(*v as f64),
302 Some(Object::Null) => None,
303 _ => {
304 return Err(PdfError::InvalidStructure(
305 "Invalid FitBV left parameter".into(),
306 ))
307 }
308 };
309 DestinationType::FitBV { left }
310 }
311 _ => {
312 return Err(PdfError::InvalidStructure(format!(
313 "Unknown destination type: {type_name}"
314 )))
315 }
316 };
317
318 Ok(Self { page, dest_type })
319 }
320
321 pub fn to_array(&self) -> Array {
323 let mut arr = Array::new();
324
325 match &self.page {
327 PageDestination::PageNumber(num) => {
328 arr.push(Object::Integer(*num as i64));
329 }
330 PageDestination::PageRef(id) => {
331 arr.push(Object::Reference(*id));
332 }
333 }
334
335 match &self.dest_type {
337 DestinationType::XYZ { left, top, zoom } => {
338 arr.push(Object::Name("XYZ".to_string()));
339 arr.push(left.map(Object::Real).unwrap_or(Object::Null));
340 arr.push(top.map(Object::Real).unwrap_or(Object::Null));
341 arr.push(zoom.map(Object::Real).unwrap_or(Object::Null));
342 }
343 DestinationType::Fit => {
344 arr.push(Object::Name("Fit".to_string()));
345 }
346 DestinationType::FitH { top } => {
347 arr.push(Object::Name("FitH".to_string()));
348 arr.push(top.map(Object::Real).unwrap_or(Object::Null));
349 }
350 DestinationType::FitV { left } => {
351 arr.push(Object::Name("FitV".to_string()));
352 arr.push(left.map(Object::Real).unwrap_or(Object::Null));
353 }
354 DestinationType::FitR { rect } => {
355 arr.push(Object::Name("FitR".to_string()));
356 arr.push(Object::Real(rect.lower_left.x));
357 arr.push(Object::Real(rect.lower_left.y));
358 arr.push(Object::Real(rect.upper_right.x));
359 arr.push(Object::Real(rect.upper_right.y));
360 }
361 DestinationType::FitB => {
362 arr.push(Object::Name("FitB".to_string()));
363 }
364 DestinationType::FitBH { top } => {
365 arr.push(Object::Name("FitBH".to_string()));
366 arr.push(top.map(Object::Real).unwrap_or(Object::Null));
367 }
368 DestinationType::FitBV { left } => {
369 arr.push(Object::Name("FitBV".to_string()));
370 arr.push(left.map(Object::Real).unwrap_or(Object::Null));
371 }
372 }
373
374 arr
375 }
376}
377
378#[cfg(test)]
379mod tests {
380 use super::*;
381 use crate::geometry::Point;
382
383 #[test]
384 fn test_destination_type_debug_clone_partial_eq() {
385 let dest_type = DestinationType::XYZ {
386 left: Some(10.0),
387 top: Some(20.0),
388 zoom: None,
389 };
390 let debug_str = format!("{dest_type:?}");
391 assert!(debug_str.contains("XYZ"));
392
393 let cloned = dest_type.clone();
394 assert_eq!(dest_type, cloned);
395
396 let different = DestinationType::Fit;
397 assert_ne!(dest_type, different);
398 }
399
400 #[test]
401 fn test_page_destination_variants() {
402 let page_num = PageDestination::PageNumber(42);
403 let page_ref = PageDestination::PageRef(ObjectId::new(5, 0));
404
405 match page_num {
406 PageDestination::PageNumber(n) => assert_eq!(n, 42),
407 _ => panic!("Wrong variant"),
408 }
409
410 match page_ref {
411 PageDestination::PageRef(id) => {
412 assert_eq!(id.number(), 5);
413 assert_eq!(id.generation(), 0);
414 }
415 _ => panic!("Wrong variant"),
416 }
417 }
418
419 #[test]
420 fn test_page_destination_debug_clone() {
421 let page_dest = PageDestination::PageNumber(10);
422 let debug_str = format!("{page_dest:?}");
423 assert!(debug_str.contains("PageNumber"));
424 assert!(debug_str.contains("10"));
425
426 let cloned = page_dest;
427 match cloned {
428 PageDestination::PageNumber(n) => assert_eq!(n, 10),
429 _ => panic!("Clone failed"),
430 }
431 }
432
433 #[test]
434 fn test_destination_debug_clone() {
435 let dest = Destination::fit(PageDestination::PageNumber(3));
436 let debug_str = format!("{dest:?}");
437 assert!(debug_str.contains("Destination"));
438 assert!(debug_str.contains("Fit"));
439
440 let cloned = dest;
441 match cloned.page {
442 PageDestination::PageNumber(n) => assert_eq!(n, 3),
443 _ => panic!("Clone failed"),
444 }
445 }
446
447 #[test]
448 fn test_xyz_destination() {
449 let dest = Destination::xyz(
450 PageDestination::PageNumber(0),
451 Some(100.0),
452 Some(200.0),
453 Some(1.5),
454 );
455
456 match dest.dest_type {
457 DestinationType::XYZ { left, top, zoom } => {
458 assert_eq!(left, Some(100.0));
459 assert_eq!(top, Some(200.0));
460 assert_eq!(zoom, Some(1.5));
461 }
462 _ => panic!("Wrong destination type"),
463 }
464
465 let arr = dest.to_array();
466 assert_eq!(arr.len(), 5);
467 assert_eq!(arr.get(0), Some(&Object::Integer(0)));
468 assert_eq!(arr.get(1), Some(&Object::Name("XYZ".to_string())));
469 assert_eq!(arr.get(2), Some(&Object::Real(100.0)));
470 assert_eq!(arr.get(3), Some(&Object::Real(200.0)));
471 assert_eq!(arr.get(4), Some(&Object::Real(1.5)));
472 }
473
474 #[test]
475 fn test_fit_destination() {
476 let dest = Destination::fit(PageDestination::PageNumber(5));
477
478 match dest.dest_type {
479 DestinationType::Fit => (),
480 _ => panic!("Wrong destination type"),
481 }
482
483 let arr = dest.to_array();
484 assert_eq!(arr.len(), 2);
485 assert_eq!(arr.get(0), Some(&Object::Integer(5)));
486 assert_eq!(arr.get(1), Some(&Object::Name("Fit".to_string())));
487 }
488
489 #[test]
490 fn test_fit_h_destination() {
491 let dest = Destination::fit_h(PageDestination::PageNumber(1), Some(150.0));
492 match dest.dest_type {
493 DestinationType::FitH { top } => assert_eq!(top, Some(150.0)),
494 _ => panic!("Wrong destination type"),
495 }
496
497 let arr = dest.to_array();
498 assert_eq!(arr.len(), 3);
499 assert_eq!(arr.get(1), Some(&Object::Name("FitH".to_string())));
500 assert_eq!(arr.get(2), Some(&Object::Real(150.0)));
501
502 let dest_none = Destination::fit_h(PageDestination::PageNumber(1), None);
503 match dest_none.dest_type {
504 DestinationType::FitH { top } => assert!(top.is_none()),
505 _ => panic!("Wrong destination type"),
506 }
507 }
508
509 #[test]
510 fn test_fit_v_destination() {
511 let dest = Destination::fit_v(PageDestination::PageNumber(2), Some(75.0));
512 match dest.dest_type {
513 DestinationType::FitV { left } => assert_eq!(left, Some(75.0)),
514 _ => panic!("Wrong destination type"),
515 }
516
517 let arr = dest.to_array();
518 assert_eq!(arr.len(), 3);
519 assert_eq!(arr.get(1), Some(&Object::Name("FitV".to_string())));
520 assert_eq!(arr.get(2), Some(&Object::Real(75.0)));
521 }
522
523 #[test]
524 fn test_fit_r_destination() {
525 let rect = Rectangle::new(Point::new(50.0, 50.0), Point::new(150.0, 150.0));
526 let dest = Destination::fit_r(PageDestination::PageNumber(2), rect);
527
528 match dest.dest_type {
529 DestinationType::FitR { rect: r } => {
530 assert_eq!(r.lower_left.x, 50.0);
531 assert_eq!(r.lower_left.y, 50.0);
532 assert_eq!(r.upper_right.x, 150.0);
533 assert_eq!(r.upper_right.y, 150.0);
534 }
535 _ => panic!("Wrong destination type"),
536 }
537
538 let arr = dest.to_array();
539 assert_eq!(arr.len(), 6);
540 assert_eq!(arr.get(1), Some(&Object::Name("FitR".to_string())));
541 assert_eq!(arr.get(2), Some(&Object::Real(50.0)));
542 assert_eq!(arr.get(3), Some(&Object::Real(50.0)));
543 assert_eq!(arr.get(4), Some(&Object::Real(150.0)));
544 assert_eq!(arr.get(5), Some(&Object::Real(150.0)));
545 }
546
547 #[test]
548 fn test_fit_b_destinations() {
549 let dest_b = Destination::fit_b(PageDestination::PageNumber(3));
550 match dest_b.dest_type {
551 DestinationType::FitB => (),
552 _ => panic!("Wrong destination type"),
553 }
554
555 let arr = dest_b.to_array();
556 assert_eq!(arr.len(), 2);
557 assert_eq!(arr.get(1), Some(&Object::Name("FitB".to_string())));
558
559 let dest_bh = Destination::fit_bh(PageDestination::PageNumber(4), Some(100.0));
560 match dest_bh.dest_type {
561 DestinationType::FitBH { top } => assert_eq!(top, Some(100.0)),
562 _ => panic!("Wrong destination type"),
563 }
564
565 let dest_bv = Destination::fit_bv(PageDestination::PageNumber(5), Some(50.0));
566 match dest_bv.dest_type {
567 DestinationType::FitBV { left } => assert_eq!(left, Some(50.0)),
568 _ => panic!("Wrong destination type"),
569 }
570 }
571
572 #[test]
573 fn test_destination_to_array_page_ref() {
574 let page_ref = ObjectId::new(10, 0);
575 let dest = Destination::fit(PageDestination::PageRef(page_ref));
576 let array = dest.to_array();
577
578 assert_eq!(array.len(), 2);
579 assert_eq!(array.get(0), Some(&Object::Reference(page_ref)));
580 assert_eq!(array.get(1), Some(&Object::Name("Fit".to_string())));
581 }
582
583 #[test]
584 fn test_destination_to_array_xyz_with_nulls() {
585 let dest = Destination::xyz(PageDestination::PageNumber(0), None, Some(100.0), None);
586 let array = dest.to_array();
587
588 assert_eq!(array.len(), 5);
589 assert_eq!(array.get(0), Some(&Object::Integer(0)));
590 assert_eq!(array.get(1), Some(&Object::Name("XYZ".to_string())));
591 assert_eq!(array.get(2), Some(&Object::Null));
592 assert_eq!(array.get(3), Some(&Object::Real(100.0)));
593 assert_eq!(array.get(4), Some(&Object::Null));
594 }
595
596 #[test]
597 fn test_destination_to_array_all_types() {
598 let destinations = vec![
600 Destination::fit(PageDestination::PageNumber(0)),
601 Destination::fit_h(PageDestination::PageNumber(1), Some(50.0)),
602 Destination::fit_v(PageDestination::PageNumber(2), Some(75.0)),
603 Destination::fit_r(
604 PageDestination::PageNumber(3),
605 Rectangle::new(Point::new(10.0, 20.0), Point::new(100.0, 200.0)),
606 ),
607 Destination::fit_b(PageDestination::PageNumber(4)),
608 Destination::fit_bh(PageDestination::PageNumber(5), Some(150.0)),
609 Destination::fit_bv(PageDestination::PageNumber(6), Some(125.0)),
610 ];
611
612 let expected_names = ["Fit", "FitH", "FitV", "FitR", "FitB", "FitBH", "FitBV"];
613 let expected_lengths = [2, 3, 3, 6, 2, 3, 3];
614
615 for (dest, (expected_name, expected_len)) in destinations
616 .iter()
617 .zip(expected_names.iter().zip(expected_lengths.iter()))
618 {
619 let array = dest.to_array();
620 assert_eq!(array.len(), *expected_len);
621 assert_eq!(
622 array.get(1),
623 Some(&Object::Name((*expected_name).to_string()))
624 );
625 }
626 }
627
628 #[test]
629 fn test_destination_type_all_variants() {
630 let variants = vec![
631 DestinationType::XYZ {
632 left: Some(1.0),
633 top: Some(2.0),
634 zoom: Some(3.0),
635 },
636 DestinationType::Fit,
637 DestinationType::FitH { top: Some(4.0) },
638 DestinationType::FitV { left: Some(5.0) },
639 DestinationType::FitR {
640 rect: Rectangle::new(Point::new(0.0, 0.0), Point::new(10.0, 10.0)),
641 },
642 DestinationType::FitB,
643 DestinationType::FitBH { top: Some(6.0) },
644 DestinationType::FitBV { left: Some(7.0) },
645 ];
646
647 for variant in variants {
648 let _ = format!("{variant:?}"); let _ = variant.clone(); }
651 }
652
653 #[test]
654 fn test_destination_edge_cases() {
655 let dest = Destination::fit(PageDestination::PageNumber(0));
657 let array = dest.to_array();
658 assert_eq!(array.get(0), Some(&Object::Integer(0)));
659
660 let dest = Destination::fit(PageDestination::PageNumber(999999));
662 let array = dest.to_array();
663 assert_eq!(array.get(0), Some(&Object::Integer(999999)));
664
665 let dest = Destination::xyz(
667 PageDestination::PageNumber(0),
668 Some(-100.0),
669 Some(-200.0),
670 Some(0.5),
671 );
672 let array = dest.to_array();
673 assert_eq!(array.get(2), Some(&Object::Real(-100.0)));
674 assert_eq!(array.get(3), Some(&Object::Real(-200.0)));
675 }
676
677 #[test]
678 fn test_destination_from_array() {
679 let mut array = Array::new();
681 array.push(Object::Integer(5));
682 array.push(Object::Name("XYZ".to_string()));
683 array.push(Object::Real(100.0));
684 array.push(Object::Real(200.0));
685 array.push(Object::Real(1.5));
686
687 let dest = Destination::from_array(&array).expect("Should parse destination");
688 match dest.page {
689 PageDestination::PageNumber(n) => assert_eq!(n, 5),
690 _ => panic!("Wrong page type"),
691 }
692 match dest.dest_type {
693 DestinationType::XYZ { left, top, zoom } => {
694 assert_eq!(left, Some(100.0));
695 assert_eq!(top, Some(200.0));
696 assert_eq!(zoom, Some(1.5));
697 }
698 _ => panic!("Wrong destination type"),
699 }
700 }
701
702 #[test]
703 fn test_destination_from_array_with_nulls() {
704 let mut array = Array::new();
705 array.push(Object::Integer(0));
706 array.push(Object::Name("XYZ".to_string()));
707 array.push(Object::Null);
708 array.push(Object::Real(100.0));
709 array.push(Object::Null);
710
711 let dest = Destination::from_array(&array).expect("Should parse destination");
712 match dest.dest_type {
713 DestinationType::XYZ { left, top, zoom } => {
714 assert!(left.is_none());
715 assert_eq!(top, Some(100.0));
716 assert!(zoom.is_none());
717 }
718 _ => panic!("Wrong destination type"),
719 }
720 }
721
722 #[test]
723 fn test_destination_from_array_with_page_ref() {
724 let page_ref = ObjectId::new(10, 0);
725 let mut array = Array::new();
726 array.push(Object::Reference(page_ref));
727 array.push(Object::Name("Fit".to_string()));
728
729 let dest = Destination::from_array(&array).expect("Should parse destination");
730 match dest.page {
731 PageDestination::PageRef(id) => assert_eq!(id, page_ref),
732 _ => panic!("Wrong page type"),
733 }
734 }
735
736 #[test]
737 fn test_destination_from_array_all_types() {
738 let mut array = Array::new();
740 array.push(Object::Integer(1));
741 array.push(Object::Name("FitH".to_string()));
742 array.push(Object::Real(100.0));
743 let dest = Destination::from_array(&array).expect("Should parse FitH");
744 assert!(matches!(
745 dest.dest_type,
746 DestinationType::FitH { top: Some(100.0) }
747 ));
748
749 let mut array = Array::new();
751 array.push(Object::Integer(2));
752 array.push(Object::Name("FitV".to_string()));
753 array.push(Object::Real(50.0));
754 let dest = Destination::from_array(&array).expect("Should parse FitV");
755 assert!(matches!(
756 dest.dest_type,
757 DestinationType::FitV { left: Some(50.0) }
758 ));
759
760 let mut array = Array::new();
762 array.push(Object::Integer(3));
763 array.push(Object::Name("FitR".to_string()));
764 array.push(Object::Real(10.0));
765 array.push(Object::Real(20.0));
766 array.push(Object::Real(100.0));
767 array.push(Object::Real(200.0));
768 let dest = Destination::from_array(&array).expect("Should parse FitR");
769 match dest.dest_type {
770 DestinationType::FitR { rect } => {
771 assert_eq!(rect.lower_left.x, 10.0);
772 assert_eq!(rect.lower_left.y, 20.0);
773 assert_eq!(rect.upper_right.x, 100.0);
774 assert_eq!(rect.upper_right.y, 200.0);
775 }
776 _ => panic!("Wrong type"),
777 }
778
779 let mut array = Array::new();
781 array.push(Object::Integer(4));
782 array.push(Object::Name("FitB".to_string()));
783 let dest = Destination::from_array(&array).expect("Should parse FitB");
784 assert!(matches!(dest.dest_type, DestinationType::FitB));
785 }
786
787 #[test]
788 fn test_destination_from_array_errors() {
789 let empty = Array::new();
791 assert!(Destination::from_array(&empty).is_err());
792
793 let mut array = Array::new();
795 array.push(Object::Integer(0));
796 assert!(Destination::from_array(&array).is_err());
797
798 let mut array = Array::new();
800 array.push(Object::Integer(0));
801 array.push(Object::Name("InvalidType".to_string()));
802 assert!(Destination::from_array(&array).is_err());
803
804 let mut array = Array::new();
806 array.push(Object::Integer(0));
807 array.push(Object::Name("XYZ".to_string()));
808 assert!(Destination::from_array(&array).is_err());
810
811 let mut array = Array::new();
813 array.push(Object::String("invalid".to_string()));
814 array.push(Object::Name("Fit".to_string()));
815 assert!(Destination::from_array(&array).is_err());
816 }
817
818 #[test]
819 fn test_destination_from_array_integer_coordinates() {
820 let mut array = Array::new();
822 array.push(Object::Integer(0));
823 array.push(Object::Name("XYZ".to_string()));
824 array.push(Object::Integer(100));
825 array.push(Object::Integer(200));
826 array.push(Object::Integer(2));
827
828 let dest = Destination::from_array(&array).expect("Should parse with integer coords");
829 match dest.dest_type {
830 DestinationType::XYZ { left, top, zoom } => {
831 assert_eq!(left, Some(100.0));
832 assert_eq!(top, Some(200.0));
833 assert_eq!(zoom, Some(2.0));
834 }
835 _ => panic!("Wrong type"),
836 }
837 }
838
839 #[test]
840 fn test_destination_roundtrip() {
841 let original = Destination::xyz(
843 PageDestination::PageNumber(42),
844 Some(123.45),
845 Some(678.90),
846 Some(1.25),
847 );
848
849 let array = original.to_array();
850 let parsed = Destination::from_array(&array).expect("Should parse");
851
852 match parsed.page {
853 PageDestination::PageNumber(n) => assert_eq!(n, 42),
854 _ => panic!("Wrong page"),
855 }
856
857 match parsed.dest_type {
858 DestinationType::XYZ { left, top, zoom } => {
859 assert_eq!(left, Some(123.45));
860 assert_eq!(top, Some(678.90));
861 assert_eq!(zoom, Some(1.25));
862 }
863 _ => panic!("Wrong type"),
864 }
865 }
866}