Skip to content

fix: accept every PDF standard and fail on invalid combinations - #38

Open
msallin wants to merge 1 commit into
evolvedlight:developfrom
swisspost:fix/pdf-standard-parsing
Open

fix: accept every PDF standard and fail on invalid combinations#38
msallin wants to merge 1 commit into
evolvedlight:developfrom
swisspost:fix/pdf-standard-parsing

Conversation

@msallin

@msallin msallin commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Problem

Two defects in how pdfStandards is handled, both ending in a document that does not conform to what was requested.

PDF/UA-1 could not be requested. The accepted names were a hand-written match in compile_internal. It listed the PDF versions and the PDF/A levels but omitted ua-1, so the accessibility standard was rejected outright even though typst-pdf 0.15.1 supports it. Any standard added by a later Typst release would need the table updated by hand.

Invalid combinations were silently downgraded. export_pdf called PdfStandards::new(standards).unwrap_or_default(). When a combination is contradictory the validation error was discarded and export fell back to the default configuration, producing an ordinary PDF while reporting success. A pipeline could believe it was writing PDF/A until an archive validator disagreed.

Running the new tests against develop:

[Test Failure] InvalidOperationException: Invalid PDF standard: ua-1
[Test Failure] AssertionException: Expected exception message to contain "PDF/A"
[Test Failure] AssertionException: Expected exception message to contain "same time"
[Test Failure] AssertionException: Expected to throw InvalidOperationException

The last is the silent downgrade: ["a-1b", "v-2.0"] contradicts itself, and the export succeeded anyway.

Change

Standard names are parsed through PdfStandard's own serde representation instead of a table, so every variant Typst defines is accepted and future ones need no change here. PdfStandard is #[non_exhaustive], so an exhaustive match was not possible from outside the crate in any case. The v- prefix on plain PDF versions is kept as an alias because the README documents v-1.7, and it only applies where a version number follows, so it does not quietly become a second spelling of every standard.

export_pdf propagates the error from PdfStandards::new instead of discarding it, including the hints Typst attaches, which name the PDF versions each standard allows. The wording for an unknown name is unchanged, so InvalidPdfStandardThrowsException still passes.

The README now lists the accepted names and documents what archival and accessibility export require of the document itself.

Compatibility

This is a breaking change for a caller who passes a contradictory combination today: they currently receive a PDF and will receive an exception. That is the point of the change, but it is called out under Changed in the release notes rather than Fixed.

Tests

Nine Rust unit tests over the parser cover every alias form, ua-1, case-insensitivity, blank entries, unknown names in either position, and that v-a-2b is not accepted. Six tests in Tests.cs cover the behaviour end to end: ua-1 marks the output with pdfuaid:part, a-2b marks it with pdfaid:part where a plain export does not, and each contradictory combination throws with the upstream message.

cargo test --release   12 passed
dotnet test            51 passed

Note for reviewers: PDF/A requires the document to carry a date, and the exporter deliberately sets no timestamp, so the conformance tests set one in the template. This is now documented in the README.

The names accepted by `pdfStandards` were a hand-written table. It omitted
`ua-1`, so PDF/UA-1 could not be requested at all even though typst-pdf
supports it, and every future standard would need the table updated.
`PdfStandard` is `#[non_exhaustive]` and derives serde, so the names are now
read from the enum itself. The `v-` prefix on plain PDF versions stays valid
as an alias because the README documents it, and applies only to a version
number so that it does not become a second spelling of every standard.

Separately, `export_pdf` called `PdfStandards::new(..).unwrap_or_default()`,
which discarded the validation error and exported an ordinary PDF while
reporting success. An archival pipeline could believe it was storing PDF/A
documents that carried no conformance at all, and nothing would reveal it
until a validator rejected them. The error is now propagated with the hints
Typst attaches, which name the PDF versions each standard allows. This is a
breaking change for a caller who passes a contradictory combination today.

The README now lists the accepted names and the document metadata that
archival and accessibility export require.
@msallin
msallin force-pushed the fix/pdf-standard-parsing branch from c8c91d7 to fcc8352 Compare September 4, 2026 11:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant