printwell-cli 0.1.11

Command-line tool for HTML to PDF conversion
Documentation
//! PDF/A commands implementation.

use anyhow::{Context, Result};

use crate::cli::args::{PdfaConvertArgs, PdfaValidateArgs};

pub fn pdfa_validate(args: &PdfaValidateArgs) -> Result<()> {
    use printwell::pdfa::{IssueSeverity, PdfALevel, validate_pdfa};

    let pdf_data = std::fs::read(&args.input)
        .with_context(|| format!("Failed to read input file: {}", args.input))?;

    let level = PdfALevel::parse_arg(&args.level).ok_or_else(|| {
        anyhow::anyhow!(
            "Invalid PDF/A level: {}. Use 1b, 1a, 2b, 2u, 2a, 3b, 3u, or 3a",
            args.level
        )
    })?;

    let result = validate_pdfa(&pdf_data, level).context("PDF/A validation failed")?;

    if args.format.as_str() == "json" {
        let json = serde_json::json!({
            "level": format!("{}", level),
            "is_compliant": result.is_compliant,
            "error_count": result.error_count,
            "warning_count": result.warning_count,
            "issues": result.issues.iter().map(|i| serde_json::json!({
                "severity": format!("{:?}", i.severity),
                "category": format!("{:?}", i.category),
                "message": i.message,
                "clause": i.clause,
                "page": i.page,
            })).collect::<Vec<_>>(),
        });
        println!(
            "{}",
            serde_json::to_string_pretty(&json).context("Failed to serialize validation result")?
        );
    } else {
        println!("PDF/A Validation Report");
        println!("=======================");
        println!("File: {}", args.input);
        println!("Target Level: {level}");
        println!();

        if result.is_compliant {
            println!("Result: COMPLIANT");
        } else {
            println!("Result: NOT COMPLIANT");
        }
        println!();
        println!("Errors: {}", result.error_count);
        println!("Warnings: {}", result.warning_count);

        if !result.issues.is_empty() {
            println!();
            println!("Issues:");
            for issue in &result.issues {
                let severity_marker = match issue.severity {
                    IssueSeverity::Error => "ERROR",
                    IssueSeverity::Warning => "WARNING",
                    IssueSeverity::Info => "INFO",
                };
                let clause = issue.clause.as_deref().unwrap_or("-");
                println!("  [{:7}] [{}] {}", severity_marker, clause, issue.message);
            }
        }
    }

    if result.is_compliant {
        Ok(())
    } else {
        anyhow::bail!("PDF is not compliant with {level}")
    }
}

pub fn pdfa_convert(args: &PdfaConvertArgs) -> Result<()> {
    use printwell::pdfa::{PdfALevel, add_pdfa_metadata};

    let pdf_data = std::fs::read(&args.input)
        .with_context(|| format!("Failed to read input file: {}", args.input))?;

    let level = PdfALevel::parse_arg(&args.level).ok_or_else(|| {
        anyhow::anyhow!(
            "Invalid PDF/A level: {}. Use 1b, 1a, 2b, 2u, 2a, 3b, 3u, or 3a",
            args.level
        )
    })?;

    let result = add_pdfa_metadata(
        &pdf_data,
        level,
        args.title.as_deref(),
        args.author.as_deref(),
    )
    .context("Failed to add PDF/A metadata")?;

    std::fs::write(&args.output, &result)
        .with_context(|| format!("Failed to write output file: {}", args.output))?;

    eprintln!("Added {} metadata, written to: {}", level, args.output);
    eprintln!("Note: This adds PDF/A identification metadata. Full PDF/A conversion");
    eprintln!("      requires ensuring fonts are embedded, no encryption, etc.");

    Ok(())
}