Skip to main content

pdf_annot/
stamp.rs

1//! Stamp, FileAttachment, FreeText, Sound, and Movie annotations.
2
3extern crate alloc;
4
5use crate::annotation::{self, Annotation};
6use crate::types::LineEnding;
7use pdf_syntax::object::dict::keys::*;
8use pdf_syntax::object::{Array, Dict, Name, Rect, Stream};
9
10/// Standard stamp names (ISO 32000-2 Table 181).
11#[derive(Debug, Clone, PartialEq, Eq)]
12pub enum StampName {
13    Approved,
14    Experimental,
15    NotApproved,
16    AsIs,
17    Expired,
18    NotForPublicRelease,
19    Confidential,
20    Final,
21    Sold,
22    Departmental,
23    ForComment,
24    TopSecret,
25    Draft,
26    ForPublicRelease,
27    /// A custom stamp name.
28    Custom(alloc::string::String),
29}
30
31impl StampName {
32    /// Parse from a PDF name.
33    pub fn from_name(name: &[u8]) -> Self {
34        match name {
35            b"Approved" => Self::Approved,
36            b"Experimental" => Self::Experimental,
37            b"NotApproved" => Self::NotApproved,
38            b"AsIs" => Self::AsIs,
39            b"Expired" => Self::Expired,
40            b"NotForPublicRelease" => Self::NotForPublicRelease,
41            b"Confidential" => Self::Confidential,
42            b"Final" => Self::Final,
43            b"Sold" => Self::Sold,
44            b"Departmental" => Self::Departmental,
45            b"ForComment" => Self::ForComment,
46            b"TopSecret" => Self::TopSecret,
47            b"Draft" => Self::Draft,
48            b"ForPublicRelease" => Self::ForPublicRelease,
49            other => {
50                let s = core::str::from_utf8(other).unwrap_or("Unknown").to_owned();
51                Self::Custom(s)
52            }
53        }
54    }
55}
56
57/// A Stamp annotation.
58#[derive(Debug)]
59pub struct StampAnnotation {
60    /// The stamp name.
61    pub name: StampName,
62}
63
64impl StampAnnotation {
65    /// Extract stamp annotation properties.
66    pub fn from_annot(annot: &Annotation<'_>) -> Self {
67        let name = annot
68            .dict()
69            .get::<Name>(NAME)
70            .map(|n: Name| StampName::from_name(n.as_ref()))
71            .unwrap_or(StampName::Draft);
72        Self { name }
73    }
74}
75
76/// A FileAttachment annotation.
77#[derive(Debug)]
78pub struct FileAttachmentAnnotation<'a> {
79    dict: Dict<'a>,
80    /// The icon name.
81    pub icon: alloc::string::String,
82}
83
84impl<'a> FileAttachmentAnnotation<'a> {
85    /// Extract file attachment annotation properties.
86    pub fn from_annot(annot: &Annotation<'a>) -> Self {
87        let dict = annot.dict().clone();
88        let icon = dict
89            .get::<Name>(NAME)
90            .map(|n: Name| alloc::string::String::from(n.as_str()))
91            .unwrap_or_else(|| alloc::string::String::from("PushPin"));
92        Self { dict, icon }
93    }
94
95    /// Return the file specification dictionary.
96    pub fn file_spec(&self) -> Option<Dict<'a>> {
97        self.dict.get::<Dict<'_>>(FS)
98    }
99
100    /// Return the embedded file stream.
101    pub fn embedded_file(&self) -> Option<Stream<'a>> {
102        let fs = self.file_spec()?;
103        let ef = fs.get::<Dict<'_>>(EF)?;
104        ef.get::<Stream<'_>>(F).or_else(|| ef.get::<Stream<'_>>(UF))
105    }
106
107    /// Return the filename.
108    pub fn filename(&self) -> Option<alloc::string::String> {
109        let fs = self.file_spec()?;
110        fs.get::<pdf_syntax::object::String>(UF)
111            .or_else(|| fs.get::<pdf_syntax::object::String>(F))
112            .map(|s| annotation::pdf_string_to_string(&s))
113    }
114}
115
116/// A FreeText annotation.
117#[derive(Debug)]
118pub struct FreeTextAnnotation {
119    /// The default appearance string.
120    pub default_appearance: alloc::string::String,
121    /// Text justification (0=left, 1=center, 2=right).
122    pub justification: u32,
123    /// Default style string.
124    pub default_style: Option<alloc::string::String>,
125    /// Rich text content.
126    pub rich_content: Option<alloc::string::String>,
127    /// Callout line points.
128    pub callout_line: Option<Vec<f32>>,
129    /// Intent.
130    pub intent: Option<alloc::string::String>,
131    /// Line ending style for callout.
132    pub line_ending: LineEnding,
133    /// Rectangle differences.
134    pub rd: Option<Rect>,
135}
136
137impl FreeTextAnnotation {
138    /// Extract free text annotation properties.
139    pub fn from_annot(annot: &Annotation<'_>) -> Self {
140        let dict = annot.dict();
141        let default_appearance = dict
142            .get::<pdf_syntax::object::String>(DA)
143            .map(|s| annotation::pdf_string_to_string(&s))
144            .unwrap_or_default();
145        let justification = dict.get::<u32>(Q).unwrap_or(0);
146        let default_style = dict
147            .get::<pdf_syntax::object::String>(DS)
148            .map(|s| annotation::pdf_string_to_string(&s));
149        let rich_content = dict
150            .get::<pdf_syntax::object::String>(RC)
151            .map(|s| annotation::pdf_string_to_string(&s));
152        let callout_line: Option<Vec<f32>> = dict
153            .get::<Array<'_>>(CL)
154            .map(|arr: Array<'_>| arr.iter::<f32>().collect());
155        let intent = dict
156            .get::<Name>(IT)
157            .map(|n: Name| alloc::string::String::from(n.as_str()));
158        let line_ending = dict
159            .get::<Name>(LE)
160            .map(|n: Name| LineEnding::from_name(n.as_ref()))
161            .unwrap_or(LineEnding::None);
162        let rd = dict.get::<Rect>(RD);
163        Self {
164            default_appearance,
165            justification,
166            default_style,
167            rich_content,
168            callout_line,
169            intent,
170            line_ending,
171            rd,
172        }
173    }
174}
175
176/// A Sound annotation.
177#[derive(Debug)]
178pub struct SoundAnnotation {
179    /// The icon name.
180    pub icon: alloc::string::String,
181}
182
183impl SoundAnnotation {
184    /// Extract sound annotation properties.
185    pub fn from_annot(annot: &Annotation<'_>) -> Self {
186        let icon = annot
187            .dict()
188            .get::<Name>(NAME)
189            .map(|n: Name| alloc::string::String::from(n.as_str()))
190            .unwrap_or_else(|| alloc::string::String::from("Speaker"));
191        Self { icon }
192    }
193}
194
195/// A Movie annotation.
196#[derive(Debug)]
197pub struct MovieAnnotation {
198    /// The movie title.
199    pub title: Option<alloc::string::String>,
200}
201
202impl MovieAnnotation {
203    /// Extract movie annotation properties.
204    pub fn from_annot(annot: &Annotation<'_>) -> Self {
205        let title = annot
206            .dict()
207            .get::<pdf_syntax::object::String>(T)
208            .map(|s| annotation::pdf_string_to_string(&s));
209        Self { title }
210    }
211}