Skip to content

Доступность PDF/UA

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

Поддерживаемые уровни

Уровень Стандарт Описание
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");
}

API валидатора

Конструктор 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);

Параметры настройки

Method Default Описание
check_heading_sequence(bool) true Валидация: H1-H6 не пропускают уровни
check_color_contrast(bool) true Отмечать потенциальные проблемы контрастности
allow_custom_types(Vec<String>) [] Разрешить нестандартные типы структуры без предупреждения

Инспекция дерева структуры

Перед запуском валидации можно проверить дерево структуры документа и информацию о пометках:

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 Объявляет ли документ себя тегированным
suspects bool Могут ли назначения тегов быть некорректными
user_properties bool Присутствуют ли пользовательские свойства

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

Что проверяется

Валидатор проверяет следующие требования PDF/UA:

Уровень документа

Проверка Пункт Описание
Язык 7.2 Запись /Lang присутствует в каталоге
Заголовок 7.1 Заголовок документа задан и отображается в строке заголовка
Тегирование 7.1 Словарь MarkInfo объявляет Marked = true
Метаданные XMP 7.1 pdfuaid:part объявлен в потоке XMP

Структура

Проверка Пункт Описание
Дерево структуры 7.1 Полное дерево структуры с корнем в StructTreeRoot
Сопоставление ролей 7.5 Нестандартные типы сопоставлены со стандартными элементами структуры
Иерархия заголовков 7.4.2 Заголовки (H1-H6) не пропускают уровни
Пометка артефактов 7.3 Декоративное содержимое помечено как артефакт
Порядок чтения 7.2 Дерево структуры определяет логический порядок чтения

Содержимое

Проверка Пункт Описание
Альтернативный текст для изображений 7.3 /Alt или /ActualText на элементах Figure
Заголовки таблиц 7.5 Элементы TH присутствуют в структурах таблиц
Метки форм 7.6.2 Поля формы имеют связанные метки или подсказки
Текст ссылок 7.18 Аннотации-ссылки имеют описательное содержимое
Структура списков 7.4.3 Списки используют структуру L, LI, Lbl, LBody

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

Проверка Пункт Описание
Отображение Unicode 7.21.3 Весь текст имеет представление в Unicode
Встраивание шрифтов 7.21.4 Шрифты встроены или являются стандартными Base14
ActualText 7.21.5 Лигатуры и специальные глифы имеют /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 (напр., "1.1.1" для нетекстового содержимого, "1.3.1" для информации и взаимосвязей).

UaErrorCode Categories

Перечисление UaErrorCode включает категории ошибок, такие как:

  • MissingLanguage — отсутствует запись /Lang в каталоге документа
  • MissingStructureTree — документ не тегирован
  • MissingAltText — элемент Figure не имеет альтернативного текста
  • HeadingSkipped — уровни заголовков пропускаются (напр., H1 к H3)
  • MissingTableHeaders — таблица не содержит элементов TH
  • FormFieldNoLabel — поле формы не имеет связанной метки
  • InvalidRoleMapping — нестандартный тип не сопоставлен со стандартным элементом
  • ArtifactNotMarked — декоративное содержимое не помечено как артефакт
  • MissingUnicode — текст без отображения Unicode

Практический пример: отчёт о доступности

Создание человекочитаемого отчёта о доступности из результатов валидации:

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

Следующие шаги