1use crate::annotations::{Annotation, AnnotationType};
7use crate::error::Result;
8use crate::geometry::{Point, Rectangle};
9use crate::graphics::Color;
10use crate::objects::{Dictionary, Object};
11
12#[derive(Debug, Clone)]
14pub struct PolygonAnnotation {
15 pub vertices: Vec<Point>,
17 pub line_color: Option<Color>,
19 pub fill_color: Option<Color>,
21 pub line_width: f64,
23 pub border_style: BorderStyle,
25 pub opacity: f64,
27}
28
29#[derive(Debug, Clone)]
31pub struct PolylineAnnotation {
32 pub vertices: Vec<Point>,
34 pub line_color: Option<Color>,
36 pub line_width: f64,
38 pub start_style: LineEndingStyle,
40 pub end_style: LineEndingStyle,
42 pub interior_color: Option<Color>,
44 pub border_style: BorderStyle,
46 pub opacity: f64,
48}
49
50#[derive(Debug, Clone, Copy, PartialEq)]
52pub enum LineEndingStyle {
53 None,
54 Square,
55 Circle,
56 Diamond,
57 OpenArrow,
58 ClosedArrow,
59 Butt,
60 ROpenArrow,
61 RClosedArrow,
62 Slash,
63}
64
65impl LineEndingStyle {
66 pub fn to_pdf_name(self) -> &'static str {
67 match self {
68 LineEndingStyle::None => "None",
69 LineEndingStyle::Square => "Square",
70 LineEndingStyle::Circle => "Circle",
71 LineEndingStyle::Diamond => "Diamond",
72 LineEndingStyle::OpenArrow => "OpenArrow",
73 LineEndingStyle::ClosedArrow => "ClosedArrow",
74 LineEndingStyle::Butt => "Butt",
75 LineEndingStyle::ROpenArrow => "ROpenArrow",
76 LineEndingStyle::RClosedArrow => "RClosedArrow",
77 LineEndingStyle::Slash => "Slash",
78 }
79 }
80}
81
82#[derive(Debug, Clone)]
84pub struct BorderStyle {
85 pub width: f64,
87 pub style: BorderStyleType,
89 pub dash_pattern: Option<Vec<f64>>,
91}
92
93#[derive(Debug, Clone, Copy, PartialEq)]
94pub enum BorderStyleType {
95 Solid,
96 Dashed,
97 Beveled,
98 Inset,
99 Underline,
100}
101
102impl BorderStyleType {
103 pub fn to_pdf_name(self) -> &'static str {
104 match self {
105 BorderStyleType::Solid => "S",
106 BorderStyleType::Dashed => "D",
107 BorderStyleType::Beveled => "B",
108 BorderStyleType::Inset => "I",
109 BorderStyleType::Underline => "U",
110 }
111 }
112}
113
114impl Default for BorderStyle {
115 fn default() -> Self {
116 Self {
117 width: 1.0,
118 style: BorderStyleType::Solid,
119 dash_pattern: None,
120 }
121 }
122}
123
124impl Default for PolygonAnnotation {
125 fn default() -> Self {
126 Self {
127 vertices: Vec::new(),
128 line_color: Some(Color::rgb(0.0, 0.0, 0.0)),
129 fill_color: None,
130 line_width: 1.0,
131 border_style: BorderStyle::default(),
132 opacity: 1.0,
133 }
134 }
135}
136
137impl Default for PolylineAnnotation {
138 fn default() -> Self {
139 Self {
140 vertices: Vec::new(),
141 line_color: Some(Color::rgb(0.0, 0.0, 0.0)),
142 line_width: 1.0,
143 start_style: LineEndingStyle::None,
144 end_style: LineEndingStyle::None,
145 interior_color: None,
146 border_style: BorderStyle::default(),
147 opacity: 1.0,
148 }
149 }
150}
151
152impl PolygonAnnotation {
153 pub fn new(vertices: Vec<Point>) -> Self {
155 Self {
156 vertices,
157 ..Default::default()
158 }
159 }
160
161 pub fn with_line_color(mut self, color: Option<Color>) -> Self {
163 self.line_color = color;
164 self
165 }
166
167 pub fn with_fill_color(mut self, color: Option<Color>) -> Self {
169 self.fill_color = color;
170 self
171 }
172
173 pub fn with_line_width(mut self, width: f64) -> Self {
175 self.line_width = width;
176 self
177 }
178
179 pub fn with_border_style(mut self, style: BorderStyle) -> Self {
181 self.border_style = style;
182 self
183 }
184
185 pub fn with_opacity(mut self, opacity: f64) -> Self {
187 self.opacity = opacity.clamp(0.0, 1.0);
188 self
189 }
190
191 pub fn calculate_rect(&self) -> Rectangle {
193 if self.vertices.is_empty() {
194 return Rectangle::new(Point::new(0.0, 0.0), Point::new(0.0, 0.0));
195 }
196
197 let mut min_x = self.vertices[0].x;
198 let mut min_y = self.vertices[0].y;
199 let mut max_x = self.vertices[0].x;
200 let mut max_y = self.vertices[0].y;
201
202 for vertex in &self.vertices[1..] {
203 min_x = min_x.min(vertex.x);
204 min_y = min_y.min(vertex.y);
205 max_x = max_x.max(vertex.x);
206 max_y = max_y.max(vertex.y);
207 }
208
209 let padding = self.line_width;
211 Rectangle::new(
212 Point::new(min_x - padding, min_y - padding),
213 Point::new(max_x + padding, max_y + padding),
214 )
215 }
216
217 pub fn to_annotation(&self) -> Result<Annotation> {
219 let rect = self.calculate_rect();
220 let mut annotation = Annotation::new(AnnotationType::Polygon, rect);
221
222 let mut vertices_array = Vec::new();
224 for vertex in &self.vertices {
225 vertices_array.push(Object::Real(vertex.x));
226 vertices_array.push(Object::Real(vertex.y));
227 }
228 annotation
229 .properties
230 .set("Vertices", Object::Array(vertices_array));
231
232 if let Some(color) = &self.line_color {
234 annotation.properties.set(
235 "C",
236 Object::Array(vec![
237 Object::Real(color.r()),
238 Object::Real(color.g()),
239 Object::Real(color.b()),
240 ]),
241 );
242 }
243
244 if let Some(color) = &self.fill_color {
246 annotation.properties.set(
247 "IC",
248 Object::Array(vec![
249 Object::Real(color.r()),
250 Object::Real(color.g()),
251 Object::Real(color.b()),
252 ]),
253 );
254 }
255
256 let mut bs_dict = Dictionary::new();
258 bs_dict.set("W", Object::Real(self.border_style.width));
259 bs_dict.set(
260 "S",
261 Object::Name(self.border_style.style.to_pdf_name().to_string()),
262 );
263
264 if let Some(dash) = &self.border_style.dash_pattern {
265 bs_dict.set(
266 "D",
267 Object::Array(dash.iter().map(|&d| Object::Real(d)).collect()),
268 );
269 }
270
271 annotation.properties.set("BS", Object::Dictionary(bs_dict));
272
273 if self.opacity < 1.0 {
275 annotation.properties.set("CA", Object::Real(self.opacity));
276 }
277
278 Ok(annotation)
279 }
280}
281
282impl PolylineAnnotation {
283 pub fn new(vertices: Vec<Point>) -> Self {
285 Self {
286 vertices,
287 ..Default::default()
288 }
289 }
290
291 pub fn with_line_color(mut self, color: Option<Color>) -> Self {
293 self.line_color = color;
294 self
295 }
296
297 pub fn with_line_width(mut self, width: f64) -> Self {
299 self.line_width = width;
300 self
301 }
302
303 pub fn with_endings(mut self, start: LineEndingStyle, end: LineEndingStyle) -> Self {
305 self.start_style = start;
306 self.end_style = end;
307 self
308 }
309
310 pub fn with_interior_color(mut self, color: Option<Color>) -> Self {
312 self.interior_color = color;
313 self
314 }
315
316 pub fn with_border_style(mut self, style: BorderStyle) -> Self {
318 self.border_style = style;
319 self
320 }
321
322 pub fn with_opacity(mut self, opacity: f64) -> Self {
324 self.opacity = opacity.clamp(0.0, 1.0);
325 self
326 }
327
328 pub fn calculate_rect(&self) -> Rectangle {
330 if self.vertices.is_empty() {
331 return Rectangle::new(Point::new(0.0, 0.0), Point::new(0.0, 0.0));
332 }
333
334 let mut min_x = self.vertices[0].x;
335 let mut min_y = self.vertices[0].y;
336 let mut max_x = self.vertices[0].x;
337 let mut max_y = self.vertices[0].y;
338
339 for vertex in &self.vertices[1..] {
340 min_x = min_x.min(vertex.x);
341 min_y = min_y.min(vertex.y);
342 max_x = max_x.max(vertex.x);
343 max_y = max_y.max(vertex.y);
344 }
345
346 let padding = self.line_width;
348 Rectangle::new(
349 Point::new(min_x - padding, min_y - padding),
350 Point::new(max_x + padding, max_y + padding),
351 )
352 }
353
354 pub fn to_annotation(&self) -> Result<Annotation> {
356 let rect = self.calculate_rect();
357 let mut annotation = Annotation::new(AnnotationType::PolyLine, rect);
358
359 let mut vertices_array = Vec::new();
361 for vertex in &self.vertices {
362 vertices_array.push(Object::Real(vertex.x));
363 vertices_array.push(Object::Real(vertex.y));
364 }
365 annotation
366 .properties
367 .set("Vertices", Object::Array(vertices_array));
368
369 if let Some(color) = &self.line_color {
371 annotation.properties.set(
372 "C",
373 Object::Array(vec![
374 Object::Real(color.r()),
375 Object::Real(color.g()),
376 Object::Real(color.b()),
377 ]),
378 );
379 }
380
381 annotation.properties.set(
383 "LE",
384 Object::Array(vec![
385 Object::Name(self.start_style.to_pdf_name().to_string()),
386 Object::Name(self.end_style.to_pdf_name().to_string()),
387 ]),
388 );
389
390 if let Some(color) = &self.interior_color {
392 annotation.properties.set(
393 "IC",
394 Object::Array(vec![
395 Object::Real(color.r()),
396 Object::Real(color.g()),
397 Object::Real(color.b()),
398 ]),
399 );
400 }
401
402 let mut bs_dict = Dictionary::new();
404 bs_dict.set("W", Object::Real(self.border_style.width));
405 bs_dict.set(
406 "S",
407 Object::Name(self.border_style.style.to_pdf_name().to_string()),
408 );
409
410 if let Some(dash) = &self.border_style.dash_pattern {
411 bs_dict.set(
412 "D",
413 Object::Array(dash.iter().map(|&d| Object::Real(d)).collect()),
414 );
415 }
416
417 annotation.properties.set("BS", Object::Dictionary(bs_dict));
418
419 if self.opacity < 1.0 {
421 annotation.properties.set("CA", Object::Real(self.opacity));
422 }
423
424 Ok(annotation)
425 }
426}
427
428pub fn create_rectangle_polygon(
430 top_left: Point,
431 top_right: Point,
432 bottom_right: Point,
433 bottom_left: Point,
434) -> PolygonAnnotation {
435 PolygonAnnotation::new(vec![top_left, top_right, bottom_right, bottom_left])
436}
437
438pub fn create_triangle(p1: Point, p2: Point, p3: Point) -> PolygonAnnotation {
440 PolygonAnnotation::new(vec![p1, p2, p3])
441}
442
443pub fn create_regular_polygon(center: Point, radius: f64, sides: usize) -> PolygonAnnotation {
445 let mut vertices = Vec::new();
446 let angle_step = 2.0 * std::f64::consts::PI / sides as f64;
447
448 for i in 0..sides {
449 let angle = i as f64 * angle_step;
450 let x = center.x + radius * angle.cos();
451 let y = center.y + radius * angle.sin();
452 vertices.push(Point::new(x, y));
453 }
454
455 PolygonAnnotation::new(vertices)
456}
457
458#[cfg(test)]
459mod tests {
460 use super::*;
461
462 #[test]
463 fn test_polygon_creation() {
464 let vertices = vec![
465 Point::new(100.0, 100.0),
466 Point::new(200.0, 100.0),
467 Point::new(200.0, 200.0),
468 Point::new(100.0, 200.0),
469 ];
470
471 let polygon = PolygonAnnotation::new(vertices.clone());
472 assert_eq!(polygon.vertices.len(), 4);
473 assert_eq!(polygon.vertices[0], vertices[0]);
474 }
475
476 #[test]
477 fn test_polyline_creation() {
478 let vertices = vec![
479 Point::new(50.0, 50.0),
480 Point::new(100.0, 75.0),
481 Point::new(150.0, 50.0),
482 Point::new(200.0, 100.0),
483 ];
484
485 let polyline = PolylineAnnotation::new(vertices.clone());
486 assert_eq!(polyline.vertices.len(), 4);
487 assert_eq!(polyline.vertices[0], vertices[0]);
488 }
489
490 #[test]
491 fn test_polygon_with_fill() {
492 let vertices = vec![
493 Point::new(0.0, 0.0),
494 Point::new(100.0, 0.0),
495 Point::new(50.0, 86.6),
496 ];
497
498 let polygon = PolygonAnnotation::new(vertices)
499 .with_line_color(Some(Color::rgb(1.0, 0.0, 0.0)))
500 .with_fill_color(Some(Color::rgb(1.0, 1.0, 0.0)))
501 .with_line_width(2.0);
502
503 assert_eq!(polygon.line_color, Some(Color::rgb(1.0, 0.0, 0.0)));
504 assert_eq!(polygon.fill_color, Some(Color::rgb(1.0, 1.0, 0.0)));
505 assert_eq!(polygon.line_width, 2.0);
506 }
507
508 #[test]
509 fn test_polyline_with_endings() {
510 let vertices = vec![
511 Point::new(100.0, 100.0),
512 Point::new(200.0, 150.0),
513 Point::new(300.0, 100.0),
514 ];
515
516 let polyline = PolylineAnnotation::new(vertices)
517 .with_endings(LineEndingStyle::Circle, LineEndingStyle::ClosedArrow)
518 .with_interior_color(Some(Color::rgb(0.0, 1.0, 0.0)));
519
520 assert_eq!(polyline.start_style, LineEndingStyle::Circle);
521 assert_eq!(polyline.end_style, LineEndingStyle::ClosedArrow);
522 assert_eq!(polyline.interior_color, Some(Color::rgb(0.0, 1.0, 0.0)));
523 }
524
525 #[test]
526 fn test_calculate_rect() {
527 let vertices = vec![
528 Point::new(50.0, 50.0),
529 Point::new(150.0, 100.0),
530 Point::new(100.0, 200.0),
531 ];
532
533 let polygon = PolygonAnnotation::new(vertices).with_line_width(5.0);
534 let rect = polygon.calculate_rect();
535
536 assert_eq!(rect.lower_left.x, 45.0);
538 assert_eq!(rect.lower_left.y, 45.0);
539 assert_eq!(rect.upper_right.x, 155.0);
540 assert_eq!(rect.upper_right.y, 205.0);
541 }
542
543 #[test]
544 fn test_border_style() {
545 let border = BorderStyle {
546 width: 3.0,
547 style: BorderStyleType::Dashed,
548 dash_pattern: Some(vec![5.0, 3.0]),
549 };
550
551 assert_eq!(border.width, 3.0);
552 assert_eq!(border.style.to_pdf_name(), "D");
553 assert_eq!(border.dash_pattern, Some(vec![5.0, 3.0]));
554 }
555
556 #[test]
557 fn test_line_ending_styles() {
558 assert_eq!(LineEndingStyle::None.to_pdf_name(), "None");
559 assert_eq!(LineEndingStyle::Circle.to_pdf_name(), "Circle");
560 assert_eq!(LineEndingStyle::ClosedArrow.to_pdf_name(), "ClosedArrow");
561 assert_eq!(LineEndingStyle::Diamond.to_pdf_name(), "Diamond");
562 }
563
564 #[test]
565 fn test_polygon_to_annotation() {
566 let vertices = vec![
567 Point::new(100.0, 100.0),
568 Point::new(200.0, 100.0),
569 Point::new(150.0, 200.0),
570 ];
571
572 let polygon = PolygonAnnotation::new(vertices)
573 .with_line_color(Some(Color::rgb(0.0, 0.0, 1.0)))
574 .with_opacity(0.5);
575
576 let annotation = polygon.to_annotation();
577 assert!(annotation.is_ok());
578 }
579
580 #[test]
581 fn test_polyline_to_annotation() {
582 let vertices = vec![
583 Point::new(50.0, 50.0),
584 Point::new(100.0, 100.0),
585 Point::new(150.0, 50.0),
586 ];
587
588 let polyline = PolylineAnnotation::new(vertices)
589 .with_endings(LineEndingStyle::OpenArrow, LineEndingStyle::OpenArrow);
590
591 let annotation = polyline.to_annotation();
592 assert!(annotation.is_ok());
593 }
594
595 #[test]
596 fn test_create_rectangle_polygon() {
597 let rect_poly = create_rectangle_polygon(
598 Point::new(100.0, 200.0), Point::new(200.0, 200.0), Point::new(200.0, 100.0), Point::new(100.0, 100.0), );
603
604 assert_eq!(rect_poly.vertices.len(), 4);
605 assert_eq!(rect_poly.vertices[0], Point::new(100.0, 200.0));
606 }
607
608 #[test]
609 fn test_create_triangle() {
610 let triangle = create_triangle(
611 Point::new(100.0, 100.0),
612 Point::new(200.0, 100.0),
613 Point::new(150.0, 186.6),
614 );
615
616 assert_eq!(triangle.vertices.len(), 3);
617 }
618
619 #[test]
620 fn test_create_regular_polygon() {
621 let hexagon = create_regular_polygon(Point::new(100.0, 100.0), 50.0, 6);
623 assert_eq!(hexagon.vertices.len(), 6);
624
625 let pentagon = create_regular_polygon(Point::new(200.0, 200.0), 30.0, 5);
627 assert_eq!(pentagon.vertices.len(), 5);
628 }
629
630 #[test]
631 fn test_opacity_clamping() {
632 let polygon = PolygonAnnotation::new(vec![]).with_opacity(1.5); assert_eq!(polygon.opacity, 1.0);
634
635 let polygon2 = PolygonAnnotation::new(vec![]).with_opacity(-0.5); assert_eq!(polygon2.opacity, 0.0);
637 }
638
639 #[test]
640 fn test_empty_vertices() {
641 let polygon = PolygonAnnotation::new(vec![]);
642 let rect = polygon.calculate_rect();
643
644 assert_eq!(rect.lower_left, Point::new(0.0, 0.0));
645 assert_eq!(rect.upper_right, Point::new(0.0, 0.0));
646 }
647
648 #[test]
649 fn test_border_style_types() {
650 assert_eq!(BorderStyleType::Solid.to_pdf_name(), "S");
651 assert_eq!(BorderStyleType::Dashed.to_pdf_name(), "D");
652 assert_eq!(BorderStyleType::Beveled.to_pdf_name(), "B");
653 assert_eq!(BorderStyleType::Inset.to_pdf_name(), "I");
654 assert_eq!(BorderStyleType::Underline.to_pdf_name(), "U");
655 }
656}