Skip to content

Validación PDF/UA

PDF/UA (ISO 14289) define requisitos para documentos PDF universalmente accesibles. PDF Oxide valida árboles de estructura, secuencias de encabezados, texto alternativo, encabezados de tablas, declaraciones de idioma y más.

Niveles soportados

Level Standard Descripción
PDF/UA-1 ISO 14289-1:2014 Requisitos base de accesibilidad
PDF/UA-2 ISO 14289-2:2024 Requisitos mejorados, alineados con WCAG 2.1

Validación rápida

from pdf_oxide import PdfDocument

doc = PdfDocument("document.pdf")
result = doc.validate_pdf_ua()
print(f"Valid: {result.valid}")
for error in result.errors:
    print(f"  {error}")
use pdf_oxide::PdfDocument;
use pdf_oxide::compliance::{validate_pdf_ua, PdfUaLevel};

let mut doc = PdfDocument::open("accessible.pdf")?;
let result = validate_pdf_ua(&mut doc, PdfUaLevel::UA1)?;

if result.has_errors() {
    println!("Not PDF/UA-1 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/UA-1 compliant");
}

API del validador

El builder PdfUaValidator permite configurar verificaciones específicas:

use pdf_oxide::PdfDocument;
use pdf_oxide::compliance::{PdfUaValidator, PdfUaLevel};

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

let result = PdfUaValidator::new()
    .check_heading_sequence(true)
    .check_color_contrast(true)
    .allow_custom_types(vec!["Caption".into(), "Aside".into()])
    .validate(&mut doc, PdfUaLevel::UA1)?;

println!("Errors: {}", result.errors.len());
println!("Warnings: {}", result.warnings.len());
println!("Structure elements checked: {}",
    result.stats.structure_elements_checked);

Opciones de configuración

Method Predeterminado Descripción
check_heading_sequence(bool) true Validar que H1-H6 no salten niveles
check_color_contrast(bool) true Marcar posibles problemas de contraste
allow_custom_types(Vec<String>) [] Permitir tipos de estructura no estándar sin advertencia

Inspección del árbol de estructura

Antes de ejecutar la validación, puede inspeccionar el árbol de estructura del documento y la información de marcado:

use pdf_oxide::PdfDocument;

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

// Verificar si el documento se declara como etiquetado
let mark_info = doc.mark_info()?;
println!("Marked: {}", mark_info.marked);
println!("Suspects: {}", mark_info.suspects);

// Access the structure tree
if let Some(tree) = doc.structure_tree()? {
    println!("Root tag: {}", tree.root_type);
    println!("Children: {}", tree.children.len());
}

El método mark_info() retorna:

Campo Tipo Descripción
marked bool Si el documento se declara como etiquetado
suspects bool Si las asignaciones de etiquetas pueden ser incorrectas
user_properties bool Si las propiedades de usuario están presentes

Cuando suspects es true, PDF Oxide recurre automáticamente al ordenamiento geométrico para la extracción de texto en lugar de depender del árbol de estructura potencialmente no confiable.

Qué se verifica

El validador cubre los siguientes requisitos de PDF/UA:

A nivel de documento

Check Clause Descripción
Language 7.2 /Lang entry present in the catalog
Title 7.1 Título del documento set and displayed in title bar
Tagged 7.1 MarkInfo dictionary declares Marked = true
XMP metadata 7.1 pdfuaid:part declared in XMP stream

Estructura

Check Clause Descripción
Structure tree 7.1 Complete structure tree rooted at StructTreeRoot
Role mapping 7.5 Non-standard types mapped to standard structure elements
Heading hierarchy 7.4.2 Headings (H1-H6) do not skip levels
Artifact marking 7.3 Decorative content marked as artifact
Reading order 7.2 Structure tree defines a logical reading order

Contenido

Check Clause Descripción
Alt text for images 7.3 /Alt or /ActualText on Figure elements
Table headers 7.5 TH elements present in table structures
Form labels 7.6.2 Form fields have associated labels or tooltips
Link text 7.18 Link annotations have descriptive content
List structure 7.4.3 Lists use L, LI, Lbl, LBody structure

Fuente y texto

Check Clause Descripción
Unicode mapping 7.21.3 All text has a Unicode representation
Font embedding 7.21.4 Fonts embedded or standard Base14 fonts
ActualText 7.21.5 Ligatures and special glyphs have /ActualText

UaValidationResult

pub struct UaValidationResult {
    pub level: PdfUaLevel,
    pub errors: Vec<UaComplianceError>,
    pub warnings: Vec<ComplianceWarning>,
    pub stats: UaValidationStats,
}

UaComplianceError

Cada error incluye alineación WCAG opcional:

pub struct UaComplianceError {
    pub code: UaErrorCode,
    pub message: String,
    pub location: Option<String>,
    pub wcag_ref: Option<String>,
    pub clause: Option<String>,
}

El campo wcag_ref mapea la violación de PDF/UA al criterio de éxito WCAG correspondiente (e.g., "1.1.1" for non-text content, "1.3.1" for info and relationships).

Categorías de UaErrorCode

El enum UaErrorCode incluye categorías de error como:

  • MissingLanguage – no /Lang entry on the document catalog
  • MissingStructureTree – document is not tagged
  • MissingAltText – Figure element lacks alt text
  • HeadingSkipped – heading levels jump (e.g., H1 to H3)
  • MissingTableHeaders – table lacks TH elements
  • FormFieldNoLabel – form field has no associated label
  • InvalidRoleMapping – non-standard type not mapped to a standard element
  • ArtifactNotMarked – decorative content not marked as artifact
  • MissingUnicode – text without Unicode mapping

Ejemplo práctico: reporte de accesibilidad

Genere un reporte de accesibilidad legible por humanos a partir de los resultados de validación:

use pdf_oxide::PdfDocument;
use pdf_oxide::compliance::{validate_pdf_ua, PdfUaLevel};

let mut doc = PdfDocument::open("document.pdf")?;
let result = validate_pdf_ua(&mut doc, PdfUaLevel::UA1)?;

println!("=== PDF/UA Accessibility Report ===");
println!("Level: PDF/UA-{}", result.level.xmp_part());
println!("Status: {}", if result.has_errors() { "FAIL" } else { "PASS" });
println!();

if result.has_errors() {
    println!("Errors ({}):", result.errors.len());
    for (i, error) in result.errors.iter().enumerate() {
        print!("  {}. [{}] {}", i + 1, error.code, error.message);
        if let Some(ref wcag) = error.wcag_ref {
            print!("  (WCAG {})", wcag);
        }
        println!();
    }
}

if result.has_warnings() {
    println!("\nWarnings ({}):", result.warnings.len());
    for warning in &result.warnings {
        println!("  - [{}] {}", warning.code, warning.message);
    }
}

println!("\nStats:");
println!("  Structure elements checked: {}",
    result.stats.structure_elements_checked);

Próximos pasos