Skip to content

Accesibilidad PDF/UA

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

Cobertura por binding. La validación PDF/UA está expuesta en Python (doc.validate_pdf_ua()), Rust (validate_pdf_ua + el builder PdfUaValidator) y Go (doc.ValidatePdfUa()). WASM expone una comprobación básica de aprobado/reprobado mediante validatePdfUa cuando está disponible. Aún no se ha publicado un wrapper público para C# — usa la CLI de Rust (pdf-oxide validate --pdfua doc.pdf) o realiza la llamada a través de uno de los bindings compatibles.

Niveles compatibles

Nivel Estándar Descripción
PDF/UA-1 ISO 14289-1:2014 Requisitos básicos de accesibilidad
PDF/UA-2 ISO 14289-2:2024 Requisitos ampliados, alineados con la 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");
}
doc, err := pdfoxide.Open("accessible.pdf")
if err != nil { log.Fatal(err) }
defer doc.Close()

valid, errs, err := doc.ValidatePdfUa()
if err != nil { log.Fatal(err) }

if valid {
    fmt.Println("Document is PDF/UA-1 compliant")
} else {
    fmt.Println("Not PDF/UA-1 compliant:")
    for _, e := range errs {
        fmt.Printf("  %s\n", e)
    }
}

API del validador

El builder PdfUaValidator permite configurar comprobaciones 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

Método Predeterminado Descripción
check_heading_sequence(bool) true Valida que H1-H6 no salten niveles
check_color_contrast(bool) true Señala posibles problemas de contraste
allow_custom_types(Vec<String>) [] Permite tipos de estructura no estándar sin advertencia

Inspección del árbol de estructura

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

use pdf_oxide::PdfDocument;

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

// Check if the document claims to be tagged
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() devuelve:

Campo Tipo Descripción
marked bool Si el documento se declara como etiquetado (tagged)
suspects bool Si las asignaciones de etiquetas podrían ser incorrectas
user_properties bool Si hay propiedades de usuario 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, que podría no ser fiable.

Qué se comprueba

El validador cubre los siguientes requisitos de PDF/UA:

Nivel de documento

Comprobación Cláusula Descripción
Idioma 7.2 Entrada /Lang presente en el catálogo
Título 7.1 Título del documento definido y mostrado en la barra de título
Etiquetado 7.1 El diccionario MarkInfo declara Marked = true
Metadatos XMP 7.1 pdfuaid:part declarado en el flujo XMP

Estructura

Comprobación Cláusula Descripción
Árbol de estructura 7.1 Árbol de estructura completo con raíz en StructTreeRoot
Asignación de roles 7.5 Tipos no estándar asignados a elementos de estructura estándar
Jerarquía de encabezados 7.4.2 Los encabezados (H1-H6) no saltan niveles
Marcado de artefactos 7.3 Contenido decorativo marcado como artefacto
Orden de lectura 7.2 El árbol de estructura define un orden de lectura lógico

Contenido

Comprobación Cláusula Descripción
Texto alternativo para imágenes 7.3 /Alt o /ActualText en los elementos Figure
Encabezados de tabla 7.5 Elementos TH presentes en las estructuras de tabla
Etiquetas de formulario 7.6.2 Los campos de formulario tienen etiquetas o sugerencias asociadas
Texto de enlace 7.18 Las anotaciones de enlace tienen contenido descriptivo
Estructura de lista 7.4.3 Las listas usan la estructura L, LI, Lbl, LBody

Fuente y texto

Comprobación Cláusula Descripción
Asignación Unicode 7.21.3 Todo el texto tiene una representación Unicode
Incrustación de fuentes 7.21.4 Fuentes incrustadas o fuentes Base14 estándar
ActualText 7.21.5 Las ligaduras y los glifos especiales tienen /ActualText

UaValidationResult

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

UaComplianceError

Cada error incluye una alineación opcional con la WCAG:

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 asigna la infracción de PDF/UA al criterio de éxito correspondiente de la WCAG (por ejemplo, "1.1.1" para contenido no textual, "1.3.1" para información y relaciones).

Categorías de UaErrorCode

El enum UaErrorCode incluye categorías de error como:

  • MissingLanguage – no hay entrada /Lang en el catálogo del documento
  • MissingStructureTree – el documento no está etiquetado
  • MissingAltText – el elemento Figure carece de texto alternativo
  • HeadingSkipped – los niveles de encabezado saltan (por ejemplo, de H1 a H3)
  • MissingTableHeaders – la tabla carece de elementos TH
  • FormFieldNoLabel – el campo de formulario no tiene etiqueta asociada
  • InvalidRoleMapping – tipo no estándar sin asignar a un elemento estándar
  • ArtifactNotMarked – contenido decorativo no marcado como artefacto
  • MissingUnicode – texto sin asignación Unicode

Ejemplo práctico: informe de accesibilidad

Genera un informe de accesibilidad legible por humanos a partir de los resultados de la 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