Skip to content

Commit 9b7b0cd

Browse files
committed
improved PDF/UA support:
- added testing via Makefile - added tagging methods - fixed examples E015, E016, E017 and E063
1 parent fb2d2c4 commit 9b7b0cd

16 files changed

Lines changed: 580 additions & 71 deletions

Makefile

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,9 @@ PHPDOC=$(shell which phpDocumentor)
101101
# phpstan version
102102
PHPSTANVER=2.1.40
103103

104+
# veraPDF binary path (can be overridden, e.g. make verapdf-ua VERAPDF_BIN=/opt/verapdf/bin/verapdf)
105+
VERAPDF_BIN?=$(shell command -v verapdf 2>/dev/null)
106+
104107
# --- MAKE TARGETS ---
105108

106109
# Display general help about this command
@@ -245,6 +248,32 @@ report: ensuretarget
245248
preflight: ensuretarget
246249
bash ./resources/preflight/run_preflight_matrix.sh
247250

251+
## Generate PDF/UA examples and validate with veraPDF
252+
.PHONY: verapdf-ua
253+
verapdf-ua: ensuretarget
254+
@if [[ -z "$(VERAPDF_BIN)" ]]; then \
255+
echo "veraPDF CLI not found. Set VERAPDF_BIN=/path/to/verapdf or add verapdf to PATH."; \
256+
exit 2; \
257+
fi
258+
$(PHP) examples/E015_pdfua.php > $(TARGETDIR)/report/E015_pdfua.pdf
259+
$(PHP) examples/E016_pdfua1.php > $(TARGETDIR)/report/E016_pdfua1.pdf
260+
$(PHP) examples/E017_pdfua2.php > $(TARGETDIR)/report/E017_pdfua2.pdf
261+
@fail=0; \
262+
for spec in "E015_pdfua.pdf:ua1" "E016_pdfua1.pdf:ua1" "E017_pdfua2.pdf:ua2"; do \
263+
file="$${spec%%:*}"; \
264+
flavour="$${spec##*:}"; \
265+
input="$(TARGETDIR)/report/$${file}"; \
266+
report="$(TARGETDIR)/report/$${file%.pdf}.verapdf.txt"; \
267+
if "$(VERAPDF_BIN)" --format text --verbose --flavour "$${flavour}" "$${input}" > "$${report}" 2>&1; then \
268+
echo "[ok] $${file} ($${flavour})"; \
269+
else \
270+
echo "[fail] $${file} ($${flavour}) - see $${report}"; \
271+
fail=1; \
272+
fi; \
273+
done; \
274+
echo "veraPDF reports written to $(TARGETDIR)/report/*.verapdf.txt"; \
275+
exit $$fail
276+
248277
## Build the RPM package for RedHat-like Linux distributions
249278
.PHONY: rpm
250279
rpm:

README.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -602,13 +602,29 @@ When a PDF/UA mode is active the library automatically:
602602
- Tags text content with MCIDs and wraps each run in the appropriate structure element (`P`, `H1``H6`, `Link`, etc.)
603603
- Tags `<img>` elements as `Figure` with their `alt` attribute written as `/Alt` in the structure element
604604
- Emits `ActualText` entries for ligatures and special glyphs so text extraction and screen readers work correctly
605+
- Provides Artifact marked-content helpers for non-semantic content (`beginArtifact()`, `endArtifact()`, `addArtifactContent()`)
605606

606607
To provide the document language explicitly:
607608

608609
```php
609610
$pdf->setDocInfo(['a_meta_language' => 'de-DE']);
610611
```
611612

613+
To tag decorative or repeated content as Artifact (for example headers, footers, and page numbers):
614+
615+
```php
616+
$pid = $pdf->addPage()['pid'];
617+
618+
$headerOperators = $pdf->graph->getLine(10, 10, 200, 10);
619+
$pdf->addArtifactContent($headerOperators, $pid, 'Pagination', 'Header');
620+
621+
$footerText = $pdf->getTextCell('Page 1', 180, 280, 20, 5);
622+
$pdf->addArtifactContent($footerText, $pid, 'Pagination', 'Footer');
623+
```
624+
625+
In PDF/UA mode, the built-in `defaultPageContent()` page-number footer is emitted as `Artifact` with
626+
`/Type /Pagination /Subtype /Footer`.
627+
612628
Runnable examples: [examples/E015_pdfua.php](examples/E015_pdfua.php) through [examples/E017_pdfua2.php](examples/E017_pdfua2.php).
613629

614630
---

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
8.16.0
1+
8.17.0

examples/E005_header_footer.php

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -100,19 +100,20 @@ public function defaultPageContent(int $pid = -1): string
100100
'lineColor' => '#555555',
101101
];
102102

103-
$out = $this->graph->getStartTransform();
104-
$out .= $this->defaultfont['out'];
103+
$out = '';
105104

106105
// ---- HEADER ------------------------------------------------
107106

108107
$headerY = self::HF_MARGIN;
108+
$headerOut = $this->graph->getStartTransform();
109+
$headerOut .= $this->defaultfont['out'];
109110

110111
// Title – left-aligned, bold
111112
if ($this->headerTitle !== '') {
112113
$bfontBold = $this->font->insert($this->pon, 'helvetica', 'B', 10);
113-
$out .= $bfontBold['out'];
114-
$out .= $this->color->getPdfColor('#1a3a6b');
115-
$out .= $this->getTextCell(
114+
$headerOut .= $bfontBold['out'];
115+
$headerOut .= $this->color->getPdfColor('#1a3a6b');
116+
$headerOut .= $this->getTextCell(
116117
$this->headerTitle,
117118
$lm,
118119
$headerY,
@@ -123,13 +124,13 @@ public function defaultPageContent(int $pid = -1): string
123124
'C', // valign: centre inside the band
124125
'L', // halign: left
125126
);
126-
$out .= $this->defaultfont['out'];
127+
$headerOut .= $this->defaultfont['out'];
127128
}
128129

129130
// Subtitle – right-aligned
130131
if ($this->headerSubtitle !== '') {
131-
$out .= $this->color->getPdfColor('#555555');
132-
$out .= $this->getTextCell(
132+
$headerOut .= $this->color->getPdfColor('#555555');
133+
$headerOut .= $this->getTextCell(
133134
$this->headerSubtitle,
134135
$lm + $tw * 0.65,
135136
$headerY,
@@ -144,18 +145,25 @@ public function defaultPageContent(int $pid = -1): string
144145

145146
// Separator line below the header
146147
$headerLineY = $headerY + self::HEADER_H;
147-
$out .= $this->graph->getLine($lm, $headerLineY, $rm, $headerLineY, $lineStyle);
148+
$headerOut .= $this->graph->getLine($lm, $headerLineY, $rm, $headerLineY, $lineStyle);
149+
$headerOut .= $this->graph->getStopTransform();
150+
151+
$out .= $this->beginArtifact('Pagination', 'Header');
152+
$out .= $headerOut;
153+
$out .= $this->endArtifact();
148154

149155
// ---- FOOTER ------------------------------------------------
150156

151157
$footerLineY = $ph - self::HF_MARGIN - self::FOOTER_H;
158+
$footerOut = $this->graph->getStartTransform();
159+
$footerOut .= $this->defaultfont['out'];
152160

153161
// Separator line above the footer
154-
$out .= $this->graph->getLine($lm, $footerLineY, $rm, $footerLineY, $lineStyle);
162+
$footerOut .= $this->graph->getLine($lm, $footerLineY, $rm, $footerLineY, $lineStyle);
155163

156164
// Page number – centred
157-
$out .= $this->color->getPdfColor('#555555');
158-
$out .= $this->getTextCell(
165+
$footerOut .= $this->color->getPdfColor('#555555');
166+
$footerOut .= $this->getTextCell(
159167
'Page ' . ($pid + 1),
160168
$lm,
161169
$footerLineY,
@@ -166,8 +174,11 @@ public function defaultPageContent(int $pid = -1): string
166174
'C', // valign: centre inside the band
167175
'C', // halign: centre
168176
);
177+
$footerOut .= $this->graph->getStopTransform();
169178

170-
$out .= $this->graph->getStopTransform();
179+
$out .= $this->beginArtifact('Pagination', 'Footer');
180+
$out .= $footerOut;
181+
$out .= $this->endArtifact();
171182

172183
return $out;
173184
}
@@ -181,7 +192,7 @@ public function defaultPageContent(int $pid = -1): string
181192
true, // bool $isunicode = true,
182193
false, // bool $subsetfont = false,
183194
true, // bool $compress = true,
184-
'', // string $mode = '',
195+
'pdfua1', // string $mode = 'pdfua1',
185196
null, // ?ObjEncrypt $objEncrypt = null,
186197
);
187198

@@ -195,6 +206,7 @@ public function defaultPageContent(int $pid = -1): string
195206
$pdf->setPDFFilename('005_header_footer.pdf');
196207

197208
$pdf->setViewerPreferences(['DisplayDocTitle' => true]);
209+
$pdf->setLanguageArray(['a_meta_language' => 'en-US']);
198210

199211
// Set the text that will appear in the header on every page.
200212
$pdf->setHeaderText('My Document Title', \date('Y-m-d'));

examples/E015_pdfua.php

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
$pdf->setPDFFilename('E015_pdfua.pdf');
3434
$pdf->setLanguage('en-US');
3535

36-
$font = $pdf->font->insert($pdf->pon, 'helvetica', '', 10);
36+
$font = $pdf->font->insert($pdf->pon, 'dejavusans', '', 10);
3737
$pdf->addPage();
3838
$pdf->page->addContent($font['out']);
3939

@@ -58,9 +58,7 @@
5858
. '<blockquote>&ldquo;Tagged PDF is the foundation of accessible PDF. Without a'
5959
. ' well-formed structure tree, automated tools and assistive technologies cannot'
6060
. ' reliably interpret document content.&rdquo; &mdash; ISO&#160;14289-1 rationale</blockquote>'
61-
. '<p>The <code>blockquote</code> tag maps to <strong>BlockQuote</strong>.'
62-
. ' The <code>pre</code> tag maps to <strong>Code</strong>:</p>'
63-
. '<pre>// pre / code block example&#10;function greet(string $name): string {&#10; return "Hello, " . $name . "!";&#10;}</pre>'
61+
. '<p>The <code>blockquote</code> tag maps to <strong>BlockQuote</strong>.</p>'
6462

6563
. '<h2>3 — List Structure (L &gt; LI &gt; Lbl + LBody)</h2>'
6664
. '<p>Both <code>ul</code> and <code>ol</code> map to the <strong>L</strong> role.'
@@ -100,14 +98,14 @@
10098
. '<tr><td>figcaption, caption</td><td>Caption</td><td>Illustration</td></tr>'
10199
. '<tr><td>table</td><td>Table</td><td>Table</td></tr>'
102100
. '<tr><td>tr</td><td>TR</td><td>Table</td></tr>'
103-
. '<tr><td>th</td><td>TH</td><td>Table</td></tr>'
104101
. '<tr><td>td</td><td>TD</td><td>Table</td></tr>'
102+
. '<tr><td>th</td><td>TH</td><td>Table</td></tr>'
105103
. '<tr><td>a</td><td>Link</td><td>Inline</td></tr>'
106104
. '</table>'
107105

108106
. '<h2>6 — Hyperlink (Link Role)</h2>'
109107
. '<p>This sentence contains a'
110-
. ' <a href="https://github.com/tecnickcom/tc-lib-pdf">hyperlink to the tc-lib-pdf repository</a>;'
108+
. ' <a href="https://github.com/tecnickcom/tc-lib-pdf" style="text-decoration:none;">hyperlink to the tc-lib-pdf repository</a>;'
111109
. ' the <code>a</code> element is tagged with the <strong>Link</strong> structure role'
112110
. ' automatically. The link text is accessible to screen readers via the structure tree.</p>';
113111

examples/E016_pdfua1.php

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
$pdf->setPDFFilename('E016_pdfua1.pdf');
4141
$pdf->setLanguage('en-US');
4242

43-
$font = $pdf->font->insert($pdf->pon, 'helvetica', '', 10);
43+
$font = $pdf->font->insert($pdf->pon, 'dejavusans', '', 10);
4444
$pdf->addPage();
4545
$pdf->page->addContent($font['out']);
4646

@@ -66,9 +66,7 @@
6666
. '<blockquote>&ldquo;Tagged PDF is the foundation of accessible PDF. Without a'
6767
. ' well-formed structure tree, automated tools and assistive technologies cannot'
6868
. ' reliably interpret document content.&rdquo; &mdash; ISO&#160;14289-1 rationale</blockquote>'
69-
. '<p>The <code>blockquote</code> tag maps to <strong>BlockQuote</strong>.'
70-
. ' The <code>pre</code> tag maps to <strong>Code</strong>:</p>'
71-
. '<pre>// PDF/UA-1 requires MarkInfo /Marked true in the catalog&#10;// and a StructTreeRoot with a ParentTree mapping&#10;// every page to its tagged content blocks.</pre>'
69+
. '<p>The <code>blockquote</code> tag maps to <strong>BlockQuote</strong>.</p>'
7270

7371
. '<h2>3 — List Structure (L &gt; LI &gt; Lbl + LBody)</h2>'
7472
. '<p>Both <code>ul</code> and <code>ol</code> map to the <strong>L</strong> role.'
@@ -109,14 +107,14 @@
109107
. '<tr><td>figcaption, caption</td><td>Caption</td><td>Illustration</td></tr>'
110108
. '<tr><td>table</td><td>Table</td><td>Table</td></tr>'
111109
. '<tr><td>tr</td><td>TR</td><td>Table</td></tr>'
112-
. '<tr><td>th</td><td>TH</td><td>Table</td></tr>'
113110
. '<tr><td>td</td><td>TD</td><td>Table</td></tr>'
111+
. '<tr><td>th</td><td>TH</td><td>Table</td></tr>'
114112
. '<tr><td>a</td><td>Link</td><td>Inline</td></tr>'
115113
. '</table>'
116114

117115
. '<h2>6 — Hyperlink (Link Role)</h2>'
118116
. '<p>This sentence contains a'
119-
. ' <a href="https://github.com/tecnickcom/tc-lib-pdf">hyperlink to the tc-lib-pdf repository</a>;'
117+
. ' <a href="https://github.com/tecnickcom/tc-lib-pdf" style="text-decoration:none;">hyperlink to the tc-lib-pdf repository</a>;'
120118
. ' the <code>a</code> element is tagged with the <strong>Link</strong> structure role'
121119
. ' automatically. PDF/UA-1 requires that link annotations have an accessible name,'
122120
. ' which the library derives from the link text in the structure tree.</p>';

examples/E017_pdfua2.php

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
$pdf->setPDFFilename('E017_pdfua2.pdf');
3939
$pdf->setLanguage('en-US');
4040

41-
$font = $pdf->font->insert($pdf->pon, 'helvetica', '', 10);
41+
$font = $pdf->font->insert($pdf->pon, 'dejavusans', '', 10);
4242
$pdf->addPage();
4343
$pdf->page->addContent($font['out']);
4444

@@ -65,9 +65,7 @@
6565
. '<blockquote>&ldquo;Tagged PDF is the foundation of accessible PDF. Without a'
6666
. ' well-formed structure tree, automated tools and assistive technologies cannot'
6767
. ' reliably interpret document content.&rdquo; &mdash; ISO&#160;14289 rationale</blockquote>'
68-
. '<p>The <code>blockquote</code> tag maps to <strong>BlockQuote</strong>.'
69-
. ' The <code>pre</code> tag maps to <strong>Code</strong>:</p>'
70-
. '<pre>// PDF/UA-2 targets PDF 2.0 — the catalog carries pdfver 2.0&#10;// and the XMP metadata block uses the PDF/UA-2 identification schema.&#10;// The structure-tree and MarkInfo rules are inherited from PDF/UA-1.</pre>'
68+
. '<p>The <code>blockquote</code> tag maps to <strong>BlockQuote</strong>.</p>'
7169

7270
. '<h2>3 — List Structure (L &gt; LI &gt; Lbl + LBody)</h2>'
7371
. '<p>Both <code>ul</code> and <code>ol</code> map to the <strong>L</strong> role.'
@@ -108,14 +106,14 @@
108106
. '<tr><td>figcaption, caption</td><td>Caption</td><td>Illustration</td></tr>'
109107
. '<tr><td>table</td><td>Table</td><td>Table</td></tr>'
110108
. '<tr><td>tr</td><td>TR</td><td>Table</td></tr>'
111-
. '<tr><td>th</td><td>TH</td><td>Table</td></tr>'
112109
. '<tr><td>td</td><td>TD</td><td>Table</td></tr>'
110+
. '<tr><td>th</td><td>TH</td><td>Table</td></tr>'
113111
. '<tr><td>a</td><td>Link</td><td>Inline</td></tr>'
114112
. '</table>'
115113

116114
. '<h2>6 — Hyperlink (Link Role)</h2>'
117115
. '<p>This sentence contains a'
118-
. ' <a href="https://github.com/tecnickcom/tc-lib-pdf">hyperlink to the tc-lib-pdf repository</a>;'
116+
. ' <a href="https://github.com/tecnickcom/tc-lib-pdf" style="text-decoration:none;">hyperlink to the tc-lib-pdf repository</a>;'
119117
. ' the <code>a</code> element is tagged with the <strong>Link</strong> structure role'
120118
. ' automatically. In PDF/UA-2 (as in PDF/UA-1), link annotations must be accessible;'
121119
. ' the library derives the accessible name from the link text in the structure tree.</p>';

0 commit comments

Comments
 (0)