C# / .NET

Last updated: 2026-06-29

The RustPdf NuGet package wraps the rust-pdf C core with idiomatic, chainable classes over source-generated P/Invoke (LibraryImport). It covers the whole product surface: vector graphics, embedded/subset fonts & Unicode text, paragraphs, images, PDF/A (1b–3a + A-4/4e/4f), tagged/accessible output, attachments, AcroForm fields, manipulation, text extraction, encryption and digital signatures. The native library ships inside the package for every runtime — no native build.

Two classes do almost everything. Document authors a new PDF; EditableDoc loads and manipulates an existing one. Both implement IDisposable and hold a native handle, so wrap them in using to free it promptly. Using a Document or EditableDoc after it has been disposed throws ObjectDisposedException (never a crash); calling Dispose() more than once is safe.

Installation

Install from NuGet. The native library (libpdf_ffi) is bundled as a per-runtime asset (runtimes/<rid>/native/ for osx-arm64, linux-x64, linux-arm64 and win-x64): .NET resolves the one matching your OS/architecture automatically, so there's nothing to compile.

shell
dotnet add package RustPdf

Targets net8.0 (works on .NET 8+). Verify it loaded:

csharp
using RustPdf;
Console.WriteLine(Pdf.Version());   // native library version
Publishing self-contained or for a specific runtime? Pass -r <rid> (e.g. dotnet publish -r linux-x64) so the matching native asset is copied into the output. RUSTPDF_LIB can point at an explicit library path to override resolution.

Quick start

A one-page document with a filled rectangle, saved to disk:

csharp
using RustPdf;

using var doc = new Document();          // A4 by default
doc.AddPage();
doc.SetFillRgb(0.86, 0.20, 0.18);
doc.Rect(72, 640, 200, 120);             // x, y, width, height (points)
doc.Fill();
doc.Save("out.pdf");

Most methods return the document, so calls chain:

csharp
using var doc = new Document();
int font = doc.AddFontFile("Roboto-Regular.ttf");
doc.AddPage()
   .SetFillRgb(0.1, 0.1, 0.12)
   .Rect(0, 800, 595, 42).Fill()
   .ShowText(font, 24, 72, 740, "Olá, açúcar — café");
byte[] data = doc.ToBytes();             // a byte[] instead of a file

Licensing & activation

Basic generation (everything above) is always free. The corporate features (PDF/A, digital signatures/PAdES, encryption, accessibility) require an active license token. Without one, those calls throw PdfException and produce no output.

Activation needs no rebuild. Easiest is an environment variable, auto-activated the first time a corporate feature is used:

shell
export RUSTPDF_LICENSE="010f0000…"           # the token we email you
# or point at a file:
export RUSTPDF_LICENSE_FILE=/etc/rustpdf/license.txt

Or activate explicitly in code:

csharp
using RustPdf;
Pdf.ActivateLicense(token);   // throws PdfException if forged / expired / malformed
Verification is fully offline: signature + expiry checked against a public key embedded in the library. No network callback, no telemetry.

Coordinate system

Threading & concurrency

Every native call is synchronous and the core is Send but not Sync: a single handle must never be touched by two threads at once.

csharp
// one document per task — no shared handle
var jobs = Enumerable.Range(0, 8).Select(i => Task.Run(() =>
{
    using var doc = new Document();
    doc.AddPage().SetFillRgb(0.1, 0.1, 0.12).Rect(72, 700, 200, 80).Fill();
    return doc.ToBytes();
}));
byte[][] pdfs = await Task.WhenAll(jobs);

Authoring: create & save

new Document() free

Creates an empty document (A4 default page size). Wrap in using (or call Dispose()) to free the native handle.

MemberDescription
AddPage((w, h)?)Append a page. Optional size tuple in points.
SetDefaultSize(w, h)Default size for subsequently added pages.
SetVersion(v)Set the PDF header version (0 → 1.4, 1 → 1.5, 2 → 1.7, 3 → 2.0).
PageCountProperty: number of pages so far.
ToBytes()Render the document to a byte[].
Save(path)Render and write to a file.
Dispose()Free the native handle.
Validity. A document must have at least one page — serializing an empty document throws an error. Color components (RGB/Gray/CMYK) are clamped to the valid 0–1 range.

Pages & vector graphics

Graphics state and path operators mirror PDF's content-stream model. Colors are RGB in 0.0–1.0.

MethodDescription
SetFillRgb(r, g, b)Fill color.
SetStrokeRgb(r, g, b)Stroke color.
SetLineWidth(w)Stroke width in points.
Rect(x, y, w, h)Add a rectangle subpath.
Fill()Fill the current path with the fill color.
Stroke()Stroke the current path with the stroke color.
csharp
using var doc = new Document();
doc.AddPage();
doc.SetStrokeRgb(0.10, 0.45, 0.90).SetLineWidth(3);
doc.Rect(72, 600, 300, 160).Stroke();
doc.SetFillRgb(0.95, 0.77, 0.06);
doc.Rect(120, 640, 120, 80).Fill();
doc.Save("shapes.pdf");

Fonts & text

Fonts are embedded and subsetted, with HarfBuzz-quality shaping, kerning and full Unicode (Type0/CIDFontType2 with ToUnicode, so text extracts and copies correctly, provided the embedded font covers those characters). Register a font once, then reference it by its integer id.

AddFontFile(path) → int   AddFont(byte[] data) → int
ShowText(font, size, x, y, text, headingLevel = 0)

headingLevel (1–6) tags the run as H1H6 in an accessible document (see Accessibility); leave it as 0 for ordinary text.

csharp
using var doc = new Document();
int regular = doc.AddFontFile("Roboto-Regular.ttf");
// …or from bytes you already have in memory:
// int regular = doc.AddFont(File.ReadAllBytes("Roboto-Regular.ttf"));

doc.AddPage();
doc.ShowText(regular, 28, 72, 760, "Invoice #1024");
doc.ShowText(regular, 12, 72, 720, "日本語 · Ελληνικά · العربية");
doc.Save("text.pdf");
Font coverage. Characters outside the embedded font's coverage are silently dropped — they render as the missing-glyph box and won't extract or copy. The bundled Roboto fallback covers Latin, Greek and Cyrillic but not CJK, Arabic, Hebrew or emoji. Embed a font that covers every script you write.
No NUL bytes in strings. Text passed across the API must not contain a NUL (\0) character — it truncates the string at the FFI boundary, silently dropping everything after the NUL. This applies to shown text, metadata and field names.

Paragraphs

The paragraph layer wraps, aligns and justifies text inside a fixed width (greedy line breaking using shaped glyph widths).

Paragraph(font, size, x, y, width, text, align = Align.Left)
csharp
using RustPdf;

string intro =
    "A long paragraph that wraps to the given width and is justified " +
    "automatically; extra space is distributed between words.";

using var doc = new Document();
int f = doc.AddFontFile("Roboto-Regular.ttf");
doc.AddPage();
doc.Paragraph(f, 12, 72, 700, 451, intro, Align.Justify);
doc.Save("paragraph.pdf");

See the Align enum for the alignment options.

Images

JPEGs are embedded verbatim (DCTDecode, no re-encode). PNGs are decoded and re-encoded (FlateDecode); alpha becomes an /SMask, palette becomes an Indexed color space. Register an image once, draw it many times.

MethodDescription
AddImageFile(path) → intLoad JPEG/PNG from a file; returns the image id.
AddImagePng(byte[] data) → intRegister a PNG from memory.
AddImageJpeg(byte[] data) → intRegister a JPEG from memory.
DrawImage(image, x, y, w, h)Draw at (x, y) scaled to w × h points.
Figure(image, x, y, w, h, alt)Draw as a tagged /Figure with alt text (accessibility).
csharp
using var doc = new Document();
int logo = doc.AddImageFile("logo.png");
doc.AddPage();
doc.DrawImage(logo, 72, 680, 160, 90);
doc.Save("with_image.pdf");

PDF/A licensed

Produce archival-grade output. Pdfa() defaults to A-2b; pass a PdfaLevel for a specific level. An embedded sRGB ICC profile, output intent, XMP metadata and document /ID are added automatically; A-1b also forces PDF 1.4 and emits a /CIDSet, and A-4 (ISO 19005-4) is based on PDF 2.0.

Pdfa(PdfaLevel? level = null)
csharp
using RustPdf;

using var doc = new Document();
doc.Pdfa(PdfaLevel.A2b).SetInfo(title: "Q3 Report", author: "Acme Inc.");
int f = doc.AddFontFile("Roboto-Regular.ttf");
doc.AddPage();
doc.ShowText(f, 20, 72, 760, "Archival report");
doc.Save("report_pdfa.pdf");     // throws PdfException without a license granting PDF/A
A document title is recommended for valid PDF/A metadata, and required for the accessible “a” levels: call SetInfo(title: …).

Accessibility (Tagged PDF / PDF/UA) licensed

Tagged() builds a logical structure tree (PDF/UA-1). Combine with Pdfa(PdfaLevel.A2a) for archival and accessible output. Use headingLevel on ShowText for H1H6, and Figure(..., alt) for described images.

Tagged()
csharp
using RustPdf;

using var doc = new Document();
doc.Pdfa(PdfaLevel.A2a).Tagged().SetInfo(title: "Accessible report");
int f = doc.AddFontFile("Roboto-Regular.ttf");
doc.AddPage();
doc.ShowText(f, 26, 72, 760, "Annual report", headingLevel: 1);   // H1
doc.ShowText(f, 14, 72, 720, "Overview", headingLevel: 2);         // H2
doc.ShowText(f, 11, 72, 690, "Body paragraph of the section…");
int chart = doc.AddImageFile("chart.png");
doc.Figure(chart, 72, 520, 300, 150, "Revenue grew 18% year over year");
doc.Save("accessible.pdf");
Figures need a tagged document. Figure() only produces an accessible, alt-texted figure inside a tagged/accessible document; on a plain document the alt text has no effect.

Attachments (PDF/A-3) licensed

PDF/A-3 allows embedding arbitrary source files (e.g. the XML behind an e-invoice). Each attachment carries a MIME type and an AFRelationship.

AttachFile(name, mime, byte[] data, relationship = AFRelationship.Source, description = "")
csharp
using RustPdf;

byte[] xml = File.ReadAllBytes("invoice.xml");
using var doc = new Document();
doc.Pdfa(PdfaLevel.A3b).SetInfo(title: "E-invoice 1024");
int f = doc.AddFontFile("Roboto-Regular.ttf");
doc.AddPage();
doc.ShowText(f, 18, 72, 760, "Invoice 1024");
doc.AttachFile("invoice.xml", "text/xml", xml,
               AFRelationship.Source, "Structured invoice data");
doc.Save("einvoice.pdf");
PDF/A-4f needs an attachment. The A4f profile requires at least one embedded file (ISO 19005-4); the library now rejects A4f output that has no attachment, so call AttachFile before serializing.
The licence gate is on PDF/A, not on AttachFile itself. The licensed badge above reflects the PDF/A-3 workflow shown here. Calling AttachFile on a plain (non-PDF/A) document does not require a licence: it succeeds and produces a valid PDF with an /EmbeddedFile. The licence is enforced only when you also request a PDF/A level, which is what makes the attachment archival.

ZUGFeRD / Factur-X e-invoices licensed

Turn the document into a ZUGFeRD / Factur-X electronic invoice: the embedded XML (the Cross-Industry Invoice) is attached as factur-x.xml, the file is marked PDF/A-3, and the Factur-X identification is written into the XMP metadata. The visible PDF is the human-readable invoice; the embedded XML is its machine-readable twin. Validates as PDF/A-3 + Factur-X under veraPDF.

Facturx(byte[] xml, FacturxProfile profile = FacturxProfile.En16931)
csharp
using RustPdf;

byte[] xml = File.ReadAllBytes("factur-x.xml");   // your Cross-Industry Invoice XML
using var doc = new Document();
doc.SetInfo(title: "Invoice INV-2026-001");
int f = doc.AddFontFile("Roboto-Regular.ttf");
doc.AddPage();
doc.ShowText(f, 18, 72, 760, "Invoice INV-2026-001");
doc.Facturx(xml, FacturxProfile.En16931);
doc.Save("einvoice.pdf");        // PDF/A-3 + Factur-X; needs a PDF/A license

See the FacturxProfile enum for the conformance levels (Minimum to Extended).

AcroForm fields

Build interactive forms with generated appearance streams (no NeedAppearances). Rectangles are (x0, y0, x1, y1) tuples; page is a 0-based page index. Dotted names ("a.b.c") create hierarchical fields.

MethodDescription
TextField(name, page, rect, value = "", size = 0)Text input (size = 0 → auto font size).
Checkbox(name, page, rect, checkedFlag)Checkbox.
Dropdown(name, page, rect, options, selected = null, size = 0)Combo box from a sequence of strings.
RadioGroup(name, page, buttons, selected = null)buttons = list of (rect, export) tuples.
csharp
using var doc = new Document();
doc.AddPage();
doc.TextField("applicant.name", 0, (72, 700, 320, 720));
doc.Checkbox("agree", 0, (72, 660, 88, 676), false);
doc.Dropdown("plan", 0, (72, 620, 240, 640),
             new[] { "Starter", "Pro", "Enterprise" }, selected: 1);
doc.RadioGroup("billing", 0, new[]
{
    ((72.0, 580.0, 88.0, 596.0), "monthly"),
    ((140.0, 580.0, 156.0, 596.0), "annual"),
}, selected: 1);
doc.Save("form.pdf");

Fill and flatten fields later with EditableDoc.

Page index and rectangle are validated. A field (or internal link) whose page does not exist, or whose rectangle is degenerate (x1 < x0 or zero area), is rejected when the document is serialized: ToBytes()/Save() throw PdfException ("targets page index N…" / "degenerate rectangle…") instead of producing an invisible, never-appearing widget. Pass a 0-based page index that exists and a rectangle with x1 > x0 and y1 > y0.

Add clickable link rectangles to the current page: a web link opens a URL; an internal link jumps to another page (optionally scrolling so a given top y-coordinate sits at the top of the view).

LinkUri(rect, uri)   LinkToPage(rect, pageIndex, top = null)
csharp
using var doc = new Document();
int f = doc.AddFontFile("Roboto-Regular.ttf");
doc.AddPage();
doc.ShowText(f, 14, 72, 760, "Visit rustpdf.dev (see page 2)");
doc.LinkUri((72, 756, 320, 776), "https://rustpdf.dev/");   // web link
doc.LinkToPage((330, 756, 430, 776), 1, top: 800);         // jump to page 2
doc.AddPage();
doc.Save("links.pdf");

Rectangles are (x0, y0, x1, y1) tuples in points; pageIndex is 0-based.

Bookmarks / outline free

Build a navigable document outline. A Bookmark has a title, a target page and an optional top; nest children with .Child(...). A document with bookmarks opens with the outline pane shown.

new Bookmark(title, page, top = null)   .Child(bookmark)   AddBookmark(bookmark)
csharp
using RustPdf;

using var doc = new Document();
int f = doc.AddFontFile("Roboto-Regular.ttf");
for (int i = 0; i < 3; i++)
    doc.AddPage();
doc.AddBookmark(
    new Bookmark("Chapter 1", 0, top: 820)
        .Child(new Bookmark("Section 1.1", 1))
        .Child(new Bookmark("Section 1.2", 2)));
doc.AddBookmark(new Bookmark("Chapter 2", 2));
doc.Save("outline.pdf");

Metadata

SetInfo(title?, author?, subject?, keywords?, creator?)

Sets the document information dictionary (and, for PDF/A, the matching XMP). Pass only the named arguments you need.

csharp
doc.SetInfo(title: "Q3 Report", author: "Acme Inc.",
            subject: "Quarterly results", keywords: "finance, q3");

Manipulation: load an existing PDF

EditableDoc parses an existing document (classic & xref streams, object streams, all standard filters, RC4/AES decryption) into an editable model. Pages are a flat list; the page tree is rebuilt on output.

EditableDoc.Load(byte[] data, password = null)
EditableDoc.LoadFile(path, password = null)
csharp
using RustPdf;

using (var ed = EditableDoc.LoadFile("in.pdf"))
    Console.WriteLine(ed.PageCount);

// encrypted input:
using var sec = EditableDoc.LoadFile("secured.pdf", "user-or-owner-pw");
sec.Save("plain.pdf");
Corrupt or truncated input. A badly damaged file (e.g. truncated mid-stream) may still load via the recovery scan but recover zero pagesLoad does not throw in that case. Serializing a zero-page document throws PdfException ("document has no pages") rather than writing an invalid file, but you should check PageCount > 0 after loading untrusted input before relying on it.

Pages: merge, split, reorder, rotate

MethodDescription
Merge(other)Append all pages of another EditableDoc (objects renumbered & remapped).
RotatePage(index, degrees)Rotate one page (90 / 180 / 270).
DeletePage(index)Remove a page.
ReorderPages(order)Reorder with a full permutation list of indices.
ExtractPages(indices) → EditableDocNew document containing just those pages.
PageCountProperty: current page count.
csharp
using var a = EditableDoc.LoadFile("a.pdf");
using (var b = EditableDoc.LoadFile("b.pdf"))
    a.Merge(b);                        // a now has a's pages followed by b's
a.RotatePage(0, 90);
a.ReorderPages(Enumerable.Range(0, a.PageCount).Reverse().ToList());
a.Save("merged.pdf");

using var subset = a.ExtractPages(new[] { 0, 2 });   // pages 1 and 3
subset.Save("subset.pdf");
Page indices are 0-based. An out-of-range index to rotate/delete is silently ignored, and extract skips out-of-range indices. ReorderPages requires a true permutation of every page index (each used exactly once); an invalid argument — wrong length, a repeated index, or out-of-range — is rejected and leaves the page order unchanged — the call is a silent no-op that raises no error, so an invalid reorder cannot be detected from a return value.

Metadata, overlay & form fill

MethodDescription
SetInfo(key, value)Set one info entry (e.g. "Title").
GetInfo(key) → stringRead an info entry.
SetXmp(byte[] xml)Replace the XMP metadata stream.
OverlayPage(index, byte[] content)Overlay a content-stream fragment onto a page (stamps/watermarks).
FillTextField(name, value) → boolFill an AcroForm text field; returns whether it was found.
csharp
using var ed = EditableDoc.LoadFile("form.pdf");
ed.SetInfo("Title", "Filled form");
bool found = ed.FillTextField("applicant.name", "Jane Doe");
Console.WriteLine($"filled: {found} | title: {ed.GetInfo("Title")}");
ed.Save("filled.pdf");

Form fill & flatten free

Fill the fields of an existing AcroForm and (optionally) flatten them: filling generates a fresh appearance stream (no NeedAppearances), and flattening bakes every widget's appearance into the page content and removes the interactive form entirely.

MethodDescription
FieldNames() → IReadOnlyList<string>Fully-qualified names of every terminal field.
FillTextField(name, value) → boolSet a text (or text-style choice) field; returns whether it matched.
SetCheckbox(name, checkedFlag = true) → boolCheck/uncheck a checkbox.
SetRadio(name, exportValue) → boolSelect a radio button by its export value.
SetChoice(name, value) → boolSet a dropdown / list-box value.
FlattenForms()Bake all fields into static content and drop the /AcroForm.
csharp
using RustPdf;

using var ed = EditableDoc.LoadFile("form.pdf");
Console.WriteLine(string.Join(", ", ed.FieldNames()));   // applicant.name, agree, plan, ...
ed.FillTextField("applicant.name", "Jane Doe");
ed.SetCheckbox("agree", true);
ed.SetRadio("billing", "annual");
ed.SetChoice("plan", "Pro");
ed.FlattenForms();                  // optional: make it non-editable
ed.Save("filled.pdf");

Watermarks free

Stamp a diagonal text watermark or a centered image watermark across every page, drawn semi-transparently over the existing content. Text uses the standard Helvetica font, so keep it to WinAnsi (Latin-1) for stamps like "CONFIDENTIAL".

WatermarkText(text, size = 64.0, color = null, opacity = 0.30, rotationDeg = 45.0)
WatermarkImageFile(path, width, height, opacity = 0.30)
csharp
using var ed = EditableDoc.LoadFile("report.pdf");
ed.WatermarkText("CONFIDENTIAL", color: (0.7, 0.1, 0.1),
                 opacity: 0.25, rotationDeg: 45);
ed.Save("stamped.pdf");

Redaction licensed

True redaction: the text and graphics whose origin falls inside a rectangle are removed from the content stream (not just covered), so the data is gone from the file and is no longer extractable. Opaque black boxes are then painted over the regions.

Redact(pageIndex, rects) → bool
csharp
using var ed = EditableDoc.LoadFile("statement.pdf");
// rects = list of (x0, y0, x1, y1) tuples on that page
ed.Redact(0, new[]
{
    (60.0, 590.0, 400.0, 620.0),
    (60.0, 540.0, 400.0, 570.0),
});
ed.Save("redacted.pdf");     // throws PdfException without a license granting redaction
Content under a rect is deleted before the file is written, so Pdf.ExtractText on the output no longer returns it.

Convert to PDF/A licensed

Convert an existing PDF to archival PDF/A (a basic profile: A-1b, A-2b or A-3b). An sRGB output intent, PDF/A XMP metadata (synced with /Info) and a document /ID are added. Fails if any font is not embedded (PDF/A requires every font embedded) or a level-A profile is requested.

ConvertToPdfa(PdfaLevel level = PdfaLevel.A2b)
csharp
using RustPdf;

using var ed = EditableDoc.LoadFile("in.pdf");
ed.ConvertToPdfa(PdfaLevel.A2b);    // throws PdfException if fonts aren't embedded
ed.Save("archival.pdf");            // veraPDF: PDF/A-2b compliant

Optimize & compact

MethodDescription
Optimize()Drop unreferenced objects, Flate-compress uncompressed streams, dedupe identical objects.
Compact(on = true)Pack objects into object streams + emit a cross-reference stream.
csharp
using var ed = EditableDoc.LoadFile("big.pdf");
ed.Optimize().Compact(true);
ed.Save("small.pdf");

Encryption licensed

Apply standard-handler encryption at output. AES-256 (V5/R6) uses OS-CSPRNG keys/IVs.

Encrypt(user = "", owner = "", method = Encryption.Aes256, readOnly = false)
csharp
using RustPdf;

using var ed = EditableDoc.LoadFile("in.pdf");
ed.Encrypt(user: "", owner: "owner-secret",
           method: Encryption.Aes256, readOnly: true);
ed.Save("secured.pdf");          // throws PdfException without an Encryption license

See the Encryption enum for RC4 / AES-128 / AES-256.

Passwords protect, permissions only advise. A non-empty user password is real cryptographic protection: opening with the wrong password is rejected (verified against qpdf for all three ciphers). The readOnly permission flags, by contrast, are advisory: they are enforced only by the viewer, and a file with an empty user password opens with no prompt, so any tool can strip the restrictions. Treat readOnly as a hint to well-behaved viewers, not as an access control.

Output & incremental update

MethodDescription
ToBytes() → byte[]Serialize the manipulated document.
Save(path)Serialize to a file.
ToBytesIncremental(byte[] original) → byte[]Append only changes to the original bytes (signature-safe, non-destructive).
csharp
byte[] original = File.ReadAllBytes("in.pdf");
using var ed = EditableDoc.Load(original);
ed.SetInfo("Subject", "reviewed");
byte[] incremental = ed.ToBytesIncremental(original);   // original bytes preserved verbatim
File.WriteAllBytes("reviewed.pdf", incremental);

Digital signatures licensed

Sign a PDF with a PKCS#7 detached signature via an incremental update (the original bytes are preserved). Keys and certificates are passed as DER byte[]. pades: true switches to PAdES-B-B.

Pdf.Sign(pdf, keyDer, certDer, reason?, location?, name?, pades = false) → byte[]
csharp
using RustPdf;

byte[] pdf     = File.ReadAllBytes("contract.pdf");
byte[] keyDer  = File.ReadAllBytes("signing-key.pkcs8.der");   // PKCS#8 private key (DER)
byte[] certDer = File.ReadAllBytes("signing-cert.der");        // X.509 certificate (DER)

byte[] signed = Pdf.Sign(pdf, keyDer, certDer,
    reason: "Approved", location: "New York",
    name: "Jane Doe", pades: true);
File.WriteAllBytes("contract.signed.pdf", signed);
// Verify in a shell: pdfsig contract.signed.pdf  →  "Signature is Valid."

Timestamp & DSS (PAdES LTV) licensed

Build long-term-validation signatures offline. Pdf.AddDss appends a Document Security Store (/DSS with certs/CRLs, PAdES-B-LT); Pdf.Timestamp appends an RFC 3161 document timestamp (/DocTimeStamp, PAdES-B-LTA).

Pdf.AddDss(pdf, certs?, crls?) → byte[]
Pdf.Timestamp(pdf, tsaKeyDer, tsaCertDer, date?) → byte[]
csharp
byte[] signed = File.ReadAllBytes("contract.signed.pdf");

// B-LT: embed validation material (caller supplies DER certs/CRLs)
byte[] lt = Pdf.AddDss(signed, new[] { certDer }, new[] { crlDer });

// B-LTA: add a document timestamp signed by a TSA key/cert
byte[] lta = Pdf.Timestamp(lt, tsaKeyDer, tsaCertDer);
File.WriteAllBytes("contract.lta.pdf", lta);

Validate signatures licensed

Validate every signature in a PDF: each report recomputes the /ByteRange digest, parses the CMS, and checks that the cryptographic signature is valid, that the messageDigest matches the covered bytes, and whether the signature covers the whole document.

Pdf.VerifySignatures(byte[] pdf) → IReadOnlyList<SignatureInfo>

Each SignatureInfo has FieldName, SubFilter, Signer, CoversWholeDocument, DigestValid, SignatureValid, IsValid and ByteRange. FieldName and Signer may be null when absent (they are string?). An empty list means the document is unsigned. Signature validation is a licensed feature: this call requires an active license (signatures) even when the document is unsigned — it is not available on the free tier.

csharp
using RustPdf;

byte[] data = File.ReadAllBytes("contract.signed.pdf");
foreach (SignatureInfo sig in Pdf.VerifySignatures(data))
    Console.WriteLine($"{sig.Signer} valid: {sig.IsValid} " +
                      $"covers whole doc: {sig.CoversWholeDocument}");

Text & image extraction

Extract a document's text, mapping shown glyph codes back to Unicode through each font's ToUnicode map, with space/line inference. Raster images can be pulled out too: JPEGs are written verbatim as .jpg, everything else as .png. The output directory is created automatically if it does not already exist.

Pdf.ExtractText(byte[] data) → string free
Pdf.ExtractImagesToDir(byte[] data, string outDir) → int free
csharp
byte[] data = File.ReadAllBytes("report.pdf");
Console.WriteLine(Pdf.ExtractText(data));

int n = Pdf.ExtractImagesToDir(data, "out_images/");   // returns how many were written
Console.WriteLine($"wrote {n} image(s)");

Render a page to an image licensed

Rasterize a page to a PNG image. A native Rust renderer (built on tiny-skia, with no headless browser) interprets the page content stream, painting real glyph outlines, vector graphics, images, color and transparency. Page rendering is a Pro feature; page_count is free.

byte[] Pdf.RenderPageToPng(byte[] pdf, int pageIndex = 0, double dpi = 150.0) licensed
int Pdf.PageCount(byte[] pdf) free
csharp
byte[] data = File.ReadAllBytes("report.pdf");
Console.WriteLine($"{Pdf.PageCount(data)} page(s)");
byte[] png = Pdf.RenderPageToPng(data, pageIndex: 0, dpi: 150.0);
File.WriteAllBytes("page1.png", png);

Enums

PdfaLevel

ValueLevel
PdfaLevel.A1bPDF/A-1b (basic, PDF 1.4)
PdfaLevel.A2bPDF/A-2b (basic): default of Pdfa()
PdfaLevel.A2aPDF/A-2a (accessible: pair with Tagged())
PdfaLevel.A3bPDF/A-3b (basic, allows attachments)
PdfaLevel.A3aPDF/A-3a (accessible + attachments)
PdfaLevel.A4PDF/A-4 (ISO 19005-4, based on PDF 2.0)
PdfaLevel.A4ePDF/A-4e (engineering)
PdfaLevel.A4fPDF/A-4f (requires at least one embedded file)

Align

ValueMeaning
Align.LeftLeft-aligned (default)
Align.RightRight-aligned
Align.CenterCentered
Align.JustifyJustified (space distributed between words)

AFRelationship

ValueMeaning
AFRelationship.SourceSource data for the document (e.g. the invoice XML)
AFRelationship.DataData used to derive the visual content
AFRelationship.AlternativeAlternative representation
AFRelationship.SupplementSupplementary material
AFRelationship.UnspecifiedUnspecified relationship

Encryption

ValueCipher
Encryption.Rc4RC4 (legacy)
Encryption.Aes128AES-128
Encryption.Aes256AES-256 (V5/R6): recommended

FacturxProfile

ValueConformance level
FacturxProfile.MinimumMinimal header data only
FacturxProfile.BasicWLBasic, without line items
FacturxProfile.BasicBasic, with line items
FacturxProfile.En16931EN 16931 (Comfort): the interoperable core, default
FacturxProfile.ExtendedEN 16931 plus extensions

Error handling

Every failing native call throws PdfException carrying the Status (PdfStatus) code and the library's last-error message. License failures (missing/expired/forged token, or a feature the token doesn't grant) surface here too. Token activation failures carry the dedicated license status code (12); a gated build, sign or encrypt call instead reports that operation’s own status (for example Serialize = 4 or Sign = 10) with the same “requires a valid license” message.

csharp
using RustPdf;

try
{
    using var doc = new Document();
    doc.Pdfa().SetInfo(title: "x");
    doc.AddPage();
    doc.Save("out.pdf");
}
catch (PdfException e)
{
    Console.Error.WriteLine($"failed: {e.Status} {e.Message}");
    // e.g. PdfStatus=4 (Serialize): feature 'pdfa' requires a valid license
}

Utilities

MemberDescription
Pdf.Version() → stringNative library version string.
Pdf.ActivateLicense(token)Activate a license token (throws on invalid/expired).
Looking for another language? The same API exists in Python, Node / TypeScript, Ruby, Delphi / Free Pascal, Swift, Go and PHP: browse all docs. They share one core, so behavior is identical.