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 works, with 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 (0 → 1.4, 1 → 1.5, 2 → 1.7, 3 → 2.0).
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.
Validity. A document must have at least one page; serializing an empty document returns 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 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, 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) 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")
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 (\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) 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.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.

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, and A-4 (ISO 19005-4) is based on PDF 2.0.

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
}
A document title is recommended for valid PDF/A metadata, and required for the accessible “a” levels: 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")
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 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.RelSource, "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 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) error
go
xml, _ := 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 license

See 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.

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

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

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())
Corrupt or truncated input. A badly damaged file (truncated mid-stream, for example) may still load via the recovery scan but recover zero pages, and 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)
go
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

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

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.

MethodDescription
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() errorBake all fields into static content and drop the /AcroForm.
go
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. opaqueBackground draws a solid white box behind the text first (a white-out stamp). WatermarkImageFile now also takes a rotationDeg.

WatermarkText(text string, size, r, g, b, opacity, rotationDeg float64, opaqueBackground bool) error
WatermarkImageFile(path string, width, height, opacity, rotationDeg float64) error
go
data, _ := os.ReadFile("report.pdf")
ed, _ := rustpdf.Load(data)
defer ed.Close()
_ = ed.WatermarkText("CONFIDENTIAL", 64, 0.5, 0.5, 0.5, 0.25, 45, false)
_ = ed.WatermarkImageFile("stamp.png", 64, 64, 0.3, 30)
_ = ed.Save("stamped.pdf")

Positioned drawing free

Place a filled rectangle or a line of text at exact coordinates on one page, handy for masking a value and stamping a new one over it. Coordinates are in the page's visible space (origin lower-left, y up), so content lands where a viewer sees it regardless of the page's /Rotate. FillRect takes RGB (each 0.0..1.0) and opacity (0.0..1.0); pass opacity 1 for an opaque white-out box. PlaceText draws in standard Helvetica (so keep text to WinAnsi / Latin-1), and rotationDeg rotates the text counter-clockwise about its anchor (x, y). DrawImage takes raw JPEG/PNG image bytes and places the image with its lower-left corner at (x, y) scaled to width × height points. All three return whether the target page existed.

PlaceTextAligned is PlaceText with a horizontal Align (AlignLeft/AlignRight/AlignCenter): the start point is shifted along the baseline by the text width so the line is anchored left, right or centered on (x, y). MaskedText is the one-call mask-and-replace: it fills an opaque box [x, y, x+width, y+height] in bgColor, then writes text in textColor horizontally aligned per align and vertically centered in the box. Colors are [3]float64 RGB triples (each 0.0..1.0). Both also draw in standard Helvetica.

FillRect(pageIndex int, x, y, width, height, r, g, b, opacity float64) bool
PlaceText(pageIndex int, x, y float64, text string, size, r, g, b, rotationDeg float64) bool
PlaceTextAligned(pageIndex int, x, y float64, text string, size, r, g, b, rotationDeg float64, align Align) bool
MaskedText(pageIndex int, x, y, width, height float64, text string, size float64, textColor, bgColor [3]float64, align Align) bool
DrawImage(pageIndex int, image []byte, x, y, width, height, rotationDeg float64) bool
go
data, _ := os.ReadFile("invoice.pdf")
ed, _ := rustpdf.Load(data)
defer ed.Close()
g, _ := rustpdf.MeasurePage(data, 0)
fmt.Printf("page is %.0fx%.0f pt\n", g.Width, g.Height)
// mask the old total with an opaque white box, then stamp the new value
ed.FillRect(0, 430, 690, 110, 18, 1, 1, 1, 1)
ed.PlaceText(0, 435, 693, "PAID IN FULL", 12, 0, 0.5, 0, 0)
// center a label on a point
ed.PlaceTextAligned(0, 300, 660, "RECEIPT", 14, 0, 0, 0, 0, rustpdf.AlignCenter)
// mask a placeholder and write right-aligned white text over a blue box in one call
ed.MaskedText(0, 430, 640, 110, 18, "R$ 1.240,00", 12,
	[3]float64{1, 1, 1}, [3]float64{0, 0, 0.6}, rustpdf.AlignRight)
logo, _ := os.ReadFile("logo.png")
ed.DrawImage(0, logo, 435, 720, 120, 48, 0) // stamp a logo, lower-left at (435, 720)
_ = ed.Save("stamped.pdf")

Advanced stamping: fonts, anchors & paragraphs free

The anchored variants extend the positioned-drawing primitives with embedded fonts, explicit vertical anchoring, padded masked text, wrapped paragraphs, an alternate coordinate space and an image rotation anchor. See the interactive positioning guide for a visual tour of the anchor and space semantics.

Embedded stamping fonts

AddFontFile/AddFont register a TrueType/OpenType font on a loaded document and return a fontID for the stamping calls below. The font is embedded as a subset, so stamped text renders with the real glyphs and metrics (full Unicode, no WinAnsi limit). Pass fontID = -1 anywhere to keep the built-in Helvetica.

AddFontFile(path string) (int, error)
AddFont(data []byte) (int, error)

Vertical anchors

PlaceTextAnchored is PlaceTextAligned plus a VerticalAnchor saying what y means: AnchorBaseline (the historical behavior), AnchorTop (the text hangs from y: baseline at y − ascent × size, legacy fixed-position layout semantics), AnchorBottom (the descender line rests on y), and AnchorLineTop/AnchorLineBottom, which anchor the layout line box (OS/2 win metrics plus a fixed half-leading); use the line-box anchors to match legacy layout engines line placement exactly. Ascent/descent come from the selected font's metrics.

PlaceTextAnchored(pageIndex int, x, y float64, text string, size, r, g, b, rotationDeg float64, align Align, anchor VerticalAnchor, fontID int) bool

Masked text: vertical alignment & padding

MaskedTextPadded is MaskedText plus a VerticalAlign for the line inside the box (VAlignMiddle keeps the historical cap-height centering; VAlignTop hangs the line from the top edge, top line-alignment in rectangle-based text APIs semantics; VAlignBottom rests the descender line on the bottom edge), a horizontal edge inset padding in points for left/right alignment (text starts at x + padding or ends at x + width − padding; a negative value keeps the historical default min(0.15 × size, width / 4), 0 starts flush with the box edge) and an optional embedded fontID.

MaskedTextPadded(pageIndex int, x, y, width, height float64, text string, size float64, textColor, bgColor [3]float64, align Align, valign VerticalAlign, padding float64, fontID int) bool

Paragraphs with word wrapping

PlaceParagraph breaks text into lines that fit width points (greedy, by word; \n forces a break) and draws them downward from the anchor (x, y). anchor positions the block: AnchorTop is the top of the box (first baseline ascent × size below y); AnchorBaseline is the first line's baseline; AnchorBottom/AnchorLineBottom are bottom-pinned: the block's bottom rests on y and grows upward by its real content height. maxHeight > 0 is a ceiling that truncates overflowing lines (with a bottom anchor they are cut from the top and the last lines stay pinned); <= 0 means unlimited. lineHeight scales the default 1.2 × size leading (<= 0 = 1.0); rotationDeg rotates the laid-out block about the anchor. It returns the number of lines drawn and the measured block height in points, so you can stack blocks without re-measuring.

PlaceParagraph(pageIndex int, x, y, width float64, text string, size, r, g, b float64, align Align, fontID int, maxHeight, lineHeight float64, anchor VerticalAnchor, rotationDeg float64) (lines int, height float64, found bool)

Coordinate space

SetStampSpace chooses the coordinate space of all positioned stamping calls that follow it. StampVisible (the default) keeps the historical behavior: coordinates in the page's displayed space, compensating /Rotate so a rotationDeg = 0 stamp reads upright on screen. StampMedia interprets coordinates and rotations in the raw PDF user space (the raw-coordinate semantics of legacy layout engines), never composing with the page's /Rotate or crop offset; use it to reproduce coordinates computed for legacy PDF libraries on rotated (scanned) pages. Watermarks and redaction are unaffected.

SetStampSpace(space StampSpace) error

Image rotation anchor

DrawImageAnchored is DrawImage plus an ImageAnchor: ImageAnchorCorner (the DrawImage behavior) rotates the image about its own lower-left corner at (x, y), so it sweeps around the anchor; ImageAnchorBoundingBox lands the rotated image's bounding box with its lower-left at (x, y) (bounding-box layout semantics: a 90° image occupies [x, x+height] × [y, y+width] and the pixels always sit at/above/right of the anchor).

DrawImageAnchored(pageIndex int, image []byte, x, y, width, height, rotationDeg float64, anchor ImageAnchor) bool
go
data, _ := os.ReadFile("form.pdf")
ed, _ := rustpdf.Load(data)
defer ed.Close()

// stamp with a real font (embedded as a subset)
roboto, _ := ed.AddFontFile("Roboto-Regular.ttf")

// hang a label from y = 700 using the layout line box
ed.PlaceTextAnchored(0, 72, 700, "Cláusula 4 — Confidencialidade", 12,
	0, 0, 0, 0, rustpdf.AlignLeft, rustpdf.AnchorLineTop, roboto)

// mask a placeholder: line hangs from the box top, flush with the left edge
ed.MaskedTextPadded(0, 430, 640, 110, 18, "R$ 1.240,00", 12,
	[3]float64{0, 0, 0}, [3]float64{1, 1, 1},
	rustpdf.AlignLeft, rustpdf.VAlignTop, 0, roboto)

// wrapped paragraph pinned to a bottom edge, capped at 80 pt
lines, height, _ := ed.PlaceParagraph(0, 72, 120, 300,
	"Long disclaimer text that wraps across several lines…", 9,
	0.3, 0.3, 0.3, rustpdf.AlignJustify, roboto,
	80, 1.0, rustpdf.AnchorBottom, 0)
fmt.Printf("drew %d lines, %0.1f pt tall\n", lines, height)

// reproduce legacy raw-space coordinates on a rotated scan
ed.SetStampSpace(rustpdf.StampMedia)
ed.PlaceTextAnchored(0, 100, 250, "legacy-style position", 10,
	0, 0, 0, 90, rustpdf.AlignLeft, rustpdf.AnchorBaseline, -1)
ed.SetStampSpace(rustpdf.StampVisible)

// rotated logo whose bounding box lands at (400, 300)
logo, _ := os.ReadFile("logo.png")
ed.DrawImageAnchored(0, logo, 400, 300, 120, 48, 90, rustpdf.ImageAnchorBoundingBox)
_ = 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)
go
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")
Content under a rect is deleted before the file is written, so 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) error
go
data, _ := 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 compliant

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.

Passwords protect, permissions only advise. A non-empty user password is real cryptographic protection: opening with the wrong password is rejected (cross-checked against qpdf for all three ciphers). The read-only 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 read-only as a hint to well-behaved viewers, not as an access control.

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)

Normalize & downgrade free

Adjust the output PDF version and strip PDF/A conformance. SetVersion sets the version code (0 = 1.4, 1 = 1.5, 2 = 1.7, 3 = 2.0). StripPdfa removes the catalog /OutputIntents and the XMP pdfaid identifier so the file is no longer claimed as PDF/A. Normalize does both in one call.

MethodDescription
SetVersion(version int) errorSet the output PDF version (codes above).
StripPdfa() errorRemove PDF/A conformance (output intents + pdfaid).
Normalize(version int) errorStrip PDF/A and set the version in one step.
go
data, _ := os.ReadFile("archival.pdf")
ed, _ := rustpdf.Load(data)
defer ed.Close()
_ = ed.Normalize(2)   // strip PDF/A + downgrade to PDF 1.7
_ = ed.Save("plain.pdf")

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."

Deferred / HSM signing licensed

Sign without ever handing the library a private key. The key stays in an HSM, cloud KMS, smartcard or PKI token; you supply only the signer certificate and a way to produce the raw RSA PKCS#1 v1.5 signature over SHA-256. This works with any PKI (eIDAS, AATL and other trust frameworks). Two models are available: a one-call form with a sign-hash callback, and a two-phase form for asynchronous signers where you build the CMS container yourself.

Model A. SignWith assembles the CMS signed attributes, calls signHash for the raw signature, then embeds the finished CMS. certDER is the signer certificate; chain are intermediate certificates (DER), supplied independently of the key; options may be nil.

rustpdf.SignWith(pdf, certDER []byte, signHash func([]byte) ([]byte, error), chain [][]byte, options *SigningOptions) ([]byte, error)
go
pdf, _ := os.ReadFile("contract.pdf")
certDER, _ := os.ReadFile("signer.der")       // X.509 certificate (DER), no key

signed, err := rustpdf.SignWith(pdf, certDER,
	func(tbs []byte) ([]byte, error) {
		return hsm.SignRSA(tbs)               // remote HSM / KMS / smartcard
	},
	nil,                                      // chain: intermediate certs (DER)
	&rustpdf.SigningOptions{Reason: "Approved", PAdES: true})
if err != nil {
	log.Fatal(err)
}
_ = os.WriteFile("contract.signed.pdf", signed, 0o644)

Model B. For an asynchronous signer, drive the two-phase flow. BeginSigning returns a SigningSession exposing the placeholder document, the exact covered bytes and their SHA-256 hash. Hand the hash to the remote signer, build the DER CMS / PKCS#7 container yourself, then complete the signature. The session methods are Document() []byte, Bytes() []byte, Hash() [32]byte and Complete(container []byte) ([]byte, error).

rustpdf.BeginSigning(pdf []byte, options *SigningOptions) (*SigningSession, error)
rustpdf.CompleteSignature(document, container []byte) ([]byte, error)
go
sess, _ := rustpdf.BeginSigning(pdf, &rustpdf.SigningOptions{PAdES: true})
digest := sess.Hash()                         // [32]byte, SHA-256 of sess.Bytes()

container := buildCMS(digest, certDER, sig)   // your DER CMS / PKCS#7
final, err := sess.Complete(container)        // or rustpdf.CompleteSignature(sess.Document(), container)
if err != nil {
	log.Fatal(err)
}
_ = os.WriteFile("contract.signed.pdf", final, 0o644)

List the signature fields already present before signing, to detect an existing signature or pick a target field. An empty slice means there are no signature fields.

rustpdf.ListSignatures(pdf []byte) ([]SignatureField, error)
go
fields, _ := rustpdf.ListSignatures(pdf)
for _, f := range fields {
	fmt.Printf("%s signed:%v\n", f.Name, f.Signed) // SignatureField{Name, Signed}
}

SigningOptions carries Reason, Location, Name, PAdES (produce a PAdES-B-B signature), Certify (a DocMDP certification level, use only on the first signature), ContainerSize (reserved /Contents bytes; 0 uses the default, raise it for large cloud CMS containers) and Policy (an optional PAdES-EPES signature-policy identifier).

For a visible signature, set Visible: true with VisiblePage (0-based), VisibleRect ([4]float64{x0, y0, x1, y1} in points) and optional VisibleText (lines separated by \n). VisibleImage accepts a PNG or JPEG (e.g. a scanned handwritten signature) drawn aspect-fit behind any text.

go
img, _ := os.ReadFile("signature.png")
opts := &rustpdf.SigningOptions{
	Visible:      true,
	VisiblePage:  0,
	VisibleRect:  [4]float64{72, 72, 272, 144},
	VisibleText:  "Jane Doe\nApproved",
	VisibleImage: img,   // PNG/JPEG drawn aspect-fit behind the text
}

Certify

ValueMeaning
rustpdf.CertifyNoneNot a certifying signature (default)
rustpdf.CertifyLockedNo changes permitted after signing
rustpdf.CertifyFormsForm-filling and signing permitted
rustpdf.CertifyFormsAndAnnotationsForm-filling, signing and annotations permitted

SignaturePolicy (for Policy) carries the policy OID (dotted-decimal), the policy document Hash, an optional HashAlgorithmOID (SHA-256 when empty) and an optional URI where the policy can be retrieved.

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)

Network timestamp (AD-RT)

For a trusted document timestamp from a real Time Stamping Authority, use the transport-agnostic helpers and do the HTTP POST yourself. BeginTimestamp prepares the document and returns the bytes to timestamp; build an RFC 3161 request with TimestampRequest, POST it to the TSA, extract the token with TimestampTokenFromResponse, then embed it with CompleteSignature. Free public TSAs work (FreeTSA https://freetsa.org/tsr, DigiCert http://timestamp.digicert.com).

rustpdf.BeginTimestamp(pdf []byte) (document, tbs []byte, err error)
rustpdf.TimestampRequest(imprint, nonce []byte, certReq bool) ([]byte, error)
rustpdf.TimestampTokenFromResponse(response []byte) ([]byte, error)
go
document, tbs, _ := rustpdf.BeginTimestamp(signed)
imprint := sha256.Sum256(tbs)
req, _ := rustpdf.TimestampRequest(imprint[:], nil, true)

// you POST the request to the TSA (application/timestamp-query)
resp, _ := http.Post("https://freetsa.org/tsr", "application/timestamp-query",
	bytes.NewReader(req))
body, _ := io.ReadAll(resp.Body)

token, _ := rustpdf.TimestampTokenFromResponse(body)
final, _ := rustpdf.CompleteSignature(document, token)
_ = os.WriteFile("contract.tsa.pdf", final, 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. Each report also carries certificate and signature details: Issuer, SerialNumber, ValidFrom, ValidTo, Algorithm (e.g. SHA256withRSA) and SigningTime (all *string, any may be nil), plus CertCount (certificates embedded in the CMS) and HasTimestamp (whether a signature timestamp is present). 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, and is not available on the free tier.

go
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)
	if sig.Issuer != nil {
		fmt.Printf("  issuer:%s certs:%d timestamp:%v\n",
			*sig.Issuer, sig.CertCount, sig.HasTimestamp)
	}
	if sig.Algorithm != nil {
		fmt.Printf("  algorithm:%s\n", *sig.Algorithm)
	}
}

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) free
rustpdf.ExtractImagesToDir(data []byte, dir string) (int, error) free
go
data, _ := 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)

Page geometry free

Read the size and rotation of every page without mutating the file. Sizes are in PDF points (72 per inch). Width and Height ignore the page's /Rotate, while RotatedWidth and RotatedHeight account for it (swapped for 90/270-degree pages). Each PageGeometry also carries the MediaBox and CropBox as a PdfRect{X0, Y0, X1, Y1} (with Width() and Height() helpers). MeasurePage returns an *Error when the index is out of range.

rustpdf.MeasurePages(pdf []byte) ([]PageGeometry, error)
rustpdf.MeasurePage(pdf []byte, index int) (PageGeometry, error)
go
data, _ := os.ReadFile("report.pdf")
pages, _ := rustpdf.MeasurePages(data)
for _, p := range pages {
	fmt.Printf("page %d: %.0fx%.0f pt, rotation %d (visible %.0fx%.0f)\n",
		p.Page, p.Width, p.Height, p.Rotation, p.RotatedWidth, p.RotatedHeight)
}

g, err := rustpdf.MeasurePage(data, 0)
if err != nil {
	log.Fatal(err)   // *Error when the index is out of range
}
fmt.Printf("crop box %.0fx%.0f\n", g.CropBox.Width(), g.CropBox.Height())

Document inspection free

Get a non-mutating overview of a PDF, its version, PDF/A level, encryption posture and page count, without opening or decrypting it. PdfaLevel is empty when the document is not PDF/A. Encryption is one of "None", "RC4", "AES-128" or "AES-256", and RequiresPassword reports whether a user password is needed to open it. Inspect never fails on a password-locked file.

rustpdf.Inspect(pdf []byte) (PdfOverview, error)
go
data, _ := os.ReadFile("report.pdf")
ov, _ := rustpdf.Inspect(data)
fmt.Printf("PDF %s, %d page(s)\n", ov.Version, ov.PageCount)
fmt.Printf("PDF/A level: %q\n", ov.PdfaLevel)        // "" when not PDF/A
fmt.Printf("encrypted: %v (%s)\n", ov.Encrypted, ov.Encryption)
fmt.Printf("needs password: %v\n", ov.RequiresPassword)

Find every occurrence of a query string and get its bounding box, useful for highlighting or redaction targeting. With caseSensitive false the match is case-insensitive. Each TextHit carries Page, Text and the box X, Y, Width, Height in PDF points (origin lower-left). An empty slice means no match. See the concept page Search inside a PDF for background.

rustpdf.FindText(pdf []byte, query string, caseSensitive bool) ([]TextHit, error)
go
data, _ := os.ReadFile("report.pdf")
hits, _ := rustpdf.FindText(data, "Invoice", false)
for _, h := range hits {
	fmt.Printf("page %d: %q at (%.1f, %.1f) %.1fx%.1f\n",
		h.Page, h.Text, h.X, h.Y, h.Width, h.Height)
}

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) licensed
rustpdf.PageCount(pdf []byte) (int, error) free
go
data, _ := 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

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)
rustpdf.A4PDF/A-4 (ISO 19005-4, based on PDF 2.0)
rustpdf.A4ePDF/A-4e (engineering)
rustpdf.A4fPDF/A-4f (requires at least one embedded file)

Align

ValueMeaning
rustpdf.AlignLeftLeft-aligned (default)
rustpdf.AlignRightRight-aligned
rustpdf.AlignCenterCentered
rustpdf.AlignJustifyJustified (space distributed between words)

VerticalAnchor

ValueMeaning
rustpdf.AnchorBaseliney is the text baseline (historical default)
rustpdf.AnchorTopText hangs from y (baseline at y − ascent × size)
rustpdf.AnchorBottomDescender line rests on y
rustpdf.AnchorLineTopTop of the layout line box (matches legacy fixed-position layout)
rustpdf.AnchorLineBottomBottom of the layout line box

VerticalAlign

ValueMeaning
rustpdf.VAlignTopLine hangs from the top edge of the box
rustpdf.VAlignMiddleCap-height centering (historical default)
rustpdf.VAlignBottomDescender line rests on the bottom edge

StampSpace

ValueMeaning
rustpdf.StampVisiblePage's displayed space, compensating /Rotate (default)
rustpdf.StampMediaRaw PDF user space, legacy layout semantics (no /Rotate composition)

ImageAnchor

ValueMeaning
rustpdf.ImageAnchorCornerRotate about the image's own lower-left corner at (x, y) (default)
rustpdf.ImageAnchorBoundingBoxRotated image's bounding box lands with its lower-left at (x, y)

AFRelationship

ValueMeaning
rustpdf.RelSourceSource data for the document (e.g. the invoice XML)
rustpdf.RelDataData used to derive the visual content
rustpdf.RelAlternativeAlternative representation
rustpdf.RelSupplementSupplementary material
rustpdf.RelUnspecifiedUnspecified relationship

Encryption

ValueCipher
rustpdf.RC4RC4 (legacy)
rustpdf.AES128AES-128
rustpdf.AES256AES-256 (V5/R6): recommended

FacturxProfile

ValueConformance level
rustpdf.FacturxMinimumMinimal header data only
rustpdf.FacturxBasicWLBasic, without line items
rustpdf.FacturxBasicBasic, with line items
rustpdf.FacturxEN16931EN 16931 (Comfort): the interoperable core, default
rustpdf.FacturxExtendedEN 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.

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=3 (IO): feature 'pdfa' requires a valid license
			// (doc.ToBytes() would report status=4, Serialize)
	}
}

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# and PHP: browse all docs. They share one core, so behavior is identical.