Go
Last updated: 2026-06-29
The rustpdf-go module wraps the rust-pdf C core with idiomatic Go types over cgo. 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 is statically linked from a prebuilt archive bundled per platform, so go get + go build just works — no separate native install.
Document authors a new PDF; EditableDoc loads and manipulates an existing one. Each holds a native handle, so defer d.Close() to free it promptly. Methods return an error (not the receiver), so they don't chain.Installation
Add the module with go get. A prebuilt static libpdf_ffi.a for your platform ships inside the module (under lib/<os>_<arch>/), so there is nothing else to install or compile.
go get github.com/rustpdf/rustpdf-go@latestRequires Go 1.21+ and a C compiler on PATH (cgo, enabled by default). Supported platforms: darwin/arm64, darwin/amd64, linux/amd64, linux/arm64, windows/amd64. Import it (the package is named rustpdf):
import rustpdf "github.com/rustpdf/rustpdf-go"
fmt.Println(rustpdf.Version()) // native library versionQuick start
A one-page document with a filled rectangle, saved to disk. Errors are elided with _ for brevity — see Error handling for the real pattern:
package main
import rustpdf "github.com/rustpdf/rustpdf-go"
func main() {
doc, _ := rustpdf.New() // A4 by default
defer doc.Close()
_ = 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")
}Get the bytes instead of writing a file with ToBytes:
doc, _ := rustpdf.New()
defer doc.Close()
font, _ := doc.AddFontFile("Roboto-Regular.ttf")
_ = doc.AddPage()
_ = doc.SetFillRGB(0.1, 0.1, 0.12)
_ = doc.Rect(0, 800, 595, 42)
_ = doc.Fill()
_ = doc.ShowText(font, 24, 72, 740, "Olá, açúcar — café", 0)
data, _ := doc.ToBytes() // []byte instead of a filecgo & deployment
The binding links the native library statically, so a built binary carries it — there is no shared library to ship alongside.
- Build host needs a C toolchain. cgo is on by default (
CGO_ENABLED=1); install a C compiler (Xcode CLT on macOS,build-essentialon Linux, MinGW-w64 on Windows). - Cross-compilation needs a cross C toolchain for the target (set
CC), because cgo invokes the platform C compiler. PureGOOS/GOARCHcross-builds without a cross-CCwon't link. - The bundled archive is per-platform. Building for a target outside the supported set above has no archive to link against.
- Containers: build inside an image that has the C toolchain; the resulting binary needs only the usual system libs at runtime (no rust-pdf shared object).
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 return an *Error 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:
if err := rustpdf.ActivateLicense(token); err != nil {
log.Fatal(err) // forged / expired / malformed
}Coordinate 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.
Goroutines & concurrency
Every native call is synchronous and the core is Send but not Sync: a single handle must never be touched by two goroutines at once.
- Documents are independent. Each
Document/EditableDocshares no state, so building several in parallel goroutines is fine — one handle per goroutine. - Don't share a live handle across goroutines; give each its own (or guard it with a mutex).
- Errors are per-thread. The native last-error is thread-local, so a failure in one goroutine never clobbers another's, and the message surfaces on the calling side.
- License is process-global.
ActivateLicense(or the env var) applies everywhere; activate once at startup.
var wg sync.WaitGroup
for i := 0; i < 4; i++ {
wg.Add(1)
go func(n int) {
defer wg.Done()
doc, _ := rustpdf.New() // one document per goroutine
defer doc.Close()
_ = doc.AddPage()
_ = doc.SetFillRGB(0.1, 0.1, 0.12)
_ = doc.Rect(72, 700, 200, 80)
_ = doc.Fill()
_ = doc.Save(fmt.Sprintf("out-%d.pdf", n))
}(i)
}
wg.Wait()Authoring: create & save
rustpdf.New() (*Document, error) freeCreates an empty document (A4 default page size). Call Close() to free the native handle.
| Method | Description |
|---|---|
AddPage() error | Append a page at the default size. |
AddPageSized(w, h float64) error | Append a page with an explicit size in points. |
SetDefaultSize(w, h float64) error | Default size for subsequently added pages. |
SetVersion(v int) error | Set the PDF header version (0 → 1.4, 1 → 1.5, 2 → 1.7, 3 → 2.0). |
PageCount() int | Number of pages so far. |
ToBytes() ([]byte, error) | Render the document to bytes. |
Save(path string) error | Render and write to a file. |
Close() | Free the native handle. |
Pages & vector graphics
Graphics state and path operators mirror PDF's content-stream model. Colors are RGB in 0.0–1.0.
| Method | Description |
|---|---|
SetFillRGB(r, g, b float64) error | Fill color. |
SetStrokeRGB(r, g, b float64) error | Stroke color. |
SetLineWidth(w float64) error | Stroke width in points. |
Rect(x, y, w, h float64) error | Add a rectangle subpath. |
Fill() error | Fill the current path with the fill color. |
Stroke() error | Stroke the current path with the stroke color. |
doc, _ := rustpdf.New()
defer doc.Close()
_ = doc.AddPage()
_ = doc.SetStrokeRGB(0.10, 0.45, 0.90)
_ = doc.SetLineWidth(3)
_ = doc.Rect(72, 600, 300, 160)
_ = doc.Stroke()
_ = doc.SetFillRGB(0.95, 0.77, 0.06)
_ = doc.Rect(120, 640, 120, 80)
_ = doc.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 string) (int, error) AddFont(data []byte) (int, error)ShowText(font int, size, x, y float64, text string, headingLevel int) errorheadingLevel (1–6) tags the run as H1–H6 in an accessible document (see Accessibility); pass 0 for ordinary text.
doc, _ := rustpdf.New()
defer doc.Close()
regular, _ := doc.AddFontFile("Roboto-Regular.ttf")
// …or from bytes you already have in memory:
// data, _ := os.ReadFile("Roboto-Regular.ttf"); regular, _ := doc.AddFont(data)
_ = doc.AddPage()
_ = doc.ShowText(regular, 28, 72, 760, "Invoice #1024", 0)
_ = doc.ShowText(regular, 12, 72, 720, "日本語 · Ελληνικά · العربية", 0)
_ = doc.Save("text.pdf")\x00) byte — it truncates the string at the cgo 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 int, size, x, y, width float64, text string, align Align) errorintro := "A long paragraph that wraps to the given width and is justified " +
"automatically; extra space is distributed between words."
doc, _ := rustpdf.New()
defer doc.Close()
f, _ := doc.AddFontFile("Roboto-Regular.ttf")
_ = doc.AddPage()
_ = doc.Paragraph(f, 12, 72, 700, 451, intro, rustpdf.AlignJustify)
_ = 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 |
|---|---|
AddImageFile(path string) (int, error) | Load JPEG/PNG from a file; returns the image id. |
AddImagePNG(data []byte) (int, error) | Register a PNG from memory. |
AddImageJPEG(data []byte) (int, error) | Register a JPEG from memory. |
DrawImage(image int, x, y, w, h float64) error | Draw at (x, y) scaled to w × h points. |
Figure(image int, x, y, w, h float64, alt string) error | Draw as a tagged /Figure with alt text (accessibility). |
doc, _ := rustpdf.New()
defer doc.Close()
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; PdfaLevel(level) selects a specific PdfaLevel. 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() error PdfaLevel(level PdfaLevel) errordoc, _ := rustpdf.New()
defer doc.Close()
_ = doc.PdfaLevel(rustpdf.A2b)
_ = doc.SetInfo(rustpdf.Info{Title: "Q3 Report", Author: "Acme Inc."})
f, _ := doc.AddFontFile("Roboto-Regular.ttf")
_ = doc.AddPage()
_ = doc.ShowText(f, 20, 72, 760, "Archival report", 0)
if err := doc.Save("report_pdfa.pdf"); err != nil {
log.Fatal(err) // *Error without a license granting PDF/A
}SetInfo(rustpdf.Info{Title: …}).Accessibility (Tagged PDF / PDF/UA) licensed
Tagged() builds a logical structure tree (PDF/UA-1). Combine with PdfaLevel(rustpdf.A2a) for archival and accessible output. Use headingLevel on ShowText for H1–H6, and Figure(..., alt) for described images.
Tagged() errordoc, _ := rustpdf.New()
defer doc.Close()
_ = doc.PdfaLevel(rustpdf.A2a)
_ = doc.Tagged()
_ = doc.SetInfo(rustpdf.Info{Title: "Accessible report"})
f, _ := doc.AddFontFile("Roboto-Regular.ttf")
_ = doc.AddPage()
_ = doc.ShowText(f, 26, 72, 760, "Annual report", 1) // H1
_ = doc.ShowText(f, 14, 72, 720, "Overview", 2) // H2
_ = doc.ShowText(f, 11, 72, 690, "Body paragraph of the section…", 0)
chart, _ := doc.AddImageFile("chart.png")
_ = doc.Figure(chart, 72, 520, 300, 150, "Revenue grew 18% year over year")
_ = doc.Save("accessible.pdf")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 string, data []byte, rel AFRelationship, desc string) errorxml, _ := os.ReadFile("invoice.xml")
doc, _ := rustpdf.New()
defer doc.Close()
_ = doc.PdfaLevel(rustpdf.A3b)
_ = doc.SetInfo(rustpdf.Info{Title: "E-invoice 1024"})
f, _ := doc.AddFontFile("Roboto-Regular.ttf")
_ = doc.AddPage()
_ = doc.ShowText(f, 18, 72, 760, "Invoice 1024", 0)
_ = doc.AttachFile("invoice.xml", "text/xml", xml,
rustpdf.RelSource, "Structured invoice data")
_ = doc.Save("einvoice.pdf")AttachFile before serializing.AttachFile itself. The licensed badge reflects the PDF/A-3 workflow shown here. AttachFile on its own does not require a licence: calling it on a plain (non-PDF/A) document succeeds and produces a valid PDF with an /EmbeddedFile. The licence is only enforced when you also request a PDF/A level (PdfaLevel/Pdfa), 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(xml []byte, profile FacturxProfile) errorxml, _ := os.ReadFile("factur-x.xml") // your Cross-Industry Invoice XML
doc, _ := rustpdf.New()
defer doc.Close()
_ = doc.SetInfo(rustpdf.Info{Title: "Invoice INV-2026-001"})
f, _ := doc.AddFontFile("Roboto-Regular.ttf")
_ = doc.AddPage()
_ = doc.ShowText(f, 18, 72, 760, "Invoice INV-2026-001", 0)
_ = doc.Facturx(xml, rustpdf.FacturxEN16931)
_ = doc.Save("einvoice.pdf") // PDF/A-3 + Factur-X; needs a PDF/A licenseSee the FacturxProfile enum for the conformance levels (FacturxMinimum through FacturxExtended).
AcroForm fields
Build interactive forms with generated appearance streams (no NeedAppearances). Rectangles are [4]float64{x0, y0, x1, y1}; page is a 0-based page index. Dotted names ("a.b.c") create hierarchical fields.
| Method | Description |
|---|---|
TextField(name string, page int, rect [4]float64, value string, size float64) error | Text input (size=0 → auto font size). |
Checkbox(name string, page int, rect [4]float64, checked bool) error | Checkbox. |
Dropdown(name string, page int, rect [4]float64, options []string, selected int, size float64) error | Combo box. |
RadioGroup(name string, page int, buttons []RadioButton, selected int) error | RadioButton{Rect, Export} per option. |
doc, _ := rustpdf.New()
defer doc.Close()
_ = doc.AddPage()
_ = doc.TextField("applicant.name", 0, [4]float64{72, 700, 320, 720}, "", 0)
_ = doc.Checkbox("agree", 0, [4]float64{72, 660, 88, 676}, false)
_ = doc.Dropdown("plan", 0, [4]float64{72, 620, 240, 640},
[]string{"Starter", "Pro", "Enterprise"}, 1, 0)
_ = doc.RadioGroup("billing", 0, []rustpdf.RadioButton{
{Rect: [4]float64{72, 580, 88, 596}, Export: "monthly"},
{Rect: [4]float64{140, 580, 156, 596}, Export: "annual"},
}, 1)
_ = doc.Save("form.pdf")Fill and flatten fields later with EditableDoc.
page does not exist, or whose rectangle is degenerate (x1 < x0 or zero area), is rejected when the document is serialized: ToBytes/Save return an error ("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.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).
LinkURI(rect [4]float64, uri string) error LinkToPage(rect [4]float64, pageIndex int, top *float64) errordoc, _ := rustpdf.New()
defer doc.Close()
f, _ := doc.AddFontFile("Roboto-Regular.ttf")
_ = doc.AddPage()
_ = doc.ShowText(f, 14, 72, 760, "Visit rustpdf.dev (see page 2)", 0)
_ = doc.LinkURI([4]float64{72, 756, 320, 776}, "https://rustpdf.dev/") // web link
top := 800.0
_ = doc.LinkToPage([4]float64{330, 756, 430, 776}, 1, &top) // jump to page 2
_ = doc.AddPage()
_ = doc.Save("links.pdf")Rectangles are [4]float64{x0, y0, x1, y1} in points; pageIndex is 0-based. Pass nil for top to keep the current scroll position.
Bookmarks / outline free
Build a navigable document outline. A Bookmark has a title, a target page and an optional Top; nest children with .Child(...) (or by setting the Children slice). A document with bookmarks opens with the outline pane shown.
Bookmark{Title string, Page int, Top *float64, Children []Bookmark} .Child(bookmark) Bookmark AddBookmark(bookmark Bookmark) errordoc, _ := rustpdf.New()
defer doc.Close()
_, _ = doc.AddFontFile("Roboto-Regular.ttf")
for i := 0; i < 3; i++ {
_ = doc.AddPage()
}
top := 820.0
_ = doc.AddBookmark(
rustpdf.Bookmark{Title: "Chapter 1", Page: 0, Top: &top}.
Child(rustpdf.Bookmark{Title: "Section 1.1", Page: 1}).
Child(rustpdf.Bookmark{Title: "Section 1.2", Page: 2}))
_ = doc.AddBookmark(rustpdf.Bookmark{Title: "Chapter 2", Page: 2})
_ = doc.Save("outline.pdf")Metadata
SetInfo(info Info) error — Info{Title, Author, Subject, Keywords, Creator string}Sets the document information dictionary (and, for PDF/A, the matching XMP). Set only the fields you need.
_ = doc.SetInfo(rustpdf.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.
rustpdf.Load(data []byte) (*EditableDoc, error)data, _ := os.ReadFile("in.pdf")
ed, _ := rustpdf.Load(data)
defer ed.Close()
fmt.Println(ed.PageCount())Load returns no error in that case. Serializing a zero-page document returns an error ("document has no pages") instead of writing an invalid file, but check PageCount() > 0 after loading untrusted input before relying on it.To open a password-protected document, pass the user or owner password. The wrong password returns an *Error rather than loading garbage.
rustpdf.LoadWithPassword(data []byte, password string) (*EditableDoc, error)data, _ := os.ReadFile("secured.pdf")
ed, err := rustpdf.LoadWithPassword(data, "owner-secret")
if err != nil {
log.Fatal(err) // *Error on a wrong password
}
defer ed.Close()
fmt.Println(ed.PageCount())Pages: merge, split, reorder, rotate
| Method | Description |
|---|---|
Merge(other *EditableDoc) error | Append all pages of another EditableDoc (objects renumbered & remapped). |
RotatePage(index, degrees int) error | Rotate one page (90 / 180 / 270). |
DeletePage(index int) error | Remove a page. |
ReorderPages(order []int) error | Reorder with a full permutation of indices. |
ExtractPages(indices []int) (*EditableDoc, error) | New document containing just those pages. |
PageCount() int | Current page count. |
ad, _ := os.ReadFile("a.pdf")
bd, _ := os.ReadFile("b.pdf")
a, _ := rustpdf.Load(ad)
defer a.Close()
b, _ := rustpdf.Load(bd)
_ = a.Merge(b) // a now has a's pages followed by b's
b.Close()
_ = a.RotatePage(0, 90)
_ = a.Save("merged.pdf")
sub, _ := a.ExtractPages([]int{0, 2}) // pages 1 and 3
defer sub.Close()
_ = sub.Save("subset.pdf")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
| Method | Description |
|---|---|
SetInfo(key, value string) error | Set one info entry (e.g. "Title"). |
GetInfo(key string) (string, error) | Read an info entry. |
SetXMP(xml []byte) error | Replace the XMP metadata stream. |
OverlayPage(index int, content []byte) error | Overlay a content-stream fragment onto a page (stamps/watermarks). |
FillTextField(name, value string) (bool, error) | Fill an AcroForm text field; reports whether it was found. |
data, _ := os.ReadFile("form.pdf")
ed, _ := rustpdf.Load(data)
defer ed.Close()
_ = ed.SetInfo("Title", "Filled form")
found, _ := ed.FillTextField("applicant.name", "Jane Doe")
title, _ := ed.GetInfo("Title")
fmt.Println("filled:", found, "| title:", 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. The setter methods return (bool, error): the bool reports whether a field with that name was found.
| Method | Description |
|---|---|
FieldNames() ([]string, error) | Fully-qualified names of every terminal field. |
FillTextField(name, value string) (bool, error) | Set a text (or text-style choice) field. |
SetCheckbox(name string, checked bool) (bool, error) | Check or uncheck a checkbox. |
SetRadio(name, exportValue string) (bool, error) | Select a radio button by its export value. |
SetChoice(name, value string) (bool, error) | Set a dropdown / list-box value. |
FlattenForms() error | Bake all fields into static content and drop the /AcroForm. |
data, _ := os.ReadFile("form.pdf")
ed, _ := rustpdf.Load(data)
defer ed.Close()
names, _ := ed.FieldNames() // ["applicant.name", "agree", "plan", ...]
fmt.Println(names)
_, _ = 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". The color is RGB in 0.0..1.0, opacity in 0.0..1.0, and rotation is in degrees.
WatermarkText(text string, size, r, g, b, opacity, rotationDeg float64) errorWatermarkImageFile(path string, width, height, opacity float64) errordata, _ := os.ReadFile("report.pdf")
ed, _ := rustpdf.Load(data)
defer ed.Close()
_ = ed.WatermarkText("CONFIDENTIAL", 64, 0.5, 0.5, 0.5, 0.25, 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. Returns whether the target page existed.
Redact(pageIndex int, rects [][4]float64) (bool, error)data, _ := os.ReadFile("statement.pdf")
ed, _ := rustpdf.Load(data)
defer ed.Close()
// rects = list of [4]float64{x0, y0, x1, y1} on that page
ok, err := ed.Redact(0, [][4]float64{
{60, 590, 400, 620},
{60, 540, 400, 570},
})
if err != nil {
log.Fatal(err) // *Error without a license granting redaction
}
fmt.Println("page found:", ok)
_ = ed.Save("redacted.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 if a level-A profile is requested.
ConvertToPdfa(level PdfaLevel) errordata, _ := os.ReadFile("in.pdf")
ed, _ := rustpdf.Load(data)
defer ed.Close()
if err := ed.ConvertToPdfa(rustpdf.A2b); err != nil {
log.Fatal(err) // *Error if fonts aren't embedded
}
_ = ed.Save("archival.pdf") // veraPDF: PDF/A-2b compliantOptimize & compact
| Method | Description |
|---|---|
Optimize() error | Drop unreferenced objects, Flate-compress uncompressed streams, dedupe identical objects. |
Compact(on bool) error | Pack objects into object streams + emit a cross-reference stream. |
data, _ := os.ReadFile("big.pdf")
ed, _ := rustpdf.Load(data)
defer ed.Close()
_ = ed.Optimize()
_ = ed.Compact(true)
_ = ed.Save("small.pdf")Encryption licensed
Apply standard-handler encryption at output. AES-256 (V5/R6) uses OS-CSPRNG keys/IVs.
Encrypt(method Encryption, user, owner string, readOnly bool) errordata, _ := os.ReadFile("in.pdf")
ed, _ := rustpdf.Load(data)
defer ed.Close()
if err := ed.Encrypt(rustpdf.AES256, "", "owner-secret", true); err != nil {
log.Fatal(err) // *Error without an Encryption license
}
_ = ed.Save("secured.pdf")See the Encryption enum for RC4 / AES-128 / AES-256.
Output & incremental update
| Method | Description |
|---|---|
ToBytes() ([]byte, error) | Serialize the manipulated document. |
Save(path string) error | Serialize to a file. |
ToBytesIncremental(original []byte) ([]byte, error) | Append only changes to the original bytes (signature-safe, non-destructive). |
original, _ := os.ReadFile("in.pdf")
ed, _ := rustpdf.Load(original)
defer ed.Close()
_ = ed.SetInfo("Subject", "reviewed")
incremental, _ := ed.ToBytesIncremental(original) // original bytes preserved verbatim
_ = os.WriteFile("reviewed.pdf", incremental, 0o644)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, keyDER, certDER []byte, opts SignOptions) ([]byte, error)pdf, _ := os.ReadFile("contract.pdf")
keyDER, _ := os.ReadFile("signing-key.pkcs8.der") // PKCS#8 private key (DER)
certDER, _ := os.ReadFile("signing-cert.der") // X.509 certificate (DER)
signed, err := rustpdf.Sign(pdf, keyDER, certDER, rustpdf.SignOptions{
Reason: "Approved", Location: "New York",
Name: "Jane Doe", PAdES: true,
})
if err != nil {
log.Fatal(err)
}
_ = os.WriteFile("contract.signed.pdf", signed, 0o644)
// Verify in a shell: pdfsig contract.signed.pdf → "Signature is Valid."Timestamp & DSS (PAdES LTV) licensed
Build long-term-validation signatures offline. AddDss appends a Document Security Store (/DSS with certs/CRLs, PAdES-B-LT); Timestamp appends an RFC 3161 document timestamp (/DocTimeStamp, PAdES-B-LTA).
rustpdf.AddDss(pdf []byte, certs, crls [][]byte) ([]byte, error)rustpdf.Timestamp(pdf, tsaKeyDER, tsaCertDER []byte, date string) ([]byte, error)signed, _ := os.ReadFile("contract.signed.pdf")
// B-LT: embed validation material (caller supplies DER certs/CRLs)
lt, _ := rustpdf.AddDss(signed, [][]byte{certDER}, [][]byte{crlDER})
// B-LTA: add a document timestamp signed by a TSA key/cert (date "" = now)
lta, _ := rustpdf.Timestamp(lt, tsaKeyDER, tsaCertDER, "")
_ = os.WriteFile("contract.lta.pdf", lta, 0o644)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.VerifySignatures(pdf []byte) ([]SignatureReport, error)Each SignatureReport has FieldName and Signer (both *string, and nil when absent, so dereference only after a nil check as the example does for Signer), plus SubFilter, CoversWholeDocument, DigestValid, SignatureValid, IsValid and ByteRange. An empty slice 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.
data, _ := os.ReadFile("contract.signed.pdf")
reports, _ := rustpdf.VerifySignatures(data)
for _, sig := range reports {
signer := "(unknown)"
if sig.Signer != nil {
signer = *sig.Signer
}
fmt.Printf("%s valid:%v covers whole doc:%v\n",
signer, sig.IsValid, 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.
rustpdf.ExtractText(data []byte) (string, error) freerustpdf.ExtractImagesToDir(data []byte, dir string) (int, error) freedata, _ := os.ReadFile("report.pdf")
text, _ := rustpdf.ExtractText(data)
fmt.Println(text)
n, _ := rustpdf.ExtractImagesToDir(data, "out_images/") // returns how many were written
fmt.Printf("wrote %d image(s)\n", n)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.RenderPageToPng(pdf []byte, pageIndex int, dpi float64) ([]byte, error) licensedrustpdf.PageCount(pdf []byte) (int, error) freedata, _ := os.ReadFile("report.pdf")
n, _ := rustpdf.PageCount(data)
fmt.Printf("%d page(s)\n", n)
png, _ := rustpdf.RenderPageToPng(data, 0, 150.0)
os.WriteFile("page1.png", png, 0o644)Enums
PdfaLevel
| Value | Level |
|---|---|
rustpdf.A1b | PDF/A-1b (basic, PDF 1.4) |
rustpdf.A2b | PDF/A-2b (basic): same as Pdfa() |
rustpdf.A2a | PDF/A-2a (accessible: pair with Tagged()) |
rustpdf.A3b | PDF/A-3b (basic, allows attachments) |
rustpdf.A3a | PDF/A-3a (accessible + attachments) |
rustpdf.A4 | PDF/A-4 (ISO 19005-4, based on PDF 2.0) |
rustpdf.A4e | PDF/A-4e (engineering) |
rustpdf.A4f | PDF/A-4f (requires at least one embedded file) |
Align
| Value | Meaning |
|---|---|
rustpdf.AlignLeft | Left-aligned (default) |
rustpdf.AlignRight | Right-aligned |
rustpdf.AlignCenter | Centered |
rustpdf.AlignJustify | Justified (space distributed between words) |
AFRelationship
| Value | Meaning |
|---|---|
rustpdf.RelSource | Source data for the document (e.g. the invoice XML) |
rustpdf.RelData | Data used to derive the visual content |
rustpdf.RelAlternative | Alternative representation |
rustpdf.RelSupplement | Supplementary material |
rustpdf.RelUnspecified | Unspecified relationship |
Encryption
| Value | Cipher |
|---|---|
rustpdf.RC4 | RC4 (legacy) |
rustpdf.AES128 | AES-128 |
rustpdf.AES256 | AES-256 (V5/R6): recommended |
FacturxProfile
| Value | Conformance level |
|---|---|
rustpdf.FacturxMinimum | Minimal header data only |
rustpdf.FacturxBasicWL | Basic, without line items |
rustpdf.FacturxBasic | Basic, with line items |
rustpdf.FacturxEN16931 | EN 16931 (Comfort): the interoperable core, default |
rustpdf.FacturxExtended | EN 16931 plus extensions |
Error handling
Every failing native call returns an *Error 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, with the same “requires a valid license” message. ToBytes reports Serialize = 4 and Sign reports 10. Note that Save (which writes a file) maps every failure, including a gated feature, to IO = 3 rather than 4, so match on the message rather than assuming 4 for serialization errors. Use errors.As to inspect the code.
import "errors"
doc, _ := rustpdf.New()
defer doc.Close()
_ = doc.Pdfa()
_ = doc.SetInfo(rustpdf.Info{Title: "x"})
_ = doc.AddPage()
if err := doc.Save("out.pdf"); err != nil {
var pe *rustpdf.Error
if errors.As(err, &pe) {
fmt.Printf("failed: status=%d: %s\n", pe.Status, pe.Message)
// e.g. status=3 (IO): feature 'pdfa' requires a valid license
// (doc.ToBytes() would report status=4, Serialize)
}
}Utilities
| Function | Description |
|---|---|
rustpdf.Version() string | Native library version string. |
rustpdf.ActivateLicense(token string) error | Activate a license token (returns an error on invalid/expired). |