1use crate::annotation_types::{
29 AnnotationColor, AnnotationFlags, BorderEffect, BorderStyleType, LineEndingStyle,
30};
31use crate::geometry::Rect;
32use crate::object::{Object, ObjectRef};
33use std::collections::HashMap;
34
35#[derive(Debug, Clone)]
43pub struct LineAnnotation {
44 pub start: (f64, f64),
46 pub end: (f64, f64),
48 pub line_endings: (LineEndingStyle, LineEndingStyle),
50 pub color: Option<AnnotationColor>,
52 pub interior_color: Option<AnnotationColor>,
54 pub opacity: Option<f32>,
56 pub border_style: Option<BorderStyleType>,
58 pub line_width: Option<f32>,
60 pub leader_line: Option<f64>,
62 pub leader_line_offset: Option<f64>,
64 pub leader_line_extension: Option<f64>,
66 pub caption: bool,
68 pub contents: Option<String>,
70 pub caption_position: CaptionPosition,
72 pub author: Option<String>,
74 pub subject: Option<String>,
76 pub flags: AnnotationFlags,
78}
79
80#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
82pub enum CaptionPosition {
83 #[default]
85 Inline,
86 Top,
88}
89
90impl CaptionPosition {
91 pub fn pdf_name(&self) -> &'static str {
93 match self {
94 Self::Inline => "Inline",
95 Self::Top => "Top",
96 }
97 }
98}
99
100impl LineAnnotation {
101 pub fn new(start: (f64, f64), end: (f64, f64)) -> Self {
103 Self {
104 start,
105 end,
106 line_endings: (LineEndingStyle::None, LineEndingStyle::None),
107 color: Some(AnnotationColor::black()),
108 interior_color: None,
109 opacity: None,
110 border_style: None,
111 line_width: Some(1.0),
112 leader_line: None,
113 leader_line_offset: None,
114 leader_line_extension: None,
115 caption: false,
116 contents: None,
117 caption_position: CaptionPosition::Inline,
118 author: None,
119 subject: None,
120 flags: AnnotationFlags::printable(),
121 }
122 }
123
124 pub fn arrow(start: (f64, f64), end: (f64, f64)) -> Self {
126 Self::new(start, end).with_line_endings(LineEndingStyle::None, LineEndingStyle::OpenArrow)
127 }
128
129 pub fn double_arrow(start: (f64, f64), end: (f64, f64)) -> Self {
131 Self::new(start, end)
132 .with_line_endings(LineEndingStyle::OpenArrow, LineEndingStyle::OpenArrow)
133 }
134
135 pub fn dimension(start: (f64, f64), end: (f64, f64), leader_length: f64) -> Self {
137 Self::new(start, end)
138 .with_line_endings(LineEndingStyle::OpenArrow, LineEndingStyle::OpenArrow)
139 .with_leader_line(leader_length)
140 }
141
142 pub fn with_line_endings(mut self, start: LineEndingStyle, end: LineEndingStyle) -> Self {
144 self.line_endings = (start, end);
145 self
146 }
147
148 pub fn with_stroke_color(mut self, r: f32, g: f32, b: f32) -> Self {
150 self.color = Some(AnnotationColor::Rgb(r, g, b));
151 self
152 }
153
154 pub fn with_fill_color(mut self, r: f32, g: f32, b: f32) -> Self {
156 self.interior_color = Some(AnnotationColor::Rgb(r, g, b));
157 self
158 }
159
160 pub fn with_line_width(mut self, width: f32) -> Self {
162 self.line_width = Some(width);
163 self
164 }
165
166 pub fn with_border_style(mut self, style: BorderStyleType) -> Self {
168 self.border_style = Some(style);
169 self
170 }
171
172 pub fn with_leader_line(mut self, length: f64) -> Self {
174 self.leader_line = Some(length);
175 self
176 }
177
178 pub fn with_leader_offset(mut self, offset: f64) -> Self {
180 self.leader_line_offset = Some(offset);
181 self
182 }
183
184 pub fn with_caption(mut self, text: impl Into<String>) -> Self {
186 self.caption = true;
187 self.contents = Some(text.into());
188 self
189 }
190
191 pub fn with_caption_position(mut self, position: CaptionPosition) -> Self {
193 self.caption_position = position;
194 self
195 }
196
197 pub fn with_opacity(mut self, opacity: f32) -> Self {
199 self.opacity = Some(opacity.clamp(0.0, 1.0));
200 self
201 }
202
203 pub fn with_author(mut self, author: impl Into<String>) -> Self {
205 self.author = Some(author.into());
206 self
207 }
208
209 pub fn with_subject(mut self, subject: impl Into<String>) -> Self {
211 self.subject = Some(subject.into());
212 self
213 }
214
215 pub fn with_flags(mut self, flags: AnnotationFlags) -> Self {
217 self.flags = flags;
218 self
219 }
220
221 pub fn calculate_rect(&self) -> Rect {
223 let min_x = self.start.0.min(self.end.0);
224 let max_x = self.start.0.max(self.end.0);
225 let min_y = self.start.1.min(self.end.1);
226 let max_y = self.start.1.max(self.end.1);
227
228 let margin = 10.0;
230 Rect::new(
231 (min_x - margin) as f32,
232 (min_y - margin) as f32,
233 (max_x - min_x + 2.0 * margin) as f32,
234 (max_y - min_y + 2.0 * margin) as f32,
235 )
236 }
237
238 pub fn build(&self, _page_refs: &[ObjectRef]) -> HashMap<String, Object> {
240 let mut dict = HashMap::new();
241
242 dict.insert("Type".to_string(), Object::Name("Annot".to_string()));
244 dict.insert("Subtype".to_string(), Object::Name("Line".to_string()));
245
246 let rect = self.calculate_rect();
248 dict.insert(
249 "Rect".to_string(),
250 Object::Array(vec![
251 Object::Real(rect.x as f64),
252 Object::Real(rect.y as f64),
253 Object::Real((rect.x + rect.width) as f64),
254 Object::Real((rect.y + rect.height) as f64),
255 ]),
256 );
257
258 dict.insert(
260 "L".to_string(),
261 Object::Array(vec![
262 Object::Real(self.start.0),
263 Object::Real(self.start.1),
264 Object::Real(self.end.0),
265 Object::Real(self.end.1),
266 ]),
267 );
268
269 if self.line_endings != (LineEndingStyle::None, LineEndingStyle::None) {
271 dict.insert(
272 "LE".to_string(),
273 Object::Array(vec![
274 Object::Name(self.line_endings.0.pdf_name().to_string()),
275 Object::Name(self.line_endings.1.pdf_name().to_string()),
276 ]),
277 );
278 }
279
280 if let Some(ref contents) = self.contents {
282 dict.insert("Contents".to_string(), Object::String(contents.as_bytes().to_vec()));
283 }
284
285 if self.flags.bits() != 0 {
287 dict.insert("F".to_string(), Object::Integer(self.flags.bits() as i64));
288 }
289
290 if let Some(ref color) = self.color {
292 if let Some(color_array) = color.to_array() {
293 if !color_array.is_empty() {
294 dict.insert(
295 "C".to_string(),
296 Object::Array(
297 color_array
298 .into_iter()
299 .map(|v| Object::Real(v as f64))
300 .collect(),
301 ),
302 );
303 }
304 }
305 }
306
307 if let Some(ref color) = self.interior_color {
309 if let Some(color_array) = color.to_array() {
310 if !color_array.is_empty() {
311 dict.insert(
312 "IC".to_string(),
313 Object::Array(
314 color_array
315 .into_iter()
316 .map(|v| Object::Real(v as f64))
317 .collect(),
318 ),
319 );
320 }
321 }
322 }
323
324 if let Some(opacity) = self.opacity {
326 dict.insert("CA".to_string(), Object::Real(opacity as f64));
327 }
328
329 if self.border_style.is_some() || self.line_width.is_some() {
331 let mut bs = HashMap::new();
332 bs.insert("Type".to_string(), Object::Name("Border".to_string()));
333 if let Some(width) = self.line_width {
334 bs.insert("W".to_string(), Object::Real(width as f64));
335 }
336 if let Some(ref style) = self.border_style {
337 let style_char = match style {
338 BorderStyleType::Solid => "S",
339 BorderStyleType::Dashed => "D",
340 BorderStyleType::Beveled => "B",
341 BorderStyleType::Inset => "I",
342 BorderStyleType::Underline => "U",
343 };
344 bs.insert("S".to_string(), Object::Name(style_char.to_string()));
345 }
346 dict.insert("BS".to_string(), Object::Dictionary(bs));
347 }
348
349 if let Some(ll) = self.leader_line {
351 dict.insert("LL".to_string(), Object::Real(ll));
352 }
353
354 if let Some(llo) = self.leader_line_offset {
356 dict.insert("LLO".to_string(), Object::Real(llo));
357 }
358
359 if let Some(lle) = self.leader_line_extension {
361 dict.insert("LLE".to_string(), Object::Real(lle));
362 }
363
364 if self.caption {
366 dict.insert("Cap".to_string(), Object::Boolean(true));
367 }
368
369 if self.caption && self.caption_position != CaptionPosition::Inline {
371 dict.insert(
372 "CP".to_string(),
373 Object::Name(self.caption_position.pdf_name().to_string()),
374 );
375 }
376
377 if let Some(ref author) = self.author {
379 dict.insert("T".to_string(), Object::String(author.as_bytes().to_vec()));
380 }
381
382 if let Some(ref subject) = self.subject {
384 dict.insert("Subj".to_string(), Object::String(subject.as_bytes().to_vec()));
385 }
386
387 dict
388 }
389}
390
391#[derive(Debug, Clone, Copy, PartialEq, Eq)]
397pub enum ShapeType {
398 Square,
400 Circle,
402}
403
404impl ShapeType {
405 pub fn pdf_name(&self) -> &'static str {
407 match self {
408 Self::Square => "Square",
409 Self::Circle => "Circle",
410 }
411 }
412}
413
414#[derive(Debug, Clone)]
416pub struct ShapeAnnotation {
417 pub rect: Rect,
419 pub shape_type: ShapeType,
421 pub color: Option<AnnotationColor>,
423 pub interior_color: Option<AnnotationColor>,
425 pub opacity: Option<f32>,
427 pub border_style: Option<BorderStyleType>,
429 pub border_width: Option<f32>,
431 pub border_effect: Option<BorderEffect>,
433 pub rect_differences: Option<[f32; 4]>,
435 pub contents: Option<String>,
437 pub author: Option<String>,
439 pub subject: Option<String>,
441 pub flags: AnnotationFlags,
443}
444
445impl ShapeAnnotation {
446 pub fn new(rect: Rect, shape_type: ShapeType) -> Self {
448 Self {
449 rect,
450 shape_type,
451 color: Some(AnnotationColor::black()),
452 interior_color: None,
453 opacity: None,
454 border_style: None,
455 border_width: Some(1.0),
456 border_effect: None,
457 rect_differences: None,
458 contents: None,
459 author: None,
460 subject: None,
461 flags: AnnotationFlags::printable(),
462 }
463 }
464
465 pub fn square(rect: Rect) -> Self {
467 Self::new(rect, ShapeType::Square)
468 }
469
470 pub fn circle(rect: Rect) -> Self {
472 Self::new(rect, ShapeType::Circle)
473 }
474
475 pub fn with_stroke_color(mut self, r: f32, g: f32, b: f32) -> Self {
477 self.color = Some(AnnotationColor::Rgb(r, g, b));
478 self
479 }
480
481 pub fn with_fill_color(mut self, r: f32, g: f32, b: f32) -> Self {
483 self.interior_color = Some(AnnotationColor::Rgb(r, g, b));
484 self
485 }
486
487 pub fn with_no_fill(mut self) -> Self {
489 self.interior_color = None;
490 self
491 }
492
493 pub fn with_border_width(mut self, width: f32) -> Self {
495 self.border_width = Some(width);
496 self
497 }
498
499 pub fn with_border_style(mut self, style: BorderStyleType) -> Self {
501 self.border_style = Some(style);
502 self
503 }
504
505 pub fn with_border_effect(mut self, effect: BorderEffect) -> Self {
507 self.border_effect = Some(effect);
508 self
509 }
510
511 pub fn with_opacity(mut self, opacity: f32) -> Self {
513 self.opacity = Some(opacity.clamp(0.0, 1.0));
514 self
515 }
516
517 pub fn with_contents(mut self, contents: impl Into<String>) -> Self {
519 self.contents = Some(contents.into());
520 self
521 }
522
523 pub fn with_author(mut self, author: impl Into<String>) -> Self {
525 self.author = Some(author.into());
526 self
527 }
528
529 pub fn with_subject(mut self, subject: impl Into<String>) -> Self {
531 self.subject = Some(subject.into());
532 self
533 }
534
535 pub fn with_flags(mut self, flags: AnnotationFlags) -> Self {
537 self.flags = flags;
538 self
539 }
540
541 pub fn build(&self, _page_refs: &[ObjectRef]) -> HashMap<String, Object> {
543 let mut dict = HashMap::new();
544
545 dict.insert("Type".to_string(), Object::Name("Annot".to_string()));
547 dict.insert("Subtype".to_string(), Object::Name(self.shape_type.pdf_name().to_string()));
548
549 dict.insert(
551 "Rect".to_string(),
552 Object::Array(vec![
553 Object::Real(self.rect.x as f64),
554 Object::Real(self.rect.y as f64),
555 Object::Real((self.rect.x + self.rect.width) as f64),
556 Object::Real((self.rect.y + self.rect.height) as f64),
557 ]),
558 );
559
560 if let Some(ref contents) = self.contents {
562 dict.insert("Contents".to_string(), Object::String(contents.as_bytes().to_vec()));
563 }
564
565 if self.flags.bits() != 0 {
567 dict.insert("F".to_string(), Object::Integer(self.flags.bits() as i64));
568 }
569
570 if let Some(ref color) = self.color {
572 if let Some(color_array) = color.to_array() {
573 if !color_array.is_empty() {
574 dict.insert(
575 "C".to_string(),
576 Object::Array(
577 color_array
578 .into_iter()
579 .map(|v| Object::Real(v as f64))
580 .collect(),
581 ),
582 );
583 }
584 }
585 }
586
587 if let Some(ref color) = self.interior_color {
589 if let Some(color_array) = color.to_array() {
590 if !color_array.is_empty() {
591 dict.insert(
592 "IC".to_string(),
593 Object::Array(
594 color_array
595 .into_iter()
596 .map(|v| Object::Real(v as f64))
597 .collect(),
598 ),
599 );
600 }
601 }
602 }
603
604 if let Some(opacity) = self.opacity {
606 dict.insert("CA".to_string(), Object::Real(opacity as f64));
607 }
608
609 if self.border_style.is_some() || self.border_width.is_some() {
611 let mut bs = HashMap::new();
612 bs.insert("Type".to_string(), Object::Name("Border".to_string()));
613 if let Some(width) = self.border_width {
614 bs.insert("W".to_string(), Object::Real(width as f64));
615 }
616 if let Some(ref style) = self.border_style {
617 let style_char = match style {
618 BorderStyleType::Solid => "S",
619 BorderStyleType::Dashed => "D",
620 BorderStyleType::Beveled => "B",
621 BorderStyleType::Inset => "I",
622 BorderStyleType::Underline => "U",
623 };
624 bs.insert("S".to_string(), Object::Name(style_char.to_string()));
625 }
626 dict.insert("BS".to_string(), Object::Dictionary(bs));
627 }
628
629 if let Some(ref be) = self.border_effect {
631 let mut be_dict = HashMap::new();
632 be_dict.insert("S".to_string(), Object::Name(be.style.pdf_name().to_string()));
633 if be.intensity > 0.0 {
634 be_dict.insert("I".to_string(), Object::Real(be.intensity as f64));
635 }
636 dict.insert("BE".to_string(), Object::Dictionary(be_dict));
637 }
638
639 if let Some(rd) = self.rect_differences {
641 dict.insert(
642 "RD".to_string(),
643 Object::Array(vec![
644 Object::Real(rd[0] as f64),
645 Object::Real(rd[1] as f64),
646 Object::Real(rd[2] as f64),
647 Object::Real(rd[3] as f64),
648 ]),
649 );
650 }
651
652 if let Some(ref author) = self.author {
654 dict.insert("T".to_string(), Object::String(author.as_bytes().to_vec()));
655 }
656
657 if let Some(ref subject) = self.subject {
659 dict.insert("Subj".to_string(), Object::String(subject.as_bytes().to_vec()));
660 }
661
662 dict
663 }
664}
665
666#[derive(Debug, Clone, Copy, PartialEq, Eq)]
672pub enum PolygonType {
673 Polygon,
675 PolyLine,
677}
678
679impl PolygonType {
680 pub fn pdf_name(&self) -> &'static str {
682 match self {
683 Self::Polygon => "Polygon",
684 Self::PolyLine => "PolyLine",
685 }
686 }
687}
688
689#[derive(Debug, Clone)]
691pub struct PolygonAnnotation {
692 pub vertices: Vec<(f64, f64)>,
694 pub polygon_type: PolygonType,
696 pub line_endings: Option<(LineEndingStyle, LineEndingStyle)>,
698 pub color: Option<AnnotationColor>,
700 pub interior_color: Option<AnnotationColor>,
702 pub opacity: Option<f32>,
704 pub border_style: Option<BorderStyleType>,
706 pub border_width: Option<f32>,
708 pub border_effect: Option<BorderEffect>,
710 pub contents: Option<String>,
712 pub author: Option<String>,
714 pub subject: Option<String>,
716 pub flags: AnnotationFlags,
718}
719
720impl PolygonAnnotation {
721 pub fn polygon(vertices: Vec<(f64, f64)>) -> Self {
723 Self {
724 vertices,
725 polygon_type: PolygonType::Polygon,
726 line_endings: None,
727 color: Some(AnnotationColor::black()),
728 interior_color: None,
729 opacity: None,
730 border_style: None,
731 border_width: Some(1.0),
732 border_effect: None,
733 contents: None,
734 author: None,
735 subject: None,
736 flags: AnnotationFlags::printable(),
737 }
738 }
739
740 pub fn polyline(vertices: Vec<(f64, f64)>) -> Self {
742 Self {
743 vertices,
744 polygon_type: PolygonType::PolyLine,
745 line_endings: None,
746 color: Some(AnnotationColor::black()),
747 interior_color: None,
748 opacity: None,
749 border_style: None,
750 border_width: Some(1.0),
751 border_effect: None,
752 contents: None,
753 author: None,
754 subject: None,
755 flags: AnnotationFlags::printable(),
756 }
757 }
758
759 pub fn with_line_endings(mut self, start: LineEndingStyle, end: LineEndingStyle) -> Self {
761 self.line_endings = Some((start, end));
762 self
763 }
764
765 pub fn with_stroke_color(mut self, r: f32, g: f32, b: f32) -> Self {
767 self.color = Some(AnnotationColor::Rgb(r, g, b));
768 self
769 }
770
771 pub fn with_fill_color(mut self, r: f32, g: f32, b: f32) -> Self {
773 self.interior_color = Some(AnnotationColor::Rgb(r, g, b));
774 self
775 }
776
777 pub fn with_no_fill(mut self) -> Self {
779 self.interior_color = None;
780 self
781 }
782
783 pub fn with_border_width(mut self, width: f32) -> Self {
785 self.border_width = Some(width);
786 self
787 }
788
789 pub fn with_border_style(mut self, style: BorderStyleType) -> Self {
791 self.border_style = Some(style);
792 self
793 }
794
795 pub fn with_border_effect(mut self, effect: BorderEffect) -> Self {
797 self.border_effect = Some(effect);
798 self
799 }
800
801 pub fn with_opacity(mut self, opacity: f32) -> Self {
803 self.opacity = Some(opacity.clamp(0.0, 1.0));
804 self
805 }
806
807 pub fn with_contents(mut self, contents: impl Into<String>) -> Self {
809 self.contents = Some(contents.into());
810 self
811 }
812
813 pub fn with_author(mut self, author: impl Into<String>) -> Self {
815 self.author = Some(author.into());
816 self
817 }
818
819 pub fn with_subject(mut self, subject: impl Into<String>) -> Self {
821 self.subject = Some(subject.into());
822 self
823 }
824
825 pub fn with_flags(mut self, flags: AnnotationFlags) -> Self {
827 self.flags = flags;
828 self
829 }
830
831 pub fn calculate_rect(&self) -> Rect {
833 if self.vertices.is_empty() {
834 return Rect::new(0.0, 0.0, 0.0, 0.0);
835 }
836
837 let mut min_x = f64::MAX;
838 let mut max_x = f64::MIN;
839 let mut min_y = f64::MAX;
840 let mut max_y = f64::MIN;
841
842 for (x, y) in &self.vertices {
843 min_x = min_x.min(*x);
844 max_x = max_x.max(*x);
845 min_y = min_y.min(*y);
846 max_y = max_y.max(*y);
847 }
848
849 let margin = 5.0;
851 Rect::new(
852 (min_x - margin) as f32,
853 (min_y - margin) as f32,
854 (max_x - min_x + 2.0 * margin) as f32,
855 (max_y - min_y + 2.0 * margin) as f32,
856 )
857 }
858
859 pub fn build(&self, _page_refs: &[ObjectRef]) -> HashMap<String, Object> {
861 let mut dict = HashMap::new();
862
863 dict.insert("Type".to_string(), Object::Name("Annot".to_string()));
865 dict.insert("Subtype".to_string(), Object::Name(self.polygon_type.pdf_name().to_string()));
866
867 let rect = self.calculate_rect();
869 dict.insert(
870 "Rect".to_string(),
871 Object::Array(vec![
872 Object::Real(rect.x as f64),
873 Object::Real(rect.y as f64),
874 Object::Real((rect.x + rect.width) as f64),
875 Object::Real((rect.y + rect.height) as f64),
876 ]),
877 );
878
879 let vertices: Vec<Object> = self
881 .vertices
882 .iter()
883 .flat_map(|(x, y)| vec![Object::Real(*x), Object::Real(*y)])
884 .collect();
885 dict.insert("Vertices".to_string(), Object::Array(vertices));
886
887 if let Some(ref contents) = self.contents {
889 dict.insert("Contents".to_string(), Object::String(contents.as_bytes().to_vec()));
890 }
891
892 if self.flags.bits() != 0 {
894 dict.insert("F".to_string(), Object::Integer(self.flags.bits() as i64));
895 }
896
897 if let Some((start, end)) = &self.line_endings {
899 if self.polygon_type == PolygonType::PolyLine {
900 dict.insert(
901 "LE".to_string(),
902 Object::Array(vec![
903 Object::Name(start.pdf_name().to_string()),
904 Object::Name(end.pdf_name().to_string()),
905 ]),
906 );
907 }
908 }
909
910 if let Some(ref color) = self.color {
912 if let Some(color_array) = color.to_array() {
913 if !color_array.is_empty() {
914 dict.insert(
915 "C".to_string(),
916 Object::Array(
917 color_array
918 .into_iter()
919 .map(|v| Object::Real(v as f64))
920 .collect(),
921 ),
922 );
923 }
924 }
925 }
926
927 if let Some(ref color) = self.interior_color {
929 if let Some(color_array) = color.to_array() {
930 if !color_array.is_empty() {
931 dict.insert(
932 "IC".to_string(),
933 Object::Array(
934 color_array
935 .into_iter()
936 .map(|v| Object::Real(v as f64))
937 .collect(),
938 ),
939 );
940 }
941 }
942 }
943
944 if let Some(opacity) = self.opacity {
946 dict.insert("CA".to_string(), Object::Real(opacity as f64));
947 }
948
949 if self.border_style.is_some() || self.border_width.is_some() {
951 let mut bs = HashMap::new();
952 bs.insert("Type".to_string(), Object::Name("Border".to_string()));
953 if let Some(width) = self.border_width {
954 bs.insert("W".to_string(), Object::Real(width as f64));
955 }
956 if let Some(ref style) = self.border_style {
957 let style_char = match style {
958 BorderStyleType::Solid => "S",
959 BorderStyleType::Dashed => "D",
960 BorderStyleType::Beveled => "B",
961 BorderStyleType::Inset => "I",
962 BorderStyleType::Underline => "U",
963 };
964 bs.insert("S".to_string(), Object::Name(style_char.to_string()));
965 }
966 dict.insert("BS".to_string(), Object::Dictionary(bs));
967 }
968
969 if let Some(ref be) = self.border_effect {
971 let mut be_dict = HashMap::new();
972 be_dict.insert("S".to_string(), Object::Name(be.style.pdf_name().to_string()));
973 if be.intensity > 0.0 {
974 be_dict.insert("I".to_string(), Object::Real(be.intensity as f64));
975 }
976 dict.insert("BE".to_string(), Object::Dictionary(be_dict));
977 }
978
979 if let Some(ref author) = self.author {
981 dict.insert("T".to_string(), Object::String(author.as_bytes().to_vec()));
982 }
983
984 if let Some(ref subject) = self.subject {
986 dict.insert("Subj".to_string(), Object::String(subject.as_bytes().to_vec()));
987 }
988
989 dict
990 }
991}
992
993#[cfg(test)]
994mod tests {
995 use super::*;
996 use crate::annotation_types::BorderEffectStyle;
997
998 #[test]
1001 fn test_line_annotation_new() {
1002 let line = LineAnnotation::new((100.0, 100.0), (200.0, 200.0));
1003 assert_eq!(line.start, (100.0, 100.0));
1004 assert_eq!(line.end, (200.0, 200.0));
1005 assert_eq!(line.line_endings, (LineEndingStyle::None, LineEndingStyle::None));
1006 }
1007
1008 #[test]
1009 fn test_line_annotation_arrow() {
1010 let line = LineAnnotation::arrow((0.0, 0.0), (100.0, 100.0));
1011 assert_eq!(line.line_endings.1, LineEndingStyle::OpenArrow);
1012 }
1013
1014 #[test]
1015 fn test_line_annotation_double_arrow() {
1016 let line = LineAnnotation::double_arrow((0.0, 0.0), (100.0, 100.0));
1017 assert_eq!(line.line_endings.0, LineEndingStyle::OpenArrow);
1018 assert_eq!(line.line_endings.1, LineEndingStyle::OpenArrow);
1019 }
1020
1021 #[test]
1022 fn test_line_annotation_build() {
1023 let line = LineAnnotation::new((100.0, 200.0), (300.0, 400.0))
1024 .with_stroke_color(1.0, 0.0, 0.0)
1025 .with_line_endings(LineEndingStyle::None, LineEndingStyle::ClosedArrow);
1026
1027 let dict = line.build(&[]);
1028
1029 assert_eq!(dict.get("Type"), Some(&Object::Name("Annot".to_string())));
1030 assert_eq!(dict.get("Subtype"), Some(&Object::Name("Line".to_string())));
1031 assert!(dict.contains_key("L")); assert!(dict.contains_key("LE")); assert!(dict.contains_key("C")); }
1035
1036 #[test]
1037 fn test_line_with_caption() {
1038 let line = LineAnnotation::new((100.0, 100.0), (200.0, 100.0))
1039 .with_caption("10 cm")
1040 .with_caption_position(CaptionPosition::Top);
1041
1042 assert!(line.caption);
1043 assert_eq!(line.contents, Some("10 cm".to_string()));
1044
1045 let dict = line.build(&[]);
1046 assert_eq!(dict.get("Cap"), Some(&Object::Boolean(true)));
1047 assert_eq!(dict.get("CP"), Some(&Object::Name("Top".to_string())));
1048 }
1049
1050 #[test]
1053 fn test_shape_annotation_square() {
1054 let rect = Rect::new(72.0, 600.0, 100.0, 80.0);
1055 let shape = ShapeAnnotation::square(rect);
1056
1057 assert_eq!(shape.shape_type, ShapeType::Square);
1058 }
1059
1060 #[test]
1061 fn test_shape_annotation_circle() {
1062 let rect = Rect::new(72.0, 600.0, 100.0, 100.0);
1063 let shape = ShapeAnnotation::circle(rect);
1064
1065 assert_eq!(shape.shape_type, ShapeType::Circle);
1066 }
1067
1068 #[test]
1069 fn test_shape_annotation_build() {
1070 let rect = Rect::new(100.0, 500.0, 150.0, 100.0);
1071 let shape = ShapeAnnotation::square(rect)
1072 .with_stroke_color(0.0, 0.0, 1.0)
1073 .with_fill_color(0.8, 0.8, 1.0)
1074 .with_border_width(2.0);
1075
1076 let dict = shape.build(&[]);
1077
1078 assert_eq!(dict.get("Type"), Some(&Object::Name("Annot".to_string())));
1079 assert_eq!(dict.get("Subtype"), Some(&Object::Name("Square".to_string())));
1080 assert!(dict.contains_key("C")); assert!(dict.contains_key("IC")); assert!(dict.contains_key("BS")); }
1084
1085 #[test]
1086 fn test_shape_annotation_circle_build() {
1087 let rect = Rect::new(200.0, 400.0, 80.0, 80.0);
1088 let shape = ShapeAnnotation::circle(rect).with_stroke_color(1.0, 0.0, 0.0);
1089
1090 let dict = shape.build(&[]);
1091
1092 assert_eq!(dict.get("Subtype"), Some(&Object::Name("Circle".to_string())));
1093 }
1094
1095 #[test]
1098 fn test_polygon_annotation_triangle() {
1099 let vertices = vec![(100.0, 100.0), (150.0, 200.0), (50.0, 200.0)];
1100 let polygon = PolygonAnnotation::polygon(vertices.clone());
1101
1102 assert_eq!(polygon.vertices, vertices);
1103 assert_eq!(polygon.polygon_type, PolygonType::Polygon);
1104 }
1105
1106 #[test]
1107 fn test_polyline_annotation() {
1108 let vertices = vec![(100.0, 100.0), (200.0, 150.0), (300.0, 100.0)];
1109 let polyline = PolygonAnnotation::polyline(vertices);
1110
1111 assert_eq!(polyline.polygon_type, PolygonType::PolyLine);
1112 }
1113
1114 #[test]
1115 fn test_polygon_build() {
1116 let vertices = vec![(100.0, 100.0), (200.0, 100.0), (150.0, 200.0)];
1117 let polygon = PolygonAnnotation::polygon(vertices)
1118 .with_stroke_color(0.0, 0.5, 0.0)
1119 .with_fill_color(0.8, 1.0, 0.8);
1120
1121 let dict = polygon.build(&[]);
1122
1123 assert_eq!(dict.get("Type"), Some(&Object::Name("Annot".to_string())));
1124 assert_eq!(dict.get("Subtype"), Some(&Object::Name("Polygon".to_string())));
1125 assert!(dict.contains_key("Vertices"));
1126 assert!(dict.contains_key("C")); assert!(dict.contains_key("IC")); }
1129
1130 #[test]
1131 fn test_polyline_with_line_endings() {
1132 let vertices = vec![(100.0, 100.0), (200.0, 150.0), (300.0, 100.0)];
1133 let polyline = PolygonAnnotation::polyline(vertices)
1134 .with_line_endings(LineEndingStyle::Circle, LineEndingStyle::OpenArrow);
1135
1136 let dict = polyline.build(&[]);
1137
1138 assert_eq!(dict.get("Subtype"), Some(&Object::Name("PolyLine".to_string())));
1139 assert!(dict.contains_key("LE")); }
1141
1142 #[test]
1143 fn test_polygon_calculate_rect() {
1144 let vertices = vec![(50.0, 50.0), (150.0, 50.0), (100.0, 150.0)];
1145 let polygon = PolygonAnnotation::polygon(vertices);
1146
1147 let rect = polygon.calculate_rect();
1148
1149 assert!(rect.x < 50.0);
1151 assert!(rect.y < 50.0);
1152 assert!(rect.x + rect.width > 150.0);
1153 assert!(rect.y + rect.height > 150.0);
1154 }
1155
1156 #[test]
1161 fn test_caption_position_pdf_name() {
1162 assert_eq!(CaptionPosition::Inline.pdf_name(), "Inline");
1163 assert_eq!(CaptionPosition::Top.pdf_name(), "Top");
1164 }
1165
1166 #[test]
1167 fn test_caption_position_default() {
1168 let pos = CaptionPosition::default();
1169 assert_eq!(pos, CaptionPosition::Inline);
1170 }
1171
1172 #[test]
1175 fn test_shape_type_pdf_name() {
1176 assert_eq!(ShapeType::Square.pdf_name(), "Square");
1177 assert_eq!(ShapeType::Circle.pdf_name(), "Circle");
1178 }
1179
1180 #[test]
1183 fn test_polygon_type_pdf_name() {
1184 assert_eq!(PolygonType::Polygon.pdf_name(), "Polygon");
1185 assert_eq!(PolygonType::PolyLine.pdf_name(), "PolyLine");
1186 }
1187
1188 #[test]
1191 fn test_line_annotation_dimension() {
1192 let line = LineAnnotation::dimension((50.0, 50.0), (200.0, 50.0), 20.0);
1193 assert_eq!(line.line_endings.0, LineEndingStyle::OpenArrow);
1194 assert_eq!(line.line_endings.1, LineEndingStyle::OpenArrow);
1195 assert_eq!(line.leader_line, Some(20.0));
1196 }
1197
1198 #[test]
1199 fn test_line_annotation_with_stroke_color() {
1200 let line = LineAnnotation::new((0.0, 0.0), (100.0, 100.0)).with_stroke_color(1.0, 0.0, 0.0);
1201 match line.color {
1202 Some(AnnotationColor::Rgb(r, g, b)) => {
1203 assert!((r - 1.0).abs() < 0.001);
1204 assert!((g - 0.0).abs() < 0.001);
1205 assert!((b - 0.0).abs() < 0.001);
1206 },
1207 _ => panic!("Expected RGB color"),
1208 }
1209 }
1210
1211 #[test]
1212 fn test_line_annotation_with_fill_color() {
1213 let line = LineAnnotation::new((0.0, 0.0), (100.0, 100.0)).with_fill_color(0.0, 1.0, 0.0);
1214 assert!(line.interior_color.is_some());
1215 }
1216
1217 #[test]
1218 fn test_line_annotation_with_line_width() {
1219 let line = LineAnnotation::new((0.0, 0.0), (100.0, 100.0)).with_line_width(3.0);
1220 assert_eq!(line.line_width, Some(3.0));
1221 }
1222
1223 #[test]
1224 fn test_line_annotation_with_border_style() {
1225 let line = LineAnnotation::new((0.0, 0.0), (100.0, 100.0))
1226 .with_border_style(BorderStyleType::Dashed);
1227 assert_eq!(line.border_style, Some(BorderStyleType::Dashed));
1228 }
1229
1230 #[test]
1231 fn test_line_annotation_with_leader_line() {
1232 let line = LineAnnotation::new((0.0, 0.0), (100.0, 100.0)).with_leader_line(15.0);
1233 assert_eq!(line.leader_line, Some(15.0));
1234 }
1235
1236 #[test]
1237 fn test_line_annotation_with_leader_offset() {
1238 let line = LineAnnotation::new((0.0, 0.0), (100.0, 100.0)).with_leader_offset(5.0);
1239 assert_eq!(line.leader_line_offset, Some(5.0));
1240 }
1241
1242 #[test]
1243 fn test_line_annotation_with_opacity() {
1244 let line = LineAnnotation::new((0.0, 0.0), (100.0, 100.0)).with_opacity(0.5);
1245 assert_eq!(line.opacity, Some(0.5));
1246 }
1247
1248 #[test]
1249 fn test_line_annotation_opacity_clamped() {
1250 let line = LineAnnotation::new((0.0, 0.0), (100.0, 100.0)).with_opacity(1.5);
1251 assert_eq!(line.opacity, Some(1.0)); let line2 = LineAnnotation::new((0.0, 0.0), (100.0, 100.0)).with_opacity(-0.5);
1254 assert_eq!(line2.opacity, Some(0.0)); }
1256
1257 #[test]
1258 fn test_line_annotation_with_author() {
1259 let line = LineAnnotation::new((0.0, 0.0), (100.0, 100.0)).with_author("John");
1260 assert_eq!(line.author, Some("John".to_string()));
1261 }
1262
1263 #[test]
1264 fn test_line_annotation_with_subject() {
1265 let line = LineAnnotation::new((0.0, 0.0), (100.0, 100.0)).with_subject("Measurement");
1266 assert_eq!(line.subject, Some("Measurement".to_string()));
1267 }
1268
1269 #[test]
1270 fn test_line_annotation_with_flags() {
1271 let flags = AnnotationFlags::new(AnnotationFlags::PRINT | AnnotationFlags::LOCKED);
1272 let line = LineAnnotation::new((0.0, 0.0), (100.0, 100.0)).with_flags(flags);
1273 assert_eq!(line.flags.bits(), AnnotationFlags::PRINT | AnnotationFlags::LOCKED);
1274 }
1275
1276 #[test]
1277 fn test_line_annotation_calculate_rect() {
1278 let line = LineAnnotation::new((50.0, 100.0), (200.0, 300.0));
1279 let rect = line.calculate_rect();
1280 assert!(rect.x < 50.0);
1282 assert!(rect.y < 100.0);
1283 assert!(rect.x as f64 + rect.width as f64 > 200.0);
1284 assert!(rect.y as f64 + rect.height as f64 > 300.0);
1285 }
1286
1287 #[test]
1288 fn test_line_annotation_build_full() {
1289 let line = LineAnnotation::new((100.0, 200.0), (300.0, 400.0))
1290 .with_stroke_color(1.0, 0.0, 0.0)
1291 .with_fill_color(0.0, 1.0, 0.0)
1292 .with_line_width(2.0)
1293 .with_border_style(BorderStyleType::Solid)
1294 .with_opacity(0.8)
1295 .with_leader_line(10.0)
1296 .with_leader_offset(5.0)
1297 .with_caption("Test caption")
1298 .with_caption_position(CaptionPosition::Top)
1299 .with_author("Author")
1300 .with_subject("Subject")
1301 .with_line_endings(LineEndingStyle::OpenArrow, LineEndingStyle::ClosedArrow);
1302
1303 let dict = line.build(&[]);
1304
1305 assert_eq!(dict.get("Type"), Some(&Object::Name("Annot".to_string())));
1306 assert_eq!(dict.get("Subtype"), Some(&Object::Name("Line".to_string())));
1307 assert!(dict.contains_key("L"));
1308 assert!(dict.contains_key("LE"));
1309 assert!(dict.contains_key("C"));
1310 assert!(dict.contains_key("IC"));
1311 assert!(dict.contains_key("CA"));
1312 assert!(dict.contains_key("BS"));
1313 assert!(dict.contains_key("LL"));
1314 assert!(dict.contains_key("LLO"));
1315 assert!(dict.contains_key("Cap"));
1316 assert!(dict.contains_key("CP"));
1317 assert!(dict.contains_key("Contents"));
1318 assert!(dict.contains_key("T")); assert!(dict.contains_key("Subj")); assert!(dict.contains_key("F")); }
1322
1323 #[test]
1324 fn test_line_annotation_build_leader_line_extension() {
1325 let mut line = LineAnnotation::new((0.0, 0.0), (100.0, 100.0));
1326 line.leader_line_extension = Some(5.0);
1327 let dict = line.build(&[]);
1328 assert_eq!(dict.get("LLE"), Some(&Object::Real(5.0)));
1329 }
1330
1331 #[test]
1332 fn test_line_annotation_build_no_caption_cp() {
1333 let line = LineAnnotation::new((0.0, 0.0), (100.0, 100.0))
1335 .with_caption_position(CaptionPosition::Top);
1336 let dict = line.build(&[]);
1338 assert!(!dict.contains_key("Cap"));
1339 assert!(!dict.contains_key("CP"));
1340 }
1341
1342 #[test]
1343 fn test_line_annotation_build_caption_inline_no_cp() {
1344 let line = LineAnnotation::new((0.0, 0.0), (100.0, 100.0)).with_caption("Test");
1346 let dict = line.build(&[]);
1348 assert!(dict.contains_key("Cap"));
1349 assert!(!dict.contains_key("CP")); }
1351
1352 #[test]
1353 fn test_line_annotation_build_all_border_styles() {
1354 for style in &[
1355 BorderStyleType::Solid,
1356 BorderStyleType::Dashed,
1357 BorderStyleType::Beveled,
1358 BorderStyleType::Inset,
1359 BorderStyleType::Underline,
1360 ] {
1361 let line = LineAnnotation::new((0.0, 0.0), (100.0, 100.0)).with_border_style(*style);
1362 let dict = line.build(&[]);
1363 assert!(dict.contains_key("BS"));
1364 match dict.get("BS") {
1365 Some(Object::Dictionary(bs)) => {
1366 assert!(bs.contains_key("S"));
1367 },
1368 _ => panic!("Expected BS dictionary"),
1369 }
1370 }
1371 }
1372
1373 #[test]
1374 fn test_line_annotation_build_no_line_endings() {
1375 let line = LineAnnotation::new((0.0, 0.0), (100.0, 100.0));
1377 let dict = line.build(&[]);
1378 assert!(!dict.contains_key("LE"));
1379 }
1380
1381 #[test]
1384 fn test_shape_annotation_with_no_fill() {
1385 let shape = ShapeAnnotation::square(Rect::new(0.0, 0.0, 100.0, 100.0))
1386 .with_fill_color(1.0, 0.0, 0.0)
1387 .with_no_fill();
1388 assert!(shape.interior_color.is_none());
1389 }
1390
1391 #[test]
1392 fn test_shape_annotation_with_border_width() {
1393 let shape =
1394 ShapeAnnotation::square(Rect::new(0.0, 0.0, 100.0, 100.0)).with_border_width(3.0);
1395 assert_eq!(shape.border_width, Some(3.0));
1396 }
1397
1398 #[test]
1399 fn test_shape_annotation_with_border_style() {
1400 let shape = ShapeAnnotation::circle(Rect::new(0.0, 0.0, 100.0, 100.0))
1401 .with_border_style(BorderStyleType::Dashed);
1402 assert_eq!(shape.border_style, Some(BorderStyleType::Dashed));
1403 }
1404
1405 #[test]
1406 fn test_shape_annotation_with_border_effect() {
1407 let effect = BorderEffect {
1408 style: BorderEffectStyle::Cloudy,
1409 intensity: 1.5,
1410 };
1411 let shape =
1412 ShapeAnnotation::square(Rect::new(0.0, 0.0, 100.0, 100.0)).with_border_effect(effect);
1413 assert!(shape.border_effect.is_some());
1414 }
1415
1416 #[test]
1417 fn test_shape_annotation_with_opacity() {
1418 let shape = ShapeAnnotation::square(Rect::new(0.0, 0.0, 100.0, 100.0)).with_opacity(0.5);
1419 assert_eq!(shape.opacity, Some(0.5));
1420 }
1421
1422 #[test]
1423 fn test_shape_annotation_opacity_clamped() {
1424 let shape = ShapeAnnotation::square(Rect::new(0.0, 0.0, 100.0, 100.0)).with_opacity(2.0);
1425 assert_eq!(shape.opacity, Some(1.0));
1426 }
1427
1428 #[test]
1429 fn test_shape_annotation_with_contents() {
1430 let shape =
1431 ShapeAnnotation::square(Rect::new(0.0, 0.0, 100.0, 100.0)).with_contents("A note");
1432 assert_eq!(shape.contents, Some("A note".to_string()));
1433 }
1434
1435 #[test]
1436 fn test_shape_annotation_with_author() {
1437 let shape = ShapeAnnotation::circle(Rect::new(0.0, 0.0, 100.0, 100.0)).with_author("Jane");
1438 assert_eq!(shape.author, Some("Jane".to_string()));
1439 }
1440
1441 #[test]
1442 fn test_shape_annotation_with_subject() {
1443 let shape =
1444 ShapeAnnotation::circle(Rect::new(0.0, 0.0, 100.0, 100.0)).with_subject("Review");
1445 assert_eq!(shape.subject, Some("Review".to_string()));
1446 }
1447
1448 #[test]
1449 fn test_shape_annotation_with_flags() {
1450 let flags = AnnotationFlags::new(AnnotationFlags::HIDDEN);
1451 let shape = ShapeAnnotation::square(Rect::new(0.0, 0.0, 100.0, 100.0)).with_flags(flags);
1452 assert_eq!(shape.flags.bits(), AnnotationFlags::HIDDEN);
1453 }
1454
1455 #[test]
1456 fn test_shape_annotation_build_full() {
1457 let effect = BorderEffect {
1458 style: BorderEffectStyle::Cloudy,
1459 intensity: 1.0,
1460 };
1461 let shape = ShapeAnnotation::square(Rect::new(50.0, 50.0, 200.0, 150.0))
1462 .with_stroke_color(0.0, 0.0, 1.0)
1463 .with_fill_color(0.9, 0.9, 1.0)
1464 .with_border_width(2.0)
1465 .with_border_style(BorderStyleType::Solid)
1466 .with_border_effect(effect)
1467 .with_opacity(0.7)
1468 .with_contents("Comment text")
1469 .with_author("Author")
1470 .with_subject("Subject");
1471
1472 let dict = shape.build(&[]);
1473
1474 assert_eq!(dict.get("Type"), Some(&Object::Name("Annot".to_string())));
1475 assert_eq!(dict.get("Subtype"), Some(&Object::Name("Square".to_string())));
1476 assert!(dict.contains_key("Rect"));
1477 assert!(dict.contains_key("C"));
1478 assert!(dict.contains_key("IC"));
1479 assert!(dict.contains_key("CA"));
1480 assert!(dict.contains_key("BS"));
1481 assert!(dict.contains_key("BE"));
1482 assert!(dict.contains_key("Contents"));
1483 assert!(dict.contains_key("T"));
1484 assert!(dict.contains_key("Subj"));
1485 assert!(dict.contains_key("F"));
1486 }
1487
1488 #[test]
1489 fn test_shape_annotation_build_border_effect_no_intensity() {
1490 let effect = BorderEffect {
1491 style: BorderEffectStyle::None,
1492 intensity: 0.0,
1493 };
1494 let shape =
1495 ShapeAnnotation::square(Rect::new(0.0, 0.0, 100.0, 100.0)).with_border_effect(effect);
1496
1497 let dict = shape.build(&[]);
1498 match dict.get("BE") {
1499 Some(Object::Dictionary(be)) => {
1500 assert!(!be.contains_key("I"));
1502 },
1503 _ => panic!("Expected BE dictionary"),
1504 }
1505 }
1506
1507 #[test]
1508 fn test_shape_annotation_rect_differences() {
1509 let mut shape = ShapeAnnotation::square(Rect::new(0.0, 0.0, 100.0, 100.0));
1510 shape.rect_differences = Some([5.0, 5.0, 5.0, 5.0]);
1511
1512 let dict = shape.build(&[]);
1513 assert!(dict.contains_key("RD"));
1514 match dict.get("RD") {
1515 Some(Object::Array(arr)) => assert_eq!(arr.len(), 4),
1516 _ => panic!("Expected RD array"),
1517 }
1518 }
1519
1520 #[test]
1521 fn test_shape_annotation_build_all_border_styles() {
1522 for style in &[
1523 BorderStyleType::Solid,
1524 BorderStyleType::Dashed,
1525 BorderStyleType::Beveled,
1526 BorderStyleType::Inset,
1527 BorderStyleType::Underline,
1528 ] {
1529 let shape = ShapeAnnotation::circle(Rect::new(0.0, 0.0, 100.0, 100.0))
1530 .with_border_style(*style);
1531 let dict = shape.build(&[]);
1532 assert!(dict.contains_key("BS"));
1533 }
1534 }
1535
1536 #[test]
1539 fn test_polygon_with_stroke_color() {
1540 let polygon = PolygonAnnotation::polygon(vec![(0.0, 0.0), (100.0, 0.0), (50.0, 100.0)])
1541 .with_stroke_color(1.0, 0.0, 0.0);
1542 assert!(polygon.color.is_some());
1543 }
1544
1545 #[test]
1546 fn test_polygon_with_fill_color() {
1547 let polygon = PolygonAnnotation::polygon(vec![(0.0, 0.0), (100.0, 0.0), (50.0, 100.0)])
1548 .with_fill_color(0.0, 1.0, 0.0);
1549 assert!(polygon.interior_color.is_some());
1550 }
1551
1552 #[test]
1553 fn test_polygon_with_no_fill() {
1554 let polygon = PolygonAnnotation::polygon(vec![(0.0, 0.0), (100.0, 0.0), (50.0, 100.0)])
1555 .with_fill_color(1.0, 0.0, 0.0)
1556 .with_no_fill();
1557 assert!(polygon.interior_color.is_none());
1558 }
1559
1560 #[test]
1561 fn test_polygon_with_border_width() {
1562 let polygon =
1563 PolygonAnnotation::polygon(vec![(0.0, 0.0), (100.0, 0.0)]).with_border_width(3.0);
1564 assert_eq!(polygon.border_width, Some(3.0));
1565 }
1566
1567 #[test]
1568 fn test_polygon_with_border_style() {
1569 let polygon = PolygonAnnotation::polygon(vec![(0.0, 0.0), (100.0, 0.0)])
1570 .with_border_style(BorderStyleType::Beveled);
1571 assert_eq!(polygon.border_style, Some(BorderStyleType::Beveled));
1572 }
1573
1574 #[test]
1575 fn test_polygon_with_border_effect() {
1576 let effect = BorderEffect {
1577 style: BorderEffectStyle::Cloudy,
1578 intensity: 2.0,
1579 };
1580 let polygon =
1581 PolygonAnnotation::polygon(vec![(0.0, 0.0), (100.0, 0.0)]).with_border_effect(effect);
1582 assert!(polygon.border_effect.is_some());
1583 }
1584
1585 #[test]
1586 fn test_polygon_with_opacity() {
1587 let polygon = PolygonAnnotation::polygon(vec![(0.0, 0.0)]).with_opacity(0.3);
1588 assert_eq!(polygon.opacity, Some(0.3));
1589 }
1590
1591 #[test]
1592 fn test_polygon_opacity_clamped() {
1593 let polygon = PolygonAnnotation::polygon(vec![]).with_opacity(-1.0);
1594 assert_eq!(polygon.opacity, Some(0.0));
1595 }
1596
1597 #[test]
1598 fn test_polygon_with_contents() {
1599 let polygon = PolygonAnnotation::polygon(vec![(0.0, 0.0)]).with_contents("Note");
1600 assert_eq!(polygon.contents, Some("Note".to_string()));
1601 }
1602
1603 #[test]
1604 fn test_polygon_with_author() {
1605 let polygon = PolygonAnnotation::polygon(vec![(0.0, 0.0)]).with_author("Bob");
1606 assert_eq!(polygon.author, Some("Bob".to_string()));
1607 }
1608
1609 #[test]
1610 fn test_polygon_with_subject() {
1611 let polygon = PolygonAnnotation::polygon(vec![(0.0, 0.0)]).with_subject("Area");
1612 assert_eq!(polygon.subject, Some("Area".to_string()));
1613 }
1614
1615 #[test]
1616 fn test_polygon_with_flags() {
1617 let flags = AnnotationFlags::new(AnnotationFlags::READ_ONLY);
1618 let polygon = PolygonAnnotation::polygon(vec![(0.0, 0.0)]).with_flags(flags);
1619 assert_eq!(polygon.flags.bits(), AnnotationFlags::READ_ONLY);
1620 }
1621
1622 #[test]
1623 fn test_polygon_calculate_rect_empty() {
1624 let polygon = PolygonAnnotation::polygon(vec![]);
1625 let rect = polygon.calculate_rect();
1626 assert_eq!(rect.x, 0.0);
1627 assert_eq!(rect.y, 0.0);
1628 assert_eq!(rect.width, 0.0);
1629 assert_eq!(rect.height, 0.0);
1630 }
1631
1632 #[test]
1633 fn test_polygon_build_full() {
1634 let effect = BorderEffect {
1635 style: BorderEffectStyle::Cloudy,
1636 intensity: 1.5,
1637 };
1638 let polygon =
1639 PolygonAnnotation::polygon(vec![(100.0, 100.0), (200.0, 100.0), (150.0, 200.0)])
1640 .with_stroke_color(0.0, 0.0, 1.0)
1641 .with_fill_color(0.8, 0.8, 1.0)
1642 .with_border_width(2.0)
1643 .with_border_style(BorderStyleType::Dashed)
1644 .with_border_effect(effect)
1645 .with_opacity(0.9)
1646 .with_contents("Comment")
1647 .with_author("Author")
1648 .with_subject("Subject");
1649
1650 let dict = polygon.build(&[]);
1651
1652 assert_eq!(dict.get("Type"), Some(&Object::Name("Annot".to_string())));
1653 assert_eq!(dict.get("Subtype"), Some(&Object::Name("Polygon".to_string())));
1654 assert!(dict.contains_key("Vertices"));
1655 assert!(dict.contains_key("C"));
1656 assert!(dict.contains_key("IC"));
1657 assert!(dict.contains_key("CA"));
1658 assert!(dict.contains_key("BS"));
1659 assert!(dict.contains_key("BE"));
1660 assert!(dict.contains_key("Contents"));
1661 assert!(dict.contains_key("T"));
1662 assert!(dict.contains_key("Subj"));
1663 assert!(dict.contains_key("F"));
1664 }
1665
1666 #[test]
1667 fn test_polyline_build_full() {
1668 let polyline = PolygonAnnotation::polyline(vec![(0.0, 0.0), (50.0, 100.0), (100.0, 0.0)])
1669 .with_line_endings(LineEndingStyle::Square, LineEndingStyle::Diamond)
1670 .with_stroke_color(1.0, 0.0, 0.0);
1671
1672 let dict = polyline.build(&[]);
1673
1674 assert_eq!(dict.get("Subtype"), Some(&Object::Name("PolyLine".to_string())));
1675 assert!(dict.contains_key("LE"));
1676 assert!(dict.contains_key("Vertices"));
1677 }
1678
1679 #[test]
1680 fn test_polygon_line_endings_ignored_for_polygon() {
1681 let polygon = PolygonAnnotation::polygon(vec![(0.0, 0.0), (100.0, 0.0), (50.0, 100.0)])
1683 .with_line_endings(LineEndingStyle::OpenArrow, LineEndingStyle::ClosedArrow);
1684
1685 let dict = polygon.build(&[]);
1686 assert!(!dict.contains_key("LE"));
1688 }
1689
1690 #[test]
1691 fn test_polygon_build_all_border_styles() {
1692 for style in &[
1693 BorderStyleType::Solid,
1694 BorderStyleType::Dashed,
1695 BorderStyleType::Beveled,
1696 BorderStyleType::Inset,
1697 BorderStyleType::Underline,
1698 ] {
1699 let polygon = PolygonAnnotation::polygon(vec![(0.0, 0.0), (100.0, 100.0)])
1700 .with_border_style(*style);
1701 let dict = polygon.build(&[]);
1702 assert!(dict.contains_key("BS"));
1703 }
1704 }
1705
1706 #[test]
1707 fn test_polygon_build_border_effect_no_intensity() {
1708 let effect = BorderEffect {
1709 style: BorderEffectStyle::None,
1710 intensity: 0.0,
1711 };
1712 let polygon =
1713 PolygonAnnotation::polygon(vec![(0.0, 0.0), (100.0, 100.0)]).with_border_effect(effect);
1714 let dict = polygon.build(&[]);
1715 match dict.get("BE") {
1716 Some(Object::Dictionary(be)) => {
1717 assert!(!be.contains_key("I")); },
1719 _ => panic!("Expected BE dictionary"),
1720 }
1721 }
1722
1723 #[test]
1724 fn test_polygon_build_vertices_array() {
1725 let polygon = PolygonAnnotation::polygon(vec![(10.0, 20.0), (30.0, 40.0), (50.0, 60.0)]);
1726 let dict = polygon.build(&[]);
1727 match dict.get("Vertices") {
1728 Some(Object::Array(arr)) => {
1729 assert_eq!(arr.len(), 6);
1731 assert_eq!(arr[0], Object::Real(10.0));
1732 assert_eq!(arr[1], Object::Real(20.0));
1733 assert_eq!(arr[4], Object::Real(50.0));
1734 assert_eq!(arr[5], Object::Real(60.0));
1735 },
1736 _ => panic!("Expected Vertices array"),
1737 }
1738 }
1739
1740 #[test]
1741 fn test_line_annotation_build_l_entry() {
1742 let line = LineAnnotation::new((10.0, 20.0), (30.0, 40.0));
1743 let dict = line.build(&[]);
1744 match dict.get("L") {
1745 Some(Object::Array(arr)) => {
1746 assert_eq!(arr.len(), 4);
1747 assert_eq!(arr[0], Object::Real(10.0));
1748 assert_eq!(arr[1], Object::Real(20.0));
1749 assert_eq!(arr[2], Object::Real(30.0));
1750 assert_eq!(arr[3], Object::Real(40.0));
1751 },
1752 _ => panic!("Expected L array"),
1753 }
1754 }
1755
1756 #[test]
1757 fn test_shape_annotation_circle_rect_values() {
1758 let rect = Rect::new(100.0, 200.0, 50.0, 50.0);
1759 let shape = ShapeAnnotation::circle(rect);
1760 let dict = shape.build(&[]);
1761 match dict.get("Rect") {
1762 Some(Object::Array(arr)) => {
1763 assert_eq!(arr.len(), 4);
1764 assert_eq!(arr[0], Object::Real(100.0));
1765 assert_eq!(arr[1], Object::Real(200.0));
1766 assert_eq!(arr[2], Object::Real(150.0)); assert_eq!(arr[3], Object::Real(250.0)); },
1769 _ => panic!("Expected Rect array"),
1770 }
1771 }
1772
1773 #[test]
1774 fn test_shape_annotation_no_color() {
1775 let mut shape = ShapeAnnotation::square(Rect::new(0.0, 0.0, 100.0, 100.0));
1776 shape.color = None;
1777 let dict = shape.build(&[]);
1778 assert!(!dict.contains_key("C"));
1779 }
1780
1781 #[test]
1782 fn test_line_annotation_no_color() {
1783 let mut line = LineAnnotation::new((0.0, 0.0), (100.0, 100.0));
1784 line.color = None;
1785 let dict = line.build(&[]);
1786 assert!(!dict.contains_key("C"));
1787 }
1788
1789 #[test]
1790 fn test_polygon_no_color() {
1791 let mut polygon = PolygonAnnotation::polygon(vec![(0.0, 0.0), (100.0, 100.0)]);
1792 polygon.color = None;
1793 let dict = polygon.build(&[]);
1794 assert!(!dict.contains_key("C"));
1795 }
1796
1797 #[test]
1798 fn test_shape_annotation_flags_zero() {
1799 let mut shape = ShapeAnnotation::square(Rect::new(0.0, 0.0, 100.0, 100.0));
1800 shape.flags = AnnotationFlags::new(0);
1801 let dict = shape.build(&[]);
1802 assert!(!dict.contains_key("F"));
1803 }
1804}