pub mod comparators;
pub mod compliance_report;
pub mod curated_matrix;
pub mod iso_matrix;
pub mod parser;
pub mod validators;
use crate::error::Result;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum VerificationLevel {
NotImplemented = 0,
CodeExists = 1,
GeneratesPdf = 2,
ContentVerified = 3,
IsoCompliant = 4,
}
impl VerificationLevel {
pub fn as_percentage(&self) -> f64 {
match self {
VerificationLevel::NotImplemented => 0.0,
VerificationLevel::CodeExists => 25.0,
VerificationLevel::GeneratesPdf => 50.0,
VerificationLevel::ContentVerified => 75.0,
VerificationLevel::IsoCompliant => 100.0,
}
}
pub fn from_u8(level: u8) -> Option<Self> {
match level {
0 => Some(VerificationLevel::NotImplemented),
1 => Some(VerificationLevel::CodeExists),
2 => Some(VerificationLevel::GeneratesPdf),
3 => Some(VerificationLevel::ContentVerified),
4 => Some(VerificationLevel::IsoCompliant),
_ => None,
}
}
}
#[derive(Debug, Clone)]
pub struct VerificationResult {
pub level: VerificationLevel,
pub passed: bool,
pub details: String,
pub external_validation: Option<ExternalValidationResult>,
}
#[derive(Debug, Clone)]
pub struct ExternalValidationResult {
pub qpdf_passed: Option<bool>,
pub verapdf_passed: Option<bool>,
pub adobe_preflight_passed: Option<bool>,
pub error_messages: Vec<String>,
}
#[derive(Debug, Clone)]
pub struct IsoRequirement {
pub id: String,
pub name: String,
pub description: String,
pub iso_reference: String,
pub implementation: Option<String>,
pub test_file: Option<String>,
pub level: VerificationLevel,
pub verified: bool,
pub notes: String,
}
pub fn verify_iso_requirement(
pdf_bytes: &[u8],
requirement: &IsoRequirement,
) -> Result<VerificationResult> {
match requirement.level {
VerificationLevel::NotImplemented => Ok(VerificationResult {
level: VerificationLevel::NotImplemented,
passed: false,
details: "Feature not implemented".to_string(),
external_validation: None,
}),
VerificationLevel::CodeExists => {
Ok(VerificationResult {
level: VerificationLevel::CodeExists,
passed: true,
details: "Code exists and executes without crash".to_string(),
external_validation: None,
})
}
VerificationLevel::GeneratesPdf => verify_pdf_generation(pdf_bytes),
VerificationLevel::ContentVerified => verify_pdf_content(pdf_bytes, requirement),
VerificationLevel::IsoCompliant => verify_iso_compliance(pdf_bytes, requirement),
}
}
fn verify_pdf_generation(pdf_bytes: &[u8]) -> Result<VerificationResult> {
if pdf_bytes.is_empty() {
return Ok(VerificationResult {
level: VerificationLevel::GeneratesPdf,
passed: false,
details: "PDF is empty".to_string(),
external_validation: None,
});
}
if !pdf_bytes.starts_with(b"%PDF-") {
return Ok(VerificationResult {
level: VerificationLevel::GeneratesPdf,
passed: false,
details: "PDF does not start with PDF header".to_string(),
external_validation: None,
});
}
if pdf_bytes.len() < 1000 {
return Ok(VerificationResult {
level: VerificationLevel::GeneratesPdf,
passed: false,
details: format!("PDF too small: {} bytes", pdf_bytes.len()),
external_validation: None,
});
}
Ok(VerificationResult {
level: VerificationLevel::GeneratesPdf,
passed: true,
details: format!("Valid PDF generated: {} bytes", pdf_bytes.len()),
external_validation: None,
})
}
fn verify_pdf_content(
pdf_bytes: &[u8],
requirement: &IsoRequirement,
) -> Result<VerificationResult> {
let gen_result = verify_pdf_generation(pdf_bytes)?;
if !gen_result.passed {
return Ok(gen_result);
}
match parser::parse_pdf(pdf_bytes) {
Ok(parsed_pdf) => {
let content_check = verify_requirement_content(&parsed_pdf, requirement);
Ok(VerificationResult {
level: VerificationLevel::ContentVerified,
passed: content_check.0,
details: content_check.1,
external_validation: None,
})
}
Err(e) => Ok(VerificationResult {
level: VerificationLevel::ContentVerified,
passed: false,
details: format!("Failed to parse PDF: {}", e),
external_validation: None,
}),
}
}
fn verify_iso_compliance(
pdf_bytes: &[u8],
requirement: &IsoRequirement,
) -> Result<VerificationResult> {
let content_result = verify_pdf_content(pdf_bytes, requirement)?;
if !content_result.passed {
return Ok(content_result);
}
let external_result = validators::validate_external(pdf_bytes)?;
let all_passed = external_result.qpdf_passed.unwrap_or(false)
&& external_result.verapdf_passed.unwrap_or(true);
Ok(VerificationResult {
level: VerificationLevel::IsoCompliant,
passed: all_passed,
details: if all_passed {
"Passed all external validation checks".to_string()
} else {
format!(
"External validation failed: {:?}",
external_result.error_messages
)
},
external_validation: Some(external_result),
})
}
fn verify_requirement_content(
parsed_pdf: &parser::ParsedPdf,
requirement: &IsoRequirement,
) -> (bool, String) {
match requirement.id.as_str() {
"7.5.2.1" => {
if let Some(catalog) = &parsed_pdf.catalog {
if catalog.contains_key("Type") {
(true, "Catalog contains required /Type entry".to_string())
} else {
(false, "Catalog missing /Type entry".to_string())
}
} else {
(false, "No document catalog found".to_string())
}
}
"8.6.3.1" => {
if parsed_pdf.uses_device_rgb {
(true, "PDF uses DeviceRGB color space correctly".to_string())
} else {
(
false,
"DeviceRGB color space not found or incorrect".to_string(),
)
}
}
"9.7.1.1" => {
let standard_fonts = &[
"Helvetica",
"Times-Roman",
"Courier",
"Symbol",
"ZapfDingbats",
];
let found_fonts: Vec<_> = parsed_pdf
.fonts
.iter()
.filter(|font| standard_fonts.contains(&font.as_str()))
.collect();
if !found_fonts.is_empty() {
(true, format!("Found standard fonts: {:?}", found_fonts))
} else {
(false, "No standard fonts found".to_string())
}
}
_ => {
(
true,
format!(
"Content verification not yet implemented for {}",
requirement.id
),
)
}
}
}
#[cfg(test)]
mod unit_tests {
use super::*;
#[test]
fn test_verification_level_percentage() {
assert_eq!(VerificationLevel::NotImplemented.as_percentage(), 0.0);
assert_eq!(VerificationLevel::CodeExists.as_percentage(), 25.0);
assert_eq!(VerificationLevel::GeneratesPdf.as_percentage(), 50.0);
assert_eq!(VerificationLevel::ContentVerified.as_percentage(), 75.0);
assert_eq!(VerificationLevel::IsoCompliant.as_percentage(), 100.0);
}
#[test]
fn test_verification_level_from_u8() {
assert_eq!(
VerificationLevel::from_u8(0),
Some(VerificationLevel::NotImplemented)
);
assert_eq!(
VerificationLevel::from_u8(4),
Some(VerificationLevel::IsoCompliant)
);
assert_eq!(VerificationLevel::from_u8(5), None);
}
#[test]
fn test_pdf_generation_verification() {
let empty_pdf = b"";
let result = verify_pdf_generation(empty_pdf).unwrap();
assert!(!result.passed);
assert!(result.details.contains("empty"));
let invalid_pdf = b"This is not a PDF";
let result = verify_pdf_generation(invalid_pdf).unwrap();
assert!(!result.passed);
assert!(result.details.contains("PDF header"));
let small_pdf = b"%PDF-1.4\n%%EOF";
let result = verify_pdf_generation(small_pdf).unwrap();
assert!(!result.passed);
assert!(result.details.contains("too small"));
let valid_pdf = format!("%PDF-1.4\n{}\n%%EOF", "x".repeat(1000));
let result = verify_pdf_generation(valid_pdf.as_bytes()).unwrap();
assert!(result.passed);
assert!(result.details.contains("Valid PDF generated"));
}
}
pub fn pdfs_structurally_equivalent(generated: &[u8], reference: &[u8]) -> bool {
comparators::pdfs_structurally_equivalent(generated, reference)
}
pub fn extract_pdf_differences(
generated: &[u8],
reference: &[u8],
) -> Result<Vec<comparators::PdfDifference>> {
comparators::extract_pdf_differences(generated, reference)
}