printwell-cli 0.1.11

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

use anyhow::{Context, Result};

use crate::cli::args::{PdfuaConvertArgs, PdfuaValidateArgs};

pub fn pdfua_validate(args: &PdfuaValidateArgs) -> Result<()> {
    use printwell::pdfua::{IssueSeverity, PdfUALevel, validate_pdfua};

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

    let level: PdfUALevel = args
        .level
        .parse()
        .with_context(|| format!("Invalid PDF/UA level: {}", args.level))?;

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

    if args.format.as_str() == "json" {
        let json = serde_json::json!({
            "level": level.to_string(),
            "is_compliant": result.is_compliant,
            "pages_checked": result.pages_checked,
            "tagged_elements": result.tagged_elements,
            "issues": result.issues.iter().map(|i| serde_json::json!({
                "severity": i.severity.to_string(),
                "category": i.category.to_string(),
                "description": i.description,
                "page": i.page,
                "clause": i.clause,
                "suggestion": i.suggestion,
            })).collect::<Vec<_>>()
        });
        println!(
            "{}",
            serde_json::to_string_pretty(&json).context("Failed to serialize validation result")?
        );
    } else {
        eprintln!("PDF/UA Validation Report for: {}", args.input);
        eprintln!("Target Level: {level}");
        eprintln!("Pages Checked: {}", result.pages_checked);
        eprintln!("Tagged Elements Found: {}", result.tagged_elements);
        eprintln!();

        if result.issues.is_empty() {
            eprintln!("No issues found! Document appears PDF/UA compliant.");
        } else {
            let errors = result
                .issues
                .iter()
                .filter(|i| i.severity == IssueSeverity::Error)
                .count();
            let warnings = result
                .issues
                .iter()
                .filter(|i| i.severity == IssueSeverity::Warning)
                .count();
            let infos = result
                .issues
                .iter()
                .filter(|i| i.severity == IssueSeverity::Info)
                .count();

            eprintln!("Found {errors} errors, {warnings} warnings, {infos} info messages");
            eprintln!();

            for issue in &result.issues {
                let prefix = match issue.severity {
                    IssueSeverity::Error => "[ERROR]  ",
                    IssueSeverity::Warning => "[WARNING]",
                    IssueSeverity::Info => "[INFO]   ",
                };
                eprintln!("{} [{}] {}", prefix, issue.category, issue.description);
                if let Some(clause) = &issue.clause {
                    eprintln!("         Clause: {clause}");
                }
                if let Some(suggestion) = &issue.suggestion {
                    eprintln!("         Suggestion: {suggestion}");
                }
                if issue.page > 0 {
                    eprintln!("         Page: {}", issue.page);
                }
            }
        }

        eprintln!();
        if result.is_compliant {
            eprintln!("Result: COMPLIANT with {level}");
        } else {
            eprintln!("Result: NOT COMPLIANT with {level}");
        }
    }

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

    Ok(())
}

pub fn pdfua_convert(args: &PdfuaConvertArgs) -> Result<()> {
    use printwell::pdfua::{AccessibilityOptions, PdfUALevel, add_pdfua_metadata};

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

    let level: PdfUALevel = args
        .level
        .parse()
        .with_context(|| format!("Invalid PDF/UA level: {}", args.level))?;

    let options = AccessibilityOptions::builder()
        .language(args.language.clone())
        .title(args.title.clone().unwrap_or_default())
        .build();

    let result =
        add_pdfua_metadata(&pdf_data, level, &options).context("Failed to add PDF/UA 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!("Language: {}", args.language);
    eprintln!("Note: This adds PDF/UA identification metadata and accessibility markers.");
    eprintln!("      Full PDF/UA compliance requires tagged structure from source HTML.");

    Ok(())
}