glu
Standards

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.html

In 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 id carries 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 /Lang on its structure element (e.g. a fenced div with lang=en in 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-2

Archival 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.pdf

PDF/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 with format: PDF/A-3b and {= zugferd.* =} placeholders
  • rechnung.lua: auto-loaded companion, which parses invoice.xml via cxpath into a flat zugferd Lua global, registers a page_init callback that calls doc:load_colorprofile(...), doc:attach_file{name="factur-x.xml", ...} and doc:add_xmp_extension{...} once per render
  • invoice.xml: CII XML
  • AdobeRGB1998.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.xml

veraPDF can validate the PDF/A-3b side:

verapdf --flavour 3b rechnung.pdf