Org mode, LaTeX, and tagged PDFs: producing accessible documents
Many of us in higher education use Emacs to produce course materials, and these must conform to the accessibility requirements of the Americans with Disabilities Act. We often export Org mode documents to LaTeX to produce very nice PDF output. But until very recently, LaTeX could not produce tagged PDFs that conform to the PDF/UA accessibility standard. Until how recently? Only starting with TeX Live 2025.
In short, creating accessible PDFs from Org mode means having to install this new version of TeX Live (the massive, annually-updated software package of all things TeX and LaTeX).
I am still on Debian 12, whose stable repositories provide TeX Live 2022.
Even the most recent Ubuntu repositories only appear to package TeX Live 2024.
The advice from the TeX Live maintainers is to remove the .deb installation and install TeX Live 2025 fresh, as below.
The steps presented here might work also for Ubuntu and later versions of Debian, but that’s only a wildly hopeful speculation that I cannot test.
Installing TeX Live 2025 on Debian 12
Set aside ninety minutes for this installation!
I successfully installed TeX Live 2025 on multiple machines running Debian 12 with the following steps. Note that I closely followed the instructions from the Debian wiki and the TeX Live website on TUG. I have added some clarifying remarks that might be helpful.
Uninstall the old .deb version of TeX Live
sudo apt autopurge texlive*
Just uninstalling TeXLive takes a couple minutes. If you don’t remember when you installed these packages, wait until you see how many dependencies they have. TeX Live is the biggest package you’re likely to find installed on any Linux system. (No, I probably don’t need every module, but it has always been much easier to install the whole monolith than to deal with missing dependencies.)
Install TeX Live 2025 from CTAN
cd /tmp wget https://mirror.ctan.org/systems/texlive/tlnet/install-tl-unx.tar.gz tar xvf install-tl-unx.tar.gz cd install-tl-2* sudo perl ./install-tl --no-interaction
The instructions have a cute little note # may take several hours to run which prompted my LOL post to Mastodon. (Again, I choose to install everything here, 4,958 TeX Live packages. In fact the instructions presume a full install.)
It took about an hour on my newish desktop with Ethernet, almost two hours on my ten-year-old laptop over Wi-Fi.
Add the binary directory to your PATH
It will be of the form /usr/local/texlive/2025/bin/PLATFORM, likely /usr/local/texlive/2025/bin/x86_64-linux.
Be certain that root can run the TeX Live binaries too!
The way I did so was to edit /etc/profile, find the appropriate lines setting the path, and edit them to add that directory. The result looks like so:
if [ "$(id -u)" -eq 0 ]; then PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/texlive/2025/bin/x86_64-linux" else PATH="/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games:/usr/local/texlive/2025/bin/x86_64-linux" fi export PATH
Install a few minimal base packages via apt
sudo apt install tex-common texinfo lmodern
Configure a “dummy package” and install via apt
This part is opaque to me. It appears we are using something called equivs to wrap our raw-dog TeX Live install into a .deb in order to trick the package manager into playing nice with it, but I am innocent of the mechanism here.
sudo apt install equivs
mkdir /tmp/tl-equivs && cd /tmp/tl-equivs
equivs-control texlive-local
We are now in a temporary directory that contains a configuration file for our dummy package called texlive-local.
Replace the contents of that file with the following:
Section: misc Priority: optional Standards-Version: 4.1.4 Package: texlive-local Version: 2025.99999999-1 Maintainer: you <you@yourdomain.example.org> Provides: asymptote, chktex, cm-super, cm-super-minimal, context, dvidvi, dvipng, dvisvgm, feynmf, fragmaster, jadetex, lacheck, latex-cjk-all, latex-cjk-chinese, latex-cjk-chinese-arphic-bkai00mp, latex-cjk-chinese-arphic-bsmi00lp, latex-cjk-chinese-arphic-gbsn00lp, latex-cjk-chinese-arphic-gkai00mp, latex-cjk-common, latex-cjk-japanese, latex-cjk-japanese-wadalab, latex-cjk-korean, latex-cjk-thai, latexdiff, latexmk, latex-sanskrit, lcdf-typetools, lmodern, luatex, musixtex, preview-latex-style, ps2eps, psutils, purifyeps, t1utils, tex4ht, tex4ht-common, tex-gyre, texinfo, texlive, texlive-base, texlive-bibtex-extra, texlive-binaries, texlive-common, texlive-extra-utils, texlive-fonts-extra, texlive-fonts-extra-doc, texlive-fonts-recommended, texlive-fonts-recommended-doc, texlive-font-utils, texlive-formats-extra, texlive-games, texlive-humanities, texlive-humanities-doc, texlive-lang-all, texlive-lang-arabic, texlive-lang-cjk, texlive-lang-cyrillic, texlive-lang-czechslovak, texlive-lang-english, texlive-lang-european, texlive-lang-japanese, texlive-lang-chinese, texlive-lang-korean, texlive-lang-french, texlive-lang-german, texlive-lang-greek, texlive-lang-italian, texlive-lang-other, texlive-lang-polish, texlive-lang-portuguese, texlive-lang-spanish, texlive-latex-base, texlive-latex-base-doc, texlive-latex-extra, texlive-latex-extra-doc, texlive-latex-recommended, texlive-latex-recommended-doc, texlive-luatex, texlive-math-extra, texlive-metapost, texlive-metapost-doc, texlive-music, texlive-pictures, texlive-pictures-doc, texlive-plain-generic, texlive-pstricks, texlive-pstricks-doc, texlive-publishers, texlive-publishers-doc, texlive-science, texlive-science-doc, texlive-xetex, thailatex, tipa, tipa-doc, xindy, xindy-rules Depends: Architecture: all Description: My local installation of TeX Live 2025. A full "vanilla" TeX Live 2025 http://tug.org/texlive/debian#vanilla
And finally:
equivs-build texlive-local sudo dpkg -i texlive-local_2025.99999999-1_all.deb
Producing a compliant PDF
Reality check: let’s make sure we can in fact use our new TeX Live install to produce a minimal accessible PDF. Note that the polyglossia package is a replacement for the babel package that adds accessibility features. But the \DocumentMetadata{} command does the heavy lifting for producing tagged PDFs. It must come before \documentclass{}.
\DocumentMetadata{ lang = en, pdfstandard = ua-2, pdfstandard = a-4f, tagging = on } \documentclass{article} \usepackage{polyglossia} \setdefaultlanguage[variant=US]{english} \begin{document} \section{Lorem ipsum} I know I promised US English but here's some nonsense not-quite-Latin. Est eveniet accusamus dolor et. Possimus fugit consectetur alias iure suscipit facere est exercitationem. Sed enim sapiente atque. Voluptas et tempora est. Recusandae velit qui nesciunt. Molestiae excepturi occaecati doloribus. Eum sunt optio aut consequatur doloremque. Quo eveniet rerum aut dicta impedit quia ut autem. Dolor nisi qui architecto sunt. Corporis quidem aut natus est quidem pariatur. Error aut repellat nobis velit corporis voluptatem. Libero hic nesciunt omnis ut quam minus soluta. Ad quo culpa facere pariatur voluptas et quis nostrum. Pariatur tempore ipsum voluptatibus iusto repudiandae. Earum ea quo saepe autem et. Eum ratione eaque non dolorum ut fugit vitae dolorum. \end{document}
Save this file as minimal.tex and run
lualatex minimal.tex
(On its first run, it will take a minute to build a font names database.)
If it doesn’t work, well, I guess you have some debugging to do? Note that the LuaLaTeX engine is required for all the accessibilty new hotness. (TIL it has been the recommended engine for a year.)
Configuring Org LaTeX export to produce compliant PDFs
Note that I am stealing everything here from Kenny Ballou’s comprehensive and excellent post to which I have little to add.
Add these expressions to your Emacs configuration.
Set the default engine to lualatex but allow individual files to override it (in case you have some weird old files, I suppose).
(setq org-latex-compiler "lualatex") ;;; %latex gets replaced with org-latex-compiler ;;; OR overridden by the #+LATEX_COMPILER header (setq org-latex-pdf-process '("latexmk -f -pdf -%latex -interaction=nonstopmode -shell-escape -output-directory=%o %f"))
Insert the \DocumentMetadata{} command before \documentclass{} for the article document type. Note that as written, only the article documentclass is defined for export. You might already have a org-latex-classes declaration in your config, in which case you should modify it to replace the article class definition with this one.
(defvar org-latex-metadata "\\DocumentMetadata{lang = en, pdfversion = 2.0, pdfstandard = ua-2, pdfstandard = a-4}" "LaTeX preamble command to specify PDF accessibility metadata.\nIt must appear before the \\documentclass{} declaration.") (setq org-latex-classes `( ("article" ,(concat org-latex-metadata "\n" "\\documentclass[11pt]{article}") ("\\section{%s}" . "\\section*{%s}") ("\\subsection{%s}" . "\\subsection*{%s}") ("\\subsubsection{%s}" . "\\subsubsection*{%s}") ("\\paragraph{%s}" . "\\paragraph*{%s}") ("\\subparagraph{%s}" . "\\subparagraph*{%s}")) ))
I have not yet attempted to make the corresponding definition for Beamer. When I get it to work I will update this post.
(Note finally that the testphase key in the \DocumentMetadata{} command has been deprecated, so I’ve removed it.)
Here is a minimal Org document to test export:
#+title: A nice title for a PDF
#+subtitle: a test of tagged PDF accessibility
#+latex_header: \usepackage{polyglossia}
#+latex_header: \setdefaultlanguage[variant=US]{english}
Est eveniet accusamus dolor et. Possimus fugit consectetur alias
iure suscipit facere est exercitationem. Sed enim sapiente atque.
* Heading 1
** Subhead 1.1
Voluptas et tempora est. Recusandae velit qui nesciunt. Molestiae
excepturi occaecati doloribus.
** Subhead 1.2
Eum sunt optio aut consequatur doloremque. Quo eveniet rerum aut
dicta impedit quia ut autem. Dolor nisi qui architecto sunt.
* Heading 2
** Subhead 2.1
Corporis quidem aut natus est quidem pariatur. Error aut repellat
nobis velit corporis voluptatem. Libero hic nesciunt omnis ut quam
minus soluta. Ad quo culpa facere pariatur voluptas et quis nostrum.
** Subhead 2.2
Pariatur tempore ipsum voluptatibus iusto repudiandae. Earum ea quo
saepe autem et. Eum ratione eaque non dolorum ut fugit vitae
dolorum.
Dealing with remaining issues
It’s important to remember that the accessibility functionality in LaTeX is still new and incomplete. But it’s a big step from zero to very good.
For me and everyone who posts our course materials to Canvas, the touchstone is the “Ally Accessibility Checker,” which is also not perfect. For instance, it insists that PDFs produced by LaTeX are missing a title when they are not. If I can get to the bottom of that bug, I’ll update this post.
You can respond on the original Mastodon thread.