Standards
A standard is one setting in glu: name the conformance level, and the rest follows from the document. Markdown and HTML reach the same result, they only declare it in different places.
Conformance level
| Value | Standard | What it is for |
|---|---|---|
PDF/UA, PDF/UA-1 |
ISO 14289-1 | Tagged, accessible PDF |
PDF/UA-2 |
ISO 14289-2 | Tagged, accessible PDF on PDF 2.0 |
PDF/A-3b |
ISO 19005-3 | Archival PDF, which may carry attachments |
PDF/X-3, PDF/X-4 |
ISO 15930 | Exchange with a print shop, see Print-ready PDF |
A Markdown document sets it in the frontmatter:
---
format: PDF/UA-2
---An HTML document has no frontmatter, so it uses a <meta> element in the <head>. A bare glu report.html then produces the tagged PDF directly:
<head>
<meta name="pdf-format" content="PDF/UA-2">
<title>Quarterly report</title>
</head>Both can also come from the command line:
glu --format PDF/UA-2 report.htmlIn HTML the flag overrides the <meta> element. In Markdown it is the other way around: the flag only applies to a document without format: in its frontmatter.
Levels can be combined, separated by a comma, as in PDF/A-3b, PDF/UA-1. The exception is PDF/UA-2: it requires PDF 2.0, while PDF/A-3 and PDF/X are based on PDF 1.x, so glu stops with an error if you combine them.
Document metadata
| Metadata | Markdown frontmatter | HTML |
|---|---|---|
| Title | title: |
<title> |
| Author | author: |
<meta name="author"> |
| Language | lang: |
lang on <html> |
The title becomes the PDF document title (/Title and, for PDF/UA, the XMP dc:title plus /DisplayDocTitle).
An HTML document takes its language from the lang attribute of <html>, or of <body> if <html> has none. A lang further down holds for that element only.
Tagged PDF (PDF/UA)
With the conformance level PDF/UA (or the explicit PDF/UA-1 and PDF/UA-2), glu produces a tagged, accessible PDF that conforms to the chosen PDF/UA revision:
---
title: Accessible Report
author: Jane Doe
format: PDF/UA
lang: en
---| Value | Standard | PDF version | XMP pdfuaid:part |
|---|---|---|---|
PDF/UA or PDF/UA-1 |
ISO 14289-1 | 1.7 | 1 |
PDF/UA-2 |
ISO 14289-2 | 2.0 | 2 (plus pdfuaid:rev = 2024) |
When PDF/UA is active, glu automatically:
- Creates a document structure tree with roles derived from HTML elements (P, H1 to H6, Table, L/LI, Figure, etc.)
- Tags table cells as TH/TD with proper THead/TBody grouping and scope attributes
- Marks page headers and footers as pagination artifacts
- Generates PDF bookmarks from headings (under UA-2 these point to structure destinations, ISO 14289-2 §8.8; an internal link to an
idcarries one too, next to its named destination) - Sets the document language, see Document metadata
- Declares language switches in the structure tree: a block element whose
lang=attribute differs from the inherited language gets/Langon its structure element (e.g. a fenced div withlang=enin a German document), and its children inherit it. Inline switches (<span lang>, bracketed spans) currently affect hyphenation only, not the structure tree.
Under PDF/UA-2 the structure tree additionally uses the HTML5 namespace for HTML-equivalent roles (lowercase p, h1 to h6, figure, table, …) with a RoleMapNS mapping each one to the corresponding PDF 2.0 Standard Structure Namespace role. Document-level and PDF-specific roles without HTML5 equivalents (Document, LBody, Lbl, L) stay in the PDF 2.0 SSN. A footnote, tagged Note under UA-1, is an FENote there, since PDF 2.0 has no Note.
No additional Lua code or CSS is needed: the structure of the document maps directly to the PDF structure tree.
The default language of the document (a BCP 47 tag) is required for PDF/UA conformance and used by screen readers to select the correct pronunciation.
veraPDF is the canonical conformance checker:
verapdf --flavour ua1 report.pdf # for PDF/UA / PDF/UA-1
verapdf --flavour ua2 report.pdf # for PDF/UA-2Archival PDF (PDF/A)
With the conformance level PDF/A-3b a plain document needs no further setup. Validate the result with veraPDF:
verapdf --flavour 3b report.pdfPDF/A-3 allows files embedded in the PDF, which is what e-invoices build on. For another output intent than the default, load an ICC profile from Lua with doc:load_colorprofile(), as the ZUGFeRD example below does.
Attachments
The attachments: frontmatter key of a Markdown document embeds arbitrary files in the PDF: supplementary specs, raw data, source CSVs, anything PDF viewers should expose in their attachment pane.
---
attachments:
- file: spec.pdf
name: Technical specification.pdf
description: source for the rendered tables
- file: data.csv
mimetype: text/csv
---| Field | Required | Default |
|---|---|---|
file |
yes | none |
name |
no | basename of file |
description |
no | empty |
mimetype |
no | application/octet-stream |
afrelationship |
no | Alternative |
Paths are resolved relative to the Markdown source file. Each attached file is written with the /AFRelationship it names (Alternative, Data, Source, Supplement or Unspecified, by default Alternative) and registered in the document’s name tree, so pdfdetach -list and any PDF viewer’s attachment pane pick them up.
HTML has no such key. Attach files from the companion Lua file instead:
local frontend = require("glu.frontend")
frontend.add_callback("page_init", "attachments", function(doc, page, pagenum, pageinfo)
if pagenum == 1 then
doc:attach_file({
filename = "data.csv",
name = "data.csv",
mimetype = "text/csv",
description = "source for the rendered tables",
afrelationship = "Data",
})
end
end)Compliance formats (ZUGFeRD, XRechnung, PEPPOL, …)
glu has no special settings for compliance formats. The pattern is a conformance level plus a companion Lua file: set PDF/A-3b (or whatever the standard requires), then do the format-specific plumbing (XML attachment, XMP extension schema, output-intent ICC, data extraction) in <stem>.lua next to the document.
This keeps glu’s core focused on typesetting, lets compliance-format requirements evolve at their own pace, and gives the author full control over what gets attached, in what order, with which metadata.
The boxesandglue-examples/glu/markdown/zugferd-invoice/ example shows the pattern end-to-end for ZUGFeRD/Factur-X:
rechnung.md: visible invoice withformat: PDF/A-3band{= zugferd.* =}placeholdersrechnung.lua: auto-loaded companion, which parsesinvoice.xmlviacxpathinto a flatzugferdLua global, registers apage_initcallback that callsdoc:load_colorprofile(...),doc:attach_file{name="factur-x.xml", ...}anddoc:add_xmp_extension{...}once per renderinvoice.xml: CII XMLAdobeRGB1998.icc: output-intent profile
Adapting the same pattern to XRechnung, PEPPOL, BSI TR-RESISCAN or any other domain-specific compliance format is a matter of editing the companion Lua, with no changes to glu and no dependency on a release.
For a walkthrough, see the how-to Create a ZUGFeRD e-invoice.
Verifying ZUGFeRD output
pdfdetach -list rechnung.pdf
# 1 embedded files
# 1: factur-x.xml
exiftool -XMP-pdfaid:Part -XMP-fx:ConformanceLevel \
-XMP-fx:DocumentFileName rechnung.pdf
# Part : 3
# Conformance Level : EN 16931
# Document File Name : factur-x.xmlveraPDF can validate the PDF/A-3b side:
verapdf --flavour 3b rechnung.pdf