1use std::fmt;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
14pub struct ObjectId {
15 pub number: u32,
17 pub generation: u16,
19}
20
21impl ObjectId {
22 pub fn new(number: u32, generation: u16) -> Self {
24 Self { number, generation }
25 }
26}
27
28impl fmt::Display for ObjectId {
29 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
30 write!(f, "{} {} R", self.number, self.generation)
31 }
32}
33
34#[derive(Debug, thiserror::Error)]
36pub enum PdfError {
37 #[error("parse error: {0}")]
39 Parse(#[from] ParseError),
40
41 #[error("circular reference detected at object {0}")]
43 CircularReference(ObjectId),
44
45 #[error("recursion limit exceeded")]
47 RecursionLimitExceeded,
48
49 #[error("unknown object: {0}")]
51 UnknownObject(ObjectId),
52
53 #[error("expected a stream object")]
55 NotAStream,
56
57 #[error("filter chain too long")]
59 FilterChainTooLong,
60
61 #[error("image too large")]
63 ImageTooLarge,
64
65 #[error("stream too large")]
67 StreamTooLarge,
68
69 #[error("compression bomb detected")]
71 CompressionBombDetected,
72
73 #[error("too many operators in content stream")]
75 TooManyOperators,
76
77 #[error("endstream scan failed")]
79 EndstreamScanFailed,
80
81 #[error("invalid PDF header")]
83 InvalidHeader,
84
85 #[error("invalid cross-reference table")]
87 InvalidXref,
88
89 #[error("invalid trailer")]
91 InvalidTrailer,
92
93 #[error("stream decode error: {0}")]
95 StreamDecodeError(String),
96
97 #[error("invalid object stream")]
99 InvalidObjectStream,
100
101 #[error("invalid object: {0}")]
103 InvalidObject(String),
104
105 #[error("invalid password")]
107 InvalidPassword,
108
109 #[error("unsupported encryption")]
111 UnsupportedEncryption,
112
113 #[error("not supported: {0}")]
115 NotSupported(String),
116
117 #[error("I/O error: {0}")]
119 Io(#[from] std::io::Error),
120}
121
122#[derive(Debug, Clone, thiserror::Error)]
127pub enum ParseError {
128 #[error("unexpected token at offset {offset}: expected {expected}, found {found}")]
130 UnexpectedToken {
131 offset: u64,
133 expected: String,
135 found: String,
137 },
138
139 #[error("unexpected end of input at offset {offset}")]
141 UnexpectedEof {
142 offset: u64,
144 },
145
146 #[error("number out of range at offset {offset}")]
148 NumberOutOfRange {
149 offset: u64,
151 },
152
153 #[error("invalid string at offset {offset}: {detail}")]
155 InvalidString {
156 offset: u64,
158 detail: String,
160 },
161
162 #[error("invalid name at offset {offset}: {detail}")]
164 InvalidName {
165 offset: u64,
167 detail: String,
169 },
170
171 #[error("invalid object header at offset {offset}")]
173 InvalidObjectHeader {
174 offset: u64,
176 },
177
178 #[error("missing endobj at offset {offset}")]
180 MissingEndobj {
181 offset: u64,
183 },
184
185 #[error("duplicate dictionary key: {key}")]
187 DuplicateKey {
188 key: String,
190 },
191
192 #[error("missing endstream at offset {offset}")]
194 MissingEndstream {
195 offset: u64,
197 },
198
199 #[error("invalid number at offset {offset}")]
201 InvalidNumber {
202 offset: u64,
204 },
205
206 #[error("invalid xref entry at offset {offset}")]
208 InvalidXrefEntry {
209 offset: u64,
211 },
212
213 #[error("parse error at offset {offset}: {message}")]
215 Other {
216 offset: u64,
218 message: String,
220 },
221}
222
223pub type PdfResult<T> = std::result::Result<T, PdfError>;
225
226#[cfg(test)]
227mod tests {
228 use super::*;
229
230 #[test]
231 fn test_object_id_display() {
232 let id = ObjectId {
233 number: 42,
234 generation: 0,
235 };
236 assert_eq!(format!("{id}"), "42 0 R");
237 }
238
239 #[test]
240 fn test_object_id_equality() {
241 let a = ObjectId {
242 number: 1,
243 generation: 0,
244 };
245 let b = ObjectId {
246 number: 1,
247 generation: 0,
248 };
249 let c = ObjectId {
250 number: 1,
251 generation: 1,
252 };
253 assert_eq!(a, b);
254 assert_ne!(a, c);
255 }
256
257 #[test]
258 fn test_pdf_error_from_parse_error() {
259 let parse_err = ParseError::UnexpectedEof { offset: 100 };
260 let pdf_err: PdfError = parse_err.into();
261 assert!(matches!(pdf_err, PdfError::Parse(_)));
262 }
263
264 #[test]
265 fn test_pdf_error_from_io_error() {
266 let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
267 let pdf_err: PdfError = io_err.into();
268 assert!(matches!(pdf_err, PdfError::Io(_)));
269 }
270
271 #[test]
272 fn test_pdf_error_display() {
273 let err = PdfError::CircularReference(ObjectId {
274 number: 5,
275 generation: 0,
276 });
277 assert_eq!(
278 format!("{err}"),
279 "circular reference detected at object 5 0 R"
280 );
281 }
282
283 #[test]
284 fn test_parse_error_display() {
285 let err = ParseError::UnexpectedToken {
286 offset: 42,
287 expected: "integer".to_string(),
288 found: "name".to_string(),
289 };
290 assert_eq!(
291 format!("{err}"),
292 "unexpected token at offset 42: expected integer, found name"
293 );
294 }
295
296 #[test]
297 fn test_pdf_error_is_send_sync() {
298 fn assert_send_sync<T: Send + Sync>() {}
299 assert_send_sync::<PdfError>();
300 }
301
302 #[test]
303 fn test_parse_error_is_send_sync() {
304 fn assert_send_sync<T: Send + Sync>() {}
305 assert_send_sync::<ParseError>();
306 }
307}