Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

LaTeX & Typst (papers, theses, resumes and slides)  Module 8 – Theses, reports and long documents

Archival PDF/A and final submission checks

Export PDF/A-2b or 2u from LaTeX and Typst, validate it with veraPDF, and check fonts, metadata, page size and file size before you submit.

  • Advanced
  • 20 minutes
  • Examples run with TeX Live 2026, TinyTeX 2026.10, Typst 0.15.1, Python 3.14.8, Pyodide 314.0.7, poppler 26.09.0, qpdf 12.4.1, mupdf 1.28.3, ghostscript 10.08.0 and verapdf 1.30
  • By MySmartCoPilot
Free lesson, Pro answers: 20 minutes
Free: the whole lesson. With a pass: the model answers to its 2 interview questions.

What you will learn

  • Export PDF/A-2b and PDF/A-2u files from LaTeX and from Typst
  • Validate a file with veraPDF and read the rules it reports as failed
  • Check fonts, metadata, page size and file size before you submit a thesis
  • Avoid the rewriting tools that silently break conformance after export

Before you start

What you’ll learn · Before you start

What you will learn

  • Export PDF/A-2b and PDF/A-2u files from LaTeX and from Typst
  • Validate a file with veraPDF and read the rules it reports as failed
  • Check fonts, metadata, page size and file size before you submit a thesis
  • Avoid the rewriting tools that silently break conformance after export

Before you start

On this page

A library that keeps theses may ask for PDF/A, and a portal that receives them may check the page size, the fonts and the file size too. In this lesson’s run, a first try on the report class’s own settings failed 6 of 7 submission checks, and a copy compressed after export lost its PDF/A identification while the compressor printed nothing.

Here you export a thesis as PDF/A-2b and PDF/A-2u from LaTeX and from Typst, validate each file with veraPDF and read its report, see a figure break conformance, run a submission check of your own, and measure which tools that rewrite a PDF keep it archival. Every run was recorded with TeX Live 2026, Typst 0.15.1 and veraPDF 1.30.

PDF/A is a PDF that carries everything it needs

PDF/A: The ISO 19005 family of standards for PDFs meant to be kept, whose files must carry what they need to show the same pages later, such as their fonts, and name their part and level in their metadata. is the ISO 19005 family of standards for PDFs that are meant to be kept: the file must hold what it needs to show the same pages later, its fonts among them, and say in its XMP metadata: The block of XML inside a PDF that describes the file, such as its title, its author and, in a PDF/A file, the part and conformance level it claims. pdfinfo -meta prints it. which part and level of the standard it follows. veraPDF, the validator this lesson uses, describes itself as designed for the needs of digital preservationists. The part sets the PDF version and what is allowed, and the conformance level: The letter after a PDF/A part, as in PDF/A-2b or 2u: b for the basic rules, u for text that maps to Unicode as well, a for an accessible tagged file as well. how much more the file promises. Typst’s documentation of its PDF export sums them up:

Part or level What it adds, as Typst’s PDF documentation puts it
PDF/A-1 based on PDF 1.4; transparency is not allowed
PDF/A-2 based on PDF 1.7
PDF/A-3 as part 2, with attachments of any kind
PDF/A-4 based on PDF 2.0; level f allows attachments
level b, basic the file follows the part’s rules
level u also, all text consists of known Unicode code points
level a also, the file is an accessible tagged PDF; PDF/A-2a also forbids private-use characters

Read your institution’s instructions for the part and level it accepts; this lesson exports 2b and 2u, two levels of part 2. Level u matters for a thesis that people search and copy from, because every character must map to Unicode, and level a needs a tagged PDF, the subject of this track’s accessibility lessons.

Checkpoint

Check: PDF/A parts and levels

  1. Question 1 of 2 What does level u add to the rules of PDF/A-2?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: All text must consist of known Unicode code points, so it can be searched and copied correctly

    b is the basic level of a part, u adds Unicode text, and a adds an accessible tagged file.

  2. Question 2 of 2 A figure uses transparency. Which part of PDF/A cannot hold it?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: PDF/A-1

    The parts follow the PDF versions they are built on; part 2 is the usual choice for a new thesis that must be PDF/A.

Ask for the standard when you export

LaTeX’s tagging project documents \DocumentMetadata, written before \documentclass, as the way to set a document’s metadata, the standards it should comply with among them. In TeX Live 2026 its pdfstandard key accepts A-1B, A-2A, A-2B, A-2U, A-3A, A-3B, A-3U, A-4, A-4F and A-4E, as its PDF management code lists them. Typst takes the standard on the command line: its PDF documentation lists --pdf-standard with values from a-1b to a-4e and ua-1. The same short thesis, in both:

A short thesis, exported as PDF/A

pdfa/thesis.tex

% thesis.tex: a short thesis exported as PDF/A-2u. \DocumentMetadata must come before \documentclass; it loads
% LaTeX's PDF management code, which writes the XMP metadata, the PDF/A identification and a colour profile.
\DocumentMetadata{lang=en-IN, pdfstandard=a-2u}
\documentclass[a4paper,12pt]{report}
\usepackage{graphicx}
\usepackage{hyperref}
\hypersetup{pdftitle={Rooftop Rainwater Harvesting in Example District}, pdfauthor={Nisha Varghese}}
\begin{document}
\chapter{Introduction}
Rooftop rainwater harvesting stores the rain that falls on a roof for later use. This thesis measures how much one
roof in Example District can collect, and in which months.

\chapter{Results}
The library gauge recorded its wettest month in August, 701 mm.
% FIGURE
\end{document}

pdfa/thesis.typ

// thesis.typ: the same short thesis in Typst. The PDF standard is chosen when it is compiled:
// typst compile --pdf-standard a-2u thesis.typ
#set document(title: [Rooftop Rainwater Harvesting in Example District], author: "Nisha Varghese")
#set text(lang: "en", region: "IN", size: 12pt)
#set page(paper: "a4", numbering: "1")
#set heading(numbering: "1.1")
#show heading.where(level: 1): it => { pagebreak(weak: true); it }

= Introduction
Rooftop rainwater harvesting stores the rain that falls on a roof for later use. This thesis measures how much one roof
in Example District can collect, and in which months.

= Results
The library gauge recorded its wettest month in August, 701 mm.
// FIGURE
Four exports, validated against PDF/A-2u, then the 2b files against 2b Python · export_session.py
from session import files, run

# pdfa/thesis.tex exported as PDF/A-2u and, in a copy, as PDF/A-2b; pdfa/thesis.typ compiled with each standard; then
# vera.py validates every file, first against the 2u profile and then the two 2b files against 2b.
files("vera.py", "pdfa/thesis.tex=thesis.tex", "pdfa/thesis.typ=thesis.typ")
run(r"""
latexmk -pdf -interaction=nonstopmode -silent thesis.tex > /dev/null 2>&1
sed 's/pdfstandard=a-2u/pdfstandard=a-2b/' thesis.tex > thesis-2b.tex
latexmk -pdf -interaction=nonstopmode -silent thesis-2b.tex > /dev/null 2>&1
typst compile --pdf-standard a-2u thesis.typ typst-2u.pdf
typst compile --pdf-standard a-2b thesis.typ typst-2b.pdf
pdfinfo -meta thesis.pdf | grep -E "pdfaid:(part|conformance)"
python3 vera.py 2u thesis.pdf typst-2u.pdf thesis-2b.pdf typst-2b.pdf
python3 vera.py 2b thesis-2b.pdf typst-2b.pdf
""")

Output

$ latexmk -pdf -interaction=nonstopmode -silent thesis.tex > /dev/null 2>&1
$ sed 's/pdfstandard=a-2u/pdfstandard=a-2b/' thesis.tex > thesis-2b.tex
$ latexmk -pdf -interaction=nonstopmode -silent thesis-2b.tex > /dev/null 2>&1
$ typst compile --pdf-standard a-2u thesis.typ typst-2u.pdf
$ typst compile --pdf-standard a-2b thesis.typ typst-2b.pdf
$ pdfinfo -meta thesis.pdf | grep -E "pdfaid:(part|conformance)"
   <pdfaid:part>2</pdfaid:part>
   <pdfaid:conformance>U</pdfaid:conformance>
$ python3 vera.py 2u thesis.pdf typst-2u.pdf thesis-2b.pdf typst-2b.pdf
thesis.pdf: PDF/A-2u validation profile: compliant, 146 rules passed, 0 failed
typst-2u.pdf: PDF/A-2u validation profile: compliant, 146 rules passed, 0 failed
thesis-2b.pdf: PDF/A-2u validation profile: NOT compliant, 145 rules passed, 1 failed
  6.6.4-3 (1 check): The "conformance" property of the PDF/A Identification Schema is B instead of "U" for PDF/A-2u conforming file
typst-2b.pdf: PDF/A-2u validation profile: NOT compliant, 145 rules passed, 1 failed
  6.6.4-3 (1 check): The "conformance" property of the PDF/A Identification Schema is B instead of "U" for PDF/A-2u conforming file
$ python3 vera.py 2b thesis-2b.pdf typst-2b.pdf
thesis-2b.pdf: PDF/A-2b validation profile: compliant, 144 rules passed, 0 failed
typst-2b.pdf: PDF/A-2b validation profile: compliant, 144 rules passed, 0 failed

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 export_session.py

pdfinfo -meta read the claim back from the XMP metadata, part 2 and conformance U. All four files then went through veraPDF. The two 2u files passed the 146 rules of the PDF/A-2u profile; the two 2b files failed it on one rule, because they claim conformance B, and passed all 144 rules of the PDF/A-2b profile, the level they claim. A file is not “PDF/A” in general: it conforms, or not, to the part and level it states.

pdfx or hyperxmp in a new document

Wrong: loading pdfx or hyperxmp to make PDF/A in a document that starts with \DocumentMetadata. The tagging status list marks both as not supported with the new PDF management: pdfx’s loading is suppressed, and the list says to set standards with \DocumentMetadata keys. Right: \DocumentMetadata{lang=en-IN, pdfstandard=a-2u} above \documentclass.

Checkpoint

Check: Exporting PDF/A from LaTeX and Typst

  1. Question 1 of 2 Where does \DocumentMetadata{lang=en-IN, pdfstandard=a-2u} go?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: Before \documentclass, at the very beginning of the file

    The key's value names the part and level, written a-2b, a-2u and so on, and the metadata then claims it.

  2. Question 2 of 2 Which command exports thesis.typ as PDF/A-2u?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: typst compile --pdf-standard a-2u thesis.typ

    Typst checks some rules at export and stops with an error, such as a PDF image or a missing document date.

veraPDF reads the file against one profile at a time

veraPDF’s command line validates a file against a built-in validation profile: The set of rules that veraPDF checks a file against, one profile for each part and level of PDF/A or PDF/UA, such as the 146 rules of PDF/A-2u. chosen with --flavour, such as 2u, or, with --flavour 0, the profile named by the file’s own metadata, and --format json prints a report that a program can read. vera.py prints the part of that report that matters: the profile, the verdict, and each failed rule with its clause of the standard and veraPDF’s message. Step through the check of the 2b file against PDF/A-2u:

vera.py: a short report from veraPDF's JSON Python · vera.py
"""Validates PDFs with veraPDF against one PDF/A or PDF/UA profile and prints a short report of each: the profile, the
verdict, how many of the profile's rules passed and failed, and every failed rule with its clause, its test number,
the number of failed checks and veraPDF's message.

    python3 vera.py 2u thesis.pdf other.pdf

The flavour is veraPDF's name for the profile (2b, 2u, 2a, ua1 …). veraPDF runs once for all the files; its JSON
report is read here because the report also holds times and build dates, which change on every run and are left out,
and the failed rules are printed in the order of the standard's clauses.
"""

import json
import subprocess
import sys


def main(flavour, pdfs):
    done = subprocess.run(["verapdf", "--format", "json", "--flavour", flavour, *pdfs], capture_output=True, text=True)
    report = json.loads(done.stdout)["report"]
    versions = {item["id"]: item["version"] for item in report["buildInformation"]["releaseDetails"]}
    if not versions.get("core", "").startswith("1.30."):
        sys.exit(f"this report needs veraPDF 1.30; found {versions}")
    for job in report["jobs"]:
        name = job["itemDetails"]["name"].rsplit("/", 1)[-1]
        results = job["validationResult"]
        result = results[0] if isinstance(results, list) else results
        details = result["details"]
        verdict = "compliant" if result["compliant"] else "NOT compliant"
        print(f"{name}: {result['profileName']}: {verdict}, "
              f"{details['passedRules']} rules passed, {details['failedRules']} failed")
        # veraPDF lists the failed rules in no fixed order, so they are sorted by clause and test number.
        for rule in sorted(details["ruleSummaries"], key=lambda r: ([int(n) for n in r["clause"].split(".")], r["testNumber"])):
            message = rule["checks"][0].get("errorMessage") if rule["checks"] else None
            checks = f"{rule['failedChecks']} check" + ("s" if rule["failedChecks"] != 1 else "")
            print(f"  {rule['clause']}-{rule['testNumber']} ({checks}): {message or rule['description']}")


if __name__ == "__main__":
    main(sys.argv[1], sys.argv[2:])
Interactive · message by message veraPDF judges a file against one profile Step through the check of thesis-2b.pdf against PDF/A-2u, from its metadata to the line of the report that explains the failure.
veraPDF judges a file against one profile, step 6 of 6. Messages so far: thesis-2b.pdf to veraPDF: part 2, conformance B; veraPDF to The report: 6.6.4-3, 1 check.thesis-2b.pdfexported as PDF/A-2bveraPDFone profile at a timeThe reportrules passed and failedpart 2, conformance B6.6.4-3, 1 check

The drawing shows the whole scene; its 6 steps are listed below.

Every step as text
  1. The file's claim. The PDF/A identification sits in the file's XMP metadata, part 2 and conformance B, written by LaTeX's PDF management code when \DocumentMetadata asked for a-2b.
  2. The profile is chosen. vera.py ran veraPDF with --flavour 2u, the PDF/A-2u profile of 146 rules. With --flavour 0, veraPDF would take the profile from the file's own claim instead.
  3. Every object is checked. Each rule is a test on the objects it concerns, such as fonts, colour spaces, streams and metadata, and each object it fails is one failed check.
  4. One rule fails. Rule 6.6.4-3 of ISO 19005-2:2011 failed once: a level U file must say conformance U, and this one says B. The other 145 rules passed.
  5. Read the message. The report names the clause, the test and the reason, which points at the fix, the pdfstandard key of \DocumentMetadata, not at the fonts or the pictures.
  6. The same file, its own profile. Against the PDF/A-2b profile, the level it claims, the same file passed all 144 rules. A validator answers one question at a time, so ask the portal's question.

The report tells you where to look, and the next session shows why that matters. Its figure, chart.pdf, uses Helvetica without embedding it, as some plotting programs and old exports do:

The figure in the LaTeX thesis, judged by veraPDF Python · failures_session.py
from session import files, read, run, shell, write

# The thesis with one more figure: chart.pdf, made by make_chart.py, which uses Helvetica without embedding it.
# pdflatex builds it as PDF/A-2u, and veraPDF judges the result.
files("vera.py", "make_chart.py", "pdfa/thesis.tex=thesis.tex")
shell("python3 make_chart.py chart.pdf")
write("with-chart.tex", read("thesis.tex").replace("% FIGURE", r"\includegraphics{chart.pdf}"))
run(r"""
pdffonts chart.pdf
latexmk -pdf -interaction=nonstopmode -silent with-chart.tex > /dev/null 2>&1
python3 vera.py 2u with-chart.pdf
""")

Output

$ pdffonts chart.pdf
name                                 type              encoding         emb sub uni object ID
------------------------------------ ----------------- ---------------- --- --- --- ---------
Helvetica                            Type 1            WinAnsi          no  no  no       5  0
$ latexmk -pdf -interaction=nonstopmode -silent with-chart.tex > /dev/null 2>&1
$ python3 vera.py 2u with-chart.pdf
with-chart.pdf: PDF/A-2u validation profile: NOT compliant, 145 rules passed, 1 failed
  6.2.11.4.1-1 (1 check): The font program is not embedded

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 failures_session.py

pdflatex built the thesis without a warning, and veraPDF found rule 6.2.11.4.1 broken: the font program is not embedded. Typst refuses such a file at export instead:

Typst refuses the figure, and a document without a date Python · refusals_session.py
from session import files, read, run, shell, write

# The same figure in the Typst thesis, exported as PDF/A-2u, and the thesis without a date, exported as PDF/A-2u and
# as PDF/A-2b: Typst checks these rules while it exports, and stops.
files("make_chart.py", "pdfa/thesis.typ=thesis.typ")
shell("python3 make_chart.py chart.pdf")
write("with-chart.typ", read("thesis.typ").replace("// FIGURE", '#image("chart.pdf", alt: "Rainfall in mm")'))
write("nodate.typ", "#set document(date: none)\n" + read("thesis.typ"))
run(r"""
!typst compile --pdf-standard a-2u with-chart.typ
!typst compile --pdf-standard a-2u nodate.typ
!typst compile --pdf-standard a-2b nodate.typ
""")

Output

$ typst compile --pdf-standard a-2u with-chart.typ
error: embedding PDFs is currently not supported in this export mode
   ┌─ with-chart.typ:15:1
   │
15 │ #image("chart.pdf", alt: "Rainfall in mm")
   │  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   │
   = hint: try converting the PDF to an SVG before embedding it

[exit status 1]
$ typst compile --pdf-standard a-2u nodate.typ
error: PDF/A-2u error: missing document date
 = hint: set the date of the document

[exit status 1]
$ typst compile --pdf-standard a-2b nodate.typ
error: PDF/A-2b error: missing document date
 = hint: set the date of the document

[exit status 1]

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 refusals_session.py

Embedding PDFs is not supported in this export mode, Typst said, with a hint to convert the PDF to SVG. It also refused a document whose date was none, with missing document date, for both levels.

PDF images in a Typst PDF/A export

Typst 0.15.1 reports a PDF image in a PDF/A export as currently not supported, so a later version may accept it. After an upgrade, try the PDF figure again before you convert it.

The fixes change the figure, not the thesis:

Two ways to fix the figure before it goes in Python · fixes_session.py
from session import files, read, run, shell, write

# The same figure fixed before it goes into the thesis: Ghostscript writes it again with its font embedded, for LaTeX,
# and pdftocairo turns it into SVG, for Typst. Both theses are then exported as PDF/A-2u and validated.
files("vera.py", "make_chart.py", "pdfa/thesis.tex=thesis.tex", "pdfa/thesis.typ=thesis.typ")
shell("python3 make_chart.py chart.pdf")
write("fixed-latex.tex", read("thesis.tex").replace("% FIGURE", r"\includegraphics{chart-embedded.pdf}"))
write("fixed-typst.typ", read("thesis.typ").replace("// FIGURE", '#image("chart.svg", alt: "Rainfall in mm")'))
run(r"""
gs -q -sDEVICE=pdfwrite -dEmbedAllFonts=true -o chart-embedded.pdf -c "<</NeverEmbed []>> setdistillerparams" -f chart.pdf
pdffonts chart-embedded.pdf
latexmk -pdf -interaction=nonstopmode -silent fixed-latex.tex > /dev/null 2>&1
pdftocairo -svg chart.pdf chart.svg
typst compile --pdf-standard a-2u fixed-typst.typ
python3 vera.py 2u fixed-latex.pdf fixed-typst.pdf
""")

Output

$ gs -q -sDEVICE=pdfwrite -dEmbedAllFonts=true -o chart-embedded.pdf -c "<</NeverEmbed []>> setdistillerparams" -f chart.pdf
$ pdffonts chart-embedded.pdf
name                                 type              encoding         emb sub uni object ID
------------------------------------ ----------------- ---------------- --- --- --- ---------
XBCPUG+Helvetica                     Type 1C           WinAnsi          yes yes no       7  0
$ latexmk -pdf -interaction=nonstopmode -silent fixed-latex.tex > /dev/null 2>&1
$ pdftocairo -svg chart.pdf chart.svg
$ typst compile --pdf-standard a-2u fixed-typst.typ
$ python3 vera.py 2u fixed-latex.pdf fixed-typst.pdf
fixed-latex.pdf: PDF/A-2u validation profile: compliant, 146 rules passed, 0 failed
fixed-typst.pdf: PDF/A-2u validation profile: compliant, 146 rules passed, 0 failed

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 fixes_session.py

Ghostscript wrote the figure again, as chart-embedded.pdf, with the font embedded; its documentation says its default settings never embed the 14 standard fonts, Helvetica among them, and that << /NeverEmbed [ ] >> setdistillerparams overrides that. pdftocairo turned the figure into an SVG for Typst. Then both theses passed all 146 rules.

Check the figures first

pdffonts figure.pdf before a figure goes into a thesis: every row should say yes under emb. A figure that fails is cheaper to fix on its own than a thesis is after export.

Checkpoint

Check: Validating with veraPDF

  1. Question 1 of 2 What does veraPDF do with --flavour 0?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: It chooses the profile from the file's own metadata, the part and level the file claims

    For a submission, name the profile the portal requires with --flavour, so a wrong claim fails instead of passing.

  2. Question 2 of 2 With chart.pdf, whose Helvetica is not embedded, what did each system do at export?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: LaTeX wrote the PDF without a warning, and veraPDF found rule 6.2.11.4.1 broken; Typst refused to export

    Validate every LaTeX export, and fix a bad figure before it goes in: embed its font, or convert it to SVG for Typst.

The portal checks more than PDF/A

PDF/A is one of several things a submission portal may check. submit_check.py makes seven checks of its own: A4 on every page, every font embedded, a title and an author in the PDF’s metadata, the size against a limit, the PDF/A claim in the XMP metadata, and veraPDF’s verdict. Its limit of 10 MB is an example; use your portal’s. It checks a first try, the thesis on the report class’s own settings with the figure as it came, and the final thesis:

submit_check.py: seven checks before you submit Python · submit_check.py
"""Checks a thesis PDF the way a submission portal might before it accepts the file, and prints one line per check:
what it found, and PASS or FAIL.

    python3 submit_check.py thesis.pdf --max-mb 10 --standard 2u

The checks are the page size (A4, 210 by 297 mm, within 1 mm, on every page), that every font is embedded (pdffonts'
emb column), that the PDF's metadata gives a title and an author (pdfinfo), that the file is no larger than the limit,
that its XMP metadata claims the PDF/A part and level asked for (pdfinfo -meta), and that veraPDF finds it compliant
with that profile. The limit and the standard are the portal's; the ones above are an example.
"""

import json
import os
import re
import subprocess
import sys

MM = 25.4 / 72


def output(*command):
    return subprocess.run(command, capture_output=True, text=True).stdout


def checks(pdf, max_mb, standard):
    info = output("pdfinfo", pdf)
    fields = dict(re.findall(r"^([A-Za-z ]+?):[ \t]*(.*)$", info, re.M))
    pages = int(fields["Pages"])
    sizes = re.findall(r"size:\s+([\d.]+) x ([\d.]+) pts", output("pdfinfo", "-f", "1", "-l", str(pages), pdf))
    mm = {(round(float(w) * MM, 1), round(float(h) * MM, 1)) for w, h in sizes}
    a4 = bool(mm) and all(abs(w - 210) <= 1 and abs(h - 297) <= 1 for w, h in mm)
    yield "page size", ", ".join(f"{w} × {h} mm" for w, h in sorted(mm)) + f" on {pages} pages", a4

    rows = output("pdffonts", pdf).splitlines()[2:]
    embedded = sum(1 for row in rows if row.split()[-5] == "yes")
    yield "fonts embedded", f"{embedded} of {len(rows)}", embedded == len(rows)

    for field in ("Title", "Author"):
        value = fields.get(field, "").strip()
        shown = value if len(value) <= 36 else value[:35] + "…"
        yield field.lower(), shown or "(none)", bool(value)

    size_mb = os.path.getsize(pdf) / 1_000_000
    yield "file size", f"{size_mb:.2f} MB, limit {max_mb:g} MB", size_mb <= max_mb

    meta = output("pdfinfo", "-meta", pdf)
    part = re.search(r"<pdfaid:part>(\d)</pdfaid:part>", meta)
    level = re.search(r"<pdfaid:conformance>([ABU])</pdfaid:conformance>", meta)
    claim = f"PDF/A-{part[1]}{level[1].lower()}" if part and level else "none"
    yield "PDF/A claim", claim, claim == f"PDF/A-{standard}"

    report = json.loads(output("verapdf", "--format", "json", "--flavour", standard, pdf))["report"]
    core = next(item["version"] for item in report["buildInformation"]["releaseDetails"] if item["id"] == "core")
    if not core.startswith("1.30."):
        sys.exit(f"these checks need veraPDF 1.30; found {core}")
    result = report["jobs"][0]["validationResult"]
    result = result[0] if isinstance(result, list) else result
    failed = result["details"]["failedRules"]
    yield "veraPDF", f"PDF/A-{standard}: " + ("compliant" if result["compliant"] else f"{failed} rules failed"), \
        result["compliant"]


def main(pdf, max_mb, standard):
    print(f"{pdf}:")
    print(f"  {'check':<16}{'found':<40}result")
    passed = total = 0
    for name, found, ok in checks(pdf, max_mb, standard):
        total += 1
        passed += ok
        print(f"  {name:<16}{found:<40}{'PASS' if ok else 'FAIL'}")
    print(f"  {passed} of {total} checks pass")


if __name__ == "__main__":
    arguments = sys.argv[1:]
    main(arguments[0], float(arguments[arguments.index("--max-mb") + 1]), arguments[arguments.index("--standard") + 1])
A first try and the final thesis, checked Python · submit_session.py
from session import files, read, run, shell, write

# Two PDFs through the same submission checks: a first try, the thesis on the report class's own settings with the
# chart as it came, and the final thesis, PDF/A-2u with the chart whose font Ghostscript embedded.
files("vera.py", "make_chart.py", "submit_check.py", "pdfa/thesis.tex=thesis.tex")
shell("python3 make_chart.py chart.pdf")
shell('gs -q -sDEVICE=pdfwrite -dEmbedAllFonts=true -o chart-embedded.pdf -c "<</NeverEmbed []>> setdistillerparams" '
      '-f chart.pdf')
final = read("thesis.tex").replace("% FIGURE", r"\includegraphics{chart-embedded.pdf}")
first_try = (final.replace("\\DocumentMetadata{lang=en-IN, pdfstandard=a-2u}\n", "")
             .replace("[a4paper,12pt]{report}", "{report}")
             .replace("\\hypersetup{pdftitle={Rooftop Rainwater Harvesting in Example District}, pdfauthor={Nisha Varghese}}\n", "")
             .replace("chart-embedded.pdf", "chart.pdf"))
write("first-try.tex", first_try)
write("final.tex", final)
run(r"""
latexmk -pdf -interaction=nonstopmode -silent first-try.tex > /dev/null 2>&1
latexmk -pdf -interaction=nonstopmode -silent final.tex > /dev/null 2>&1
python3 submit_check.py first-try.pdf --max-mb 10 --standard 2u
python3 submit_check.py final.pdf --max-mb 10 --standard 2u
""")

Output

$ latexmk -pdf -interaction=nonstopmode -silent first-try.tex > /dev/null 2>&1
$ latexmk -pdf -interaction=nonstopmode -silent final.tex > /dev/null 2>&1
$ python3 submit_check.py first-try.pdf --max-mb 10 --standard 2u
first-try.pdf:
  check           found                                   result
  page size       215.9 × 279.4 mm on 2 pages             FAIL
  fonts embedded  2 of 3                                  FAIL
  title           (none)                                  FAIL
  author          (none)                                  FAIL
  file size       0.03 MB, limit 10 MB                    PASS
  PDF/A claim     none                                    FAIL
  veraPDF         PDF/A-2u: 5 rules failed                FAIL
  1 of 7 checks pass
$ python3 submit_check.py final.pdf --max-mb 10 --standard 2u
final.pdf:
  check           found                                   result
  page size       210.0 × 297.0 mm on 2 pages             PASS
  fonts embedded  3 of 3                                  PASS
  title           Rooftop Rainwater Harvesting in Exa…    PASS
  author          Nisha Varghese                          PASS
  file size       0.05 MB, limit 10 MB                    PASS
  PDF/A claim     PDF/A-2u                                PASS
  veraPDF         PDF/A-2u: compliant                     PASS
  7 of 7 checks pass

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 submit_session.py

The first try passed only its file size. Its pages were US letter, 215.9 by 279.4 mm, because the report class’s default paper is letter; one of its three fonts was not embedded; it had no title and no author in its metadata, so a library catalogue or a viewer’s title bar would show none; and with no \DocumentMetadata it claimed no PDF/A, so veraPDF found 5 rules of PDF/A-2u broken. The final thesis passed all seven.

PDF Metadata Viewer & Editor Open your thesis to see its title, author and page sizes, the metadata a portal and a catalogue read.

Checkpoint

Check: Submission checks beyond PDF/A

  1. Question 1 of 2 Why were the first try's pages 215.9 by 279.4 mm?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: The report class's default paper is US letter, and the first try gave no a4paper option

    Give a4paper, or the size your manual names, in the class options or the class itself.

  2. Question 2 of 2 The first try had no title in its metadata. What sets one in LaTeX, and in Typst?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: \hypersetup{pdftitle={…}} with hyperref in LaTeX, and set document(title: …) in Typst

    Catalogues and viewers show the metadata's title, so a thesis without one appears as untitled.

Rewriting a PDF after export can undo the work

A portal’s size limit tempts you to compress the PDF after export, and online forms sometimes ask you to “optimise” it. This session passes the PDF/A-2u thesis through four programs that rewrite a PDF and validates each result:

Four rewrites of a PDF/A-2u file, validated Python · postprocess_session.py
from session import files, run

# pdfa/thesis.tex exported as PDF/A-2u, then passed through four programs that rewrite a PDF after export, as
# compressors, optimisers and cleaners do: Ghostscript with its ebook settings, qpdf linearising without and with its
# --newline-before-endstream option, and MuPDF's mutool clean. vera.py validates every result; wc -c gives the sizes.
files("vera.py", "pdfa/thesis.tex=thesis.tex")
run(r"""
latexmk -pdf -interaction=nonstopmode -silent thesis.tex > /dev/null 2>&1
gs -q -sDEVICE=pdfwrite -dPDFSETTINGS=/ebook -o ebook.pdf thesis.pdf
qpdf --linearize thesis.pdf linearized.pdf
qpdf --linearize --newline-before-endstream thesis.pdf linearized-eol.pdf
mutool clean -gggg thesis.pdf cleaned.pdf
wc -c thesis.pdf ebook.pdf linearized.pdf linearized-eol.pdf cleaned.pdf
python3 vera.py 2u thesis.pdf ebook.pdf linearized.pdf linearized-eol.pdf cleaned.pdf
""")

Output

$ latexmk -pdf -interaction=nonstopmode -silent thesis.tex > /dev/null 2>&1
$ gs -q -sDEVICE=pdfwrite -dPDFSETTINGS=/ebook -o ebook.pdf thesis.pdf
$ qpdf --linearize thesis.pdf linearized.pdf
$ qpdf --linearize --newline-before-endstream thesis.pdf linearized-eol.pdf
$ mutool clean -gggg thesis.pdf cleaned.pdf
$ wc -c thesis.pdf ebook.pdf linearized.pdf linearized-eol.pdf cleaned.pdf
   48302 thesis.pdf
   11750 ebook.pdf
   49402 linearized.pdf
   49411 linearized-eol.pdf
   50763 cleaned.pdf
  209628 total
$ python3 vera.py 2u thesis.pdf ebook.pdf linearized.pdf linearized-eol.pdf cleaned.pdf
thesis.pdf: PDF/A-2u validation profile: compliant, 146 rules passed, 0 failed
ebook.pdf: PDF/A-2u validation profile: NOT compliant, 143 rules passed, 3 failed
  6.1.7.1-2 (2 checks): Extra spacings or missing EOL characters around keywords 'stream' and 'endstream'
  6.2.4.3-4 (12 checks): DeviceGray colour space is used without output intent profile
  6.6.4-1 (1 check): The document metadata stream doesn't contain PDF/A Identification Schema
linearized.pdf: PDF/A-2u validation profile: NOT compliant, 145 rules passed, 1 failed
  6.1.7.1-2 (10 checks): Extra spacings or missing EOL characters around keywords 'stream' and 'endstream'
linearized-eol.pdf: PDF/A-2u validation profile: compliant, 146 rules passed, 0 failed
cleaned.pdf: PDF/A-2u validation profile: compliant, 146 rules passed, 0 failed

This output changes from run to run: Ghostscript writes the time it ran and a new document ID into ebook.pdf, so its size and the total change by a few bytes from run to run; every other line does not

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 postprocess_session.py

Rewrite Size in bytes PDF/A-2u
the thesis as exported 48,302 compliant
Ghostscript, -dPDFSETTINGS=/ebook 11,750 3 rules failed, the PDF/A identification gone
qpdf --linearize 49,402 1 rule failed: spacing around stream keywords
qpdf --linearize --newline-before-endstream 49,411 compliant
mutool clean -gggg 50,763 compliant

Ghostscript made the file four times smaller and, with these settings, wrote a plain PDF: its metadata kept no PDF/A identification, its grey colours were used without an output intent: A colour profile embedded in a PDF that says how its device colours, such as plain grey or RGB, are meant to look. PDF/A requires one when such colours are used., and its streams broke rule 6.1.7.1-2. qpdf’s linearisation broke that one rule only. Its manual calls the newline before endstream a requirement of PDF/A, and with --newline-before-endstream the file stayed compliant; so did MuPDF’s clean. None of the four commands printed a word. Ghostscript also wrote the time it ran and a new document ID into its file, so that file’s size changes by a few bytes from one run to the next; the other sizes stay the same. Make the file small before export, with images at the size and resolution they are printed at, and validate again after any program touches the PDF.

Key idea

The last program that writes the PDF decides whether it conforms, so shrink the inputs before export and validate after every rewrite.

Checkpoint

Check: Rewriting a PDF after export

  1. Question 1 of 2 Ghostscript's ebook settings made the thesis four times smaller. What happened to its PDF/A-2u conformance?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: It was lost; 3 rules failed, and the metadata no longer held the PDF/A identification

    Shrink the images before export instead; then the exported file is both small and conforming.

  2. Question 2 of 2 Which qpdf command linearised the thesis and kept it PDF/A-2u?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: qpdf --linearize --newline-before-endstream thesis.pdf out.pdf

    A tool may keep conformance with the right option; check with the validator after every run.

Practise: read veraPDF’s report and check a PDF before you submit

Two short functions automate what this lesson read by eye: a summary of a veraPDF report, and four of the submission checks from what pdfinfo and pdffonts print. Both run in the page against sample tests built from this lesson’s files, each with its first hint open:

Code lab · 2 steps

Read the validator, then check the PDF

Summarise a veraPDF JSON report file by file, then list what keeps a PDF from passing the page-size, font, metadata and size checks.

  1. Read a veraPDF report To do
  2. Check a PDF before you submit it To do

Exercise · Medium · Python

Read a veraPDF report

vera.py turned veraPDF's JSON report into a few readable lines. Write summary(report) in report.py, which does the same for the part of a report that matters here. report is the JSON that verapdf --format json printed, already loaded with json.loads:

  • report["report"]["jobs"] is a list with one job per file; a job's itemDetails has the file's name, and its validationResult is a list whose first item holds compliant, profileName and details;
  • details["ruleSummaries"] lists the failed rules, each with its clause, testNumber and failedChecks.

Return one string per file, in order: "<file>: compliant", or "<file>: <n> rules failed: <rules>", where the file is the last part of the name, after the last /, and the rules are written clause-test and joined by ", ". veraPDF lists failed rules in no fixed order, so sort them as vera.py does: by the numbers of the clause, compared one by one (so 6.1.7.1 comes before 6.2.4.3), then by the test number. One failed rule is written "1 rule failed".

The lesson's thesis-2b.pdf, checked against PDF/A-2u, gives "thesis-2b.pdf: 1 rule failed: 6.6.4-3".

Starter code · report.py

def summary(report):
    """Return one line per file of a veraPDF JSON report: compliant, or the rules that failed."""
    # Replace this line: read every job's result and list its failed rules.
    return []
The sample tests · test_report.py
from report import summary


def job(name, compliant, rules):
    return {
        "itemDetails": {"name": name, "size": 48302},
        "validationResult": [{
            "compliant": compliant,
            "profileName": "PDF/A-2u validation profile",
            "details": {"passedRules": 146 - len(rules), "failedRules": len(rules),
                        "ruleSummaries": [{"clause": c, "testNumber": t, "failedChecks": n} for c, t, n in rules]},
        }],
    }


def report(*jobs):
    return {"report": {"jobs": list(jobs)}}


def test_compliant():
    """says compliant for the lesson's thesis.pdf"""
    assert summary(report(job("~/demo/thesis.pdf", True, []))) == ["thesis.pdf: compliant"]


def test_one_rule():
    """names the one rule that thesis-2b.pdf failed against PDF/A-2u"""
    assert summary(report(job("~/demo/thesis-2b.pdf", False, [("6.6.4", 3, 1)]))) == [
        "thesis-2b.pdf: 1 rule failed: 6.6.4-3"
    ]


def test_several_rules_sorted():
    """sorts the failed rules of ebook.pdf by clause, whatever order the report gives"""
    rules = [("6.6.4", 1, 1), ("6.2.4.3", 4, 12), ("6.1.7.1", 2, 2)]
    assert summary(report(job("~/demo/ebook.pdf", False, rules))) == [
        "ebook.pdf: 3 rules failed: 6.1.7.1-2, 6.2.4.3-4, 6.6.4-1"
    ]


def test_clauses_compare_as_numbers():
    """puts clause 6.2.11.4.1 after 6.2.4.3, and test 3 after test 1 of one clause"""
    rules = [("6.2.11.4.1", 1, 1), ("6.6.4", 3, 1), ("6.2.4.3", 4, 2), ("6.6.4", 1, 1)]
    assert summary(report(job("~/demo/with-chart.pdf", False, rules))) == [
        "with-chart.pdf: 4 rules failed: 6.2.4.3-4, 6.2.11.4.1-1, 6.6.4-1, 6.6.4-3"
    ]


def test_several_files():
    """gives one line per file, in order"""
    jobs = [job("~/demo/thesis.pdf", True, []), job("~/demo/linearized.pdf", False, [("6.1.7.1", 2, 10)])]
    assert summary(report(*jobs)) == ["thesis.pdf: compliant", "linearized.pdf: 1 rule failed: 6.1.7.1-2"]


def test_plain_file_name():
    """keeps a name without a folder as it is"""
    assert summary(report(job("cleaned.pdf", True, []))) == ["cleaned.pdf: compliant"]
A hint

Loop over report["report"]["jobs"], take job["validationResult"][0], and build each rule's name with f"{rule['clause']}-{rule['testNumber']}". name.rsplit("/", 1)[-1] keeps the part after the last slash, and sorted(rules, key=lambda r: ([int(n) for n in r["clause"].split(".")], r["testNumber"])) puts the rules in order.

The sample tests run on this device, in your browser (Pyodide): nothing is sent to mysmartcopilot.com. The first run downloads Python (about 13.5 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.

Exercise · Medium · Python

Check a PDF before you submit it

Write problems(pdfinfo, pdffonts, size_bytes, max_mb) in problems.py: four of the lesson's submission checks, read from what the tools print. pdfinfo is the text that pdfinfo -f 1 -l <pages> printed, pdffonts the text of pdffonts, size_bytes the file's size and max_mb the portal's limit in megabytes (1 MB is 1,000,000 bytes).

Return the problems as strings, in this order:

  1. "page <n> is <w> × <h> mm, not A4" for each line Page <n> size: <w> x <h> pts whose size, in millimetres (25.4 / 72 to the point) and rounded to 0.1, is more than 1 mm away from 210 by 297;
  2. "font <name> is not embedded" for each font row whose emb column, the fifth from the end, is no;
  3. "no title" and "no author" when the Title: or Author: line is missing or empty;
  4. "file is <x> MB, over <max_mb> MB" when the file is larger than the limit, with <x> to two decimals.

The lesson's first try gives ["page 1 is 215.9 × 279.4 mm, not A4", "page 2 is 215.9 × 279.4 mm, not A4", "font Helvetica is not embedded", "no title", "no author"].

Starter code · problems.py

import re


def problems(pdfinfo, pdffonts, size_bytes, max_mb):
    """Return what keeps this PDF from passing the four submission checks."""
    # Replace this line: read the page sizes, the fonts, the metadata and the size.
    return []
The sample tests · test_problems.py
from problems import problems

FIRST_TRY = """Title:           
Subject:         
Keywords:        
Author:          
Creator:         LaTeX with hyperref
Producer:        pdfTeX-1.40.29
Pages:           2
Encrypted:       no
Page    1 size:  612 x 792 pts (letter)
Page    1 rot:   0
Page    2 size:  612 x 792 pts (letter)
Page    2 rot:   0
File size:       30165 bytes
"""
FIRST_TRY_FONTS = """name                                 type              encoding         emb sub uni object ID
------------------------------------ ----------------- ---------------- --- --- --- ---------
ZRRFJI+CMBX12                        Type 1            Builtin          yes yes yes     16  0
VNVMRQ+CMR10                         Type 1            Builtin          yes yes yes     17  0
Helvetica                            Type 1            WinAnsi          no  no  no      24  0
"""
FINAL = """Title:           Rooftop Rainwater Harvesting in Example District
Author:          Nisha Varghese
Pages:           2
Page    1 size:  595.276 x 841.89 pts (A4)
Page    2 size:  595.276 x 841.89 pts (A4)
"""
FINAL_FONTS = """name                                 type              encoding         emb sub uni object ID
------------------------------------ ----------------- ---------------- --- --- --- ---------
QVQPVE+SFBX2488                      Type 1            Custom           yes yes yes     18  0
FOIFPW+SFRM1200                      Type 1            Custom           yes yes yes     19  0
XBCPUG+Helvetica                     Type 1C           WinAnsi          yes yes no      30  0
"""


def test_the_first_try():
    """reports every problem of the lesson's first try"""
    assert problems(FIRST_TRY, FIRST_TRY_FONTS, 30165, 10) == [
        "page 1 is 215.9 × 279.4 mm, not A4", "page 2 is 215.9 × 279.4 mm, not A4",
        "font Helvetica is not embedded", "no title", "no author",
    ]


def test_the_final_thesis():
    """finds nothing wrong with the final thesis"""
    assert problems(FINAL, FINAL_FONTS, 48302, 10) == []


def test_too_large():
    """reports a file over the limit, to two decimals"""
    assert problems(FINAL, FINAL_FONTS, 12_400_000, 10) == ["file is 12.40 MB, over 10 MB"]


def test_empty_title():
    """treats an empty Title line as no title"""
    info = FINAL.replace("Title:           Rooftop Rainwater Harvesting in Example District", "Title:")
    assert problems(info, FINAL_FONTS, 48302, 10) == ["no title"]


def test_one_wrong_page():
    """reports only the page that is not A4, within 1 mm"""
    info = FINAL.replace("Page    2 size:  595.276 x 841.89 pts (A4)", "Page    2 size:  841.89 x 595.276 pts (A4)")
    assert problems(info, FINAL_FONTS, 48302, 10) == ["page 2 is 297.0 × 210.0 mm, not A4"]


def test_a_millimetre_is_fine():
    """accepts a page within 1 mm of A4"""
    info = FINAL.replace("595.276 x 841.89", "596 x 842")
    assert problems(info, FINAL_FONTS, 48302, 10) == []


def test_one_side_wrong():
    """reports a page whose width is right but whose height is not"""
    info = FINAL.replace("595.276 x 841.89", "595.276 x 793.7")
    assert problems(info, FINAL_FONTS, 48302, 10) == [
        "page 1 is 210.0 × 280.0 mm, not A4", "page 2 is 210.0 × 280.0 mm, not A4",
    ]
A hint

Regular expressions do most of the reading: re.findall(r"^Page +(\d+) size: +([\d.]+) x ([\d.]+) pts", pdfinfo, re.M) for the sizes, and re.search(r"^Title:[ \t]*(\S.*)$", pdfinfo, re.M) for a title that is not empty. The font rows are the lines after the first two of pdffonts.

The sample tests run on this device, in your browser (Pyodide): nothing is sent to mysmartcopilot.com. The first run downloads Python (about 13.5 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.

Finally, practise the ideas. Each question explains its answer as soon as you check it, and the session returns to what you have not yet got right:

Practice that adapts to you

Practise this lesson

15 questions about 5 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.

  1. Question 1 of 15 What makes a PDF a PDF/A file?

    Choose one answer.

    Show the answer: Question 1 of 15

    Answer: It follows the rules of one part and level of ISO 19005, and its XMP metadata says which

    A file conforms, or not, to the part and level it states.

  2. Question 2 of 15 A repository asks for PDF/A-2a. What must your export have that a 2u export does not?

    Choose one answer.

    Show the answer: Question 2 of 15

    Answer: Tags that make it an accessible tagged PDF, and no private-use characters

    Level a builds on u; the track's accessibility lessons show how to make tagged PDFs.

  3. Question 3 of 15 Your library asks for "PDF/A". Which level do you export, and how do you decide?

    Choose one answer.

    Show the answer: Question 3 of 15

    Answer: Read the library's instructions for the part and level; if they name none, ask, and validate against what they confirm

    Ask for the exact part and level, export that, and validate against that profile.

  4. Question 4 of 15 Which values does the pdfstandard key of \DocumentMetadata accept for PDF/A in TeX Live 2026?

    Choose one answer.

    Show the answer: Question 4 of 15

    Answer: A-1B, A-2A, A-2B, A-2U, A-3A, A-3B, A-3U, A-4, A-4F and A-4E

    Written in lower case in the document, as in pdfstandard=a-2u.

  5. Question 5 of 15 typst compile --pdf-standard a-2u stopped with PDF/A-2u error: missing document date. What fixes it?

    Choose one answer.

    Show the answer: Question 5 of 15

    Answer: Give the document a date instead of date: none, or leave Typst's default date in place

    Typst checks PDF/A's rules as it exports and names the one that would break, with a hint to fix it.

  6. Question 6 of 15 A guide from a few years ago tells you to load pdfx with the a-2u option. What do you do in a TeX Live 2026 document?

    Choose one answer.

    Show the answer: Question 6 of 15

    Answer: Use \DocumentMetadata{pdfstandard=a-2u} instead, because the tagging status list says pdfx is suppressed under the PDF management

    Old advice is a common source of broken exports; check the status list for every package a guide names.

  7. Question 7 of 15 Why does vera.py read veraPDF's JSON report rather than print it?

    Choose one answer.

    Show the answer: Question 7 of 15

    Answer: The report also holds build dates and times that change on every run, and the useful part is short

    A short, stable report is one you can compare between builds.

  8. Question 8 of 15 veraPDF reports 6.2.11.4.1: The font program is not embedded. Where do you look first?

    Choose one answer.

    Show the answer: Question 8 of 15

    Answer: At pdffonts for the PDF and for each included figure, to find the font whose emb column says no

    The rule's message names the problem; pdffonts names the font, and the figure is where it came from.

  9. Question 9 of 15 Why did the lesson fix chart.pdf before it went into the thesis, instead of fixing the thesis PDF afterwards?

    Choose one answer.

    Show the answer: Question 9 of 15

    Answer: Rewriting the exported thesis can break its PDF/A conformance, while a fixed figure lets the export conform from the start

    Fix inputs, export once, validate the export; rewrite the export only with a tool you validate afterwards.

  10. Question 10 of 15 How many of the first try's fonts were embedded?

    Type a number.

    Show the answer: Question 10 of 15

    Answer: 2 fonts

    submit_check.py found 2 of 3: the two fonts of the text were embedded, and the figure's Helvetica was not.

  11. Question 11 of 15 Your thesis is 14 MB and the portal accepts 10 MB. What do you do?

    Choose one answer.

    Show the answer: Question 11 of 15

    Answer: Scale the images to the size and resolution they are printed at, export again, and validate the new file

    The size limit and the standard are both met when the inputs are right before export.

  12. Question 12 of 15 Why run a submission check of your own before the portal runs its own?

    Choose one answer.

    Show the answer: Question 12 of 15

    Answer: It shows every problem at once, with the numbers, while there is still time to rebuild

    Write down the portal's rules as checks, as the template lesson did for the manual, and run them on every build.

  13. Question 13 of 15 Which of the four rewrites kept the thesis PDF/A-2u without any special option?

    Choose one answer.

    Show the answer: Question 13 of 15

    Answer: mutool clean -gggg

    A tool keeping conformance once is no promise for the next version or file: validate after every rewrite.

  14. Question 14 of 15 Ghostscript's default settings never embed the 14 standard fonts. How did the lesson still use Ghostscript to embed Helvetica in its figure?

    Choose one answer.

    Show the answer: Question 14 of 15

    Answer: It set the distiller parameter NeverEmbed to an empty list with setdistillerparams

    pdffonts then showed XBCPUG+Helvetica with yes under emb, and the thesis passed PDF/A-2u.

  15. Question 15 of 15 An online form offers to optimise your PDF/A thesis before upload. What do you do?

    Choose one answer.

    Show the answer: Question 15 of 15

    Answer: Decline, or run veraPDF on the optimised file before you submit it

    The last program to touch the file decides whether it conforms, so the validator runs after the last program.

Interview questions

Warm-up (fresher to 2–5 years): what does PDF/A-2u promise that PDF/A-2b does not, and which should a thesis claim?

Both are PDF/A-2, the part of ISO 19005 based on PDF 1.7, and a file of either level follows that part’s rules: for example, every font is embedded, device colours come with an output intent, and the XMP metadata names the part and level. Level u adds one promise: all the text consists of known Unicode code points, so it can be searched, copied and read aloud correctly. The level is also a claim in the file’s metadata, and validators check the claim: in this lesson, a LaTeX file exported with pdfstandard=a-2b passed all 144 rules of the PDF/A-2b profile but failed the PDF/A-2u profile on one rule, because it said conformance B. For a thesis, export the level the repository asks for, and validate against exactly that level.

Two harder questions follow: a thesis rejected by a repository after it was compressed, and the checks a department should run on every thesis it receives. Decide your first step before you open each one:

Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.

  1. Fresher to mid

    A university repository rejects your thesis as "not PDF/A compliant". You exported it as PDF/A-2b from LaTeX, and then compressed it with an online tool to get under the 10 MB limit. Walk through how you find the cause and what you submit instead.

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  2. Mid to senior

    A department receives about 150 theses a year, written in LaTeX, Typst and word processors, and the library wants PDF/A-2u with A4 pages, embedded fonts, a title and an author, and at most 20 MB. Design the checks that run on every submission, and say what a student sees when one fails.

    Model answer, Pro. First step and follow-ups

Key takeaways

  • PDF/A is a family of standards for PDFs meant to be kept; a file conforms to the part and level its XMP metadata states, such as 2b or 2u, where u adds text that maps to Unicode.
  • LaTeX exports PDF/A with \DocumentMetadata{…, pdfstandard=a-2u} before \documentclass, Typst with --pdf-standard a-2u, and Typst stops at export where LaTeX writes a file that does not conform.
  • veraPDF validates against one profile at a time, and its report names the clause and the reason for every failed rule, which points at the fix.
  • A submission check covers more than PDF/A: A4 pages, embedded fonts, a title and an author, and the size limit.
  • Rewriting a PDF after export can silently remove its conformance, so shrink the inputs instead, and validate after any program has touched the file.

Flashcards · 8 cards

Recall it, then check

Say the answer to yourself before you look: recalling it is what makes it stick.

  1. What is PDF/A?

    The ISO 19005 family of standards for PDFs meant to be kept: the file carries what it needs to show the same pages later, its fonts among them, and names its part and level in its XMP metadata.

  2. How do PDF/A-2b and PDF/A-2u differ?

    Both follow part 2, which is based on PDF 1.7; level u also promises that all the text maps to known Unicode code points, so it can be searched and copied.

  3. How do you ask for PDF/A-2u when you export?

    LaTeX: \DocumentMetadata{lang=en-IN, pdfstandard=a-2u} above \documentclass. Typst: typst compile --pdf-standard a-2u.

  4. Why do pdfx and hyperxmp not belong in a new document?

    The tagging status list marks them as not supported with the new PDF management; set standards with the keys of \DocumentMetadata instead.

  5. How do you choose the profile veraPDF checks against?

    --flavour 2u for a given part and level, or --flavour 0 for the profile the file's own metadata names; --format json gives a report a program can read.

  6. What did a figure with Helvetica not embedded do in each system?

    pdflatex built the thesis without a warning and veraPDF failed rule 6.2.11.4.1; Typst refused the PDF image at export. Embed the font with Ghostscript, or convert the figure to SVG for Typst.

  7. What did the first-try thesis fail in the submission check?

    US letter pages, the report class's default; a font not embedded; no title or author in the metadata; and no PDF/A claim. Only its file size passed.

  8. Which rewrites of a PDF/A-2u file broke it?

    Ghostscript's /ebook settings removed its PDF/A identification, and qpdf --linearize broke one rule, both without a word. qpdf with --newline-before-endstream and mutool clean kept it compliant.

Terms in this lesson

conformance level
The letter after a PDF/A part, as in PDF/A-2b or 2u: b for the basic rules, u for text that maps to Unicode as well, a for an accessible tagged file as well.
output intent
A colour profile embedded in a PDF that says how its device colours, such as plain grey or RGB, are meant to look. PDF/A requires one when such colours are used.
PDF/A
The ISO 19005 family of standards for PDFs meant to be kept, whose files must carry what they need to show the same pages later, such as their fonts, and name their part and level in their metadata.
validation profile
The set of rules that veraPDF checks a file against, one profile for each part and level of PDF/A or PDF/UA, such as the 146 rules of PDF/A-2u.
XMP metadata
The block of XML inside a PDF that describes the file, such as its title, its author and, in a PDF/A file, the part and conformance level it claims. pdfinfo -meta prints it.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.