PDF/A validation

The pikepdf.pdfa module prepares documents for PDF/A-1b, PDF/A-2b and PDF/A-3b, checks them, and saves them with validation of the written bytes. It needs the optional extra pikepdf[pdfa]. See PDF/A validation and repair for a discussion of how to use it and what it does not cover.

Allowlist validator for PDF/A-1b, 2b and 3b files.

The validator approves a file only if every construct it encounters is recognized and known to conform. Anything it does not recognize is reported as a finding, so a denial may be a false alarm but an approval should never be.

This module needs optional dependencies; install them with pip install 'pikepdf[pdfa]'.

Functions

pikepdf.pdfa.save(pdf, filename_or_stream, flavour, *, output_intent='sRGB', output_condition_identifier=None, repair=True, **user_save_kwargs)

Prepare a document for PDF/A, write it, and validate what was written.

save is the only call that promises anything: it reopens the file it wrote and validates it, so a returned report always describes the bytes at the destination. The cost is one extra parse of the output.

The steps are:

  1. Resolve the save settings with resolve_save_kwargs. A conflicting setting raises before the document is changed.

  2. If repair is True, run prepare on pdf. This modifies the in-memory document (output intents, metadata, annotations and so on); the changes remain whether or not the save succeeds. If repair is False, nothing is repaired, and the caller is responsible for making the document conform.

  3. Write the document to a temporary file and validate it. Only a 'pass' verdict is accepted; 'fail' and 'not_checked' both raise PdfaError.

  4. Move the verified file into place (for a path), or copy its bytes into the stream, replacing its previous contents.

A failed save leaves an existing destination file untouched, and does not create a new one. A stream destination is written only on success.

Parameters:
  • pdf (pikepdf.Pdf) – The document to save.

  • filename_or_stream (pathlib.Path | str | bytes | os.PathLike | BinaryIO | None) – A path or a seekable, writable binary stream, positioned at its start. If None, the document is saved over the file it was opened from, which requires pikepdf.open(..., allow_overwriting_input=True), as for pikepdf.Pdf.save.

  • flavour (pikepdf.pdfa._flavour.Flavour | str) – '1b', '2b' or '3b'.

  • output_intent (str | bytes | None) – Passed to prepare.

  • output_condition_identifier (str | None) – Passed to prepare.

  • repair (bool) – If True, run prepare before writing.

  • **user_save_kwargs (Any) – pikepdf.Pdf.save settings, as accepted by resolve_save_kwargs.

Returns:

The report on the written file; it passed. report.save_kwargs holds the settings used and report.prepared the PrepareResult (None if repair is False).

Raises:
  • PdfaError – If the written file did not pass; e.report explains why, and e.report.prepared holds the PrepareResult as for a successful save. Nothing was written to the destination.

  • ValueError – If flavour is not a supported flavour, a save setting conflicts with the flavour, the output intent is invalid, or the destination is the input file and overwriting it was not allowed, or a stream destination is not at its start.

  • PermissionError – If the destination file is not writable.

  • TypeError – If a keyword is not a supported save setting, or the destination is not a path or binary stream.

Return type:

pikepdf.pdfa._report.Report

pikepdf.pdfa.prepare(pdf, flavour, *, output_intent='sRGB', output_condition_identifier=None)

Apply the repairs that make a document acceptable to PDF/A, in memory.

The repairs change structure and metadata, never page content: the output intents are replaced with a single PDF/A output intent, image interpolation is removed, hidden or non-viewable annotations are removed and the Print flag is set on the others, alternate presentations and document-level JavaScript are removed, PDF/A-1 subset CIDFonts are given a /CIDSet, PDF/A-2 and PDF/A-3 non-symbolic TrueType fonts are given the /BaseEncoding their encoding lacks where that changes no glyph drawn, PDF/A-3 embedded files are given the MIME type, /AFRelationship and /AF entry they lack, and the XMP metadata is rewritten with only what the flavour permits and a declaration of PDF/A conformance (level B), with the DocInfo entries set to agree with it.

Calling prepare again on the same document makes no further changes (except the time recorded in xmp:MetadataDate). The document must still be saved with settings suited to the flavour and validated; prepare does not convert colour, embed fonts or fix page content.

Parameters:
  • pdf (pikepdf.Pdf) – The document to change.

  • flavour (pikepdf.pdfa._flavour.Flavour | str) – '1b', '2b' or '3b'.

  • output_intent (bytes | str | None) – 'sRGB' (any case) for the sRGB profile shipped with pikepdf, the bytes of an ICC output profile (RGB, CMYK or gray; version 2 for PDF/A-1, up to version 4 otherwise), or None to keep the document’s output intents as they are.

  • output_condition_identifier (str | None) – The /OutputConditionIdentifier of the output intent; by default 'sRGB' for the sRGB profile, else the profile’s description, or 'Custom'.

Returns:

What was changed.

Raises:
  • ValueError – If flavour is not a supported flavour, or output_intent is not usable. The document is not changed.

  • TypeError – If output_intent is of the wrong type.

Return type:

PrepareResult

pikepdf.pdfa.check(pdf, flavour, **user_save_kwargs)

Check an open document against a PDF/A flavour as it would be saved.

The check evaluates the file that pdf.save(path, **report.save_kwargs) would write: the stream filters qpdf rewrites, the trailer, the PDF version and the objects written (orphaned objects are not written, so they are not checked). Nothing is written and the document is not modified.

check is pure and cheap. It describes the document at the moment of the call, so run it after the last metadata edit. It is advisory: save is the only call that promises anything, because it validates the bytes it writes.

Problems with the document never raise: unexpected errors become unsupported findings with rule id pikepdf:internal.

Parameters:
  • pdf (pikepdf.Pdf) – The document to check.

  • flavour (pikepdf.pdfa._flavour.Flavour | str) – '1b', '2b' or '3b'.

  • **user_save_kwargs (Any) – pikepdf.Pdf.save settings the caller intends to use, as accepted by resolve_save_kwargs.

Returns:

The report; report.save_kwargs holds the complete settings the verdict assumes.

Raises:
  • ValueError – If flavour is not a supported flavour, or a save setting conflicts with the flavour.

  • TypeError – If a keyword is not a supported save setting.

Return type:

pikepdf.pdfa._report.Report

pikepdf.pdfa.repair_annotation_flags(pdf)

Make the annotation flags of every page acceptable to PDF/A.

PDF/A requires every annotation to set the Print flag and to clear the Hidden, Invisible and NoView flags; PDF/A-2 and PDF/A-3 also require ToggleNoView to be clear. An annotation with any of those four flags set is removed, as Ghostscript does, rather than made visible. This function does not take a PDF/A part, so it removes annotations with ToggleNoView for every part, including PDF/A-1, whose rule predates that flag and does not mention it; the flags that remain are acceptable to every part. Along with a removed annotation goes its /Popup annotation, and a /Popup entry that refers to a removed annotation is deleted. The remaining annotations are given the Print flag if they lack it, leaving their other flags alone. Only annotations listed in a page’s /Annots are considered. The repair is idempotent.

pikepdf.pdfa.prepare() calls this function, but it may also be used on its own, for example to preserve hyperlinks before handing a file to another PDF/A converter: Ghostscript’s PDF/A mode silently deletes any annotation without the Print flag, which includes a /Link annotation written with no /F entry. It needs nothing beyond importing pikepdf.pdfa, and it neither validates the document nor changes anything other than page annotations.

Logs up to two debug messages to a pikepdf.pdfa logger: one if any annotation was removed, and one if any annotation was given the Print flag.

Parameters:

pdf (pikepdf.Pdf) – An open pikepdf.Pdf object, modified in place.

Returns:

What was changed.

Return type:

AnnotationRepairResult

pikepdf.pdfa.resolve_save_kwargs(flavour, **user)

Return the complete pikepdf.Pdf.save() keyword arguments for PDF/A.

Settings that could make the written file differ from what the validator checked, or that PDF/A forbids, are pinned; passing a pinned key with its pinned value is allowed, any other value raises ValueError. The remaining settings are the caller’s choice, with defaults. The result is the complete set of keyword arguments for pikepdf.Pdf.save(), including progress and linearize, so pdf.save(path, **resolve_save_kwargs('2b')) works directly. Pass your own choices into resolve_save_kwargs (for example resolve_save_kwargs('2b', linearize=True, progress=callback)) rather than merging them into the result afterwards, so that they are checked against the flavour. The destination is not included.

Raises:
  • TypeError – A keyword is not an argument of pikepdf.Pdf.save().

  • ValueError – A pinned setting conflicts, or a version is invalid or too high for the flavour.

Parameters:
  • flavour (pikepdf.pdfa._flavour.Flavour | str)

  • user (Any)

Return type:

dict[str, Any]

pikepdf.pdfa.validate_written(input_file, flavour, save_kwargs=None)

Validate a saved PDF/A candidate file.

Problems with the file never raise: unexpected errors become unsupported findings with rule id pikepdf:internal.

Parameters:
  • input_file (pathlib.Path | str | BinaryIO) – The saved candidate file, or a seekable binary stream holding it.

  • flavour (pikepdf.pdfa._flavour.Flavour | str) – '1b', '2b' or '3b'.

  • save_kwargs (collections.abc.Mapping[str, Any] | None) – The pikepdf.Pdf.save settings the file was written with, recorded in report.save_kwargs.

Returns:

The report; report.passed is True if the file is approved.

Raises:

ValueError – If flavour is not a supported flavour.

Return type:

pikepdf.pdfa._report.Report

Results

class pikepdf.pdfa.Report

Result of checking one document against one PDF/A flavour.

flavour

The PDF/A flavour checked.

findings

Every reason the document was not approved.

save_kwargs

The pikepdf.Pdf.save keyword arguments the check assumed, or that were used to write the file.

output_intent

ICC colour space signature of the PDF/A OutputIntent profile ('RGB', 'CMYK' or 'GRAY'), or None if unknown.

prepared

What prepare changed before save wrote the file, or None if the report did not come from save or nothing was prepared.

property passed: bool

True if nothing was found that prevents approval.

Return type:

bool

summary(limit=20)

Return a multi-line description of the result.

Parameters:

limit (int) – Maximum number of findings to list.

Return type:

str

property unsupported: tuple[Finding, ...]

Findings of constructs the validator does not check.

Return type:

tuple[Finding, …]

property verdict: Verdict

'pass', 'fail' or 'not_checked'.

'fail' if any finding is a violation; 'not_checked' if there are findings but all of them are constructs the validator does not check; 'pass' if there are no findings.

Return type:

Verdict

property violations: tuple[Finding, ...]

Findings of constructs that break PDF/A.

Return type:

tuple[Finding, …]

class pikepdf.pdfa.Finding

One reason a file was not approved.

rule

veraPDF rule id (ISO_19005_2:6.2.8-3), pikepdf:schema-<role> for an unrecognized construct in a schema role, pikepdf:<name> for a local policy, or pikepdf:internal for an unexpected error.

where

Human-readable location, e.g. obj 12 0 (Page) /AA.

message

What was wrong.

kind

violation if the construct breaks PDF/A; unsupported if it may be valid PDF/A but the validator does not check it.

class pikepdf.pdfa.PrepareResult

What prepare changed.

output_intent_replaced

True if the output intents were replaced. False if the requested intent was already the document’s only output intent, or if the output intents were kept.

output_intent

The colour space ('RGB', 'CMYK' or 'GRAY') of the requested output intent, whether it was installed now or already present; None if the document’s output intents were kept.

interpolation_removed

The number of images whose /Interpolate entry was removed.

annotations_removed

The number of hidden or non-viewable annotations removed, by subtype (without the leading slash).

annotations_removed_pages

The pages (1-based) annotations were removed from.

print_flags_set

The number of annotations given the Print flag.

cidsets_added

The number of /CIDSet streams added to subset CIDFonts (PDF/A-1 only).

base_encodings_added

The number of non-symbolic TrueType fonts whose encoding, with /Differences but no /BaseEncoding, was given /BaseEncoding /WinAnsiEncoding (PDF/A-2 and PDF/A-3 only).

alternate_presentations_removed

True if the name dictionary’s /AlternatePresentations (slideshows) was removed.

document_javascript_removed

True if the name dictionary’s /JavaScript (document-level JavaScript) was removed.

embedded_file_subtypes_set

The number of embedded file streams without a MIME type given /application/octet-stream (PDF/A-3 only).

af_relationships_set

The number of file specifications of embedded files given /AFRelationship /Unspecified (PDF/A-3 only).

associated_files_added

The number of embedded files added to the catalog’s /AF array, making them associated files (PDF/A-3 only).

xmp_dropped

Labels (prefix:name) of the XMP properties removed because PDF/A does not permit them.

xmp_unreadable

True if an XMP packet that could not be read was replaced.

xmp_problem

Why the XMP packet could not be read, if xmp_unreadable; otherwise None.

dates_zoned

The number of document dates given the local time zone.

pdfa_declared

True if the metadata did not already declare the flavour’s PDF/A conformance.

property changed: bool

True if prepare changed anything but the metadata timestamps.

prepare always records pikepdf as the producer and the time in xmp:MetadataDate; that alone does not count as a change.

Return type:

bool

describe()

Return one sentence for each kind of change made.

The sentences of messages, without their levels.

Return type:

list[str]

messages()

Return a (level, sentence) pair for each kind of change made.

The level suggests how prominently to report the change: 'warning' for annotations removed, which discards content; 'info' for Print flags set, alternate presentations and document-level JavaScript removed, XMP properties removed and an unreadable XMP packet replaced; 'debug' for the rest. It is one of the lowercase names of the logging levels, so logging.getLevelName(level.upper()) gives the level number.

Return type:

list[tuple[str, str]]

class pikepdf.pdfa.AnnotationRepairResult

What pikepdf.pdfa.repair_annotation_flags() changed.

An empty result (no removals and no Print flags set) means the annotation flags of every page were already acceptable to PDF/A.

print_flags_set: int = 0

The number of annotations that were kept and given the Print flag, because they had no /F entry or had one without the Print bit.

removed: collections.Counter[str]

The number of annotations removed, keyed by subtype without the leading slash (for example 'Link' or 'Popup'); 'unknown' counts annotations that have no valid /Subtype.

removed_pages: set[int]

The page numbers (1-based) from which annotations were removed.

class pikepdf.pdfa.Flavour

A PDF/A part and conformance level that the validator can check.

PDFA_1B = '1b'
PDFA_2B = '2b'
PDFA_3B = '3b'
property part: int

ISO 19005 part number (1, 2 or 3).

Return type:

int

exception pikepdf.pdfa.PdfaError(report)

A document did not pass PDF/A validation.

Parameters:

report (Report)

report

The Report that explains why.