Skip to main content

pdf_annot/
geometric.rs

1//! Geometric annotations: Line, Square, Circle, Polygon, PolyLine, Ink.
2
3use crate::annotation::Annotation;
4use crate::types::*;
5use pdf_syntax::object::dict::keys::*;
6use pdf_syntax::object::{Array, Dict, Name, Rect};
7
8/// A line annotation (ISO 32000-2 §12.5.6.7).
9#[derive(Debug)]
10pub struct LineAnnotation {
11    /// The line endpoints [x1, y1, x2, y2].
12    pub endpoints: Option<[f32; 4]>,
13    /// Line ending styles [start, end].
14    pub line_endings: (LineEnding, LineEnding),
15    /// Whether a caption is shown.
16    pub caption: bool,
17    /// Caption offset [horizontal, vertical].
18    pub caption_offset: Option<[f32; 2]>,
19    /// Leader line length.
20    pub leader_line: f32,
21    /// Leader line extension.
22    pub leader_line_extension: f32,
23    /// Leader line offset.
24    pub leader_line_offset: f32,
25}
26
27impl LineAnnotation {
28    /// Extract line annotation properties.
29    pub fn from_annot(annot: &Annotation<'_>) -> Self {
30        let dict = annot.dict();
31        let endpoints = dict.get::<Array<'_>>(L).and_then(|arr| {
32            let mut iter = arr.iter::<f32>();
33            Some([iter.next()?, iter.next()?, iter.next()?, iter.next()?])
34        });
35        let line_endings = parse_line_endings(dict);
36        let caption = dict.get::<bool>(CAP).unwrap_or(false);
37        let caption_offset = dict.get::<Array<'_>>(CO).and_then(|arr| {
38            let mut iter = arr.iter::<f32>();
39            Some([iter.next()?, iter.next()?])
40        });
41        let leader_line = dict.get::<f32>(LL).unwrap_or(0.0);
42        let leader_line_extension = dict.get::<f32>(LLE).unwrap_or(0.0);
43        let leader_line_offset = dict.get::<f32>(LLO).unwrap_or(0.0);
44        Self {
45            endpoints,
46            line_endings,
47            caption,
48            caption_offset,
49            leader_line,
50            leader_line_extension,
51            leader_line_offset,
52        }
53    }
54}
55
56/// A Square or Circle annotation.
57#[derive(Debug)]
58pub struct SquareCircleAnnotation {
59    /// `true` for Circle, `false` for Square.
60    pub is_circle: bool,
61    /// Rectangle differences.
62    pub rd: Option<Rect>,
63}
64
65impl SquareCircleAnnotation {
66    /// Extract square/circle annotation properties.
67    pub fn from_annot(annot: &Annotation<'_>) -> Self {
68        let is_circle = annot.annotation_type() == AnnotationType::Circle;
69        let rd = annot.dict().get::<Rect>(RD);
70        Self { is_circle, rd }
71    }
72}
73
74/// A Polygon or PolyLine annotation.
75#[derive(Debug)]
76pub struct PolygonAnnotation {
77    /// `true` for Polygon (closed), `false` for PolyLine (open).
78    pub closed: bool,
79    /// The vertices as pairs of coordinates.
80    pub vertices: Vec<f32>,
81    /// Line ending styles [start, end].
82    pub line_endings: (LineEnding, LineEnding),
83}
84
85impl PolygonAnnotation {
86    /// Extract polygon/polyline annotation properties.
87    pub fn from_annot(annot: &Annotation<'_>) -> Self {
88        let dict = annot.dict();
89        let closed = annot.annotation_type() == AnnotationType::Polygon;
90        let vertices = dict
91            .get::<Array<'_>>(VERTICES)
92            .map(|arr| arr.iter::<f32>().collect())
93            .unwrap_or_default();
94        let line_endings = parse_line_endings(dict);
95        Self {
96            closed,
97            vertices,
98            line_endings,
99        }
100    }
101}
102
103/// An Ink (freehand drawing) annotation.
104#[derive(Debug)]
105pub struct InkAnnotation {
106    /// List of ink strokes.
107    pub ink_list: Vec<Vec<f32>>,
108}
109
110impl InkAnnotation {
111    /// Extract ink annotation properties.
112    pub fn from_annot(annot: &Annotation<'_>) -> Self {
113        let dict = annot.dict();
114        let ink_list = dict
115            .get::<Array<'_>>(INKLIST)
116            .map(|outer| {
117                outer
118                    .iter::<Array<'_>>()
119                    .map(|inner| inner.iter::<f32>().collect())
120                    .collect()
121            })
122            .unwrap_or_default();
123        Self { ink_list }
124    }
125}
126
127/// Parse line ending styles from `/LE` array.
128fn parse_line_endings(dict: &Dict<'_>) -> (LineEnding, LineEnding) {
129    dict.get::<Array<'_>>(LE)
130        .and_then(|arr| {
131            let mut iter = arr.iter::<Name>();
132            let start = iter.next().map(|n| LineEnding::from_name(n.as_ref()))?;
133            let end = iter.next().map(|n| LineEnding::from_name(n.as_ref()))?;
134            Some((start, end))
135        })
136        .unwrap_or((LineEnding::None, LineEnding::None))
137}