use core::fmt;
use pdfrum_object::ObjRef;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Level {
A1b,
A2b,
}
impl Level {
#[must_use]
pub const fn name(self) -> &'static str {
match self {
Level::A1b => "PDF/A-1b",
Level::A2b => "PDF/A-2b",
}
}
#[must_use]
pub const fn part(self) -> u8 {
match self {
Level::A1b => 1,
Level::A2b => 2,
}
}
#[must_use]
pub const fn forbids_transparency(self) -> bool {
matches!(self, Level::A1b)
}
}
impl fmt::Display for Level {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.name())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[non_exhaustive]
pub enum Clause {
FontNotEmbedded,
FontSubsetIncomplete,
FontEncodingInvalid,
Encrypted,
JavaScript,
ForbiddenAction,
EmbeddedMultimedia,
XmpMissing,
XmpMalformed,
XmpIdentificationMissing,
XmpIdentificationMismatch,
XmpInfoMismatch,
OutputIntentMissing,
OutputIntentProfileMissing,
AnnotationFlagsIllegal,
AnnotationSubtypeForbidden,
AnnotationAppearanceMissing,
ExternalContentReference,
Transparency,
DeviceColorWithoutOutputIntent,
OptionalContent,
EmbeddedFile,
LzwFilter,
JpxFilter,
}
impl Clause {
#[must_use]
#[allow(clippy::match_same_arms)]
pub const fn iso(self, level: Level) -> &'static str {
match (self, level) {
(Clause::FontNotEmbedded, Level::A1b) => "ISO 19005-1:2005, 6.3.4",
(Clause::FontNotEmbedded, Level::A2b) => "ISO 19005-2:2011, 6.2.11.4.1",
(Clause::FontSubsetIncomplete, Level::A1b) => "ISO 19005-1:2005, 6.3.5",
(Clause::FontSubsetIncomplete, Level::A2b) => "ISO 19005-2:2011, 6.2.11.4.2",
(Clause::FontEncodingInvalid, Level::A1b) => "ISO 19005-1:2005, 6.3.6",
(Clause::FontEncodingInvalid, Level::A2b) => "ISO 19005-2:2011, 6.2.11.5",
(Clause::Encrypted, Level::A1b) => "ISO 19005-1:2005, 6.1.3",
(Clause::Encrypted, Level::A2b) => "ISO 19005-2:2011, 6.1.3",
(Clause::JavaScript, Level::A1b) => "ISO 19005-1:2005, 6.6.1",
(Clause::JavaScript, Level::A2b) => "ISO 19005-2:2011, 6.5.1",
(Clause::ForbiddenAction, Level::A1b) => "ISO 19005-1:2005, 6.6.1",
(Clause::ForbiddenAction, Level::A2b) => "ISO 19005-2:2011, 6.5.1",
(Clause::EmbeddedMultimedia, Level::A1b) => "ISO 19005-1:2005, 6.6.1",
(Clause::EmbeddedMultimedia, Level::A2b) => "ISO 19005-2:2011, 6.5.1",
(Clause::XmpMissing, Level::A1b) => "ISO 19005-1:2005, 6.7.2",
(Clause::XmpMissing, Level::A2b) => "ISO 19005-2:2011, 6.6.2.1",
(Clause::XmpMalformed, Level::A1b) => "ISO 19005-1:2005, 6.7.2",
(Clause::XmpMalformed, Level::A2b) => "ISO 19005-2:2011, 6.6.2.1",
(Clause::XmpIdentificationMissing, Level::A1b) => "ISO 19005-1:2005, 6.7.11",
(Clause::XmpIdentificationMissing, Level::A2b) => "ISO 19005-2:2011, 6.6.4",
(Clause::XmpIdentificationMismatch, Level::A1b) => "ISO 19005-1:2005, 6.7.11",
(Clause::XmpIdentificationMismatch, Level::A2b) => "ISO 19005-2:2011, 6.6.4",
(Clause::XmpInfoMismatch, Level::A1b) => "ISO 19005-1:2005, 6.7.3",
(Clause::XmpInfoMismatch, Level::A2b) => "ISO 19005-2:2011, 6.6.2.3.1",
(Clause::OutputIntentMissing, Level::A1b) => "ISO 19005-1:2005, 6.2.3.3",
(Clause::OutputIntentMissing, Level::A2b) => "ISO 19005-2:2011, 6.2.10",
(Clause::OutputIntentProfileMissing, Level::A1b) => "ISO 19005-1:2005, 6.2.3.3",
(Clause::OutputIntentProfileMissing, Level::A2b) => "ISO 19005-2:2011, 6.2.10",
(Clause::AnnotationFlagsIllegal, Level::A1b) => "ISO 19005-1:2005, 6.5.3",
(Clause::AnnotationFlagsIllegal, Level::A2b) => "ISO 19005-2:2011, 6.3.2",
(Clause::AnnotationSubtypeForbidden, Level::A1b) => "ISO 19005-1:2005, 6.5.2",
(Clause::AnnotationSubtypeForbidden, Level::A2b) => "ISO 19005-2:2011, 6.3.1",
(Clause::AnnotationAppearanceMissing, Level::A1b) => "ISO 19005-1:2005, 6.5.3",
(Clause::AnnotationAppearanceMissing, Level::A2b) => "ISO 19005-2:2011, 6.3.3",
(Clause::ExternalContentReference, Level::A1b) => "ISO 19005-1:2005, 6.1.6",
(Clause::ExternalContentReference, Level::A2b) => "ISO 19005-2:2011, 6.2.2",
(Clause::Transparency, Level::A1b) => "ISO 19005-1:2005, 6.4",
(Clause::Transparency, Level::A2b) => "ISO 19005-2:2011, 6.2.4.3",
(Clause::DeviceColorWithoutOutputIntent, Level::A1b) => "ISO 19005-1:2005, 6.2.3.3",
(Clause::DeviceColorWithoutOutputIntent, Level::A2b) => "ISO 19005-2:2011, 6.2.4.3",
(Clause::OptionalContent, Level::A1b) => "ISO 19005-1:2005, 6.1.13",
(Clause::OptionalContent, Level::A2b) => "ISO 19005-2:2011, 6.1.13",
(Clause::EmbeddedFile, Level::A1b) => "ISO 19005-1:2005, 6.1.11",
(Clause::EmbeddedFile, Level::A2b) => "ISO 19005-2:2011, 6.8",
(Clause::LzwFilter, Level::A1b) => "ISO 19005-1:2005, 6.1.10",
(Clause::LzwFilter, Level::A2b) => "ISO 19005-2:2011, 6.1.7.2",
(Clause::JpxFilter, Level::A1b) => "ISO 19005-1:2005, 6.1.3",
(Clause::JpxFilter, Level::A2b) => "ISO 19005-2:2011, 6.1.3",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum Subject {
Document,
Catalog,
Page(u32),
Object(ObjRef),
Resource {
page: u32,
name: String,
},
}
impl fmt::Display for Subject {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Subject::Document => f.write_str("document"),
Subject::Catalog => f.write_str("catalog"),
Subject::Page(index) => write!(f, "page {}", index + 1),
Subject::Object(reference) => {
write!(f, "object {} {}", reference.num, reference.generation)
}
Subject::Resource { page, name } => write!(f, "page {} resource /{name}", page + 1),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Violation {
pub clause: Clause,
pub subject: Subject,
pub detail: String,
}
impl fmt::Display for Violation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}: {}", self.subject, self.detail)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Report {
pub level: Level,
pub violations: Vec<Violation>,
}
impl Report {
#[must_use]
pub fn conforms(&self) -> bool {
self.violations.is_empty()
}
pub fn by_clause(&self, clause: Clause) -> impl Iterator<Item = &Violation> {
self.violations.iter().filter(move |v| v.clause == clause)
}
#[must_use]
pub fn clauses(&self) -> Vec<Clause> {
let mut out: Vec<Clause> = Vec::new();
for violation in &self.violations {
if !out.contains(&violation.clause) {
out.push(violation.clause);
}
}
out
}
}