1use crate::hnc8::Variant;
4use std::{fmt, io};
5
6#[derive(Debug)]
10pub struct Error {
11 pub kind: ErrorKind,
12 pub offset: Option<u64>,
15 pub context: Context,
16 pub reason: &'static str,
18}
19
20#[derive(Debug)]
22pub enum ErrorKind {
23 UnsupportedFormat,
25 Malformed,
28 Encrypted,
29 Truncated {
31 expected: u64,
32 available: u64,
33 },
34 LimitExceeded {
36 resource: &'static str,
37 limit: u64,
38 attempted: u64,
39 },
40 Io(io::Error),
42 Cancelled,
44}
45
46#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
48pub enum Context {
49 #[default]
50 None,
51 Caj { record: Option<u32> },
53 Kdh,
55 Pdf {
58 object: Option<(u32, u16)>,
59 repair: bool,
60 },
61 Hnc8 {
64 variant: Option<Variant>,
65 page: Option<u32>,
66 image: Option<u32>,
67 segment: Option<u32>,
68 stage: Option<Hnc8Stage>,
69 },
70 Jbig2 { segment: Option<u32> },
72}
73
74#[derive(Clone, Copy, Debug, Eq, PartialEq)]
76pub enum Hnc8Stage {
77 Preflight,
78 Container,
79 Text,
80 Headers,
81 Geometry,
82 Decode,
83 Pdf,
84 Visitor,
85 Type3(Type3Stage),
87}
88
89#[derive(Clone, Copy, Debug, Eq, PartialEq)]
91pub enum Type3Stage {
92 Directory,
93 PageInfo,
94 TextHeader,
95 GenericHeader,
96 Profile,
97 FirstDictionary,
98 SecondDictionary,
99 TextInstances,
100 TextCompose,
101 GenericRegion,
102 PageCompose,
103 Contexts,
104}
105
106pub type Result<T> = std::result::Result<T, Error>;
107
108impl From<ErrorKind> for Error {
109 fn from(kind: ErrorKind) -> Self {
110 Self {
111 kind,
112 offset: None,
113 context: Context::None,
114 reason: "",
115 }
116 }
117}
118
119impl Error {
120 pub fn invalid(reason: &'static str) -> Self {
122 Self::from(ErrorKind::Malformed).because(reason)
123 }
124
125 pub fn malformed(offset: u64, reason: &'static str) -> Self {
127 Self::invalid(reason).at(offset)
128 }
129
130 pub fn unsupported(offset: u64, reason: &'static str) -> Self {
132 Self::from(ErrorKind::UnsupportedFormat)
133 .at(offset)
134 .because(reason)
135 }
136
137 pub fn cancelled() -> Self {
138 ErrorKind::Cancelled.into()
139 }
140
141 pub fn truncated(offset: u64, expected: u64, available: u64) -> Self {
143 Self::from(ErrorKind::Truncated {
144 expected,
145 available,
146 })
147 .at(offset)
148 }
149
150 pub fn limit(resource: &'static str, limit: u64, attempted: u64) -> Self {
152 Self::from(ErrorKind::LimitExceeded {
153 resource,
154 limit,
155 attempted,
156 })
157 }
158
159 #[must_use]
161 pub fn at(self, offset: u64) -> Self {
162 Self {
163 offset: Some(offset),
164 ..self
165 }
166 }
167
168 #[must_use]
170 pub fn because(self, reason: &'static str) -> Self {
171 Self { reason, ..self }
172 }
173
174 #[must_use]
176 pub fn within(self, context: Context) -> Self {
177 Self { context, ..self }
178 }
179
180 #[must_use]
181 pub fn in_caj(self, record: Option<u32>) -> Self {
182 self.within(Context::Caj { record })
183 }
184
185 #[must_use]
186 pub fn in_pdf(self, object: Option<(u32, u16)>) -> Self {
187 self.within(Context::Pdf {
188 object,
189 repair: false,
190 })
191 }
192
193 #[must_use]
194 pub fn in_jbig2(self, segment: Option<u32>) -> Self {
195 self.within(Context::Jbig2 { segment })
196 }
197
198 #[must_use]
200 pub fn or_at(self, offset: u64, context: Context) -> Self {
201 if self.context != Context::None {
202 return self;
203 }
204 Self {
205 offset: self.offset.or(Some(offset)),
206 context,
207 ..self
208 }
209 }
210}
211
212impl Context {
213 pub(crate) const HNC8: Self = Self::Hnc8 {
215 variant: None,
216 page: None,
217 image: None,
218 segment: None,
219 stage: None,
220 };
221
222 fn write_name(self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
223 match self {
224 Self::None => Ok(()),
225 Self::Caj { .. } => f.write_str("CAJ"),
226 Self::Kdh => f.write_str("KDH"),
227 Self::Pdf { .. } => f.write_str("PDF"),
228 Self::Hnc8 { variant, .. } => {
229 f.write_str("HN/C8")?;
230 match variant {
231 Some(variant) => write!(f, " {}", variant.as_str()),
232 None => Ok(()),
233 }
234 }
235 Self::Jbig2 { .. } => f.write_str("JBIG2"),
236 }
237 }
238
239 fn write_location(self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
240 let mut item = |name: &str, value: Option<u32>| match value {
241 Some(value) => write!(f, ", {name} {value}"),
242 None => Ok(()),
243 };
244 match self {
245 Self::None | Self::Kdh => Ok(()),
246 Self::Caj { record } => item("record", record),
247 Self::Pdf { object, .. } => match object {
248 Some((number, generation)) => write!(f, ", object {number} {generation}"),
249 None => Ok(()),
250 },
251 Self::Hnc8 {
252 page,
253 image,
254 segment,
255 ..
256 } => {
257 item("page", page)?;
258 item("image", image)?;
259 item("segment", segment)
260 }
261 Self::Jbig2 { segment } => item("segment", segment),
262 }
263 }
264}
265
266struct Name(Context);
267
268impl fmt::Display for Name {
269 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
270 self.0.write_name(f)
271 }
272}
273
274impl fmt::Display for Error {
277 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
278 let name = Name(self.context);
279 let named = self.context != Context::None;
280 let input = |f: &mut fmt::Formatter<'_>, adjective: &str, fallback: &str| {
281 if named {
282 write!(f, "{adjective} {name}")
283 } else {
284 write!(f, "{adjective} {fallback}")
285 }
286 };
287 let event = |f: &mut fmt::Formatter<'_>, event: &str| {
288 f.write_str(event)?;
289 if named {
290 write!(f, " in {name}")?;
291 }
292 Ok(())
293 };
294 match &self.kind {
295 ErrorKind::Malformed if matches!(self.context, Context::Pdf { repair: true, .. }) => {
296 f.write_str("ambiguous repair PDF")?
297 }
298 ErrorKind::UnsupportedFormat if matches!(self.context, Context::Pdf { .. }) => {
299 f.write_str("unsupported feature PDF")?
300 }
301 ErrorKind::Malformed if !named => f.write_str("invalid input")?,
302 ErrorKind::Malformed => input(f, "malformed", "")?,
303 ErrorKind::UnsupportedFormat => input(f, "unsupported", "input format")?,
304 ErrorKind::Encrypted => input(f, "encrypted", "input")?,
305 ErrorKind::Truncated { .. } => input(f, "truncated", "input")?,
306 ErrorKind::LimitExceeded { resource, .. } if named => {
307 write!(f, "{name} {resource} limit exceeded")?
308 }
309 ErrorKind::LimitExceeded { resource, .. } => write!(f, "{resource} limit exceeded")?,
310 ErrorKind::Io(_) => event(f, "I/O error")?,
311 ErrorKind::Cancelled => event(f, "operation cancelled")?,
312 }
313 if let Some(offset) = self.offset {
314 write!(f, " at byte {offset}")?;
315 }
316 self.context.write_location(f)?;
317 if !self.reason.is_empty() {
318 write!(f, ": {}", self.reason)?;
319 }
320 match &self.kind {
321 ErrorKind::Truncated {
322 expected,
323 available,
324 } => write!(f, ": expected {expected} bytes, available {available}"),
325 ErrorKind::LimitExceeded {
326 limit, attempted, ..
327 } => write!(f, ": maximum {limit}, attempted {attempted}"),
328 ErrorKind::Io(error) => write!(f, ": {error}"),
329 _ => Ok(()),
330 }
331 }
332}
333
334impl std::error::Error for Error {
335 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
336 match &self.kind {
337 ErrorKind::Io(error) => Some(error),
338 _ => None,
339 }
340 }
341}
342
343impl From<io::Error> for Error {
346 fn from(error: io::Error) -> Self {
347 match error.downcast::<Self>() {
348 Ok(error) => error,
349 Err(error) => ErrorKind::Io(error).into(),
350 }
351 }
352}
353
354impl From<Error> for io::Error {
357 fn from(error: Error) -> Self {
358 let kind = match &error.kind {
359 ErrorKind::Io(inner) => inner.kind(),
360 _ => io::ErrorKind::Other,
361 };
362 match error {
363 Error {
364 kind: ErrorKind::Io(error),
365 offset: None,
366 context: Context::None,
367 reason: "",
368 } => error,
369 located => io::Error::new(kind, located),
370 }
371 }
372}
373
374#[cfg(test)]
375mod tests;