Review PDFs for accessibility on upload
Prevention, not just remediation
Thousands of documents, zero visibility, and a deadline
Great at
Not built for
Scan the structure of a PDF at upload, tell the editor what is wrong, and let them fix the source before it ever publishes.
Heavily inspired by Editoria11y: immediate feedback, right where editors work.
Built with a client who wanted prevention and education, not another subscription.
smalot/pdfparser (LGPL 3.0) as a Composer dependency
drush pa:check --bundle=documentRead the structure, look for known bad patterns, report back in seconds
Typical scan time is seconds, depending on file size. Results are stored per file and per media item.
document_language
Screen readers use the language to pick pronunciation rules.
tagged_pdf
Tags carry reading order, headings, lists. No tags, no navigation.
document_title
Announced when the file opens; shown in tabs and bookmarks.
heading_structure
Structure tree exists, starts at H1, and never skips a level.
pdf_version
Configurable minimum (default 1.4). Older versions cannot be tagged.
document_title_filename
"Report.docx" is a leftover, not a title. Can be disabled for forms.
Each check is a plugin. Add your own with an attribute and one method.
#[AccessibilityCheck(
id: 'figure_alt_text',
label: new TranslatableMarkup('Figures have alt text'),
)]
final class FigureAltTextCheck extends AccessibilityCheckBase {
public function check(Document $document): CheckResult {
// Walk the structure tree for /Figure nodes without /Alt.
}
}
Views field and filter plugins ship with the module, so any report is just another view.
What a mechanical scan can and cannot tell you
A PDF can contain links, and you can link to a PDF. It is still not HTML and should never be treated as a page.
Pair the module with editor training. The checks catch what can be caught mechanically.
Some PDFs cannot or should not be parsed. Say so, and move on.
Rolling this out on real libraries is what produced this list.
Existing libraries grow before the module is installed
/admin/reports/pdf-accessibility with pass, fail, and error countsQueue mode for unattended backfills. Each file runs in its own worker, memory is reclaimed between items.
drush pa:check --bundle=document --missing-only --queue
drush queue:run pdfa11y_check
Chunked sync runs when you are watching and SSH sessions have a time cap.
drush pa:check --bundle=document --missing-only --limit=1500 --format=summary
/admin/config/media/pdf-accessibility
Questions?
Josh Mitchell on LinkedIn
Drupal Slack: @joshuami
Slides: joshuami.com/presentations/pdfa11y