printwell-pdf 0.1.11

PDF manipulation features (forms, signing) for Printwell
Documentation
//! PDF/A compliance support.
//!
//! This module provides functionality for validating and converting PDFs
//! to PDF/A format (ISO 19005).
//!
//! **Note:** This feature requires a commercial license.
//! Purchase at: <https://printwell.dev/pricing>

use crate::{PdfAError, Result};

/// PDF/A conformance level
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PdfALevel {
    /// PDF/A-1b: Basic conformance (visual preservation)
    PdfA1b,
    /// PDF/A-1a: Full conformance (tagged/accessible)
    PdfA1a,
    /// PDF/A-2b: Basic conformance with modern features
    PdfA2b,
    /// PDF/A-2u: Level 2b with Unicode mapping
    PdfA2u,
    /// PDF/A-2a: Full conformance with modern features
    PdfA2a,
    /// PDF/A-3b: Basic conformance with embedded files
    PdfA3b,
    /// PDF/A-3u: Level 3b with Unicode mapping
    PdfA3u,
    /// PDF/A-3a: Full conformance with embedded files
    PdfA3a,
}

impl PdfALevel {
    /// Get the PDF/A part number (1, 2, or 3)
    #[must_use]
    pub const fn part(&self) -> u8 {
        match self {
            Self::PdfA1b | Self::PdfA1a => 1,
            Self::PdfA2b | Self::PdfA2u | Self::PdfA2a => 2,
            Self::PdfA3b | Self::PdfA3u | Self::PdfA3a => 3,
        }
    }

    /// Get the conformance level (a, b, or u)
    #[must_use]
    pub const fn conformance(&self) -> &'static str {
        match self {
            Self::PdfA1b | Self::PdfA2b | Self::PdfA3b => "B",
            Self::PdfA1a | Self::PdfA2a | Self::PdfA3a => "A",
            Self::PdfA2u | Self::PdfA3u => "U",
        }
    }

    /// Parse from string (e.g., "1b", "2a", "3u")
    #[must_use]
    pub fn parse_arg(s: &str) -> Option<Self> {
        match s.to_lowercase().as_str() {
            "1b" | "pdfa1b" | "pdf/a-1b" => Some(Self::PdfA1b),
            "1a" | "pdfa1a" | "pdf/a-1a" => Some(Self::PdfA1a),
            "2b" | "pdfa2b" | "pdf/a-2b" => Some(Self::PdfA2b),
            "2u" | "pdfa2u" | "pdf/a-2u" => Some(Self::PdfA2u),
            "2a" | "pdfa2a" | "pdf/a-2a" => Some(Self::PdfA2a),
            "3b" | "pdfa3b" | "pdf/a-3b" => Some(Self::PdfA3b),
            "3u" | "pdfa3u" | "pdf/a-3u" => Some(Self::PdfA3u),
            "3a" | "pdfa3a" | "pdf/a-3a" => Some(Self::PdfA3a),
            _ => None,
        }
    }
}

impl std::fmt::Display for PdfALevel {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "PDF/A-{}{}",
            self.part(),
            self.conformance().to_lowercase()
        )
    }
}

/// Severity of a PDF/A compliance issue
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum IssueSeverity {
    /// Error: Must be fixed for compliance
    Error,
    /// Warning: Should be fixed
    Warning,
    /// Info: Informational note
    Info,
}

/// Category of a PDF/A compliance issue
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum IssueCategory {
    /// Font-related issues
    Fonts,
    /// Color-related issues
    Color,
    /// Metadata issues
    Metadata,
    /// Structure issues
    Structure,
    /// Action/JavaScript issues
    Actions,
    /// Encryption issues
    Encryption,
    /// Annotation issues
    Annotations,
    /// Transparency issues
    Transparency,
    /// File attachment issues
    Attachments,
}

/// A PDF/A compliance issue
#[derive(Debug, Clone)]
pub struct PdfAIssue {
    /// Issue severity
    pub severity: IssueSeverity,
    /// Issue category
    pub category: IssueCategory,
    /// Human-readable message
    pub message: String,
    /// Clause reference (e.g., "6.1.3")
    pub clause: Option<String>,
    /// Page number where issue occurs
    pub page: Option<u32>,
}

/// Result of PDF/A validation
#[derive(Debug, Clone)]
pub struct ValidationResult {
    /// PDF/A level that was validated against
    pub level: PdfALevel,
    /// Whether the PDF is compliant
    pub is_compliant: bool,
    /// List of issues found
    pub issues: Vec<PdfAIssue>,
    /// Number of errors
    pub error_count: usize,
    /// Number of warnings
    pub warning_count: usize,
}

impl ValidationResult {
    /// Check if the PDF passes validation (no errors)
    #[must_use]
    pub const fn passed(&self) -> bool {
        self.error_count == 0
    }
}

/// Validate a PDF against PDF/A requirements.
///
/// **Note:** This feature requires a commercial license.
///
/// # Errors
///
/// Always returns an error as this feature requires a commercial license.
pub fn validate_pdfa(_pdf_data: &[u8], _level: PdfALevel) -> Result<ValidationResult> {
    Err(PdfAError::RequiresLicense.into())
}

/// Generate XMP metadata for PDF/A identification.
///
/// **Note:** This feature requires a commercial license.
#[must_use]
pub const fn generate_pdfa_xmp(
    _level: PdfALevel,
    _title: Option<&str>,
    _author: Option<&str>,
    _creator: Option<&str>,
) -> String {
    String::new()
}

/// Add PDF/A XMP metadata to a PDF.
///
/// **Note:** This feature requires a commercial license.
///
/// # Errors
///
/// Always returns an error as this feature requires a commercial license.
pub fn add_pdfa_metadata(
    _pdf_data: &[u8],
    _level: PdfALevel,
    _title: Option<&str>,
    _author: Option<&str>,
) -> Result<Vec<u8>> {
    Err(PdfAError::RequiresLicense.into())
}