Python
The rustpdf package wraps the rust-pdf C core with idiomatic, context-managed classes. It covers the whole product surface: vector graphics, embedded/subset fonts & Unicode text, paragraphs, images, PDF/A (1b–3a), tagged/accessible output, attachments, AcroForm fields, manipulation, text extraction, encryption and digital signatures.
Document authors a new PDF; EditableDoc loads and manipulates an existing one. Both are context managers: use them with with so native handles are always freed.Installation
Install the package from PyPI. The wheel bundles the native shared library (libpdf_ffi) built from the Rust core for your platform — nothing else to build or configure.
pip install rustpdfVerify it loaded:
import rustpdf
print(rustpdf.version()) # native library version
print(rustpdf.library_path()) # which .dylib/.so/.dll was loadedQuick start
A one-page document with a filled rectangle, saved to disk:
import rustpdf
with rustpdf.Document() as doc: # A4 by default
doc.add_page()
doc.set_fill_rgb(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:
with rustpdf.Document() as doc:
font = doc.add_font_file("Roboto-Regular.ttf")
(doc.add_page()
.set_fill_rgb(0.1, 0.1, 0.12)
.rect(0, 800, 595, 42).fill()
.show_text(font, 24, 72, 740, "Olá, açúcar — café"))
data = doc.to_bytes() # in-memory bytes instead of a fileLicensing & 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 raise PdfError and produce no output.
Activation needs no rebuild. Easiest is an environment variable, auto-activated the first time a corporate feature is used:
export RUSTPDF_LICENSE="010f0000…" # the token we email you
# or point at a file:
export RUSTPDF_LICENSE_FILE=/etc/rustpdf/license.txtOr activate explicitly in code:
rustpdf.activate_license(token) # raises PdfError if forged / expired / malformedCoordinate system
- Units are points (1 pt = 1/72 inch). A4 is
595 × 842, US Letter612 × 792. - The origin
(0, 0)is the bottom-left corner;ygrows upward. - For text,
(x, y)is the baseline of the first glyph. - Drawing/text always targets the most recently added page.
Threading & concurrency
The core is Send but not Sync: you can build many documents in parallel, but a single handle must never be touched by two threads at once.
- Generate in parallel. Give each thread its own
Document/EditableDoc— independent documents share no state and run truly concurrently (ctypesreleases the GIL during each native call). - Move between threads. A handle may be created on one thread and used on another.
- Never share a live handle. Two threads calling into the same
Documentat the same time is unsupported; protect it with your own lock if you really must. - Errors are per-thread. The native last-error is thread-local, so a failure on one thread never clobbers another's —
PdfErroris always raised on the calling thread. - License is process-global.
activate_license(or the env var) applies to every thread; activate once at startup.
from concurrent.futures import ThreadPoolExecutor
import rustpdf
def render(i: int) -> bytes:
with rustpdf.Document() as doc: # one document per task
doc.add_page()
doc.set_fill_rgb(0.1, 0.1, 0.12)
doc.rect(72, 700, 200, 80).fill()
return doc.to_bytes()
with ThreadPoolExecutor(max_workers=8) as pool:
pdfs = list(pool.map(render, range(100))) # 100 PDFs built concurrentlyAuthoring: create & save
rustpdf.Document() freeCreates an empty document (A4 default page size). Use as a context manager; call close() manually only if you can't.
| Method | Description |
|---|---|
add_page(size=None) | Append a page. size is an optional (width, height) tuple in points. |
set_default_size(w, h) | Default size for subsequently added pages. |
set_version(v) | Set the PDF header version (0 → 1.4, 1 → 1.5, 2 → 1.7, 3 → 2.0). |
page_count | Property: number of pages so far. |
to_bytes() | Render the document to bytes. |
save(path) | Render and write to a file. |
Pages & vector graphics
Graphics state and path operators mirror PDF's content-stream model. Colors are RGB in 0.0–1.0.
| Method | Description |
|---|---|
set_fill_rgb(r, g, b) | Fill color. |
set_stroke_rgb(r, g, b) | Stroke color. |
set_line_width(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. |
with rustpdf.Document() as doc:
doc.add_page()
doc.set_stroke_rgb(0.10, 0.45, 0.90).set_line_width(3)
doc.rect(72, 600, 300, 160).stroke()
doc.set_fill_rgb(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). Register a font once, then reference it by its integer id.
add_font_file(path) → int add_font(data: bytes) → intshow_text(font, size, x, y, text, heading_level=0)heading_level (1–6) tags the run as H1–H6 in an accessible document (see Accessibility); leave 0 for ordinary text.
with rustpdf.Document() as doc:
regular = doc.add_font_file("Roboto-Regular.ttf")
# …or from bytes you already have in memory:
# regular = doc.add_font(open("Roboto-Regular.ttf", "rb").read())
doc.add_page()
doc.show_text(regular, 28, 72, 760, "Invoice #1024")
doc.show_text(regular, 12, 72, 720, "日本語 · Ελληνικά · العربية")
doc.save("text.pdf")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)from rustpdf import Document, Align
intro = ("A long paragraph that wraps to the given width and is justified "
"automatically; extra space is distributed between words.")
with Document() as doc:
f = doc.add_font_file("Roboto-Regular.ttf")
doc.add_page()
doc.paragraph(f, 12, 72, 700, 451, intro, align=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.
| Method | Description |
|---|---|
add_image_file(path) → int | Load JPEG/PNG from a file; returns the image id. |
add_image_png(data: bytes) → int | Register a PNG from memory. |
add_image_jpeg(data: bytes) → int | Register a JPEG from memory. |
draw_image(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). |
with rustpdf.Document() as doc:
logo = doc.add_image_file("logo.png")
doc.add_page()
doc.draw_image(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(level=None)from rustpdf import Document, PdfaLevel
with Document() as doc:
doc.pdfa(PdfaLevel.A2B).set_info(title="Q3 Report", author="Acme Inc.")
f = doc.add_font_file("Roboto-Regular.ttf")
doc.add_page()
doc.show_text(f, 20, 72, 760, "Archival report")
doc.save("report_pdfa.pdf") # raises PdfError without a license granting PDF/Aset_info(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 heading_level on show_text for H1–H6, and figure(..., alt=…) for described images.
tagged()from rustpdf import Document, PdfaLevel
with Document() as doc:
doc.pdfa(PdfaLevel.A2A).tagged().set_info(title="Accessible report")
f = doc.add_font_file("Roboto-Regular.ttf")
doc.add_page()
doc.show_text(f, 26, 72, 760, "Annual report", heading_level=1)
doc.show_text(f, 14, 72, 720, "Overview", heading_level=2)
doc.show_text(f, 11, 72, 690, "Body paragraph of the section…")
chart = doc.add_image_file("chart.png")
doc.figure(chart, 72, 520, 300, 150, alt="Revenue grew 18% year over year")
doc.save("accessible.pdf")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.
attach_file(name, mime, data, relationship=AFRelationship.SOURCE, description="")from rustpdf import Document, PdfaLevel, AFRelationship
xml = open("invoice.xml", "rb").read()
with Document() as doc:
doc.pdfa(PdfaLevel.A3B).set_info(title="E-invoice 1024")
f = doc.add_font_file("Roboto-Regular.ttf")
doc.add_page()
doc.show_text(f, 18, 72, 760, "Invoice 1024")
doc.attach_file("invoice.xml", "text/xml", xml,
AFRelationship.SOURCE, "Structured invoice data")
doc.save("einvoice.pdf")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(xml: bytes, profile=FacturxProfile.EN16931)from rustpdf import Document, FacturxProfile
xml = open("factur-x.xml", "rb").read() # your Cross-Industry Invoice XML
with Document() as doc:
doc.set_info(title="Invoice INV-2026-001")
f = doc.add_font_file("Roboto-Regular.ttf")
doc.add_page()
doc.show_text(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 licenseSee the FacturxProfile enum for the conformance levels (MINIMUM … EXTENDED).
AcroForm fields
Build interactive forms with generated appearance streams (no NeedAppearances). Rectangles are (x0, y0, x1, y1); page is a 0-based page index. Dotted names ("a.b.c") create hierarchical fields.
| Method | Description |
|---|---|
text_field(name, page, rect, value="", size=0.0) | Text input (size=0 → auto font size). |
checkbox(name, page, rect, checked=False) | Checkbox. |
dropdown(name, page, rect, options, selected=None, size=0.0) | Combo box from a list of strings. |
radio_group(name, page, buttons, selected=None) | buttons = list of (rect, export_value) tuples. |
with rustpdf.Document() as doc:
doc.add_page()
doc.text_field("applicant.name", 0, (72, 700, 320, 720), value="")
doc.checkbox("agree", 0, (72, 660, 88, 676), checked=False)
doc.dropdown("plan", 0, (72, 620, 240, 640),
["Starter", "Pro", "Enterprise"], selected=1)
doc.radio_group("billing", 0, [
((72, 580, 88, 596), "monthly"),
((140, 580, 156, 596), "annual"),
], selected=1)
doc.save("form.pdf")Fill and flatten fields later with EditableDoc.
Hyperlinks free
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).
link_uri(rect, uri) link_to_page(rect, page_index, top=None)with rustpdf.Document() as doc:
f = doc.add_font_file("Roboto-Regular.ttf")
doc.add_page()
doc.show_text(f, 14, 72, 760, "Visit rustpdf.dev (see page 2)")
doc.link_uri((72, 756, 320, 776), "https://rustpdf.dev/") # web link
doc.link_to_page((330, 756, 430, 776), 1, top=800) # jump to page 2
doc.add_page()
doc.save("links.pdf")Rectangles are (x0, y0, x1, y1) in points; page_index 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.
Bookmark(title, page, top=None) .child(bookmark) add_bookmark(bookmark)from rustpdf import Document, Bookmark
with Document() as doc:
f = doc.add_font_file("Roboto-Regular.ttf")
for _ in range(3):
doc.add_page()
doc.add_bookmark(
Bookmark("Chapter 1", 0, top=820)
.child(Bookmark("Section 1.1", 1))
.child(Bookmark("Section 1.2", 2)))
doc.add_bookmark(Bookmark("Chapter 2", 2))
doc.save("outline.pdf")Metadata
set_info(title=None, author=None, subject=None, keywords=None, creator=None)Sets the document information dictionary (and, for PDF/A, the matching XMP). Pass only the fields you need.
doc.set_info(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(data: bytes, password=None)EditableDoc.load_file(path, password=None)from rustpdf import EditableDoc
with EditableDoc.load_file("in.pdf") as ed:
print(ed.page_count)
# encrypted input:
with EditableDoc.load_file("secured.pdf", password="user-or-owner-pw") as ed:
ed.save("plain.pdf")Pages: merge, split, reorder, rotate
| Method | Description |
|---|---|
merge(other) | Append all pages of another EditableDoc (objects renumbered & remapped). |
rotate_page(index, degrees) | Rotate one page (90 / 180 / 270). |
delete_page(index) | Remove a page. |
reorder_pages(order) | Reorder with a full permutation list of indices. |
extract_pages(indices) → EditableDoc | New document containing just those pages. |
page_count | Property: current page count. |
with EditableDoc.load_file("a.pdf") as a, EditableDoc.load_file("b.pdf") as b:
a.merge(b) # a now has a's pages followed by b's
a.rotate_page(0, 90)
a.reorder_pages(list(reversed(range(a.page_count))))
a.save("merged.pdf")
with EditableDoc.load_file("merged.pdf") as doc:
with doc.extract_pages([0, 2]) as subset: # pages 1 and 3
subset.save("subset.pdf")Metadata, overlay & form fill
| Method | Description |
|---|---|
set_info(key, value) | Set one info entry (e.g. "Title"). |
get_info(key) → str | Read an info entry. |
set_xmp(xml: bytes) | Replace the XMP metadata stream. |
overlay_page(index, content: bytes) | Overlay a content-stream fragment onto a page (stamps/watermarks). |
fill_text_field(name, value) → bool | Fill an AcroForm text field; returns whether it was found. |
with EditableDoc.load_file("form.pdf") as ed:
ed.set_info("Title", "Filled form")
found = ed.fill_text_field("applicant.name", "Jane Doe")
print("filled:", found, "| title:", ed.get_info("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.
| Method | Description |
|---|---|
field_names() → list[str] | Fully-qualified names of every terminal field. |
fill_text_field(name, value) → bool | Set a text (or text-style choice) field; returns whether it matched. |
set_checkbox(name, checked=True) → bool | Check/uncheck a checkbox. |
set_radio(name, export_value) → bool | Select a radio button by its export value. |
set_choice(name, value) → bool | Set a dropdown / list-box value. |
flatten_forms() | Bake all fields into static content and drop the /AcroForm. |
from rustpdf import EditableDoc
with EditableDoc.load_file("form.pdf") as ed:
print(ed.field_names()) # ['applicant.name', 'agree', 'plan', ...]
ed.fill_text_field("applicant.name", "Jane Doe")
ed.set_checkbox("agree", True)
ed.set_radio("billing", "annual")
ed.set_choice("plan", "Pro")
ed.flatten_forms() # 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".
watermark_text(text, *, size=64.0, color=(0.5,0.5,0.5), opacity=0.30, rotation_deg=45.0)watermark_image_file(path, width, height, opacity=0.30)with EditableDoc.load_file("report.pdf") as ed:
ed.watermark_text("CONFIDENTIAL", opacity=0.25, rotation_deg=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(page_index, rects) → boolwith EditableDoc.load_file("statement.pdf") as ed:
# rects = list of (x0, y0, x1, y1) on that page
ed.redact(0, [(60, 590, 400, 620), (60, 540, 400, 570)])
ed.save("redacted.pdf") # raises PdfError without a license granting redactionextract_text 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.
convert_to_pdfa(level=PdfaLevel.A2B)from rustpdf import EditableDoc, PdfaLevel
with EditableDoc.load_file("in.pdf") as ed:
ed.convert_to_pdfa(PdfaLevel.A2B) # raises PdfError if fonts aren't embedded
ed.save("archival.pdf") # veraPDF: PDF/A-2b compliantOptimize & compact
| Method | Description |
|---|---|
optimize() | Drop unreferenced objects, Flate-compress uncompressed streams, dedupe identical objects. |
compact(on=True) | Pack objects into object streams + emit a cross-reference stream. |
with EditableDoc.load_file("big.pdf") as ed:
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, read_only=False)from rustpdf import EditableDoc, Encryption
with EditableDoc.load_file("in.pdf") as ed:
ed.encrypt(user="", owner="owner-secret",
method=Encryption.AES256, read_only=True)
ed.save("secured.pdf") # raises PdfError without an Encryption licenseSee the Encryption enum for RC4 / AES-128 / AES-256.
Output & incremental update
| Method | Description |
|---|---|
to_bytes() → bytes | Serialize the manipulated document. |
save(path) | Serialize to a file. |
to_bytes_incremental(original: bytes) → bytes | Append only changes to the original bytes (signature-safe, non-destructive). |
original = open("in.pdf", "rb").read()
with EditableDoc.load(original) as ed:
ed.set_info("Subject", "reviewed")
incremental = ed.to_bytes_incremental(original) # original bytes preserved verbatim
open("reviewed.pdf", "wb").write(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 bytes. pades=True switches to PAdES-B-B.
rustpdf.sign(pdf, key_der, cert_der, *, reason=None, location=None, name=None, pades=False) → bytesimport rustpdf
pdf_bytes = open("contract.pdf", "rb").read()
key_der = open("signing-key.pkcs8.der", "rb").read() # PKCS#8 private key (DER)
cert_der = open("signing-cert.der", "rb").read() # X.509 certificate (DER)
signed = rustpdf.sign(pdf_bytes, key_der, cert_der,
reason="Approved", location="New York",
name="Jane Doe", pades=True)
open("contract.signed.pdf", "wb").write(signed)
# Verify in a shell: pdfsig contract.signed.pdf → "Signature is Valid."Timestamp & DSS (PAdES LTV) licensed
Build long-term-validation signatures offline. add_dss appends a Document Security Store (/DSS with certs/CRLs, PAdES-B-LT); timestamp appends an RFC 3161 document timestamp (/DocTimeStamp, PAdES-B-LTA).
rustpdf.add_dss(pdf, certs=(), crls=()) → bytesrustpdf.timestamp(pdf, tsa_key_der, tsa_cert_der, *, date=None) → bytessigned = open("contract.signed.pdf", "rb").read()
# B-LT: embed validation material (caller supplies DER certs/CRLs)
lt = rustpdf.add_dss(signed, certs=[cert_der], crls=[crl_der])
# B-LTA: add a document timestamp signed by a TSA key/cert
lta = rustpdf.timestamp(lt, tsa_key_der, tsa_cert_der)
open("contract.lta.pdf", "wb").write(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.
rustpdf.verify_signatures(data: bytes) → list[dict]Each entry has field_name, sub_filter, signer, covers_whole_document, digest_valid, signature_valid, is_valid and byte_range. An empty list means the document is unsigned.
data = open("contract.signed.pdf", "rb").read()
for sig in rustpdf.verify_signatures(data):
print(sig["signer"], "valid:", sig["is_valid"],
"covers whole doc:", sig["covers_whole_document"])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.
rustpdf.extract_text(data: bytes) → str freerustpdf.extract_images_to_dir(data: bytes, out_dir: str) → int freedata = open("report.pdf", "rb").read()
print(rustpdf.extract_text(data))
n = rustpdf.extract_images_to_dir(data, "out_images/") # returns how many were written
print(f"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.
rustpdf.render_page_to_png(data: bytes, page: int = 0, dpi: float = 150.0) → bytes licensedrustpdf.page_count(data: bytes) → int freedata = open("report.pdf", "rb").read()
print(f"{rustpdf.page_count(data)} page(s)")
png = rustpdf.render_page_to_png(data, page=0, dpi=150.0)
open("page1.png", "wb").write(png)Enums
PdfaLevel
| Value | Level |
|---|---|
PdfaLevel.A1B | PDF/A-1b (basic, PDF 1.4) |
PdfaLevel.A2B | PDF/A-2b (basic): default of pdfa() |
PdfaLevel.A2A | PDF/A-2a (accessible: pair with tagged()) |
PdfaLevel.A3B | PDF/A-3b (basic, allows attachments) |
PdfaLevel.A3A | PDF/A-3a (accessible + attachments) |
PdfaLevel.A4 | PDF/A-4 (ISO 19005-4, based on PDF 2.0) |
PdfaLevel.A4E | PDF/A-4e (engineering) |
PdfaLevel.A4F | PDF/A-4f (allows attachments) |
Align
| Value | Meaning |
|---|---|
Align.LEFT | Left-aligned (default) |
Align.RIGHT | Right-aligned |
Align.CENTER | Centered |
Align.JUSTIFY | Justified (space distributed between words) |
AFRelationship
| Value | Meaning |
|---|---|
AFRelationship.SOURCE | Source data for the document (e.g. the invoice XML) |
AFRelationship.DATA | Data used to derive the visual content |
AFRelationship.ALTERNATIVE | Alternative representation |
AFRelationship.SUPPLEMENT | Supplementary material |
AFRelationship.UNSPECIFIED | Unspecified relationship |
Encryption
| Value | Cipher |
|---|---|
Encryption.RC4 | RC4 (legacy) |
Encryption.AES128 | AES-128 |
Encryption.AES256 | AES-256 (V5/R6): recommended |
FacturxProfile
| Value | Conformance level |
|---|---|
FacturxProfile.MINIMUM | Minimal header data only |
FacturxProfile.BASIC_WL | Basic, without line items |
FacturxProfile.BASIC | Basic, with line items |
FacturxProfile.EN16931 | EN 16931 (Comfort): the interoperable core, default |
FacturxProfile.EXTENDED | EN 16931 plus extensions |
Error handling
Every failing native call raises PdfError (a RuntimeError) carrying the 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.
from rustpdf import Document, PdfError
try:
with Document() as doc:
doc.pdfa().set_info(title="x")
doc.add_page()
doc.save("out.pdf")
except PdfError as e:
print("failed:", e) # e.g. PdfStatus=7: feature 'pdfa' requires a valid licenseUtilities
| Function | Description |
|---|---|
rustpdf.version() → str | Native library version string. |
rustpdf.library_path() → Path | Path of the loaded shared library. |
rustpdf.activate_license(token) | Activate a license token (raises on invalid/expired). |