Skip to content

Валідація PDF/UA

PDF/UA (ISO 14289) визначає вимоги до універсально доступних PDF-документів. PDF Oxide валідує дерева структури, послідовності заголовків, альтернативний текст, заголовки таблиць, оголошення мов та інше.

Підтримувані рівні

Рівень Стандарт Опис
PDF/UA-1 ISO 14289-1:2014 Base accessibility requirements
PDF/UA-2 ISO 14289-2:2024 Enhanced requirements, aligned with 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");
}

API валідатора

Builder PdfUaValidator дозволяє налаштовувати конкретні перевірки:

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

Параметри конфігурації

Метод За замовчуванням Опис
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

Інспекція дерева структури

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

Метод mark_info() повертає:

Поле Тип Опис
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

Коли suspects є true, PDF Oxide автоматично повертається до геометричного упорядкування для вилучення тексту замість покладання на потенційно ненадійне дерево структури.

Що перевіряється

Валідатор охоплює наступні вимоги PDF/UA:

Рівень документа

Перевірка Стаття Опис
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 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

Вміст

Перевірка Стаття Опис
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

Шрифт та текст

Перевірка Стаття Опис
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

Кожна помилка включає необов’язкове вирівнювання WCAG:

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

Поле wcag_ref зіставляє порушення PDF/UA з відповідним критерієм успіху WCAG (e.g., "1.1.1" for non-text content, "1.3.1" for info and relationships).

UaErrorCode Categories

Enum UaErrorCode включає категорії помилок, такі як:

  • 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

Практичний приклад: Звіт про доступність

Згенерувати зрозумілий для людини звіт про доступність з результатів валідації:

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

Наступні кроки