Skip to content

PDF/VT composition and print identity

Reference imported from v2.4.0. Check the installed runtime for your exact version.

pdfx4 and pdfvt1 force PDF 1.6 and require a nonempty document title, an explicit timestamp, a structurally valid embedded ICC output intent, and embedded fonts for text. Read fullbleed capabilities --json and fullbleed.pdf_profile_catalog() for the authoritative profile catalog.

These are emission and internal inspection contracts. Dedicated external PDF/VT validation is still required before claiming ISO PDF/VT-1 conformance. The engine does not implement PDF/VT-2, PDF/VT-2s, or PDF/VT-3.

Identity and dates

Python accepts document_timestamp="2026-09-30T12:34:56Z", "current", or "source-date-epoch". The CLI accepts --timestamp with the first two forms or --timestamp-source SOURCE_DATE_EPOCH. Fixed dates use Gregorian UTC; SOURCE_DATE_EPOCH must contain nonnegative integer Unix seconds.

current and the environment value resolve once when the engine is created. The resolved value is exposed as engine.document_timestamp and retained in CLI manifests. Reuse that value for replay; create a new engine or explicitly provide the new write date for a later production job. The runtime does not silently replace an omitted print timestamp with the Unix epoch.

XMP CreateDate, ModifyDate, MetadataDate and PDF/VT modification date share that value. Info dates, title, and trapping state agree with XMP. DocumentID is a content-derived SHA-256 UUIDv8; InstanceID includes the write date. The trailer contains deterministic 16-byte IDs. Metadata includes VersionID 1, RenditionClass default, and Trapped False. These identifiers describe a freshly generated document; they are not an editing history or persistent customer identifier.

Records, documents, and final page ranges

import fullbleed

job = {
    "id": "september-statements",
    "metadata": {"product": "statement", "copies": 1},
    "records": [
        {"id": "000001", "metadata": {"postal_code": "60601"},
         "documents": [{"id": "statement"}, {"id": "insert"}]},
        {"id": "000002", "documents": [{"id": "statement"}]},
    ],
}
engine = fullbleed.PdfEngine(
    pdf_profile="pdfvt1",
    document_title="September statements",
    document_timestamp="2026-09-30T12:34:56Z",
    output_intent_icc="assets/sRGB.icc",
    output_intent_identifier="sRGB IEC61966-2.1",
    font_files=["assets/Inter-Variable.ttf"],
    pdf_vt_job=job,
)
engine.render_pdf_batch_to_file(
    ["<p>Statement A</p>", "<p>Insert A</p>", "<p>Statement B</p>"],
    "body { font-family: Inter; }", "statements.pdf",
)

The tree is Job → Record → Document → pages, with NodeNameList [/Job /Record /Document] and RecordLevel 1 (zero-based). Input documents consume the declared leaves in order. Each compiled copy or binding row also consumes one document leaf. Page ranges are derived after pagination, so a long reflow row remains one document and stays with its recipient. Buffered, streamed, and parallel batch APIs retain these boundaries. The declared leaf count must match the rendered input count.

Without a job description, the engine generates one record/document per input, copy, or binding row. A job with an omitted or empty records list uses the same automatic grouping while retaining the job ID and metadata. IDs must be nonempty; record IDs are unique within a job, and document IDs within a record. pdf_vt_job is rejected for other PDF profiles.

The CLI accepts --pdf-vt-job job.json or inline JSON. A single-document CLI render consumes one leaf. MCP render and compile tools expose pdf_vt_job, document_timestamp, output_intent_icc_path, and font_paths; paths are confined to the server workspace. Rust exposes PdfVtJob, PdfVtRecord, PdfVtDocument, DpmMetadata, and DpmValue through the builder.

DPM and reuse hints

Every node carries /DPM << /Fullbleed << /ID (...) /Metadata << ... >> >> >>. Metadata accepts UTF-8 strings, signed 64-bit integers, booleans, finite reals within the PDF reader's single-precision range, lists, and nested dictionaries. Use strings for exact decimal business values. Nulls and nonfinite numbers are rejected. Limits are 16 levels and 10,000 values per node, 127 UTF-8 bytes per key, 65,536 bytes per string, and 4,096 bytes per ID. Dictionary order is canonical for deterministic output.

This is a private Fullbleed vocabulary. Postal codes, copies, media, finishing, or binding fields carry caller-provided data; the engine does not interpret them as finishing instructions or claim CIP4/JDF/ISO 21812 interoperability. Agree those semantics with the print provider.

PDF/VT XObjects carry GTS_Scope /File. Only opaque images with an explicit rendering intent receive GTS_Encapsulated true; forms and alpha images are conservative false. Resources remain shared by the existing reuse machinery. No cross-file reuse, DFE speedup, or RIP benchmark is implied by these hints.

Inspection and independent validation

fullbleed inspect pdf statements.pdf --json reports the ordered parts, IDs, typed DPM, record/document counts, final page ranges, graph validity, and reuse hints. render and verify also parse print output and report pdf_profile_verification with scope="internal_writer_contract" and independent_conformance=false. Checks include identity consistency, ICC structure, page boxes, and forbidden interactive/external constructs. This does not prove arbitrary ICC colorimetry or every ISO requirement.

tools/validate_pdf_profiles.py retains PDFs, replay hashes, input hashes, internal reports, and external reports. The required release gate combines veraPDF for PDF/A, PDF/UA, and WTPDF; pdf_oxide for PDF/X-4; and tools/check_pdfvt_structure.py using pypdf in a process that never imports Fullbleed. The latter checks basic, multipage, grouped-record, fixed-binding, and reflow specimens against their known source assignments. It verifies metadata, resources, page ranges, backlinks, and the private DPM contract. Twenty-one damaged specimens must fail for their expected reasons, while an unmodified rewrite control must pass. This is engineering verification of a documented contract, not exhaustive ISO PDF/VT validation.

Install the release-tool dependency pypdf==6.19.0 and pass --require-pdfvt-structure --strict-external to require those checks. No third-party parser or validator is added to the core package.

Use --pdfvt-cmd (or FULLBLEED_PDFVT_VALIDATOR_CMD) with a {pdf} placeholder and optional {report} placeholder. Templates use POSIX argument quoting on all hosts and execute without a shell. A wrapper must select the actual PDF/VT-1 validation profile and normalize success to exit 0 and nonconformance to exit 1; crashes and configuration errors should use exit 2 or higher. --pdfvt-version-cmd must retain the product version and selected profile. Add --require-dedicated-pdfvt --strict-external to require this additional standards-preflight evidence. Its negative controls remove the DPart root reference, change the PDF version, and break the VT/XMP date agreement.

The optional dedicated-validator job uses repository variables PDFVT_VALIDATOR_RUNNER (JSON label array, e.g. ["self-hosted","pdfvt"]), PDFVT_VALIDATOR_CMD, PDFVT_VALIDATOR_VERSION_CMD, and PDFVT_OUTPUT_INTENT_ICC. The runner must already have a licensed or otherwise usable dedicated validator. With no dedicated runner configured, that additional job is skipped; the required engineering gate still runs. A configured dedicated job must pass. See the 2.4.0 runbook for evidence and publication requirements.