Skip to main content

pdf_annot/
types.rs

1//! Core annotation types shared across all annotation categories.
2
3use pdf_syntax::object::dict::keys::*;
4use pdf_syntax::object::{Array, Dict, Name, Rect};
5
6/// The type of a PDF annotation (ISO 32000-2 Table 170).
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub enum AnnotationType {
9    /// A text annotation (sticky note).
10    Text,
11    /// A link annotation.
12    Link,
13    /// A free text annotation.
14    FreeText,
15    /// A line annotation.
16    Line,
17    /// A square annotation.
18    Square,
19    /// A circle annotation.
20    Circle,
21    /// A polygon annotation.
22    Polygon,
23    /// A polyline annotation.
24    PolyLine,
25    /// A highlight annotation.
26    Highlight,
27    /// An underline annotation.
28    Underline,
29    /// A squiggly underline annotation.
30    Squiggly,
31    /// A strikeout annotation.
32    StrikeOut,
33    /// A rubber stamp annotation.
34    Stamp,
35    /// A caret annotation.
36    Caret,
37    /// An ink (freehand) annotation.
38    Ink,
39    /// A popup annotation.
40    Popup,
41    /// A file attachment annotation.
42    FileAttachment,
43    /// A sound annotation.
44    Sound,
45    /// A widget annotation (form field).
46    Widget,
47    /// A watermark annotation.
48    Watermark,
49    /// A redaction annotation.
50    Redact,
51    /// A movie annotation.
52    Movie,
53    /// An unknown annotation type.
54    Unknown,
55}
56
57impl AnnotationType {
58    /// Parse an annotation type from a PDF `/Subtype` name.
59    pub fn from_name(name: &[u8]) -> Self {
60        match name {
61            b"Text" => Self::Text,
62            b"Link" => Self::Link,
63            b"FreeText" => Self::FreeText,
64            b"Line" => Self::Line,
65            b"Square" => Self::Square,
66            b"Circle" => Self::Circle,
67            b"Polygon" => Self::Polygon,
68            b"PolyLine" => Self::PolyLine,
69            b"Highlight" => Self::Highlight,
70            b"Underline" => Self::Underline,
71            b"Squiggly" => Self::Squiggly,
72            b"StrikeOut" => Self::StrikeOut,
73            b"Stamp" => Self::Stamp,
74            b"Caret" => Self::Caret,
75            b"Ink" => Self::Ink,
76            b"Popup" => Self::Popup,
77            b"FileAttachment" => Self::FileAttachment,
78            b"Sound" => Self::Sound,
79            b"Widget" => Self::Widget,
80            b"Watermark" => Self::Watermark,
81            b"Redact" => Self::Redact,
82            b"Movie" => Self::Movie,
83            _ => Self::Unknown,
84        }
85    }
86}
87
88/// Annotation flags (ISO 32000-2 Table 175).
89#[derive(Debug, Clone, Copy, PartialEq, Eq)]
90pub struct AnnotationFlags(pub u32);
91
92impl AnnotationFlags {
93    /// Bit 1: Invisible.
94    pub fn invisible(&self) -> bool {
95        self.0 & (1 << 0) != 0
96    }
97    /// Bit 2: Hidden.
98    pub fn hidden(&self) -> bool {
99        self.0 & (1 << 1) != 0
100    }
101    /// Bit 3: Print.
102    pub fn print(&self) -> bool {
103        self.0 & (1 << 2) != 0
104    }
105    /// Bit 4: No zoom.
106    pub fn no_zoom(&self) -> bool {
107        self.0 & (1 << 3) != 0
108    }
109    /// Bit 5: No rotate.
110    pub fn no_rotate(&self) -> bool {
111        self.0 & (1 << 4) != 0
112    }
113    /// Bit 6: No view.
114    pub fn no_view(&self) -> bool {
115        self.0 & (1 << 5) != 0
116    }
117    /// Bit 7: Read only.
118    pub fn read_only(&self) -> bool {
119        self.0 & (1 << 6) != 0
120    }
121    /// Bit 8: Locked.
122    pub fn locked(&self) -> bool {
123        self.0 & (1 << 7) != 0
124    }
125    /// Bit 9: Toggle no view.
126    pub fn toggle_no_view(&self) -> bool {
127        self.0 & (1 << 8) != 0
128    }
129    /// Bit 10: Locked contents.
130    pub fn locked_contents(&self) -> bool {
131        self.0 & (1 << 9) != 0
132    }
133}
134
135/// Border style (ISO 32000-2 Table 168).
136#[derive(Debug, Clone)]
137pub struct BorderStyle {
138    /// Border width in points.
139    pub width: f32,
140    /// Border style type.
141    pub style: BorderStyleType,
142    /// Dash pattern.
143    pub dash_pattern: Vec<f32>,
144}
145
146/// Border style types.
147#[derive(Debug, Clone, Copy, PartialEq, Eq)]
148pub enum BorderStyleType {
149    /// Solid border.
150    Solid,
151    /// Dashed border.
152    Dashed,
153    /// Beveled border.
154    Beveled,
155    /// Inset border.
156    Inset,
157    /// Underline border.
158    Underline,
159}
160
161impl BorderStyle {
162    /// Parse a border style from a `/BS` dictionary.
163    pub fn from_dict(dict: &Dict<'_>) -> Self {
164        let width = dict.get::<f32>(W).unwrap_or(1.0);
165        let style = dict
166            .get::<Name>(S)
167            .map(|n| match n.as_ref() {
168                b"D" => BorderStyleType::Dashed,
169                b"B" => BorderStyleType::Beveled,
170                b"I" => BorderStyleType::Inset,
171                b"U" => BorderStyleType::Underline,
172                _ => BorderStyleType::Solid,
173            })
174            .unwrap_or(BorderStyleType::Solid);
175        let dash_pattern = dict
176            .get::<Array<'_>>(D)
177            .map(|arr| arr.iter::<f32>().collect())
178            .unwrap_or_else(|| vec![3.0]);
179        Self {
180            width,
181            style,
182            dash_pattern,
183        }
184    }
185}
186
187/// A color value.
188#[derive(Debug, Clone)]
189pub enum Color {
190    /// Transparent.
191    Transparent,
192    /// Grayscale.
193    Gray(f32),
194    /// RGB.
195    Rgb(f32, f32, f32),
196    /// CMYK.
197    Cmyk(f32, f32, f32, f32),
198}
199
200impl Color {
201    /// Parse a color from an annotation color array.
202    pub fn from_array(arr: &Array<'_>) -> Self {
203        let values: Vec<f32> = arr.iter::<f32>().collect();
204        match values.len() {
205            0 => Self::Transparent,
206            1 => Self::Gray(values[0]),
207            3 => Self::Rgb(values[0], values[1], values[2]),
208            4 => Self::Cmyk(values[0], values[1], values[2], values[3]),
209            _ => Self::Transparent,
210        }
211    }
212}
213
214/// Border effect.
215#[derive(Debug, Clone)]
216pub struct BorderEffect {
217    /// The effect style.
218    pub style: BorderEffectStyle,
219    /// Effect intensity.
220    pub intensity: f32,
221}
222
223/// Border effect style.
224#[derive(Debug, Clone, Copy, PartialEq, Eq)]
225pub enum BorderEffectStyle {
226    /// No effect.
227    None,
228    /// Cloudy border.
229    Cloudy,
230}
231
232impl BorderEffect {
233    /// Parse from a `/BE` dictionary.
234    pub fn from_dict(dict: &Dict<'_>) -> Self {
235        let style = dict
236            .get::<Name>(S)
237            .map(|n| match n.as_ref() {
238                b"C" => BorderEffectStyle::Cloudy,
239                _ => BorderEffectStyle::None,
240            })
241            .unwrap_or(BorderEffectStyle::None);
242        let intensity = dict.get::<f32>(I).unwrap_or(0.0);
243        Self { style, intensity }
244    }
245}
246
247/// Quadrilateral points for text markup annotations.
248#[derive(Debug, Clone)]
249pub struct QuadPoints {
250    /// Raw coordinate values.
251    pub points: Vec<f32>,
252}
253
254impl QuadPoints {
255    /// Parse quad points from an array.
256    pub fn from_array(arr: &Array<'_>) -> Self {
257        Self {
258            points: arr.iter::<f32>().collect(),
259        }
260    }
261
262    /// Return bounding rectangles.
263    pub fn bounding_rects(&self) -> Vec<Rect> {
264        self.points
265            .chunks_exact(8)
266            .map(|chunk| {
267                let xs = [chunk[0], chunk[2], chunk[4], chunk[6]];
268                let ys = [chunk[1], chunk[3], chunk[5], chunk[7]];
269                let x0 = xs.iter().copied().reduce(f32::min).unwrap_or(0.0) as f64;
270                let y0 = ys.iter().copied().reduce(f32::min).unwrap_or(0.0) as f64;
271                let x1 = xs.iter().copied().reduce(f32::max).unwrap_or(0.0) as f64;
272                let y1 = ys.iter().copied().reduce(f32::max).unwrap_or(0.0) as f64;
273                Rect::new(x0, y0, x1, y1)
274            })
275            .collect()
276    }
277}
278
279/// Line ending styles (ISO 32000-2 Table 179).
280#[derive(Debug, Clone, Copy, PartialEq, Eq)]
281pub enum LineEnding {
282    /// No line ending.
283    None,
284    /// A square.
285    Square,
286    /// A circle.
287    Circle,
288    /// A diamond.
289    Diamond,
290    /// An open arrowhead.
291    OpenArrow,
292    /// A closed arrowhead.
293    ClosedArrow,
294    /// Butt.
295    Butt,
296    /// Reverse open arrowhead.
297    ROpenArrow,
298    /// Reverse closed arrowhead.
299    RClosedArrow,
300    /// A slash.
301    Slash,
302}
303
304impl LineEnding {
305    /// Parse from a PDF name.
306    pub fn from_name(name: &[u8]) -> Self {
307        match name {
308            b"Square" => Self::Square,
309            b"Circle" => Self::Circle,
310            b"Diamond" => Self::Diamond,
311            b"OpenArrow" => Self::OpenArrow,
312            b"ClosedArrow" => Self::ClosedArrow,
313            b"Butt" => Self::Butt,
314            b"ROpenArrow" => Self::ROpenArrow,
315            b"RClosedArrow" => Self::RClosedArrow,
316            b"Slash" => Self::Slash,
317            _ => Self::None,
318        }
319    }
320}