use crate::hnc8::Variant;
use std::{fmt, io};
#[derive(Debug)]
pub struct Error {
pub kind: ErrorKind,
pub offset: Option<u64>,
pub context: Context,
pub reason: &'static str,
}
#[derive(Debug)]
pub enum ErrorKind {
UnsupportedFormat,
Malformed,
Encrypted,
Truncated {
expected: u64,
available: u64,
},
LimitExceeded {
resource: &'static str,
limit: u64,
attempted: u64,
},
Io(io::Error),
Cancelled,
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub enum Context {
#[default]
None,
Caj { record: Option<u32> },
Kdh,
Pdf {
object: Option<(u32, u16)>,
repair: bool,
},
Hnc8 {
variant: Option<Variant>,
page: Option<u32>,
image: Option<u32>,
segment: Option<u32>,
stage: Option<Hnc8Stage>,
},
Jbig2 { segment: Option<u32> },
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Hnc8Stage {
Preflight,
Container,
Text,
Headers,
Geometry,
Decode,
Pdf,
Visitor,
Type3(Type3Stage),
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Type3Stage {
Directory,
PageInfo,
TextHeader,
GenericHeader,
Profile,
FirstDictionary,
SecondDictionary,
TextInstances,
TextCompose,
GenericRegion,
PageCompose,
Contexts,
}
pub type Result<T> = std::result::Result<T, Error>;
impl From<ErrorKind> for Error {
fn from(kind: ErrorKind) -> Self {
Self {
kind,
offset: None,
context: Context::None,
reason: "",
}
}
}
impl Error {
pub fn invalid(reason: &'static str) -> Self {
Self::from(ErrorKind::Malformed).because(reason)
}
pub fn malformed(offset: u64, reason: &'static str) -> Self {
Self::invalid(reason).at(offset)
}
pub fn unsupported(offset: u64, reason: &'static str) -> Self {
Self::from(ErrorKind::UnsupportedFormat)
.at(offset)
.because(reason)
}
pub fn cancelled() -> Self {
ErrorKind::Cancelled.into()
}
pub fn truncated(offset: u64, expected: u64, available: u64) -> Self {
Self::from(ErrorKind::Truncated {
expected,
available,
})
.at(offset)
}
pub fn limit(resource: &'static str, limit: u64, attempted: u64) -> Self {
Self::from(ErrorKind::LimitExceeded {
resource,
limit,
attempted,
})
}
#[must_use]
pub fn at(self, offset: u64) -> Self {
Self {
offset: Some(offset),
..self
}
}
#[must_use]
pub fn because(self, reason: &'static str) -> Self {
Self { reason, ..self }
}
#[must_use]
pub fn within(self, context: Context) -> Self {
Self { context, ..self }
}
#[must_use]
pub fn in_caj(self, record: Option<u32>) -> Self {
self.within(Context::Caj { record })
}
#[must_use]
pub fn in_pdf(self, object: Option<(u32, u16)>) -> Self {
self.within(Context::Pdf {
object,
repair: false,
})
}
#[must_use]
pub fn in_jbig2(self, segment: Option<u32>) -> Self {
self.within(Context::Jbig2 { segment })
}
#[must_use]
pub fn or_at(self, offset: u64, context: Context) -> Self {
if self.context != Context::None {
return self;
}
Self {
offset: self.offset.or(Some(offset)),
context,
..self
}
}
}
impl Context {
pub(crate) const HNC8: Self = Self::Hnc8 {
variant: None,
page: None,
image: None,
segment: None,
stage: None,
};
fn write_name(self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::None => Ok(()),
Self::Caj { .. } => f.write_str("CAJ"),
Self::Kdh => f.write_str("KDH"),
Self::Pdf { .. } => f.write_str("PDF"),
Self::Hnc8 { variant, .. } => {
f.write_str("HN/C8")?;
match variant {
Some(variant) => write!(f, " {}", variant.as_str()),
None => Ok(()),
}
}
Self::Jbig2 { .. } => f.write_str("JBIG2"),
}
}
fn write_location(self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut item = |name: &str, value: Option<u32>| match value {
Some(value) => write!(f, ", {name} {value}"),
None => Ok(()),
};
match self {
Self::None | Self::Kdh => Ok(()),
Self::Caj { record } => item("record", record),
Self::Pdf { object, .. } => match object {
Some((number, generation)) => write!(f, ", object {number} {generation}"),
None => Ok(()),
},
Self::Hnc8 {
page,
image,
segment,
..
} => {
item("page", page)?;
item("image", image)?;
item("segment", segment)
}
Self::Jbig2 { segment } => item("segment", segment),
}
}
}
struct Name(Context);
impl fmt::Display for Name {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.write_name(f)
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let name = Name(self.context);
let named = self.context != Context::None;
let input = |f: &mut fmt::Formatter<'_>, adjective: &str, fallback: &str| {
if named {
write!(f, "{adjective} {name}")
} else {
write!(f, "{adjective} {fallback}")
}
};
let event = |f: &mut fmt::Formatter<'_>, event: &str| {
f.write_str(event)?;
if named {
write!(f, " in {name}")?;
}
Ok(())
};
match &self.kind {
ErrorKind::Malformed if matches!(self.context, Context::Pdf { repair: true, .. }) => {
f.write_str("ambiguous repair PDF")?
}
ErrorKind::UnsupportedFormat if matches!(self.context, Context::Pdf { .. }) => {
f.write_str("unsupported feature PDF")?
}
ErrorKind::Malformed if !named => f.write_str("invalid input")?,
ErrorKind::Malformed => input(f, "malformed", "")?,
ErrorKind::UnsupportedFormat => input(f, "unsupported", "input format")?,
ErrorKind::Encrypted => input(f, "encrypted", "input")?,
ErrorKind::Truncated { .. } => input(f, "truncated", "input")?,
ErrorKind::LimitExceeded { resource, .. } if named => {
write!(f, "{name} {resource} limit exceeded")?
}
ErrorKind::LimitExceeded { resource, .. } => write!(f, "{resource} limit exceeded")?,
ErrorKind::Io(_) => event(f, "I/O error")?,
ErrorKind::Cancelled => event(f, "operation cancelled")?,
}
if let Some(offset) = self.offset {
write!(f, " at byte {offset}")?;
}
self.context.write_location(f)?;
if !self.reason.is_empty() {
write!(f, ": {}", self.reason)?;
}
match &self.kind {
ErrorKind::Truncated {
expected,
available,
} => write!(f, ": expected {expected} bytes, available {available}"),
ErrorKind::LimitExceeded {
limit, attempted, ..
} => write!(f, ": maximum {limit}, attempted {attempted}"),
ErrorKind::Io(error) => write!(f, ": {error}"),
_ => Ok(()),
}
}
}
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match &self.kind {
ErrorKind::Io(error) => Some(error),
_ => None,
}
}
}
impl From<io::Error> for Error {
fn from(error: io::Error) -> Self {
match error.downcast::<Self>() {
Ok(error) => error,
Err(error) => ErrorKind::Io(error).into(),
}
}
}
impl From<Error> for io::Error {
fn from(error: Error) -> Self {
let kind = match &error.kind {
ErrorKind::Io(inner) => inner.kind(),
_ => io::ErrorKind::Other,
};
match error {
Error {
kind: ErrorKind::Io(error),
offset: None,
context: Context::None,
reason: "",
} => error,
located => io::Error::new(kind, located),
}
}
}
#[cfg(test)]
mod tests;