Skip to content

PDF/UA Accessibility

PDF/UA(ISO 14289)는 보편적으로 접근 가능한 PDF 문서에 대한 요구 사항을 정의합니다. PDF Oxide는 구조 트리, 제목 시퀀스, 대체 텍스트, 표 헤더, 언어 선언 등을 검증합니다.

지원 레벨

Level Standard 설명
PDF/UA-1 ISO 14289-1:2014 기본 접근성 요구 사항
PDF/UA-2 ISO 14289-2:2024 WCAG 2.1에 맞춘 향상된 요구 사항

빠른 검증

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");
}

Validator API

The PdfUaValidator builder allows configuring specific checks:

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);

Configuration Options

메서드 기본값 설명
check_heading_sequence(bool) true Validate H1-H6 do not skip levels
check_color_contrast(bool) true Flag potential contrast issues
allow_custom_types(Vec<String>) [] Permit non-standard structure types without warning

Structure Tree Inspection

Before running validation, you can inspect the document’s structure tree and mark info:

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());
}

The mark_info() method returns:

Field 타입 설명
marked bool Whether the document declares itself as tagged
suspects bool Whether tag assignments may be incorrect
user_properties bool Whether user properties are present

When suspects is true, PDF Oxide automatically falls back to geometric ordering for text extraction instead of relying on the potentially unreliable structure tree.

What Gets Checked

The validator covers the following PDF/UA requirements:

Document-Level

Check Clause 설명
Language 7.2 /Lang entry present in the catalog
Title 7.1 문서 제목 set and displayed in title bar
Tagged 7.1 MarkInfo dictionary declares Marked = true
XMP metadata 7.1 pdfuaid:part declared in XMP stream

Structure

Check Clause 설명
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

Content

Check Clause 설명
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

글꼴 및 텍스트

Check Clause 설명
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

Each error includes optional WCAG alignment:

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

The wcag_ref field maps the PDF/UA violation to the corresponding WCAG success criterion (e.g., "1.1.1" for non-text content, "1.3.1" for info and relationships).

UaErrorCode Categories

The UaErrorCode enum includes error categories such as:

  • 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

Practical Example: Accessibility Report

Generate a human-readable accessibility report from validation results:

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);

다음 단계