%<*driver> \def\nameofplainTeX{plain} \ifx\fmtname\nameofplainTeX \input l3docstrip.tex \keepsilent \askforoverwritefalse \preamble ======================================================================== Package tagbridge - Tagging-compatibility hooks for packages without native PDF/UA support. (C) 2026 Matthias Werner E-mail: matthias.werner@informatik.tu-chemnitz.de Released under the LaTeX Project Public License v1.3c or later See http://www.latex-project.org/lppl.txt ======================================================================== \endpreamble \postamble This work is "maintained" (as per LPPL maintenance status) by Matthias Werner. \endpostamble \generate{\file{tagbridge.sty}{\from{tagbridge.dtx}{package}}} \expandafter\endbatchfile \fi \iffalse % %<*package> \NeedsTeXFormat{LaTeX2e}[2022-06-01] \ProvidesExplPackage{tagbridge}{2026-09-14}{v0.1.0} {Tagging-compatibility hooks for packages without native PDF/UA support} % %<*driver> \fi \documentclass[add-bib=false]{osgdoc} \OnlyDescription \usepackage{enumitem} \usepackage{microtype} \usepackage[ languages={de,en}, map={de=german,en=english}, targetlang={job=2}, load babel ]{langselect} \newpackagename{\tagbridgepkg}{tagbridge} \makeindex \setcnltx{ package = tagbridge, version = \UseVersionOf{tagbridge.sty}, date = \UseDateOf{tagbridge.sty}, title = {\ldeen{Das \tagbridgepkg\ Paket}{The \tagbridgepkg\ Package}}, info = {\ldeen{Tagging-Kompatibilitätshooks für Fremdpakete ohne eigene PDF/UA-Unterstützung}{Tagging-compatibility hooks for third-party packages without native PDF/UA support}}, authors = {Matthias Werner[matthias.werner@informatik.tu-chemnitz.de]}, abstract={ \ldeen*{ \tagbridgepkg\ sammelt an einem Ort, was einzelne Fremdpakete brauchen, um unter \cs{DocumentMetadata}\Marg{tagging=on} sauber getaggten statt fehlerhaften oder anonymen PDF-Output zu erzeugen. Jeder Hook greift nur, wenn \pkg{tagpdf} aktiv ist (getestet über \cs{tagstructbegin}), und bleibt ansonsten wirkungslos. Aktuell enthalten: ein Hook für \pkg{qrcode} (das seine Module einzeln über \cs{rule}s zeichnet und ohne Eingriff Hunderte Tagging-Warnungen pro Code erzeugt) sowie das Nachladen von \pkg*{latex-lab-testphase-tikz} (das \env{tikzpicture} die Schlüssel \code{alt}/\code{artifact} gibt, die \pkg{pgf}/\pkg{tikz} selbst nur reserviert, aber nie auswertet). Das Paket ist als Übergangslösung gedacht: sobald das \pkg*{LaTeX-Project} selbst -- über \pkg*{latex-lab} oder dessen Nachfolger -- native Unterstützung für ein hier behandeltes Paket liefert, wird der entsprechende Hook hier überflüssig und kann entfernt werden. \pkg*{pdfpages}-Import fällt bewusst nicht in diesen Zuständigkeitsbereich; dafür existiert bereits \pkg*{tagpax}. }{ \tagbridgepkg\ collects in one place what individual third-party packages need in order to produce cleanly tagged rather than broken or anonymous PDF output under \cs{DocumentMetadata}\Marg{tagging=on}. Each hook only fires while \pkg{tagpdf} is active (tested via \cs{tagstructbegin}) and stays inert otherwise. Currently included: a hook for \pkg{qrcode} (which draws its modules individually via \cs{rule}s and, left alone, produces hundreds of tagging warnings per code) and loading \pkg*{latex-lab-testphase-tikz} (which gives \env{tikzpicture} the \code{alt}/\code{artifact} keys that \pkg{pgf}/\pkg{tikz} itself only reserves but never evaluates). The package is meant as a stopgap: once the \pkg*{LaTeX-Project} itself -- via \pkg*{latex-lab} or its successor -- ships native support for a package handled here, the corresponding hook becomes redundant and can be removed. Importing PDFs via \pkg*{pdfpages} is deliberately out of scope here; \pkg*{tagpax} already covers that. } }, url =https://github.com/werner-matthias/osglecture, build-title } \begin{document} \section{\ldeen{Zweck}{Purpose}} \ldeen{Das Paket wird üblicherweise nicht direkt geladen, sondern von einer Dokumentklasse oder einem anderen Paket (zum Beispiel \pkg*{osglecture}) als Abhängigkeit eingebunden. Es hat keine Paketoptionen und keine öffentlichen Befehle -- alle Hooks greifen automatisch, sobald das jeweils betroffene Fremdpaket (\pkg{qrcode}, \pkg{tikz}) geladen wird, und nur, wenn \pkg{tagpdf}-Tagging aktiv ist.}{The package is usually not loaded directly but pulled in as a dependency by a document class or another package (for example \pkg*{osglecture}). It has no package options and no public commands -- every hook fires automatically once the respective third-party package (\pkg{qrcode}, \pkg{tikz}) loads, and only while \pkg{tagpdf} tagging is active.} \ldeen{Die \pkg{qrcode}-Optionen \code{tag} (Default \code{Figure}) und \code{alt} (Default: sprachabhängiger Text mit dem codierten Inhalt) lassen sich wie gewohnt pro Aufruf oder via \cs{qrset} setzen; \code{tag=artifact} markiert den Code als rein dekoratives Artifact.} {The \pkg{qrcode} options \code{tag} (default \code{Figure}) and \code{alt} (default: language-dependent text carrying the encoded content) can be set per call or via \cs{qrset} as usual; \code{tag=artifact} marks the code as a purely decorative artifact.} \section{Implementation} \MaybeStop{\ldeen*{Um die Implementationsbeschreibung zu erhalten, kommentieren Sie bitte \cs{OnlyDescription} in @1 aus.}{To view the implementation description, please uncomment \cs{OnlyDescription} in @1.}{\File{tagbridge.dtx}}} \AutoDocInput{tagbridge.dtx}{package} \end{document} % %<*package> \ExplSyntaxOn % \ldeen*{@1 zeichnet jedes Modul des Codes einzeln über \cs{rule}s statt % über ein Bild-XObject. Bei aktivem \pkg{tagpdf} greift dafür in der % Lua-Variante der automatische \code{activate/tagunmarked}-Mechanismus % und wrappt jedes sonst ungetaggte Modul separat als Artifact -- mit % einer eigenen Warnung pro Modul, bei einem mittelgroßen Code schnell % mehrere hundert. Der Hook patcht \cs{qr@printmatrix} und % \cs{qr@printsavedbinarymatrix} (die einzigen beiden Stellen, an denen % @1 tatsächlich zeichnet), sodass der komplette Code in \emph{einer} % eigenen Tag-Struktur steckt, bevor der automatische Mechanismus je % einzelne Module sieht. Der Hook hängt an \code{package/qrcode/after} % und greift daher unabhängig davon, von wem @1 geladen wird. Die neuen % @1-Optionen \code{tag} (Default \code{Figure}) und \code{alt} (Default: % der codierte Text) erlauben, das pro Aufruf oder via \cs{qrset} zu % steuern; \code{tag=artifact} markiert den Code als rein dekoratives % Artifact ohne eigene Struktur -- sinnvoll, wenn eine Beschriftung % daneben den Inhalt bereits als Fließtext trägt.}{@1 draws every module % of the code individually via \cs{rule}s instead of an image XObject. % With \pkg{tagpdf} active, the Lua variant's automatic % \code{activate/tagunmarked} mechanism steps in and wraps each % otherwise-untagged module separately as an artifact -- with its own % warning per module, quickly reaching several hundred for a % medium-sized code. The hook patches \cs{qr@printmatrix} and % \cs{qr@printsavedbinarymatrix} (the only two places where @1 actually % draws) so that the whole code sits inside \emph{one} tag structure % before the automatic mechanism ever sees an individual module. The % hook attaches to \code{package/qrcode/after} and therefore fires % regardless of who loads @1. The new @1 options \code{tag} (default % \code{Figure}) and \code{alt} (default: the encoded text) let this be % controlled per call or via \cs{qrset}; \code{tag=artifact} marks the % code as a purely decorative artifact with no structure of its own -- % useful when a caption next to it already carries the content as flow % text.}{\pkg{qrcode}} % \ldeen*{\code{alt}-Default und Kritikprüfung: Ohne explizites % \code{alt} übernimmt \cs{__tagbridge_qrcode_alt_text:} den codierten % Text sprachabhängig -- über \cs{olsTargetLanguage}, falls das existiert % (z.\,B.\ von \pkg*{langselect} gesetzt), sonst über % \cs{OsgLectureLanguage}, falls \pkg*{osglecture} geladen ist, sonst % \code{en}. Beide Integrationen sind rein optional: ohne sie fällt der % Default einfach auf Englisch zurück, das Paket funktioniert auch ohne % \pkg*{osglecture} oder \pkg*{langselect}. Für Inhalte, die mit einem % der wenigen tatsächlich \emph{geheimnistragenden} Protokollpräfixe % beginnen (\code{WIFI:}, \code{otpauth://} -- WLAN-Passwort bzw.\ % TOTP-Seed), würde dieser Default das Geheimnis im Klartext in den % Tag-Baum des PDFs schreiben. Andere strukturierte Inhalte wie % \code{MECARD:}/\code{BEGIN:VCARD} tragen zwar persönliche, aber keine % Zugangsdaten und bleiben bewusst außen vor. Für die kritische % Teilmenge bricht \cs{__tagbridge_qrcode_check_critical_content:} mit % einem Fehler ab, statt den Default stillschweigend zu verwenden -- % explizites \code{alt=\{...\}} (das den Inhalt beschreibt, ohne ihn zu % wiederholen) oder \code{tag=artifact} ist dann erforderlich.}{Default % for \code{alt} and criticality check: without an explicit \code{alt}, % \cs{__tagbridge_qrcode_alt_text:} falls back to the encoded text, % language-dependent -- via \cs{olsTargetLanguage} if that exists (e.g.\ % set by \pkg*{langselect}), otherwise via \cs{OsgLectureLanguage} if % \pkg*{osglecture} is loaded, otherwise \code{en}. Both integrations are % purely optional: without them the default simply falls back to % English, the package works fine without \pkg*{osglecture} or % \pkg*{langselect}. For content starting with one of the few protocol % prefixes that actually carry a secret (\code{WIFI:}, \code{otpauth://} % -- Wi-Fi password resp.\ TOTP seed), this default would write that % secret in the clear into the PDF's tag tree. Other structured content % such as \code{MECARD:}/\code{BEGIN:VCARD} carries personal but not % credential data and is deliberately left out. For the critical subset, % \cs{__tagbridge_qrcode_check_critical_content:} aborts with an error % instead of silently using the default -- an explicit \code{alt=\{...\}} % (describing the content without repeating it) or \code{tag=artifact} % is then required.} \cs_if_exist:NT \tagstructbegin { \tl_new:N \l__tagbridge_qrcode_tag_tl \tl_new:N \l__tagbridge_qrcode_alt_tl \tl_new:N \l__tagbridge_qrcode_tmpa_tl \msg_new:nnn { tagbridge } { qrcode-critical-content } { This~QR~code's~content~starts~with~WIFI:~or~otpauth://~--~a~ format~that~typically~carries~a~credential~or~secret~(Wi-Fi~ password,~one-time-password~seed,~...).~Falling~back~to~the~ encoded~text~as~the~PDF~alt~text~would~leak~it~into~the~ document's~accessibility~tree,~where~it~is~trivially~ extractable.~Set~the~qrcode~option~alt=\{...\}~explicitly~ (describing~the~code~without~repeating~the~secret),~or~ tag=artifact~if~nothing~about~it~should~be~exposed~at~all. } \cs_new_protected:Npn \__tagbridge_qrcode_check_critical_content: { \tl_if_empty:NT \l__tagbridge_qrcode_alt_tl { \tl_if_eq:NnF \l__tagbridge_qrcode_tag_tl { artifact } { \tl_set:Ne \l__tagbridge_qrcode_tmpa_tl { \qr@texttoencode } \regex_match:nVT { \A(WIFI:|otpauth://) } \l__tagbridge_qrcode_tmpa_tl { \msg_error:nn { tagbridge } { qrcode-critical-content } } } } } \cs_new:Npn \__tagbridge_qrcode_alt_text: { \tl_if_empty:NTF \l__tagbridge_qrcode_alt_tl { \str_if_eq:eeTF { \cs_if_exist:NTF \olsTargetLanguage { \olsTargetLanguage } { \cs_if_exist:NTF \OsgLectureLanguage { \OsgLectureLanguage } { en } } } { de } { QR-Code:~\qr@texttoencode } { QR~code:~\qr@texttoencode } } { \l__tagbridge_qrcode_alt_tl } } \cs_new_protected:Npn \__tagbridge_qrcode_tag_begin: { % qrcode's minipage would start the paragraph anyway, but only % after paragraph tagging is switched off: the paragraph's end, % outside qrcode's group, then closes a structure never opened. \mode_leave_vertical: % Close the surrounding marked content and reopen it afterwards; % otherwise text following the code in the same paragraph is left % unmarked. \tag_mc_end_push: \tagpdfparaOff \__tagbridge_qrcode_check_critical_content: \tl_if_eq:NnTF \l__tagbridge_qrcode_tag_tl { artifact } { \tagmcbegin{artifact} } { \exp_args:Ne \tagstructbegin { tag=\l__tagbridge_qrcode_tag_tl, alt={\__tagbridge_qrcode_alt_text:} } \exp_args:Ne \tagmcbegin { tag=\l__tagbridge_qrcode_tag_tl } } % The code is one marked-content item: qrcode's minipage and its % one paragraph per row must not add structures of their own % (as artifact they would even end up as a Div below a P). \tag_suspend:n { tagbridge/qrcode } } \cs_new_protected:Npn \__tagbridge_qrcode_tag_end: { \tag_resume:n { tagbridge/qrcode } \tl_if_eq:NnTF \l__tagbridge_qrcode_tag_tl { artifact } { \tagmcend } { \tagmcend \tagstructend } \tag_mc_begin_pop:n { } } \AddToHook { package/qrcode/after } [ tagbridge/qrcode-tagging ] { \define@key{qr}{tag}{\tl_set:Nn \l__tagbridge_qrcode_tag_tl {#1}} \define@key{qr}{alt}{\tl_set:Nn \l__tagbridge_qrcode_alt_tl {#1}} \qrset{tag=Figure} \let\osg@qrcode@printmatrix@orig\qr@printmatrix \let\osg@qrcode@printsavedmatrix@orig\qr@printsavedbinarymatrix \def\qr@printmatrix#1{% \__tagbridge_qrcode_tag_begin: \osg@qrcode@printmatrix@orig{#1}% \__tagbridge_qrcode_tag_end: }% \def\qr@printsavedbinarymatrix#1{% \__tagbridge_qrcode_tag_begin: \osg@qrcode@printsavedmatrix@orig{#1}% \__tagbridge_qrcode_tag_end: }% } } % \ldeen{Ohne aktives Tagging bleiben die Schlüssel \code{tag} und % \code{alt} trotzdem bekannt, nur wirkungslos: Pakete und Dokumente, % die sie setzen (etwa \pkg*{semcat} mit \code{tag=artifact}), müssen % auch ungetaggt übersetzbar bleiben.}{Without active tagging the keys % \code{tag} and \code{alt} are still known, merely inert: packages and % documents that set them (\pkg*{semcat} with \code{tag=artifact}, say) % must remain compilable untagged as well.} \cs_if_exist:NF \tagstructbegin { \AddToHook { package/qrcode/after } [ tagbridge/qrcode-keys ] { \define@key{qr}{tag}{} \define@key{qr}{alt}{} } } % \ldeen*{pgf/tikz reserviert die Schlüssel \code{alt}, \code{artifact} % und \code{actualtext} für \env{tikzpicture} ausdrücklich für % zukünftige Tagging-Unterstützung, wertet sie selbst aber nirgends % aus -- ein damit angegebener Alt-Text verschwindet kommentarlos, und % das ganze Bild landet als anonymes Artifact im Tag-Baum, ohne dass % \pkg{tagpdf} das meldet. Das \pkg{latex-lab}-Bundle bringt mit % \pkg{latex-lab-testphase-tikz} eine vom LaTeX-Projekt selbst % gepflegte Implementierung dieser Schlüssel über das % Tagging-Socket-System mit, die auch mehrzeiligen Node-Text korrekt % als echten Text taggt statt als Teil der Grafik; das Laden dieses % einen Pakets ist robuster als eine eigene Nachbildung. Die Schlüssel % gelten für die \env{tikzpicture}-Umgebung selbst % (\code{\textbackslash begin\{tikzpicture\}[alt=\{...\}]}), nicht für % einzelne \cs{node}s darin -- ein \code{alt} an einem einzelnen Knoten % bleibt wirkungslos.}{pgf/tikz reserves the keys \code{alt}, % \code{artifact} and \code{actualtext} for \env{tikzpicture} % explicitly for future tagging support but never evaluates them % itself -- an \code{alt} text given this way vanishes without a % trace, and the whole picture ends up as an anonymous artifact in the % tag tree, with no diagnostic from \pkg{tagpdf}. The \pkg{latex-lab} % bundle ships \pkg{latex-lab-testphase-tikz}, a % LaTeX-Project-maintained implementation of these keys via the % tagging socket system that also tags multi-line node text correctly % as real text rather than as part of the graphic; loading that one % package is more robust than reimplementing it. The keys apply to the % \env{tikzpicture} environment itself % (\code{\textbackslash begin\{tikzpicture\}[alt=\{...\}]}), not to % individual \cs{node}s inside it -- an \code{alt} on a single node % remains ineffective.}{tikzpicture} \cs_if_exist:NT \tagstructbegin { \RequirePackage{latex-lab-testphase-tikz} } \ExplSyntaxOff %