Controlling PDF/A and PDF/UA conformance level in SkPDF

16 views
Skip to first unread message

mattl...@live.com

unread,
May 21, 2026, 7:50:13 PM (6 days ago) May 21
to skia-discuss
Currently `SkPDF::Metadata::fPDFA` is a bool that, when true, writes a hardcoded PDF/A-2B declaration in the XMP:

```xml
<pdfaid:part>2</pdfaid:part>
<pdfaid:conformance>B</pdfaid:conformance>
```


I'm running into cases where callers need to declare other levels (PDF/A-1B for archival, PDF/A-3B for EU e-invoicing, PDF/UA-1 for accessibility). The XMP values are just three fields:

- `<pdfaid:part>` (1, 2, or 3)
- `<pdfaid:conformance>` (A, B, or U)
- `<pdfuaid:part>` (1)


No rendering behavior needs to change. It's the same XMP block, same output intent, same UUID generation. The caller is responsible for ensuring the content satisfies whichever level they declare, which is how `fPDFA = true` works today.

Three possible approaches, following the pattern used for `DateTime`:

**Option A -- Additive, backward compatible:**

```cpp
struct PDFAStandard {
    uint8_t fPart = 0;         // 0=none, 1, 2, 3
    char    fConformance = 0;  // 0=none, 'A', 'B', 'U'
    uint8_t fUAPart = 0;       // 0=none, 1
};

// In Metadata:
bool fPDFA = false;                       // existing, unchanged
PDFAStandard fPDFAStandard = {0, 0, 0};   // new, zero = not set
```


When `fPDFAStandard.fPart > 0`, it takes precedence. Otherwise falls back to the existing `fPDFA` bool. No breakage.

**Option B -- Clean replacement:**

```cpp
PDFAStandard fPDFAStandard = {0, 0, 0};  // replaces bool fPDFA
```


Cleaner, but breaks existing callers. Migration is trivial (`fPDFA = true` becomes `fPDFAStandard = {2, 'B', 0}`).

**Option C -- Separate XMP emission from conformance:**

Today `fPDFA` does double duty: it toggles XMP emission AND declares PDF/A-2B conformance. These could be separated:

```cpp
bool fEmitXMP = false;                   // controls XMP, UUID, output intent
PDFAStandard fPDFAStandard = {0, 0, 0};  // controls conformance declaration only
```


`fEmitXMP = true` with a zero `fPDFAStandard` gives you full XMP metadata (title, author, dates, UUIDs, output intent) without declaring any conformance level. Setting a non-zero `fPDFAStandard` automatically implies XMP emission. The existing `fPDFA` becomes a convenience alias for setting both (`fEmitXMP = true` + `fPDFAStandard = {2, 'B', 0}`).

A few questions:

1. Is there interest in exposing control over these values?
2. Would Option A (additive, backward compatible) be preferred, or is a clean break acceptable?
3. PDF/UA-1 also needs an extension schema block in the XMP. Is that something the Skia team would want in-tree, or better left to callers?

For context, QuestPDF maintains a custom Skia build that patches these files to expose conformance selection. Would be great to get this upstream so they (and others) don't have to carry that.

Happy to put together a CL if there's interest.

Matthew
Reply all
Reply to author
Forward
0 new messages