Bei jsPDF handelt es sich um eine freie JavaScript-Bibliothek zur Erzeugung von PDF-Dokumenten. Sie läuft sowohl in modernen Browsern als auch in Node.js und unterstützt die gängigen Modulformate (UMD, ES-Module, CommonJS). jsPDF wird von MrRio und der yWorks GmbH als Open-Source-Projekt gepflegt und kommt typischerweise dort zum Einsatz, wo PDF-Dokumente direkt im Client – also ohne serverseitigen Zwischenschritt – erzeugt werden sollen. Typische Anwendungsfälle sind dynamisch generierte Berichte, Formulare, Rechnungen oder Export-Funktionen in Web-Anwendungen.

Der hier beschriebene Entwicklungsstrang jsPDF-UA ist kein Bestandteil des offiziellen jsPDF-Projekts, sondern ein eigenständiger Fork auf Basis von jsPDF. Er ergänzt die Bibliothek um eine Implementierung des Standards PDF/UA-1 (ISO 14289-1). Damit lassen sich programmatisch Strukturbaum, Alternativtexte, ausgezeichnete Sprache, eingebettete Schriften, Artefakt-Markierungen sowie zahlreiche weitere Merkmale erzeugen, die für die Barrierefreiheit eines PDF-Dokuments erforderlich sind.

Solange der Pull Request mit den Änderungen des Forks nicht in das jsPDF-Kernprojekt integriert ist, müssen Anwenderinnen und Anwender, die PDF/UA-Erzeugung mit jsPDF nutzen möchten, direkt den Fork verwenden:

Für jsPDF-UA ist die Installation direkt aus dem Fork-Repository erforderlich (z. B. per npm install github:heikofolkerts/jsPDF-UA).

Aktualisierungen des jsPDF-Kernprojekts werden kontinuierlich in den Entwicklungsstrang jsPDF-UA integriert.

Der Einsatz von jsPDF-UA ist insbesondere dann empfehlenswert, wenn

  • PDF-Dokumente programmatisch und reproduzierbar aus strukturierten Daten (z. B. JSON, Datenbank-Auszügen oder XML) erzeugt werden sollen,
  • die Erzeugung direkt im Browser erfolgen soll, ohne dass personenbezogene Daten den Client verlassen müssen, oder
  • eine bestehende jsPDF-Codebasis um Barrierefreiheit erweitert werden soll.

Im Unterschied insbesondere zu Apache-FOP wird die Dokumentstruktur bei jsPDF-UA nicht deklarativ aus einer Markup-Datei abgeleitet, sondern programmatisch mittels API-Aufrufen erzeugt. Das bedeutet: Sie bestimmen explizit, welches Strukturelement gerade geöffnet ist und welcher Textaufruf dazugehört. Dies erfordert Sorgfalt, bietet aber zugleich maximale Kontrolle über den Strukturbaum.

Eine vollständige Referenz der API finden Sie in der Projektdokumentation des Forks unter docs/pdfua/.

Ein vollständig ausgestattetes Beispieldokument steht mit showcase-pdfua-complete.js zur Verfügung und dient auch als Grundlage der folgenden Code-Auszüge.

Das damit erzeugte PDF-Dokument lässt sich unter showcase-pdfua-complete.pdf abrufen.

Hinweise zur Erstellung von barrierefreien PDF-Dokumenten mit jsPDF-UA

Permalink "Hinweise zur Erstellung von barrierefreien PDF-Dokumenten mit jsPDF-UA"

Die Methode setDocumentTitle schreibt den Titel zusammen mit den erforderlichen XMP-Kennungen für PDF/UA-1:

1doc.setDocumentTitle("PDF/UA Complete Feature Showcase - jsPDF Implementation");

setDocumentTitle aktiviert implizit den Viewer-Eintrag DisplayDocTitle mit dem Wert „true“. Dadurch zeigt der PDF-Betrachter den vergebenen Titel statt des Dateinamens an.

Die alternativ ebenfalls verfügbare Methode setProperties({ title: "..." }) setzt lediglich den Info-Dictionary-Eintrag. Für einen PDF/UA-konformen Titel ist stets setDocumentTitle zu verwenden.

Die primäre Dokumentsprache wird über die Methode setLanguage auf dem „Catalog“ gesetzt und zusätzlich in die XMP-Metadaten übernommen. Erwartet wird ein BCP-47-Sprachcode:

1doc.setLanguage("en-US"); // alternativ z. B. "de-DE" oder "de-AT"

jsPDF-UA schreibt den Sprachcode zusätzlich in jeden Marked-Content-Operator (/Lang-Eintrag innerhalb der BDC-Property-List). Dies ist notwendig, damit sowohl Adobe Acrobat Reader als auch Screenreader wie NVDA die Sprachauszeichnung zuverlässig auswerten. Firefox ist an dieser Stelle toleranter, deckt aber nicht alle Hilfsmittel ab.

Sprache (einzelne Teilbereiche)

Permalink "Sprache (einzelne Teilbereiche)"

Für Passagen in abweichender Sprache innerhalb eines Absatzes steht ein Span-Element mit eigenem lang-Attribut zur Verfügung (siehe auch unten unter Span-Tag).

Alternativtexte werden in jsPDF-UA über das alt-Attribut der jeweiligen Strukturelement-Methoden übergeben. Zwingend erforderlich sind sie insbesondere für Figure-, Formula- und Annot-Elemente.

Der Alternativtext für Links wird über die Methode textWithLink übergeben (siehe auch unten unter Link-Tag).

Aktivierung des PDF/UA-Modus

Permalink "Aktivierung des PDF/UA-Modus"

Der PDF/UA-Modus wird bereits beim Anlegen der Instanz aktiviert. Mit dieser einen Option werden intern zahlreiche Voreinstellungen vorgenommen, die für einen gültigen PDF/UA-Output zwingend erforderlich sind – insbesondere MarkInfo/Marked true, ViewerPreferences/DisplayDocTitle true sowie der Aufbau des Strukturbaums mit StructTreeRoot:

1const { jsPDF } = require("jspdf");
2const doc = new jsPDF({ pdfUA: true });

Ohne die Option pdfUA: true werden keine Strukturbaum- und Marked-Content-Operatoren erzeugt; auch die automatische Einbettung der barrierearmen Schrift Atkinson Hyperlegible unterbleibt (siehe auch unten unter Schriften und Font-Embedding). Die Aktivierung des Modus sollte daher am Anfang stehen, noch bevor Inhalte hinzugefügt werden.

Die Strukturelemente des PDF-Dokuments werden mit beginStructureElement geöffnet und mit endStructureElement geschlossen. Zwischen diesen Aufrufen werden die Textausgaben (doc.text(...)) automatisch mit passenden BDC/EMC-Operatoren und eindeutigen Marked-Content-IDs (MCIDs) versehen. Das wird in jsPDF-UA im Abschnitt Marked Content (Auto) als automatisches Tagging bezeichnet.

 1doc.beginStructureElement("Document");
 2
 3doc.beginStructureElement("H1");
 4doc.setFontSize(22);
 5doc.setFont(undefined, "bold");
 6doc.text("PDF/UA Complete Feature Showcase", 20, 25);
 7doc.endStructureElement(); // H1
 8
 9doc.beginStructureElement("P");
10doc.setFontSize(11);
11doc.setFont(undefined, "normal");
12doc.text(
13  "This document demonstrates all PDF/UA accessibility features " +
14    "implemented in jsPDF.",
15  20,
16  52
17);
18doc.endStructureElement(); // P
19
20doc.endStructureElement(); // Document

Die oberste Ebene des Strukturbaums wird stets mit „Document“ geöffnet. Erst dann folgen Überschriften (H1 bis H6), Absätze (P) und alle weiteren Tags.

beginStructureElement und endStructureElement müssen paarweise geschachtelt sein. Wird ein Element nicht geschlossen, entsteht ein defekter Strukturbaum, der von Prüftools beanstandet wird.

Die Textposition (x, y) hat keinen Einfluss auf die logische Lesereihenfolge. Maßgeblich für Screenreader ist die Reihenfolge der Aufrufe – sie sollte der gewünschten Lesereihenfolge entsprechen, unabhängig vom visuellen Layout.

Lesezeichen (Outlines) werden über die „outline“-API erzeugt. Dafür steht die Methode bm zur Verfügung, die auf Elemente mit id verweist (“sec-1”, etc.) und zusätzlich die zugehörige Seitenangabe benötigt:

1bm(null, "1. Text Elements", "sec-1", 1);
2const listsBookmark = bm(null, "2. Lists", "sec-2", 2);
3bm(listsBookmark, "2.1 Unordered List", "sec-2-1", 2);
4bm(listsBookmark, "2.2 Ordered List", "sec-2-2", 2);

Der erste Parameter ist das Eltern-Lesezeichen oder „null“ für eine oberste Ebene. Die Füllelemente (Punkte) zwischen Überschrift und Seitenzahl sind als dekorative Bestandteile gekennzeichnet. Falls in der Screenreader-Ansicht zwischen Überschrift und Seitenzahl ein – auch im PDF-Dokument sichtbares – Leerzeichen stehen soll, kann das Leerzeichen wunschgemäß eingefügt werden:

1bm(null, "1. Text Elements ", "sec-1", 1);

Für ein semantisch korrektes, maschinenlesbares Inhaltsverzeichnis stellt jsPDF-UA die Methoden beginTOC, addTOCEntry und endTOC bereit. Intern entstehen dabei die Strukturelemente TOC und TOCI (Table Of Contents Item), jeweils mit Sprung-Link auf die Zielseite:

 1doc.beginTOC();
 2const tocItems = [
 3  { title: "1. Text Elements", page: 1, level: 1, id: "sec-1" },
 4  { title: "2. Lists", page: 2, level: 1, id: "sec-2" },
 5  { title: "2.1 Unordered List", page: 2, level: 2, id: "sec-2-1" },
 6  // ...
 7];
 8let tocY = 88;
 9doc.setFontSize(8);
10doc.setFont(undefined, "normal");
11tocItems.forEach(item => {
12  doc.addTOCEntry({
13    title: item.title,
14    page: item.page,
15    targetId: item.id,
16    y: tocY,
17    level: item.level,
18    indent: 25,
19    subIndent: 5,
20    rightMargin: 190
21  });
22  tocY += 5;
23});
24doc.endTOC();

Kopf- und Fußzeilen sowie rein dekorative Elemente dürfen nicht im Strukturbaum erscheinen. jsPDF-UA stellt für solche Inhalte das Methodenpaar beginArtifact/endArtifact bereit:

1doc.beginArtifact({ type: "Pagination", subtype: "Header" });
2doc.setFontSize(8);
3doc.setTextColor(89, 89, 89);
4doc.text("PDF/UA Complete Showcase", 20, 10);
5doc.text("Page 1", 180, 10);
6doc.setTextColor(0, 0, 0);
7doc.endArtifact();

type kann die Werte „Pagination“ (Kopf-/Fußzeilen), „Layout“ (Trennlinien, Hintergründe) oder „Page“ (Seitenzahlen, Hintergrund-Bilder) annehmen. „Pagination“ nutzt zusätzlich subtype: "Header" oder subtype: "Footer".

Erzeugung von PDF-Dokumenten mit jsPDF-UA

Permalink "Erzeugung von PDF-Dokumenten mit jsPDF-UA"

Die Erzeugung des eigentlichen PDF-Dokuments erfolgt wahlweise im Browser oder in einer Node.js-Umgebung. Im Browser wird typischerweise doc.save("out.pdf") aufgerufen, das über den FileSaver einen Download auslöst. Unter Node.js wird das PDF als ArrayBuffer ausgegeben und mit fs.writeFileSync gespeichert:

1const fs = require("fs");
2const arrayBuffer = doc.output("arraybuffer");
3fs.writeFileSync("out.pdf", Buffer.from(arrayBuffer));

Der Node-Build von jsPDF bindet keinen FileSaver ein; doc.save ist dort nicht verfügbar.

Die Node-basierte Erzeugung eignet sich insbesondere für CI/CD-Pipelines, in denen PDF-Dokumente gemeinsam mit veraPDF automatisiert geprüft werden sollen.

Schriften und Font-Embedding

Permalink "Schriften und Font-Embedding"

Bei aktivem PDF/UA-Modus bindet jsPDF-UA die Schriftfamilie Atkinson Hyperlegible (Regular, Bold, Italic und BoldItalic) automatisch ein und verwendet sie als Standardschrift. Diese Schrift ist gezielt für Menschen mit Sehbeeinträchtigung entworfen worden und deckt den lateinischen Zeichenvorrat inklusive deutscher Umlaute ab.

Sollen eigene TrueType-Schriften verwendet werden, lassen sich diese manuell einbetten:

1doc.addFileToVFS("MyFont.ttf", base64EncodedFont);
2doc.addFont("MyFont.ttf", "MyFont", "normal");
3doc.setFont("MyFont");

jsPDF-UA blendet die 14 PDF-Standardschriften (Helvetica, Times usw.) im aktiven PDF/UA-Modus aus und ersetzt Aufrufe wie setFont("Helvetica") durch die Atkinson-Variante.

Prüfung der Barrierefreiheit

Permalink "Prüfung der Barrierefreiheit"

Für die Prüfung der Barrierefreiheit haben sich in jsPDF-UA die folgenden zwei Werkzeuge bewährt:

  • PDF Accessibility Checker (PAC) prüft stärker auf semantische Korrektheit und ist besonders streng bei Platzierungs-Attributen (/Placement) und der korrekten Verwendung von Inline- vs. Block-Elementen.
  • veraPDF liefert die technische Prüfung gegen ISO 14289-1 und ist automatisierbar.

Der Aufruf von veraPDF über Docker:

1docker run --rm -v "$(pwd)/examples/temp:/data" verapdf/cli \ --flavour ua1 /data/pdfua-complete-showcase.pdf

Ergänzend empfiehlt sich der Test mit Adobe Acrobat Reader in Kombination mit einem Screenreader (NVDA unter Windows, VoiceOver unter macOS). Firefox ist für Screenreader-Tests ungeeignet, da sein PDF-Renderer Strukturfehler häufig verdeckt. Vergleiche dazu auch den Abschnitt Hinweise zu Tools zur Überprüfung der Barrierefreiheit.

Das Beispieldokument des Projekts (showcase-pdfua-complete.js) besteht die veraPDF-Prüfung mit dem Flavour „ua1“ ohne Befund. In PAC sind für einzelne Strukturelemente die Platzierungs-Attribute zu beachten, die jsPDF-UA über die jeweiligen begin*-Methoden als Parameter entgegennimmt (siehe auch unten unter Platzierungs-Attribute).

Automatisches Tagging vs. manueller Eingriff

Permalink "Automatisches Tagging vs. manueller Eingriff"

Solange Textausgaben (doc.text(...)) innerhalb eines geöffneten Strukturelements erfolgen, werden Marked-Content-IDs und BDC/EMC-Operatoren vollautomatisch erzeugt. Ein manuelles Setzen von MCIDs ist nicht erforderlich und auch nicht vorgesehen.

Soll ein Text ausdrücklich kein Strukturelement erhalten (z. B. weil er dekorativ ist), ist die Ausgabe in das Methodenpaar beginArtifact/endArtifact zu klammern.

jsPDF kennt zwei Betriebsmodi: den kompatiblen („compat“) und den erweiterten („advanced“). Der Advanced-Modus stellt u. a. Transformationsmatrizen, Muster (Patterns) und Form-Objects zur Verfügung. Für die Erzeugung von PDF/UA-Inhalten genügt der Standardmodus „compat“. Wird „advanced“ benötigt, sollten strukturrelevante Aufrufe gezielt in doc.compatAPI(...)-Blöcken erfolgen, damit die Marked-Content-Logik erwartungsgemäß greift.

Das Prüftool PAC verlangt für bestimmte Elemente die explizite Angabe des Platzierungs-Attributs /Placement /Block, sobald sie als Block-Element – also nicht innerhalb eines P-Elements – verwendet werden. Die jsPDF-UA-API setzt dafür sinnvolle Voreinstellungen:

ElementVoreinstellungMethodeBemerkung
LinkInlinebeginCodebei allein stehenden Links Block-Platzierung angeben
CodeInlinebeginCodebei Bedarf Block-Platzierung angeben
FormulaInlinebeginFormulabei Bedarf Block-Platzierung angeben
AnnotBlockbeginAnnotbei Bedarf Inline-Platzierung angeben
NoteBlockaddFootnotebei Bedarf Inline-Platzierung angeben
FormBlockbeginFormFieldbei Bedarf Inline-Platzierung angeben
BibEntryBlockbeginBibEntrybei Bedarf Inline-Platzierung angeben
FigureBlockbeginFigurebei Bedarf Inline-Platzierung angeben

Überschriften und Abschnitte

Permalink "Überschriften und Abschnitte"

jsPDF-UA unterstützt die Überschriftenebenen H1 bis H6. Zusätzlich existieren gruppierende Elemente wie Sect, Art, Div und Part, mit denen semantische Abschnitte gekennzeichnet werden:

 1doc.beginSect();
 2doc.beginStructureElement("H2");
 3doc.markLinkTarget("sec-1");
 4doc.setFontSize(14);
 5doc.setFont(undefined, "bold");
 6doc.text("1. Text Elements", 20, 200);
 7doc.endStructureElement();
 8
 9doc.beginStructureElement("P");
10doc.setFontSize(11);
11doc.setFont(undefined, "normal");
12doc.text("This section demonstrates various text formatting options:", 20, 213);
13doc.endStructureElement();
14// ...
15doc.endSect();

beginSect und beginArt eignen sich zum logischen Gruppieren. Art ist semantisch für eigenständige, publizierbare Abschnitte gedacht (z. B. Zeitschriftenartikel), Sect für beliebige Abschnitte wie Kapitel oder nummerierte Bereiche.

Listen werden aus den Elementen L (Liste), LI (Listenpunkt), Lbl (Aufzählungszeichen bzw. Nummerierung) und LBody (eigentlicher Inhalt) aufgebaut:

 1doc.beginStructureElement("L");
 2const items = ["First item", "Second item", "Third item"];
 3let y = 50;
 4items.forEach(item => {
 5  doc.beginStructureElement("LI");
 6  doc.beginStructureElement("Lbl");
 7  doc.text("•", 25, y);
 8  doc.endStructureElement(); // Lbl
 9  doc.beginStructureElement("LBody");
10  doc.text(item, 32, y);
11  doc.endStructureElement(); // LBody
12  doc.endStructureElement(); //LI
13  y += 10;
14});
15doc.endStructureElement();

Bei nummerierten Listen wird in Lbl die Nummerierung ausgegeben (z. B. „1.“, „2.“), in LBody der Text.

Tabellen werden mit Table, THead, TBody, TR, TH und TD aufgebaut. Für jede Kopfzelle muss ein scope-Attribut gesetzt werden, damit die Zuordnung zu Spalten- bzw. Zeilen-Daten für Screenreader erkennbar ist:

 1doc.beginStructureElement("Table");
 2
 3doc.beginTableHead();
 4doc.beginStructureElement("TR");
 5doc.beginStructureElement("TH", { scope: "Column" });
 6doc.setFont(undefined, "bold");
 7doc.text("Product", 25, 175);
 8doc.endStructureElement(); // TH
 9doc.beginStructureElement("TH", { scope: "Column" });
10doc.text("Price", 80, 175);
11doc.endStructureElement(); // TH
12// weitere Spaltenköpfe …
13doc.endStructureElement(); // TR
14doc.endTableHead();
15
16doc.beginTableBody();
17doc.beginStructureElement("TR");
18doc.beginStructureElement("TH", { scope: "Row" });
19doc.setFont(undefined, "bold");
20doc.text("Widget A", 25, 188);
21doc.endStructureElement(); // TH
22doc.setFont(undefined, "normal");
23doc.beginStructureElement("TD");
24doc.text("$10.00", 80, 188);
25doc.endStructureElement(); // TD
26// weitere Datenzellen …
27doc.endStructureElement(); // TR
28doc.endTableBody();
29
30doc.endStructureElement(); // Table

scope kennt die Werte „Column“, „Row“ und „Both“. Bei Tabellen mit gleichzeitigem Zeilen- und Spaltenkopf (z. B. Pivot-Tabellen) ist „Both“ zu verwenden.

Die erste Spalte eines Datensatzes kann bei Bedarf als TH mit scope: "Row" ausgezeichnet werden, um Bezeichnungen wie Produktnamen oder Kalenderwochen als Zeilenkopf zu markieren (so im Beispiel oben).

Tabellen-Trennlinien werden in jsPDF-UA derzeit nicht automatisch als Artefakt gekennzeichnet. Falls Sie dekorative Linien zeichnen, klammern Sie diese im Methodenpaar beginArtifact({ type: "Layout" })/endArtifact() ein, damit sie nicht im Strukturbaum auftauchen.

Bilddateien werden als Figure-Element getaggt. Sie benötigen zwingend einen Alternativtext. Die jsPDF-Methode addImage fügt die einzubettende Datei ein:

1doc.beginFigure({
2  alt:
3    "A bar chart showing quarterly sales data with Q1 at 25%, Q2 at 30%, Q3 at 20%, and Q4 at 25%",
4  bbox: [57, 338, 219, 185] // x, y (from bottom), width, height
5});
6doc.addImage({
7  ...
8});
9doc.endFigure();

Das bbox-Attribut (Bounding Box) in PDF-Koordinaten ist für das Figure-Element optional, wird vom Prüftool PAC jedoch empfohlen, da es die Grafik in alternativen Darstellungsformen verortbar macht.

Für rein dekorative Grafiken soll kein Figure-Element verwendet werden – dafür ist die Kennzeichnung als Artefakt vorgesehen (siehe auch unten unter Kopf- und Fußzeilen). Vergleiche dazu auch den Abschnitt Hinweise zur Erstellung von Alternativtexten.

Links werden im Strukturbaum mit dem Link-Element ausgezeichnet und technisch über die jsPDF-Methode textWithLink umgesetzt. Gemäß PDF/UA ist ein Link inline innerhalb eines P-Elements zu platzieren:

 1doc.beginStructureElement("P");
 2doc.setFontSize(11);
 3doc.setFont(undefined, "normal");
 4doc.text("External link: ", 20, 253);
 5doc.beginLink();
 6doc.setTextColor(0, 0, 255);
 7doc.textWithLink("jsPDF on GitHub", 52, 253, { url: "https://github.com/parallax/jsPDF" });
 8doc.setTextColor(0, 0, 0);
 9doc.endLink();
10doc.endStructureElement();

Interne Links auf eine andere Seite werden über die gleiche Methode umgesetzt:

1doc.beginStructureElement("P");
2doc.text("Internal link: ", 20, 265);
3doc.beginLink();
4doc.setTextColor(0, 0, 255);
5doc.textWithLink("Jump to Bibliography (Page 6)", 51, 265, { targetId: "sec-10"});
6doc.setTextColor(0, 0, 0);
7doc.endLink();
8doc.endStructureElement();

Für einen alleinstehenden (Block-artigen) Link – also außerhalb eines Fließtextabsatzes – erwartet das Prüftool PAC eine Platzierungs-Angabe /Placement /Block. Diese wird mit beginLink({ placement: "Block" }) gesetzt.

Abbreviation-Tag (Abkürzungen)

Permalink "Abbreviation-Tag (Abkürzungen)"

Abkürzungen werden mit beginAbbreviation(expansion) ausgezeichnet. Die ausgeschriebene Form wird als Attribut E (Expansion) in die Strukturbaum-Eigenschaften übernommen:

 1doc.beginStructureElement("P");
 2doc.text("The ", 20, 250);
 3doc.beginAbbreviation("World Wide Web");
 4doc.text("WWW", 32, 250);
 5doc.endAbbreviation();
 6doc.text(" and ", 46, 250);
 7doc.beginAbbreviation("Hypertext Markup Language");
 8doc.text("HTML", 58, 250);
 9doc.endAbbreviation();
10doc.text(" are fundamental web technologies.", 73, 250);
11doc.endStructureElement();

NVDA liest anstelle der Kurzform die ausgeschriebene Variante, sofern im Screenreader die Abkürzungserkennung aktiviert ist.

Für Inline-Container (z. B. zur Auszeichnung abweichender Sprache) steht das Methodenpaar beginSpan/endSpan zur Verfügung:

1doc.beginStructureElement("P");
2doc.text("English text with ", 20, 262);
3doc.beginSpan({ lang: "de-DE" });
4doc.text("deutscher Text eingebettet", 61, 262);
5doc.endSpan();
6doc.text(" and back to English.", 130, 262);
7doc.endStructureElement();

Der Sprachcode wird sowohl am Span-Strukturelement als auch in den zugehörigen BDC-Properties (/Lang-Eintrag) gesetzt. Ohne diese doppelte Auszeichnung wechseln Acrobat Reader und NVDA die Sprache nicht zuverlässig.

Kurze, in den Fließtext eingebettete Zitate werden mit dem Methodenpaar beginQuote/endQuote ausgezeichnet, längere, eigenständige Zitate mit beginBlockQuote/endBlockQuote:

 1// Inline-Zitat
 2doc.beginStructureElement("P");
 3doc.setFontSize(11);
 4doc.setFont(undefined, "normal");
 5doc.text("Shakespeare wrote: ", 20, 145);
 6doc.beginQuote();
 7doc.setFont(undefined, "italic");
 8doc.text('"To be or not to be, that is the question."', 63, 145);
 9doc.setFont(undefined, "normal");
10doc.endQuote();
11doc.endStructureElement();
12
13// Blockzitat
14doc.beginBlockQuote();
15doc.beginStructureElement("P");
16doc.setFontSize(11);
17doc.setFont(undefined, "italic");
18doc.text('"The only way to do great work is to love what you do."', 30, 175);
19doc.text("If you haven't found it yet, keep looking. Don't settle.\"", 30, 183);
20doc.setFont(undefined, "normal");
21doc.text("- Steve Jobs", 30, 195);
22doc.endStructureElement();
23doc.endBlockQuote();

Für Quellcode steht das Methodenpaar beginCode/endCode bereit. Für inline eingefügten Code genügt die Standardform; für größere Code-Blöcke ist die Platzierung als Block-Element vorzugeben:

 1// Inline-Code
 2doc.beginStructureElement("P");
 3doc.setFontSize(11);
 4doc.setFont(undefined, "normal");
 5doc.text("Inline code: ", 20, 228);
 6doc.beginCode();
 7doc.text("const x = 42;", 52, 228);
 8doc.endCode();
 9doc.endStructureElement();
10
11// Block-Code
12doc.beginCode({ placement: "Block" });
13doc.text("function greet(name) {", 25, 255);
14doc.text("  return `Hello, ${name}!`;", 25, 263);
15doc.text("}", 25, 271);
16doc.endCode();

Mathematische Formeln werden als Formula-Element getaggt. Sie benötigen zwingend einen Alternativtext:

 1doc.beginStructureElement("P");
 2doc.text("Famous equation: ", 20, 274);
 3doc.beginFormula(
 4  alt:
 5  "E equals m times c squared, where E is energy, " +
 6    "m is mass, and c is speed of light"
 7);
 8doc.text("E = mc²", 62, 274);
 9doc.endFormula();
10doc.endStructureElement();

Darstellung in der Screenreader-Vorschau von PAC

Permalink "Darstellung in der Screenreader-Vorschau von PAC"

In der Screenreader-Vorschau von PAC erscheint ein Formula-Element als (leeres) Form-Tag mit zugehörigem Alternativtext.

Kommentar-Annotationen (gelbe Klebezettel, Freitext) werden über ein Annot-Strukturelement eingebunden. Die Annotation selbst wird mit createAnnotation erzeugt und über addAnnotationRef an die Struktur angebunden:

 1doc.beginAnnot({ alt: "Reviewer comment about quarterly sales figure" });
 2const annotId = doc.createAnnotation({
 3  type: "text",
 4  title: "Reviewer",
 5  contents: "Comment: This figure shows quarterly sales distribution.",
 6  bounds: { x: 90, y: 180, w: 20, h: 20 },
 7  open: false
 8});
 9if (annotId) doc.addAnnotationRef(annotId);
10doc.endAnnot();

Kommentar-Annotationen benötigen zwingend einen Alternativtext.

Technisch hängt ein Annot-Strukturelement nicht am Contentstrom, sondern an der Seite. Screenreader greifen daher häufig erst nach den umgebenden Absätzen auf Annotations zu – unabhängig von deren Position im Strukturbaum.

Wenn eine bestimmte Lesereihenfolge erzwungen werden muss, kann statt einer Annot-Annotation ein Note-Element verwendet werden. Note ist eine echte Inline-Struktur mit eigenem Text, die vom Screenreader exakt an der im Strukturbaum festgelegten Stelle vorgelesen wird.

Darstellung in der Screenreader-Vorschau von PAC

Permalink "Darstellung in der Screenreader-Vorschau von PAC"

In der Screenreader-Vorschau von PAC erscheint ein Annot-Element per Default als eigenständiges Element. Angezeigt wird sowohl der sichtbare Text als auch der Alternativtext.

Für Fußnoten stellt jsPDF-UA die Hilfsmethoden addFootnoteRef (Referenz im Fließtext) und addFootnote (Fußnoten-Eintrag) zur Verfügung. Im Strukturbaum entstehen daraus das Reference- und das Note-Element. Beide werden intern über eine id miteinander verknüpft und als Link mit sichtbarer Sprungmarke abgebildet:

 1// Fließtext mit Fußnoten-Referenz
 2doc.beginStructureElement("P");
 3doc.setFontSize(11);
 4doc.setFont(undefined, "normal");
 5let fnX = 20;
 6doc.text("PDF/UA (Universal Accessibility) is an ISO standard", fnX, 40);
 7fnX += doc.getTextWidth("PDF/UA (Universal Accessibility) is an ISO standard");
 8const ref1X = fnX;
 9doc.addFootnoteRef("¹", fnX, 40, { noteId: "fn1", xNote: "25", yNote: "260" });
10fnX += doc.getTextWidth("¹") * 0.7 + 1;
11doc.text(" that ensures", fnX, 40);
12doc.text("PDFs can be read by assistive technologies.", 20, 50);
13doc.endStructureElement();
14
15// Fußnoten-Eintrag am Seitenfuß
16doc.setFontSize(9);
17doc.addFootnote({
18  id: "fn1",
19  label: "¹",
20  text: "ISO 14289-1:2014, Document management — Electronic document file format enhancement for accessibility",
21  x: 25,
22  y: 260,
23  xRef: ref1X,
24  yRef: 40,
25  labelX: 20,
26  link: true
27});

Für eine saubere Trennung zwischen Fließtext und Fußnotenblock empfiehlt sich eine dekorative Trennlinie. Diese ist als Artefakt zu markieren (beginArtifact({ type: "Layout" })).

addFootnote setzt intern placement: "Block", weil eine allein stehende Fußnote in PAC als Block-Element erwartet wird.

Wird eine Fußnote nicht mithilfe der Convenience-API, sondern über das Methodenpaar beginNote/endNote manuell erzeugt, muss die id explizit gesetzt werden – andernfalls fehlt die Verknüpfung zwischen Referenz und Fußnotentext.

Form-Tag (barrierefreie Formularfelder)

Permalink "Form-Tag (barrierefreie Formularfelder)"

Interaktive Formularfelder werden nicht über die generischen beginStructureElement-Aufrufe erzeugt, sondern über spezialisierte Convenience-Methoden, die sowohl das AcroForm-Widget als auch das Form-Strukturelement anlegen:

 1doc.addAccessibleTextField({
 2  name: "name",
 3  label: "Name:", // label is visible
 4  tooltip: "Enter your full name.",
 5  x: 20,
 6  y: 55,
 7  width: 80,
 8  height: 12,
 9  required: true
10});
11
12doc.addAccessibleCheckBox({
13  name: "subscribe",
14  label: "Subscribe to newsletter.", // label is visible
15  tooltip: "Check to receive our newsletter.",
16  x: 20,
17  y: 71,
18  width: 7,
19  height: 7
20});
21
22doc.addAccessibleComboBox({
23  name: "country",
24  label: "Country:", // label is visible
25  tooltip: "Select your country from the list.",
26  x: 20,
27  y: 88,
28  width: 60,
29  height: 12,
30  options: ["USA", "Germany", "France", "UK", "Japan"]
31});

Der tooltip wird als TU-Eintrag (Alternativer Name) in das Widget geschrieben. In der Screenreadervorschau von PAC wird bei addAccessibleCheckBox zusätzlich ein Doppelpunkt angefügt.

Pflichtfelder sind über required: true zu kennzeichnen. Der Wert landet im /Ff-Flag des Widgets.

Vergleiche dazu auch den Abschnitt PDF-Formular.

Für Bibliografie-Einträge steht das Methodenpaar beginBibEntry/endBibEntry zur Verfügung:

1doc.beginBibEntry();
2doc.text("[2] PDF Association. (2024). The Matterhorn Protocol 1.1. PDF Association.", 25, 62);
3doc.endBibEntry();

Index-Einträge lassen sich mithilfe des Methodenpaars beginIndex/endIndex einbinden:

 1doc.beginIndex();
 2doc.setFontSize(10);
 3doc.setFont(undefined, "normal");
 4doc.beginStructureElement("P");
 5doc.text("Abbreviations ............................................... 1", 25, 140);
 6doc.endStructureElement();
 7doc.beginStructureElement("P");
 8doc.text("Annotations ................................................. 4", 25, 140);
 9doc.endStructureElement();
10// ...
11doc.endIndex();
Permalink "Externe Links in diesem Artikel"

Informationen zu diesem Artikel

Gerne können Sie uns Feedback per E-Mail zu unserer Handreichung senden!