Go

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), 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.

Two types do almost everything. 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.

shell
go get github.com/rustpdf/rustpdf-go@latest

Requires 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):

go
import rustpdf "github.com/rustpdf/rustpdf-go"

fmt.Println(rustpdf.Version())   // native library version

Quick 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:

go
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:

go
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 file

cgo & deployment

The binding links the native library statically, so a built binary carries it — there is no shared library to ship alongside.

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:

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:

go
if err := rustpdf.ActivateLicense(token); err != nil {
	log.Fatal(err)   // 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

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.

go
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) free

Creates an empty document (A4 default page size). Call Close() to free the native handle.

MethodDescription
AddPage() errorAppend a page at the default size.
AddPageSized(w, h float64) errorAppend a page with an explicit size in points.
SetDefaultSize(w, h float64) errorDefault size for subsequently added pages.
SetVersion(v int) errorSet the PDF header version (e.g. 14 → PDF 1.4, 17 → 1.7).
PageCount() intNumber of pages so far.
ToBytes() ([]byte, error)Render the document to bytes.
Save(path string) errorRender 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.

MethodDescription
SetFillRGB(r, g, b float64) errorFill color.
SetStrokeRGB(r, g, b float64) errorStroke color.
SetLineWidth(w float64) errorStroke width in points.
Rect(x, y, w, h float64) errorAdd a rectangle subpath.
Fill() errorFill the current path with the fill color.
Stroke() errorStroke the current path with the stroke color.
go
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). 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) error

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

go
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")

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) error
go
intro := "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.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 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) errorDraw at (x, y) scaled to w × h points.
Figure(image int, x, y, w, h float64, alt string) errorDraw as a tagged /Figure with alt text (accessibility).
go
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.

Pdfa() error   PdfaLevel(level PdfaLevel) error
go
doc, _ := 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
}
PDF/A requires the title to be set for valid metadata: call 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 H1H6, and Figure(..., alt) for described images.

Tagged() error
go
doc, _ := 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")

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) error
go
xml, _ := 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.Source, "Structured invoice data")
_ = doc.Save("einvoice.pdf")

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.

MethodDescription
TextField(name string, page int, rect [4]float64, value string, size float64) errorText input (size=0 → auto font size).
Checkbox(name string, page int, rect [4]float64, checked bool) errorCheckbox.
Dropdown(name string, page int, rect [4]float64, options []string, selected int, size float64) errorCombo box.
RadioGroup(name string, page int, buttons []RadioButton, selected int) errorRadioButton{Rect, Export} per option.
go
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 fields later with EditableDoc.FillTextField.

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.

go
_ = 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)
go
data, _ := os.ReadFile("in.pdf")
ed, _ := rustpdf.Load(data)
defer ed.Close()
fmt.Println(ed.PageCount())

Pages: merge, split, reorder, rotate

MethodDescription
Merge(other *EditableDoc) errorAppend all pages of another EditableDoc (objects renumbered & remapped).
RotatePage(index, degrees int) errorRotate one page (90 / 180 / 270).
DeletePage(index int) errorRemove a page.
ReorderPages(order []int) errorReorder with a full permutation of indices.
ExtractPages(indices []int) (*EditableDoc, error)New document containing just those pages.
PageCount() intCurrent page count.
go
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")

Metadata, overlay & form fill

MethodDescription
SetInfo(key, value string) errorSet one info entry (e.g. "Title").
GetInfo(key string) (string, error)Read an info entry.
SetXMP(xml []byte) errorReplace the XMP metadata stream.
OverlayPage(index int, content []byte) errorOverlay 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.
go
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")

Optimize & compact

MethodDescription
Optimize() errorDrop unreferenced objects, Flate-compress uncompressed streams, dedupe identical objects.
Compact(on bool) errorPack objects into object streams + emit a cross-reference stream.
go
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) error
go
data, _ := 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

MethodDescription
ToBytes() ([]byte, error)Serialize the manipulated document.
Save(path string) errorSerialize to a file.
ToBytesIncremental(original []byte) ([]byte, error)Append only changes to the original bytes (signature-safe, non-destructive).
go
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)
go
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)
go
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)

Text extraction

Extract a document's text, mapping shown glyph codes back to Unicode through each font's ToUnicode map, with space/line inference.

rustpdf.ExtractText(data []byte) (string, error) free
go
data, _ := os.ReadFile("report.pdf")
text, _ := rustpdf.ExtractText(data)
fmt.Println(text)

Enums

PdfaLevel

ValueLevel
rustpdf.A1bPDF/A-1b (basic, PDF 1.4)
rustpdf.A2bPDF/A-2b (basic): same as Pdfa()
rustpdf.A2aPDF/A-2a (accessible: pair with Tagged())
rustpdf.A3bPDF/A-3b (basic, allows attachments)
rustpdf.A3aPDF/A-3a (accessible + attachments)

Align

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

AFRelationship

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

Encryption

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

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. Use errors.As to inspect the code.

go
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=7: feature 'pdfa' requires a valid license
	}
}

Utilities

FunctionDescription
rustpdf.Version() stringNative library version string.
rustpdf.ActivateLicense(token string) errorActivate a license token (returns an error on invalid/expired).
Looking for another language? The same API exists in Python, Node / TypeScript, Ruby, Delphi / Free Pascal, Swift, C#, PHP and Java: browse all docs. They share one core, so behavior is identical.