A modern, dependency-free PDF generation library for .NET 10, implemented clean-room from the open ISO 32000 standard.
Status: stable. The public API is locked (analyzer-enforced) and the library targets .NET 10. Core features are CI-validated — including PDF/A-2a/2b/2u and PDF/UA-1 conformance proven on every push with veraPDF.
- Zero runtime dependencies. The core is built entirely on the .NET base
class library — no native binaries, no third-party packages. (The optional
signing package is the sole exception: it uses
System.Security.Cryptography.Pkcs.) - AOT- and trim-ready. Ships as managed DLLs; ideal for Native AOT, trimming, containers, and serverless. A Native-AOT smoke test guards this.
- Unicode-first text. Embeds and subsets TrueType and OpenType-CFF fonts,
emits composite (CID) fonts with subset tags, and writes
ToUnicodemaps so output stays searchable and copy-paste-able. - Two API tiers. A low-level canvas for precise drawing, and a high-level document/layout engine with paragraphs, headings, lists, tables, images, and automatic pagination.
- Built for the hard standards. PDF/A-2a (accessible archival) and PDF/UA-1 (universal accessibility) are implemented and CI-validated with veraPDF, alongside PDF/A-2b/2u, PAdES digital signatures, and interactive AcroForms.
- Permissive license. Apache-2.0 — free to use in proprietary products.
# Core
dotnet add package VellumPdf.Layout
dotnet add package VellumPdf.Kernel
# Add-ons
dotnet add package VellumPdf.Signing
dotnet add package VellumPdf.Fonts.Standard14
dotnet add package VellumPdf.Barcodes
dotnet add package VellumPdf.Conformance
# Preview
dotnet add package VellumPdf.Reader
# Tooling (.NET global tool)
dotnet tool install -g VellumPdf.CliVellumPdf.Layout pulls in VellumPdf.Kernel as a dependency, so most apps only need the
first line. Quick start below shows VellumPdf.Layout/VellumPdf.Kernel in
use, and Barcodes shows VellumPdf.Barcodes.
Every package is strong-named from 2.0 onward, which changes assembly identity. The new identity is:
VellumPdf.Kernel, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b2757187a6d18ae5
All eight packages share that public key token, and AssemblyVersion is pinned to 2.0.0.0
for the whole 2.x line, so servicing releases do not force a rebind.
Ordinary PackageReference consumers need to do nothing — the SDK resolves the new identity
on restore. Rebinding is only needed where an assembly identity is written down by hand:
- an
<AssemblyIdentity>binding redirect or<dependentAssembly>element namingPublicKeyToken="null"; [assembly: InternalsVisibleTo("...")]targeting a VellumPdf assembly, which must now carry the full public key;Assembly.Loadwith an explicitPublicKeyToken=nullin the string;- a plugin host or serializer configured with fully-qualified type names.
A 1.x and a 2.x assembly are different identities, so both can be loaded side by side if a transitive dependency still needs the old one.
dotnet add package VellumPdf.Layoutusing VellumPdf.Document; // PdfConformance
using VellumPdf.Fonts; // Standard14
using VellumPdf.Layout; // Document
using VellumPdf.Layout.Core; // TextStyle
using VellumPdf.Layout.Elements; // Paragraph, Heading
using VellumPdf.Layout.Elements.Table; // TableElement
// Basic document — defaults to A4.
using var doc = new Document();
doc.SetDefaultFont(new TextStyle { Font = Standard14.Helvetica, FontSize = 11 });
doc.Add(new Heading("Hello, world!"));
doc.Add(new Paragraph("Generated with VellumPdf — no native dependencies."));
doc.Save("hello.pdf");// PDF/A-2b archival document. PDF/A requires every glyph to come from an
// embedded font, so load a TrueType font and use it for all text.
using var archive = new Document { Conformance = PdfConformance.PdfA2b };
var font = archive.LoadTrueTypeFont("/path/to/DejaVuSans.ttf");
var style = new TextStyle { FontRef = font, FontSize = 12 };
archive.Add(new Paragraph("This document validates as PDF/A-2b.", style));
var table = new TableElement { DefaultCellStyle = style };
table.SetColumnWidths(200, 200);
table.AddHeaderRow().AddCell("Item").AddCell("Value");
table.AddRow().AddCell("Format").AddCell("PDF/A-2b");
archive.Add(table);
archive.Save("archive.pdf");For precise canvas control, bypass the Layout engine and write directly to the PDF content stream:
using VellumPdf.Canvas; // PdfCanvas
using VellumPdf.Document; // PdfDocument, PageSize
using VellumPdf.Fonts; // Standard14
using var doc = new PdfDocument();
var page = doc.AddPage(PageSize.A4);
var font = doc.UseFont(Standard14.Helvetica);
var canvas = new PdfCanvas(page);
canvas
.BeginText()
.SetFont(font, 12)
.SetTextMatrix(1, 0, 0, 1, 72, 720)
.ShowText("Hello from the Kernel API!")
.EndText();
canvas.Finish();
using var stream = File.OpenWrite("kernel-hello.pdf");
doc.Save(stream);For a full walkthrough of the canvas, graphics primitives, and font handling, see docs/kernel-guide.md.
dotnet add package VellumPdf.Barcodesusing VellumPdf.Barcodes;
// Flow API: adds a QR Code to the document like any other element.
doc.Add(new QrCode("https://example.com") { TargetWidth = 120 });
// Low-level API: draws directly onto a PdfCanvas, with human-readable text below the bars.
canvas.DrawBarcode(new EanBarcode(EanSymbology.Ean13, "400638133393"), 72, 700, font);QR (including Micro QR), PDF417, Code 128, EAN-13/EAN-8/UPC-A, and ITF-14 are all covered; see docs/barcodes-guide.md for sizing, human-readable text, GS1/FNC1, and the QR charset policy. QR Code is a registered trademark of DENSO WAVE INCORPORATED.
- Units. All coordinates and sizes are in PDF user-space points (1 pt = 1/72 inch).
PageSizeprovides the common ISO-A sizes plus aPageSize.Mm(width, height)helper for custom millimetre dimensions. - Synchronous and asynchronous I/O.
Save,Sign, andLoadTrueTypeFonteach have an*Asynccounterpart (SaveAsync,SignAsync,LoadTrueTypeFontAsync) that accepts aCancellationToken. Document serialization is CPU-bound, so the async methods offload it to a thread-pool thread; the token is honoured before that work starts and during the final write, but does not interrupt serialization already in progress.SignAsyncperforms non-blocking network calls for PAdES B-T/B-LT/B-LTA timestamp and revocation lookups. Image loaders remain synchronous: they parse in-memory bytes and never touch disk or the network. - Signing with HSM/PKCS#11/cloud-KMS certificates.
PdfSignatureSettings.Certificatemust normally include its own private key, butPdfSignatureSettings.ExternalPrivateKeyaccepts a separateRSA/ECDsaobject for certificates whose key isn't attached to theX509Certificate2— for example a certificate fetched from Azure Key Vault, AWS KMS, or a PKCS#11 device (via a library such asPkcs11Interop.X509Store).Certificateis still required in that case, for the public key, subject, and certificate chain. Windows-attached smart cards and CNG-integrated hardware tokens already work without this: loading the certificate fromX509Storereturns a CNG-backed private key that plugs into the normalCertificate-only path. - Signing with an async cloud KMS or remote HSM.
ExternalPrivateKeystill needs a synchronousRSA/ECDsaobject, so a KMS whose signing call is a network round-trip (Azure Key Vault, AWS KMS, GCP KMS) can only be bridged by blocking a thread. To avoid that, setPdfSignatureSettings.ExternalSignerto anIExternalSignerand sign withSignAsync: VellumPdf computes the CMS signed-attributes digest, hands it to the async signer, and assembles the resulting signature itself. The synchronousSignoverloads throw whenExternalSigneris set.EcdsaSignatureConverter.RawToDerconverts the raw ECDSA signature format Azure Key Vault returns into the DER encoding CMS requires.
Correctness is enforced on every push by running real external validators as CI oracles — a missing tool fails the build, so the gates can never silently skip:
qpdf --check— structural integrity of every generated document type.pdftotext(poppler) — text-extraction round-trip provingToUnicodemaps.pdfsig(poppler) — signature validity for PAdES documents.- veraPDF (official
verapdf/cliDocker image) — strict PDF/A-2a/2b/2u and PDF/UA-1 conformance over embedded-font, table, image, and tagged documents. A non-compliant report fails CI with the full rule list attached. - zxing-cpp — decode round-trip for every barcode symbology: each generated PDF is
rasterized with
pdftoppmand decoded back, so the CI oracle catches encoding regressions a unit test's own vectors could miss.
vellum-preflight validates a PDF against PDF/A-2b/2u/2a and PDF/UA-1 in-process — no JVM or Docker.
Install it as a cross-platform .NET tool, or download a self-contained native binary (Windows x64/Arm64,
macOS Arm64, Linux x64) from the latest release.
dotnet tool install -g VellumPdf.Cli # then run: vellum-preflight <file># Does the PDF honour the conformance level it claims? (pass/fail, with reasons)
vellum-preflight invoice.pdf
# A specific profile, machine-readable output
vellum-preflight invoice.pdf -p 2u -f json -o report.json
# Validate a whole tree; fail CI on any error
vellum-preflight ./out --recurse --fail-on error -q
# Several profiles at once
vellum-preflight report.pdf -p 2b,2a,ua1
# See exactly what the tool checks for a profile
vellum-preflight --coverage 2bEvery report accounts for each check in the profile's catalogue exactly once, so a clean result is never mistaken for an absolute guarantee:
| Bucket | Meaning |
|---|---|
failed |
Rules that reported a problem: rule id, ISO clause, reason, offending object. |
passed |
Catalogued checks this file satisfies. |
failedChecks |
Catalogued checks a failing rule named directly, by test id. |
inconclusive |
A rule failed in the same ISO clause but did not say which catalogued check it corresponds to, so the check can be neither claimed nor blamed. |
notEvaluated |
Checks the library implements only partially, defers, or puts out of scope. |
failed counts rule assertions; the other four count catalogue entries and sum to summary.total.
Exit codes are 0 (conformant), 1 (non-conformant), and 2 (usage or I/O error).
Planned direction, tracked as GitHub milestones. These are scopes, not commitments — the milestones carry no due dates. 2.3.0 is the latest published release.
Scope past 2.5 runs as two parallel tracks, because auditing the layout engine turned up more work than the PDF 2.0 conformance gap did. Both ship from the same version numbers.
Kernel and conformance
| Milestone | Scope |
|---|---|
| 2.3 — Reader robustness (released) | Cross-reference reconstruction for damaged files (#184), a decrypted-copy writer (#186), the /XRefStm precedence decision (#206), reader fuzzing (#99), and the CI and oracle debt that makes all of it verifiable. |
| 2.4 — PDF content extraction | Text and image extraction on top of the reader (#98), and graduating VellumPdf.Reader from Preview (#187). |
| 2.5 — PDF/A-4 and PDF/A-1 profiles | PDF/A-4 (#222) so conformance output stops downgrading to %PDF-1.7, PDF/A-1 (#218), and dropping the keys ISO 32000-2 deprecates (#325). |
| 2.6 — ISO/TS extensions to PDF 2.0 | The four Technical Specifications that amend PDF 2.0: AES-GCM (#236), PDF MAC integrity (#237), SHA-3 (#238) and EdDSA (#239). The reader currently rejects every AES-GCM file, so this closes an interoperability bug as well as adding features. |
| 2.7 — Embedded files and associated files | Attachments, the /Names tree, the missing annotation subtypes, and ISO 32000-2 §14.13 associated files. Unblocks PDF/A-4F. |
| 2.8 — Colour and paint | PDF functions, the full shading model, tiling and shading patterns, and the colour spaces beyond the device ones and ICCBased. |
| 2.9 — Rendering and resources | Transparency and blend modes, the complete ExtGState, form XObjects, inline images, optional content, and the page boundary boxes. |
| 2.10 — Font completeness | Type 1, bare CFF, simple TrueType and Type 3 fonts; predefined CMaps and vertical writing; kerning; and enforcing the fsType embedding permission the library currently parses and ignores. |
| 2.11 — Tagged PDF 2.0 writer | Structure namespaces, structure destinations, and the ISO/TS 32005 containment matrix. |
| 2.12 — PDF/UA-2 and WTPDF | The ua2, wt1a and wt1r rule sets. |
| 2.13 — Signature verification | Verifying signatures, not only producing them: integrity, coverage, chains, revocation and achieved PAdES level. |
Layout
| Milestone | Scope |
|---|---|
| Layout A — Foundations | Sections, a containment model, inline runs in every element, a unit type, named styles, renderer extensibility, and fixing a defect where an element spanning a page break emits two structure elements instead of one. |
| Layout B — Accessibility structure | The PDF/UA-2 element set, generated contents and indexes, captions, table header semantics, footnotes and endnotes, automatic numbering, tab stops with leaders, and internal links. |
| Layout C — Forms and semantics | Form elements that carry their own labels, ListNumbering fidelity, artifact property lists, and build-time accessibility diagnostics. |
| Layout D — Typography | Text decoration, optimal line breaking, widow and orphan control, hyphenation and floats, cell borders, and inline content such as images in a line of text. |
| Layout E — Composition | Composite elements and templates, page breaks, anchoring and rotation, page backgrounds, running bands that hold more than a single string, and prepress bleed and printer marks. |
| Layout F — Advanced layout | Multi-column flow, nested tables, a chart family, streaming generation, and the SVG import epic. |
| Layout G — International text | Unicode bidi and right-to-left, vertical and CJK writing, and the complex-script shaping epic. |
Editing existing documents. The only breaking milestone is 3.0.
| Milestone | Scope |
|---|---|
| 3.0 — Round-trip document model | A new round-trip type alongside the write-once PdfDocument, a memory-mapped backing store, and the new VellumPdf.Editing package (Epic #101). |
| 3.1 — Sign and fill existing documents | Signing a PDF this library did not write, and filling its form fields. Neither is possible today. |
| 3.2 — Append-only content edits | Annotations, metadata, watermarks, attachments, and appending flowed content. |
| 3.3 — Full-rewrite operations | Revision-history discard, re-encryption, and signature removal. |
| 3.4 — Structural editing | Page operations, merge and split, optimise and sanitise. |
| 3.5 — Redaction | Marking regions and applying them. Deliberately isolated: marking alone is not redaction. |
| 3.6 — Content editing | Reflowing text edits, and structure-tree round-trip editing of tagged documents. |
What this library implements of ISO 32000-2 today is inventoried, reference by reference, in
PDF 2.0 conformance. It emits a %PDF-2.0 header, which is not the
same as conformance, and the table says which is which. The Reader guide
and Layout guide each carry a capability table doing the same job for
their own package, checked against the code and test suite rather than against this roadmap.
VellumPdf.Reader is marked Preview in the table above; expect its public surface to settle
as these milestones land. VellumPdf.Conformance graduated to Stable in 2.0.
Requires the .NET 10 SDK.
dotnet build VellumPdf.slnx
dotnet test VellumPdf.slnxThe veraPDF conformance gate runs automatically in CI; to reproduce it locally,
install veraPDF (or use its Docker image) and either put
the verapdf CLI on your PATH or point VERAPDF_HOME at the install
directory (required on Windows, where the installer's launcher is
verapdf.bat, which a bare verapdf on PATH cannot resolve directly), then
run the oracle tests with REQUIRE_VERAPDF=1 set.
Licensed under the Apache License 2.0. VellumPdf is an original, independent implementation written from open published specifications and, for patented barcode symbologies, the original patents; no third-party source is copied. See NOTICE and docs/architecture.md for the full provenance statement.