Skip to content

PDF/A Validation

PDF/A (ISO 19005) is the international standard for long-term archival of electronic documents. PDF Oxide validates all major PDF/A levels and can convert non-compliant documents toward compliance.

Binding coverage. PDF/A validation is available in Python (doc.validate_pdf_a(level)), Rust (validate_pdf_a + PdfAValidator builder), WASM (doc.validatePdfA(level)), and Go (doc.ValidatePdfA(level)). A public C# wrapper is not yet exposed — use the Rust CLI (pdf-oxide validate --pdfa 2b doc.pdf) or invoke through one of the supported bindings. PDF/A conversion (PdfAConverter) is currently exposed in Rust and Python only.

Supported Levels

Level Standard Structure Unicode Transparency Embedded Files
1a ISO 19005-1 Required Required No No
1b ISO 19005-1 No No No No
2a ISO 19005-2 Required Required Yes No
2b ISO 19005-2 No No Yes No
2u ISO 19005-2 No Required Yes No
3a ISO 19005-3 Required Required Yes Yes
3b ISO 19005-3 No No Yes Yes
3u ISO 19005-3 No Required Yes Yes

Level “a” (accessible) requires a tagged structure tree and Unicode character mapping. Level “b” (basic) requires only visual reproducibility. Level “u” (Unicode) requires Unicode text mapping without the full structure tree.

Quick Validation

Use the convenience function for a one-call check:

from pdf_oxide import PdfDocument

doc = PdfDocument("document.pdf")
result = doc.validate_pdf_a("2b")
print(f"Valid: {result.valid}")
print(f"Level: {result.level}")
for error in result.errors:
    print(f"  Error: {error}")
const doc = new WasmPdfDocument(bytes);
const result = doc.validatePdfA("2b");
console.log(`Valid: ${result.valid}`);
console.log(`Errors: ${result.errors.length}`);
doc.free();
use pdf_oxide::PdfDocument;
use pdf_oxide::compliance::{validate_pdf_a, PdfALevel};

let mut doc = PdfDocument::open("archive.pdf")?;
let result = validate_pdf_a(&mut doc, PdfALevel::A1b)?;

if result.has_errors() {
    println!("Not PDF/A-1b compliant:");
    for error in &result.errors {
        println!("  [{}] {} (clause {})",
            error.code, error.message,
            error.clause.as_deref().unwrap_or("n/a"));
    }
} else {
    println!("Document is PDF/A-1b compliant");
}
package main

import (
    "fmt"
    "log"
    pdfoxide "github.com/yfedoseev/pdf_oxide/go"
)

func main() {
    doc, err := pdfoxide.Open("archive.pdf")
    if err != nil { log.Fatal(err) }
    defer doc.Close()

    // Level encoding: 0 = 1b, 1 = 1a, 2 = 2b, 3 = 2a, 4 = 2u, 5 = 3b, 6 = 3a, 7 = 3u
    result, err := doc.ValidatePdfA(0) // PDF/A-1b
    if err != nil { log.Fatal(err) }

    if result.Valid {
        fmt.Println("Document is PDF/A-1b compliant")
    } else {
        fmt.Println("Not PDF/A-1b compliant:")
        for _, e := range result.Errors {
            fmt.Printf("  %s\n", e)
        }
    }
}

Validator API

The PdfAValidator provides a builder pattern for fine-grained control:

use pdf_oxide::PdfDocument;
use pdf_oxide::compliance::{PdfAValidator, PdfALevel};

let mut doc = PdfDocument::open("report.pdf")?;

let result = PdfAValidator::new()
    .stop_on_first_error(false)
    .include_warnings(true)
    .validate(&mut doc, PdfALevel::A2b)?;

println!("Errors: {}", result.errors.len());
println!("Warnings: {}", result.warnings.len());

Targeted Checks

Run individual validation categories instead of the full suite:

use pdf_oxide::PdfDocument;
use pdf_oxide::compliance::{PdfAValidator, PdfALevel};

let mut doc = PdfDocument::open("report.pdf")?;
let validator = PdfAValidator::new();

// Check only metadata
let result = validator.check_metadata(&mut doc, PdfALevel::A1b)?;

// Check only fonts
let result = validator.check_fonts(&mut doc, PdfALevel::A1b)?;

// Check only color spaces
let result = validator.check_colors(&mut doc, PdfALevel::A1b)?;

// Check only transparency
let result = validator.check_transparency(&mut doc, PdfALevel::A2b)?;

// Check only structure tags
let result = validator.check_structure(&mut doc, PdfALevel::A1a)?;

Standalone Validators

Each validation category is also available as a standalone function for maximum flexibility:

use pdf_oxide::PdfDocument;
use pdf_oxide::compliance::validators::*;
use pdf_oxide::compliance::{PdfALevel, ValidationResult};

let mut doc = PdfDocument::open("document.pdf")?;
let mut result = ValidationResult::new(PdfALevel::A1b);

// Run each validator independently
validate_xmp_metadata(&mut doc, PdfALevel::A1b, &mut result)?;
validate_fonts(&mut doc, PdfALevel::A1b, &mut result)?;
validate_colors(&mut doc, PdfALevel::A1b, &mut result)?;
validate_encryption(&mut doc, PdfALevel::A1b, &mut result)?;
validate_transparency(&mut doc, PdfALevel::A1b, &mut result)?;
validate_structure(&mut doc, PdfALevel::A1b, &mut result)?;
validate_javascript(&mut doc, PdfALevel::A1b, &mut result)?;
validate_embedded_files(&mut doc, PdfALevel::A1b, &mut result)?;
validate_annotations(&mut doc, PdfALevel::A1b, &mut result)?;

println!("Total errors: {}", result.errors.len());

Validator Summary

Function What It Checks
validate_xmp_metadata() XMP stream exists, pdfaid:part and pdfaid:conformance entries present, metadata consistency
validate_fonts() All fonts embedded, glyph widths present, Unicode mapping available (for level “a” and “u”)
validate_colors() No device-dependent color operators (rg, RG, k, K, g, G) without output intent
validate_encryption() No encryption permitted in PDF/A documents
validate_transparency() No transparency in PDF/A-1; allowed in PDF/A-2 and later
validate_structure() Tagged structure tree present with valid role mapping (required for level “a”)
validate_javascript() No JavaScript actions or triggers present
validate_embedded_files() Not allowed in PDF/A-1 or PDF/A-2; PDF/A-3 requires AFRelationship key on each file spec
validate_annotations() Annotation types restricted per the relevant ISO 19005 part

ValidationResult

The ValidationResult struct contains the full outcome of a validation run:

pub struct ValidationResult {
    pub level: PdfALevel,
    pub errors: Vec<ComplianceError>,
    pub warnings: Vec<ComplianceWarning>,
    pub stats: ValidationStats,
}
Field Type Description
level PdfALevel The target compliance level
errors Vec<ComplianceError> Blocking violations that prevent compliance
warnings Vec<ComplianceWarning> Non-blocking issues that may affect quality
stats ValidationStats Counts of pages, fonts, and objects checked

ComplianceError

pub struct ComplianceError {
    pub code: ErrorCode,
    pub message: String,
    pub location: Option<String>,
    pub clause: Option<String>,
}

The code field uses the ErrorCode enum with categories like MissingXmpMetadata, FontNotEmbedded, DeviceDependentColor, EncryptionPresent, TransparencyNotAllowed, MissingStructureTree, JavaScriptPresent, and InvalidEmbeddedFile.

ComplianceWarning

pub struct ComplianceWarning {
    pub code: WarningCode,
    pub message: String,
    pub location: Option<String>,
}

PDF/A Conversion

Convert a non-compliant document toward PDF/A compliance:

use pdf_oxide::PdfDocument;
use pdf_oxide::compliance::{convert_to_pdf_a, PdfALevel};

let mut doc = PdfDocument::open("input.pdf")?;
let result = convert_to_pdf_a(&mut doc, PdfALevel::A1b)?;

println!("Conversion actions taken:");
for action in &result.actions {
    println!("  - {}: {}", action.action_type, action.description);
}

if result.remaining_errors.is_empty() {
    println!("Document is now PDF/A-1b compliant");
} else {
    println!("{} issues could not be resolved automatically",
        result.remaining_errors.len());
}

Conversion Config

Fine-tune the conversion process:

use pdf_oxide::PdfDocument;
use pdf_oxide::compliance::{PdfAConverter, PdfALevel, ConversionConfig};

let mut doc = PdfDocument::open("input.pdf")?;

let config = ConversionConfig::new()
    .embed_fonts(true)
    .remove_javascript(true)
    .flatten_transparency(true)
    .add_structure(true);

let result = PdfAConverter::new(PdfALevel::A2b)
    .with_config(config)
    .convert(&mut doc)?;

The converter performs these actions automatically:

  1. XMP metadata injection – adds pdfaid:part and pdfaid:conformance entries
  2. Font embedding – embeds any referenced but non-embedded fonts
  3. JavaScript removal – strips JavaScript actions and triggers
  4. Transparency flattening – renders transparent elements to opaque (PDF/A-1 only)
  5. ICC profile conversion – converts device-dependent colors to ICC-based color spaces
  6. Structure tagging – adds basic structure tags (for level “a” targets)

Workflow: Validate, Fix, Re-validate

A typical archival workflow validates, attempts automatic conversion, then re-validates:

use pdf_oxide::PdfDocument;
use pdf_oxide::compliance::{validate_pdf_a, convert_to_pdf_a, PdfALevel};

let level = PdfALevel::A2b;
let mut doc = PdfDocument::open("input.pdf")?;

// Step 1: Initial validation
let result = validate_pdf_a(&mut doc, level)?;
if !result.has_errors() {
    println!("Already compliant");
    return Ok(());
}

println!("{} errors found, attempting conversion...", result.errors.len());

// Step 2: Automatic conversion
let conversion = convert_to_pdf_a(&mut doc, level)?;
println!("{} actions taken", conversion.actions.len());

// Step 3: Re-validate
let result = validate_pdf_a(&mut doc, level)?;
if result.has_errors() {
    println!("{} errors remain after conversion:", result.errors.len());
    for e in &result.errors {
        println!("  {} -- {}", e.code, e.message);
    }
} else {
    println!("Document is now PDF/A-2b compliant");
}

PdfALevel Methods

The PdfALevel enum includes helper methods for querying level capabilities:

Method Return Description
part() PdfAPart ISO 19005 part (Part1, Part2, Part3)
conformance() char Conformance letter (‘a’, ‘b’, or ‘u’)
requires_structure() bool Whether tagged structure tree is mandatory
requires_unicode() bool Whether Unicode mapping is mandatory
allows_transparency() bool Whether transparency is permitted
allows_jpeg2000() bool Whether JPEG 2000 images are permitted
allows_embedded_files() bool Whether file attachments are permitted
xmp_part() &str XMP pdfaid:part value
xmp_conformance() &str XMP pdfaid:conformance value
from_xmp(part, conformance) Option<Self> Parse level from XMP metadata values

Next Steps