Node.js / TypeScript

Last updated: 2026-06-29

The rustpdf package wraps the rust-pdf C core with idiomatic, chainable classes over Koffi (pure FFI, no node-gyp, no native build). 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. Ships with TypeScript types.

Two classes do almost everything. Document authors a new PDF; EditableDoc loads and manipulates an existing one. Each holds a native handle, so call close() when done to free it promptly.

Installation

Install from npm. The native library (libpdf_ffi) ships as per-platform optional dependencies (@rustpdf/darwin-arm64, @rustpdf/linux-x64-gnu, @rustpdf/linux-arm64-gnu, @rustpdf/win32-x64-msvc): npm downloads only the one matching your OS/architecture, so there's nothing to compile and no node-gyp.

shell
npm install rustpdf

Requires Node.js 18+. Verify it loaded:

javascript
const rustpdf = require("rustpdf");
console.log(rustpdf.version());   // native library version
Bundling for a serverless target (AWS Lambda, etc.)? Make sure the matching @rustpdf/<platform> package is included for the deploy architecture: e.g. install on a Linux x64 host, or add it explicitly as an optionalDependency.

Quick start

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

javascript
const { Document } = require("rustpdf");

const doc = new Document();              // A4 by default
doc.addPage();
doc.setFillRgb(0.86, 0.20, 0.18);
doc.rect(72, 640, 200, 120);             // x, y, width, height (points)
doc.fill();
doc.save("out.pdf");
doc.close();

Most methods return the document, so calls chain:

javascript
const doc = new Document();
const font = doc.addFontFile("Roboto-Regular.ttf");
doc.addPage()
   .setFillRgb(0.1, 0.1, 0.12)
   .rect(0, 800, 595, 42).fill()
   .showText(font, 24, 72, 740, "Olá, açúcar — café");
const data = doc.toBytes();              // a Node Buffer instead of a file
doc.close();

TypeScript

The package ships type declarations (index.d.ts), so no @types are needed. Both require and ES-module import work.

typescript
import { Document, PdfaLevel, Align } from "rustpdf";

const doc = new Document();
doc.pdfa(PdfaLevel.A2a).setInfo({ title: "Report" });
const f = doc.addFontFile("Roboto-Regular.ttf");
doc.addPage().showText(f, 20, 72, 760, "Title", 1);   // headingLevel 1 = H1
const bytes: Buffer = doc.toBytes();
doc.close();

Licensing & activation

Basic generation (everything above) is always free. The corporate features (PDF/A, digital signatures/PAdES, encryption, accessibility) require an active license token. Without one, those calls throw PdfError 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:

javascript
const rustpdf = require("rustpdf");
rustpdf.activateLicense(token);   // throws PdfError if forged / expired / malformed
Verification is fully offline: signature + expiry checked against a public key embedded in the library. No network callback, no telemetry.

Coordinate system

Threading & concurrency

Every native call is synchronous (it blocks the event loop for its duration) and the core is Send but not Sync: a single handle must never be touched by two threads at once.

javascript
// worker.js
const { parentPort, workerData } = require("worker_threads");
const { Document } = require("rustpdf");

const doc = new Document();              // one document per worker
doc.addPage().setFillRgb(0.1, 0.1, 0.12).rect(72, 700, 200, 80).fill();
parentPort.postMessage(doc.toBytes());
doc.close();

Authoring: create & save

new Document() free

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

MethodDescription
addPage(size?)Append a page. size is an optional { width, height } object in points.
setDefaultSize(w, h)Default size for subsequently added pages.
setVersion(v)Set the PDF header version (0 → 1.4, 1 → 1.5, 2 → 1.7, 3 → 2.0).
pageCountProperty: number of pages so far.
toBytes()Render the document to a Buffer.
save(path)Render and write to a file.
close()Free the native handle.
Validity. A document must have at least one page — serializing an empty document raises an error. Color components (RGB/Gray/CMYK) are clamped to the valid 0–1 range.

Pages & vector graphics

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

MethodDescription
setFillRgb(r, g, b)Fill color.
setStrokeRgb(r, g, b)Stroke color.
setLineWidth(w)Stroke width in points.
rect(x, y, w, h)Add a rectangle subpath.
fill()Fill the current path with the fill color.
stroke()Stroke the current path with the stroke color.
javascript
const doc = new Document();
doc.addPage();
doc.setStrokeRgb(0.10, 0.45, 0.90).setLineWidth(3);
doc.rect(72, 600, 300, 160).stroke();
doc.setFillRgb(0.95, 0.77, 0.06);
doc.rect(120, 640, 120, 80).fill();
doc.save("shapes.pdf");
doc.close();

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) → number   addFont(data: Buffer) → number
showText(font, size, x, y, text, headingLevel?)

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

javascript
const doc = new Document();
const regular = doc.addFontFile("Roboto-Regular.ttf");
// …or from bytes you already have in memory:
// const regular = doc.addFont(fs.readFileSync("Roboto-Regular.ttf"));

doc.addPage();
doc.showText(regular, 28, 72, 760, "Invoice #1024");
doc.showText(regular, 12, 72, 720, "日本語 · Ελληνικά · العربية");
doc.save("text.pdf");
doc.close();
Font coverage. Characters outside the embedded font's coverage are silently dropped — they render as the missing-glyph box and won't extract or copy. The bundled Roboto fallback covers Latin, Greek and Cyrillic but not CJK, Arabic, Hebrew or emoji. Embed a font that covers every script you write.
No NUL bytes in strings. Text passed across the API must not contain a NUL (\0) character — it truncates the string at the FFI boundary, silently dropping everything after the NUL. This applies to shown text, metadata and field names.
Font ids belong to the document that registered them. The id returned by addFontFile/addFont is just an integer index into that Document. An id that was never registered, an id from a different document, or a negative number is not caught at the showText call. It is rejected only when you serialize (toBytes()/save()), throwing PdfError (and currently logging a Rust panic line to stderr). Use each id only with the document it came from.

Paragraphs

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

paragraph(font, size, x, y, width, text, align?)
javascript
const { Document, Align } = require("rustpdf");

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

const doc = new Document();
const f = doc.addFontFile("Roboto-Regular.ttf");
doc.addPage();
doc.paragraph(f, 12, 72, 700, 451, intro, Align.Justify);
doc.save("paragraph.pdf");
doc.close();

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) → numberLoad JPEG/PNG from a file; returns the image id.
addImagePng(data: Buffer) → numberRegister a PNG from memory.
addImageJpeg(data: Buffer) → numberRegister a JPEG from memory.
drawImage(image, x, y, w, h)Draw at (x, y) scaled to w × h points.
figure(image, x, y, w, h, alt)Draw as a tagged /Figure with alt text (accessibility).
javascript
const doc = new Document();
const logo = doc.addImageFile("logo.png");
doc.addPage();
doc.drawImage(logo, 72, 680, 160, 90);
doc.save("with_image.pdf");
doc.close();

PDF/A licensed

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

pdfa(level?)
javascript
const { Document, PdfaLevel } = require("rustpdf");

const doc = new Document();
doc.pdfa(PdfaLevel.A2b).setInfo({ title: "Q3 Report", author: "Acme Inc." });
const f = doc.addFontFile("Roboto-Regular.ttf");
doc.addPage();
doc.showText(f, 20, 72, 760, "Archival report");
doc.save("report_pdfa.pdf");     // throws PdfError without a license granting PDF/A
doc.close();
A document title is recommended for valid PDF/A metadata. It is mandatory for PDF/UA-1: a tagged pdfa(PdfaLevel.A2a) document without a title still passes verapdf -f 2a but fails verapdf -f ua1 (clause 7.1, missing dc:title), and the library does not warn. Always call setInfo({ title: … }) for accessible output.

Accessibility (Tagged PDF / PDF/UA) licensed

tagged() builds a logical structure tree (PDF/UA-1). Combine with pdfa(PdfaLevel.A2a) for archival and accessible output. Use headingLevel on showText for H1H6, and figure(..., alt) for described images.

tagged() alone is not PDF/UA-1 conformant. By itself it builds the structure tree but does not emit the XMP /Metadata stream that PDF/UA-1 requires, so veraPDF fails clause 7.1 ("Catalog … shall contain the Metadata key"). For output that validates as PDF/UA-1, pair it with pdfa(PdfaLevel.A2a): doc.pdfa(PdfaLevel.A2a).tagged() emits the XMP and passes both verapdf -f 2a and verapdf -f ua1.
tagged()
javascript
const { Document, PdfaLevel } = require("rustpdf");

const doc = new Document();
doc.pdfa(PdfaLevel.A2a).tagged().setInfo({ title: "Accessible report" });
const 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…");
const chart = doc.addImageFile("chart.png");
doc.figure(chart, 72, 520, 300, 150, "Revenue grew 18% year over year");
doc.save("accessible.pdf");
doc.close();
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, data, relationship?, description?)
javascript
const fs = require("fs");
const { Document, PdfaLevel, AFRelationship } = require("rustpdf");

const xml = fs.readFileSync("invoice.xml");
const doc = new Document();
doc.pdfa(PdfaLevel.A3b).setInfo({ title: "E-invoice 1024" });
const f = doc.addFontFile("Roboto-Regular.ttf");
doc.addPage();
doc.showText(f, 18, 72, 760, "Invoice 1024");
doc.attachFile("invoice.xml", "text/xml", xml,
               AFRelationship.Source, "Structured invoice data");
doc.save("einvoice.pdf");
doc.close();
PDF/A-4f needs an attachment. The A4f profile requires at least one embedded file (ISO 19005-4); the library now rejects A4f output that has no attachment, so call attachFile before serializing.
The licence gate is on PDF/A, not on attachFile itself. The licensed badge above reflects the PDF/A-3 workflow shown here. Calling attachFile on a plain (non-PDF/A) document does not require a licence: it succeeds and produces a valid PDF with an /EmbeddedFile. The licence is enforced only when you also request a PDF/A level, which is what makes the attachment archival.

ZUGFeRD / Factur-X e-invoices licensed

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

facturx(xml: Buffer, profile?)
javascript
const fs = require("fs");
const { Document, FacturxProfile } = require("rustpdf");

const xml = fs.readFileSync("factur-x.xml");      // your Cross-Industry Invoice XML
const doc = new Document();
doc.setInfo({ title: "Invoice INV-2026-001" });
const f = doc.addFontFile("Roboto-Regular.ttf");
doc.addPage();
doc.showText(f, 18, 72, 760, "Invoice INV-2026-001");
doc.facturx(xml, FacturxProfile.EN16931);
doc.save("einvoice.pdf");        // PDF/A-3 + Factur-X; needs a PDF/A license
doc.close();

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

AcroForm fields

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

MethodDescription
textField(name, page, rect, value?, size?)Text input (size=0 → auto font size).
checkbox(name, page, rect, checked)Checkbox.
dropdown(name, page, rect, options, selected?, size?)Combo box from an array of strings.
radioGroup(name, page, buttons, selected?)buttons = array of { rect, export } objects.
javascript
const doc = new Document();
doc.addPage();
doc.textField("applicant.name", 0, [72, 700, 320, 720], "");
doc.checkbox("agree", 0, [72, 660, 88, 676], false);
doc.dropdown("plan", 0, [72, 620, 240, 640],
             ["Starter", "Pro", "Enterprise"], 1);
doc.radioGroup("billing", 0, [
  { rect: [72, 580, 88, 596], export: "monthly" },
  { rect: [140, 580, 156, 596], export: "annual" },
], 1);
doc.save("form.pdf");
doc.close();

Fill and flatten fields later with EditableDoc.

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

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

linkUri(rect, uri)   linkToPage(rect, pageIndex, top?)
javascript
const doc = new Document();
const f = doc.addFontFile("Roboto-Regular.ttf");
doc.addPage();
doc.showText(f, 14, 72, 760, "Visit rustpdf.dev (see page 2)");
doc.linkUri([72, 756, 320, 776], "https://rustpdf.dev/");   // web link
doc.linkToPage([330, 756, 430, 776], 1, 800);              // jump to page 2
doc.addPage();
doc.save("links.pdf");
doc.close();

Rectangles are [x0, y0, x1, y1] in points; pageIndex is 0-based.

Bookmarks / outline free

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

new Bookmark(title, page, top?)   .child(bookmark)   addBookmark(bookmark)
javascript
const { Document, Bookmark } = require("rustpdf");

const doc = new Document();
const f = doc.addFontFile("Roboto-Regular.ttf");
for (let i = 0; i < 3; i++) doc.addPage();

const ch1 = new Bookmark("Chapter 1", 0, 820);
ch1.child(new Bookmark("Section 1.1", 1));
ch1.child(new Bookmark("Section 1.2", 2));
doc.addBookmark(ch1);
doc.addBookmark(new Bookmark("Chapter 2", 2));
doc.save("outline.pdf");
doc.close();

.child(...) returns the child it appended, so you can keep building deeper nesting from it.

Metadata

setInfo({ title?, author?, subject?, keywords?, creator? })

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

javascript
doc.setInfo({ title: "Q3 Report", author: "Acme Inc.",
              subject: "Quarterly results", keywords: "finance, q3" });

Manipulation: load an existing PDF

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

EditableDoc.load(data: Buffer, password?)
EditableDoc.loadFile(path, password?)
javascript
const { EditableDoc } = require("rustpdf");

const ed = EditableDoc.loadFile("in.pdf");
console.log(ed.pageCount);
ed.close();

// encrypted input:
const sec = EditableDoc.loadFile("secured.pdf", "user-or-owner-pw");
sec.save("plain.pdf");
sec.close();
loadFile reads the file in JS first. A non-existent path throws a plain Node ENOENT error, not a PdfError, so an instanceof PdfError handler will not catch it. Only parsing failures of an existing file surface as PdfError.
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 does not throw in that case. Serializing a zero-page document throws PdfError ("document has no pages") instead of writing an invalid file, but check pageCount > 0 after loading untrusted input before relying on it.

Pages: merge, split, reorder, rotate

MethodDescription
merge(other)Append all pages of another EditableDoc (objects renumbered & remapped).
rotatePage(index, degrees)Rotate one page (90 / 180 / 270).
deletePage(index)Remove a page.
reorderPages(order)Reorder with a full permutation array of indices.
extractPages(indices) → EditableDocNew document containing just those pages.
pageCountProperty: current page count.
javascript
const a = EditableDoc.loadFile("a.pdf");
const b = EditableDoc.loadFile("b.pdf");
a.merge(b);                        // a now has a's pages followed by b's
a.rotatePage(0, 90);
a.reorderPages([...Array(a.pageCount).keys()].reverse());
a.save("merged.pdf");
b.close();

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

Metadata, overlay & form fill

MethodDescription
setInfo(key, value)Set one info entry (e.g. "Title").
getInfo(key) → stringRead an info entry.
setXmp(xml: Buffer)Replace the XMP metadata stream.
overlayPage(index, content: Buffer)Overlay a content-stream fragment onto a page (stamps/watermarks).
fillTextField(name, value) → booleanFill an AcroForm text field; returns whether it was found.
javascript
const ed = EditableDoc.loadFile("form.pdf");
ed.setInfo("Title", "Filled form");
const found = ed.fillTextField("applicant.name", "Jane Doe");
console.log("filled:", found, "| title:", ed.getInfo("Title"));
ed.save("filled.pdf");
ed.close();

Form fill & flatten free

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

MethodDescription
fieldNames() → string[]Fully-qualified names of every terminal field.
fillTextField(name, value) → booleanSet a text (or text-style choice) field; returns whether it matched.
setCheckbox(name, checked?) → booleanCheck/uncheck a checkbox (defaults to true).
setRadio(name, exportValue) → booleanSelect a radio button by its export value.
setChoice(name, value) → booleanSet a dropdown / list-box value.
flattenForms()Bake all fields into static content and drop the /AcroForm.
javascript
const { EditableDoc } = require("rustpdf");

const ed = EditableDoc.loadFile("form.pdf");
console.log(ed.fieldNames());          // ['applicant.name', 'agree', 'plan', ...]
ed.fillTextField("applicant.name", "Jane Doe");
ed.setCheckbox("agree", true);
ed.setRadio("billing", "annual");
ed.setChoice("plan", "Pro");
ed.flattenForms();                     // optional: make it non-editable
ed.save("filled.pdf");
ed.close();

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". Characters outside Latin-1 (Cyrillic, Greek, CJK, emoji) are not dropped: they render as visible mojibake, so do not pass them here.

watermarkText(text, { size?, color?, opacity?, rotationDeg?, opaqueBackground? }?)
watermarkImageFile(path, width, height, opacity?, rotationDeg?)

Set opaqueBackground: true to draw a solid (white-out) box behind the text stamp instead of letting the page show through. watermarkImageFile takes an optional rotationDeg to rotate the image stamp.

javascript
const ed = EditableDoc.loadFile("report.pdf");
ed.watermarkText("CONFIDENTIAL", { opacity: 0.25, rotationDeg: 45, opaqueBackground: false });
ed.watermarkImageFile("stamp.png", 200, 120, 0.3, 30);   // rotate the image 30°
ed.save("stamped.pdf");
ed.close();

Defaults: size: 64, color: [0.5, 0.5, 0.5], opacity: 0.30, rotationDeg: 45 (text) / 0 (image), opaqueBackground: false.

Positioned drawing free

Place a filled rectangle, a line of text, a wrapped paragraph or an image at exact coordinates on a page, for stamping a value into a form, masking a region, or adding a note. Coordinates are in the page's visible space (origin lower-left, y up), so content lands where the viewer sees it regardless of the page's /Rotate (switch to raw media space with setStampSpace below). Text uses the standard Helvetica font by default (keep it to WinAnsi / Latin-1), or an embedded TrueType/OpenType font registered with addFontFile/addFont. See the Interactive positioning guide to explore the anchors and coordinate spaces visually.

fillRect(pageIndex, x, y, width, height, color = [1, 1, 1], opacity = 1.0) → boolean
placeText(pageIndex, x, y, text, size = 12, color = [0, 0, 0], rotationDeg = 0.0, align = Align.Left, fontId = -1, anchor = VerticalAnchor.Baseline) → boolean
maskedText(pageIndex, x, y, width, height, text, size = 12, textColor = [0, 0, 0], bgColor = [1, 1, 1], align = Align.Left, fontId = -1, valign = VerticalAlign.Middle, padding = null) → boolean
placeParagraph(pageIndex, x, y, width, text, { size?, color?, align?, fontId?, maxHeight?, lineHeight?, anchor?, rotationDeg? }?) → boolean
placeParagraphMeasured(pageIndex, x, y, width, text, opts?) → { lines, height }
drawImage(pageIndex, image, x, y, width, height, rotationDeg = 0.0, anchor = ImageAnchor.Corner) → boolean

All return false if the page index (or fontId) does not exist. rotationDeg rotates the text or image counter-clockwise about its anchor (x, y). placeText's align (an Align value) shifts the start point along the baseline so the text is left/right/center aligned about (x, y) (Justify behaves like Left). maskedText bundles the mask-then-stamp pattern: it fills the box [x, y, x+width, y+height] in bgColor, then writes the text over it — handy for replacing a placeholder without hand-computing the baseline. drawImage takes raw JPEG/PNG image bytes (a Buffer or Uint8Array) scaled to width × height points.

javascript
const ed = EditableDoc.loadFile("form.pdf");
const { width, height } = rustpdf.measurePage(fs.readFileSync("form.pdf"), 0);

// mask a region with an opaque white box, then stamp a value over it
ed.fillRect(0, 60, height - 100, 200, 24, [1, 1, 1], 1.0);
ed.placeText(0, 64, height - 94, "APPROVED", 18, [0, 0, 0], 0.0);

// or do both in one call — fill + centered text over the box
ed.maskedText(0, 60, height - 160, 200, 24, "APPROVED", 18,
              [0, 0, 0], [1, 1, 1], rustpdf.Align.Center);

ed.drawImage(0, fs.readFileSync("logo.png"), 64, height - 200, 120, 48);
ed.save("stamped.pdf");
ed.close();

Embedded stamping fonts

Register a TrueType/OpenType font and pass its fontId to placeText, maskedText or placeParagraph to stamp with the real font's glyphs and metrics (Unicode included) instead of the built-in Helvetica. The font is embedded as a subset, exactly like Document.addFontFile plus showText; -1 (the default) keeps Helvetica.

addFontFile(path) → number   addFont(data) → number
typescript
const font: number = ed.addFontFile("TimesNewRoman.ttf");
ed.placeText(0, 72, 500, "Assinatura — João", 14, [0, 0, 0], 0, Align.Left, font);

Vertical anchors

placeText's anchor (a VerticalAnchor value) says what y means: Baseline (default, the historical behavior), Top (the text hangs from y: the baseline lands ascent × size below it, matching legacy fixed-position layout), Bottom (the descender line rests on y), or LineTop/LineBottom, which anchor via the layout line box (OS/2 win metrics, or typo × 1.2, plus a fixed half-leading of 0.21 em) and so reproduce the legacy engine's line placement exactly. Ascent/descent come from the selected font's metrics.

javascript
const { VerticalAnchor, Align } = require("rustpdf");
// same y a migrated legacy fixed-position layout call used — same visual result
ed.placeText(0, 72, 500, "Total: € 1.234,00", 12, [0, 0, 0], 0,
             Align.Left, -1, VerticalAnchor.LineBottom);

maskedText: vertical alignment & padding

valign (a VerticalAlign value) controls where the line sits inside the box: Middle (default) keeps the historical cap-height centering, Top hangs the line from the top edge (baseline at y + height − ascent × size, top line-alignment in rectangle-based text APIs semantics), Bottom rests the descender line on the bottom edge. padding is the horizontal edge inset (points) for Left/Right alignment: text starts at x + padding (or ends at x + width − padding); null keeps the historical min(0.15 × size, width / 4), and 0 starts flush with the box edge like rectangle-based DrawString APIs.

javascript
const { VerticalAlign, Align } = require("rustpdf");
ed.maskedText(0, 60, 700, 220, 40, "Maria Silva", 12,
              [0, 0, 0], [1, 1, 1], Align.Left, -1, VerticalAlign.Top, 0);

Wrapped paragraphs

placeParagraph breaks text into lines that fit width points (greedy, by word; '\n' forces a break) and draws them downward: with the default anchor: VerticalAnchor.Top the first baseline lands ascent × size below y, like legacy fixed-position layout. align: Align.Justify stretches the word gaps of every line but the last of each paragraph. maxHeight is a ceiling: lines whose descender would cross it are cut (a hard height ceiling). anchor: Bottom/LineBottom bottom-pins the block: its bottom rests on y and it grows upward by its real content height; with maxHeight the box is [y, y+maxHeight] and overflowing lines are cut from the top (the last lines stay pinned). lineHeight scales the default 1.2 × size leading, and rotationDeg rotates the laid-out block about the anchor. placeParagraphMeasured additionally returns the number of lines drawn and the consumed height in points, so blocks can be stacked without re-measuring.

typescript
import { EditableDoc, Align, VerticalAnchor } from "rustpdf";

const notes = "Long remittance information that will not fit on one line…";
const { lines, height } = ed.placeParagraphMeasured(0, 72, 720, 220, notes, {
  size: 10, align: Align.Justify, maxHeight: 120,
});
// stack the next block right below the consumed height
ed.placeParagraph(0, 72, 720 - height - 8, 220, "Second block", { size: 10 });

// bottom-pinned: the block's bottom rests on y = 60 and grows upward
ed.placeParagraph(0, 350, 60, 200, notes, {
  size: 9, anchor: VerticalAnchor.Bottom, maxHeight: 90,
});

Stamp coordinate space

setStampSpace chooses the coordinate space of all the positioned primitives above for subsequent calls. StampSpace.Visible (default) keeps the historical behavior: coordinates in the page's displayed space, compensating /Rotate so a rotationDeg = 0 stamp reads upright on screen. StampSpace.Media interprets coordinates and rotationDeg 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) → this
javascript
const { StampSpace } = require("rustpdf");
ed.setStampSpace(StampSpace.Media);        // legacy layout engines-compatible raw user space
ed.placeText(0, 100, 100, "as legacy layout engines placed it", 10);
ed.setStampSpace(StampSpace.Visible);      // back to the default

Image rotation anchor

drawImage's anchor (an ImageAnchor value) controls how a rotated image is anchored at (x, y). With Corner (the default), (x, y) is the image's own lower-left corner, which the image sweeps around when rotated. With BoundingBox, the rotated image's bounding box lands with its lower-left at (x, y) (bounding-box layout semantics: the drawn pixels always sit at/above/right of the anchor; a 90° image occupies [x, x+height] × [y, y+width]).

javascript
const { ImageAnchor } = require("rustpdf");
ed.drawImage(0, fs.readFileSync("stamp.png"), 400, 600, 80, 40, 90.0,
             ImageAnchor.BoundingBox);

Redaction licensed

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

redact(pageIndex, rects) → boolean
javascript
const ed = EditableDoc.loadFile("statement.pdf");
// rects = array of [x0, y0, x1, y1] on that page
ed.redact(0, [[60, 590, 400, 620], [60, 540, 400, 570]]);
ed.save("redacted.pdf");     // throws PdfError without a license granting redaction
ed.close();
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 a level-A profile is requested.

convertToPdfa(level?)
javascript
const { EditableDoc, PdfaLevel } = require("rustpdf");

const ed = EditableDoc.loadFile("in.pdf");
ed.convertToPdfa(PdfaLevel.A2b);   // throws PdfError if fonts aren't embedded
ed.save("archival.pdf");           // veraPDF: PDF/A-2b compliant
ed.close();

Optimize & compact

MethodDescription
optimize()Drop unreferenced objects, Flate-compress uncompressed streams, dedupe identical objects.
compact(on?)Pack objects into object streams + emit a cross-reference stream.
javascript
const ed = EditableDoc.loadFile("big.pdf");
ed.optimize().compact(true);
ed.save("small.pdf");
ed.close();

Encryption licensed

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

encrypt({ method?, user?, owner?, readOnly? })
javascript
const { EditableDoc, Encryption } = require("rustpdf");

const ed = EditableDoc.loadFile("in.pdf");
ed.encrypt({ user: "", owner: "owner-secret",
             method: Encryption.Aes256, readOnly: true });
ed.save("secured.pdf");          // throws PdfError without an Encryption license
ed.close();

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.

Normalize & downgrade free

Remove PDF/A conformance or downgrade the PDF version of a loaded document. setVersion rewrites the header version; stripPdfa drops the OutputIntents, the XMP pdfaid identifier and the catalog /Version; normalize does both at once (strip PDF/A and set the version), producing a plain PDF.

MethodDescription
setVersion(version)Set the output PDF version (PdfVersion: 0→1.4, 1→1.5, 2→1.7, 3→2.0).
stripPdfa()Strip PDF/A conformance (OutputIntents, XMP pdfaid, /Version).
normalize(version?)Strip PDF/A and set the version in one call (defaults to 2 → 1.7).
javascript
const { EditableDoc, PdfVersion } = require("rustpdf");

const ed = EditableDoc.loadFile("archival.pdf");
ed.normalize(PdfVersion.V1_7);   // strip PDF/A + downgrade to 1.7
// or step by step:
// ed.stripPdfa().setVersion(PdfVersion.V1_7);
ed.save("plain.pdf");
ed.close();

Output & incremental update

MethodDescription
toBytes() → BufferSerialize the manipulated document.
save(path)Serialize to a file.
toBytesIncremental(original: Buffer) → BufferAppend only changes to the original bytes (signature-safe, non-destructive).
javascript
const fs = require("fs");
const original = fs.readFileSync("in.pdf");
const ed = EditableDoc.load(original);
ed.setInfo("Subject", "reviewed");
const incremental = ed.toBytesIncremental(original);   // original bytes preserved verbatim
fs.writeFileSync("reviewed.pdf", incremental);
ed.close();

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 Buffers. pades: true switches to PAdES-B-B.

rustpdf.sign(pdf, keyDer, certDer, { reason?, location?, name?, pades? }?) → Buffer
javascript
const fs = require("fs");
const rustpdf = require("rustpdf");

const pdf     = fs.readFileSync("contract.pdf");
const keyDer  = fs.readFileSync("signing-key.pkcs8.der");   // PKCS#8 private key (DER)
const certDer = fs.readFileSync("signing-cert.der");        // X.509 certificate (DER)

const signed = rustpdf.sign(pdf, keyDer, certDer, {
  reason: "Approved", location: "New York",
  name: "Jane Doe", pades: true,
});
fs.writeFileSync("contract.signed.pdf", signed);
// Verify in a shell: pdfsig contract.signed.pdf  →  "Signature is Valid."

HSM / deferred signing licensed

Sign without ever handing this library a private key. The key stays in an HSM, cloud KMS, smartcard or PKI token, and the binding only builds and embeds the CMS. It works with any PKI (eIDAS, AATL and other trust lists). It offers two models, and both expect an RSA PKCS#1 v1.5 signature over SHA-256.

Model A: bring your own signer

signWith builds the CMS signed attributes and calls your signHash callback for the raw RSA signature over the bytes it passes in, then assembles and embeds the container. certDer is the signer certificate; chain are intermediate certificates (DER), supplied independently of the key.

rustpdf.signWith(pdf, certDer, signHash, chain?, options?) → Buffer
javascript
const { signWith } = require("rustpdf");

const pdf     = fs.readFileSync("contract.pdf");
const certDer = fs.readFileSync("signing-cert.der");        // X.509 certificate (DER)

// signHash receives the bytes to sign and returns the raw RSA signature
// produced by your HSM / KMS / smartcard. The private key never enters the library.
const signed = signWith(pdf, certDer, (data) => hsm.signRsaSha256(data), [], {
  reason: "Approved", location: "Berlin", name: "Jane Doe", pades: true,
});
fs.writeFileSync("contract.signed.pdf", signed);

Model B: two-phase signing

When the signer is remote or asynchronous, split signing in two. beginSigning prepares the PDF and returns a SigningSession; send its hash to the signer, wrap the result in a DER CMS / PKCS#7 container, then call complete (or the standalone completeSignature). The key never reaches this library.

rustpdf.beginSigning(pdf, options?) → SigningSession
session.complete(container) → Buffer
rustpdf.completeSignature(document, container) → Buffer
javascript
const { beginSigning } = require("rustpdf");

const session = beginSigning(pdf, { pades: true });
// session.document : the prepared PDF (zero-filled /Contents placeholder)
// session.bytes    : the exact bytes the signature covers
// session.hash     : SHA-256 of those bytes (the value the HSM signs)

const container = await remoteSigner.buildCms(session.hash);   // DER CMS / PKCS#7
const signed = session.complete(container);
fs.writeFileSync("contract.signed.pdf", signed);

Inventory existing signatures

Before signing, list the signature fields already present (for example to add a second signature without disturbing the first). Returns one object per field with name and signed; an empty array means there are none.

rustpdf.listSignatures(pdf) → SignatureField[]
javascript
for (const field of rustpdf.listSignatures(pdf)) {
  console.log(field.name, "signed:", field.signed);
}

Signing options

The optional options object (SigningOptions) is shared by signWith and beginSigning.

FieldMeaning
reason, location, nameFree-text signature metadata.
padestrue produces a PAdES-B-B signature (ETSI.CAdES.detached).
certifyA Certify level (DocMDP), applied only by the first signature: Certify.None (0), Locked (1, no changes), Forms (2, form-filling and signing), FormsAndAnnotations (3).
containerSizeReserved /Contents bytes; 0 or omitted uses the default. Raise it for large cloud-HSM containers.
policyA signature-policy identifier (PAdES-EPES): { oid, hash, hashAlgorithmOid?, uri? }. Omit for none.
visible, visiblePage, visibleRect, visibleTextDraw a visible signature: set visible: true, the 0-based visiblePage, the appearance visibleRect [x0, y0, x1, y1], and optional visibleText lines.
visibleImagePNG/JPEG bytes (Buffer) of a handwritten-signature image, drawn aspect-fit behind any visibleText in the appearance rectangle.
javascript
const signed = signWith(pdf, certDer, (data) => hsm.signRsaSha256(data), [], {
  reason: "Approved", pades: true,
  visible: true, visiblePage: 0, visibleRect: [380, 60, 560, 140],
  visibleText: "Signed by Jane Doe",
  visibleImage: fs.readFileSync("signature.png"),   // drawn aspect-fit behind the text
});
The private key never crosses the FFI boundary. Both models build the CMS in-process from the certificate plus the signature your signer returns, so the key can stay in an HSM, cloud KMS, smartcard or PKI token. signHash (Model A) and your CMS builder (Model B) must produce an RSA PKCS#1 v1.5 signature over SHA-256.

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, certs?, crls?) → Buffer
rustpdf.timestamp(pdf, tsaKeyDer, tsaCertDer, date?) → Buffer
javascript
const signed = fs.readFileSync("contract.signed.pdf");

// B-LT: embed validation material (caller supplies DER certs/CRLs)
const lt = rustpdf.addDss(signed, [certDer], [crlDer]);

// B-LTA: add a document timestamp signed by a TSA key/cert
const lta = rustpdf.timestamp(lt, tsaKeyDer, tsaCertDer);
fs.writeFileSync("contract.lta.pdf", lta);

Network timestamp (AD-RT)

To timestamp against a real RFC 3161 TSA, use the transport-agnostic helpers: the binding builds the request and embeds the token, while you do the HTTP POST. beginTimestamp prepares the PDF and returns { document, bytes }; build a request with timestampRequest over the SHA-256 of bytes, POST the DER to the TSA, extract the token with timestampTokenFromResponse, then embed it via completeSignature. Free public TSAs work, such as FreeTSA (https://freetsa.org/tsr) and DigiCert (http://timestamp.digicert.com).

rustpdf.beginTimestamp(pdf) → { document, bytes }
rustpdf.timestampRequest(imprint, nonce?, certReq?) → Buffer
rustpdf.timestampTokenFromResponse(response) → Buffer
javascript
const crypto = require("crypto");
const rustpdf = require("rustpdf");

const session = rustpdf.beginTimestamp(signed);
const imprint = crypto.createHash("sha256").update(session.bytes).digest();
const req = rustpdf.timestampRequest(imprint);          // RFC 3161 TimeStampReq (DER)

const resp = await fetch("https://freetsa.org/tsr", {   // you do the HTTP POST
  method: "POST",
  headers: { "Content-Type": "application/timestamp-query" },
  body: req,
});
const token = rustpdf.timestampTokenFromResponse(Buffer.from(await resp.arrayBuffer()));
const lta = rustpdf.completeSignature(session.document, token);
fs.writeFileSync("contract.lta.pdf", lta);

Validate signatures licensed

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

rustpdf.verifySignatures(data: Buffer) → object[]

Each entry has field_name, sub_filter, signer, covers_whole_document, digest_valid, signature_valid, is_valid and byte_range. Each report also carries certificate and signature metadata: issuer, serial_number, valid_from, valid_to, algorithm (e.g. SHA256withRSA), signing_time, cert_count and has_timestamp. field_name, signer and any of the certificate fields may be null when absent. An empty array means the document is unsigned. Signature validation is a licensed feature: this call requires an active license (signatures) even when the document is unsigned, so it is not available on the free tier.

javascript
const rustpdf = require("rustpdf");

const data = fs.readFileSync("contract.signed.pdf");
for (const sig of rustpdf.verifySignatures(data)) {
  console.log(sig.signer, "valid:", sig.is_valid,
              "covers whole doc:", sig.covers_whole_document);
  console.log("  issuer:", sig.issuer, "| serial:", sig.serial_number);
  console.log("  algorithm:", sig.algorithm, "| signed at:", sig.signing_time);
  console.log("  cert valid:", sig.valid_from, "→", sig.valid_to);
  console.log("  certs:", sig.cert_count, "| timestamped:", sig.has_timestamp);
}

Find where text appears on the page, not just whether it does. findText returns every occurrence of query as a bounding box, so you can place a highlight, link or redaction over a match. Search is case-insensitive by default; pass true to match case. See the PDF text search concept page for background.

rustpdf.findText(data: Buffer, query: string, caseSensitive?: boolean) → TextHit[]

Each TextHit has page (0-based), text, and x, y, width, height in PDF points with the origin at the lower-left. An empty array means no match.

javascript
const rustpdf = require("rustpdf");

const data = fs.readFileSync("report.pdf");
for (const hit of rustpdf.findText(data, "Total")) {
  console.log(`p${hit.page}: "${hit.text}" @ ${hit.x},${hit.y} ${hit.width}x${hit.height}`);
}

// case-sensitive:
const exact = rustpdf.findText(data, "Total", true);

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. Pass a 0-based page index to extractPageText to read just one page (the fast per-page path, no whole-document scan). 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: Buffer) → string free
rustpdf.extractPageText(data: Buffer, pageIndex: number) → string free
rustpdf.extractImagesToDir(data: Buffer, outDir: string) → number free
javascript
const data = fs.readFileSync("report.pdf");
console.log(rustpdf.extractText(data));
console.log(rustpdf.extractPageText(data, 0));   // just the first page

const n = rustpdf.extractImagesToDir(data, "out_images/");   // returns how many were written
console.log(`wrote ${n} image(s)`);

Page geometry free

Read the size and box geometry of every page without authoring or editing the document. measurePages returns one PageGeometry per page; measurePage returns a single page (0-based) and throws RangeError when the index is out of range. Sizes are in PDF points (72 per inch).

rustpdf.measurePages(pdf: Buffer) → PageGeometry[]
rustpdf.measurePage(pdf: Buffer, index: number) → PageGeometry

Each PageGeometry has page (0-based), width, height, rotation, rotatedWidth, rotatedHeight, and the mediaBox and cropBox rectangles. width/height ignore /Rotate; rotatedWidth/rotatedHeight account for it (swapped for 90/270). Each box is a PdfRect with x0, y0, x1, y1 plus convenience width and height.

javascript
const rustpdf = require("rustpdf");

const data = fs.readFileSync("report.pdf");
for (const g of rustpdf.measurePages(data)) {
  console.log(`p${g.page}: ${g.width}x${g.height} pt, rotate ${g.rotation}° → ${g.rotatedWidth}x${g.rotatedHeight}`);
}

const first = rustpdf.measurePage(data, 0);
console.log(`media box: ${first.mediaBox.width}x${first.mediaBox.height}`);

Document inspection free

Get a non-mutating overview of a PDF (its version, PDF/A level and encryption posture) without opening, decrypting or rewriting it. Useful as a pre-flight check before deciding how to process a file. inspect never fails on a password-locked document.

rustpdf.inspect(pdf: Buffer) → PdfOverview

A PdfOverview has version (e.g. "1.7"), pdfaLevel (a string like "2b", or null when the file is not PDF/A), encrypted, encryption (one of "None", "RC4", "AES-128", "AES-256"), requiresPassword, and pageCount.

javascript
const rustpdf = require("rustpdf");

const ov = rustpdf.inspect(fs.readFileSync("report.pdf"));
console.log(`PDF ${ov.version}, ${ov.pageCount} page(s)`);
console.log(`PDF/A: ${ov.pdfaLevel ?? "no"}`);
if (ov.encrypted) {
  console.log(`encryption: ${ov.encryption}, needs password: ${ov.requiresPassword}`);
}

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.

renderPageToPng(pdf, page = 0, dpi = 150.0) → Buffer licensed
pageCount(pdf) → number free
javascript
const { renderPageToPng, pageCount } = require("rustpdf");
const fs = require("fs");
const data = fs.readFileSync("report.pdf");
console.log(`${pageCount(data)} page(s)`);
fs.writeFileSync("page1.png", renderPageToPng(data, 0, 150.0));

Enums

PdfaLevel

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

Align

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

AFRelationship

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

Encryption

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

FacturxProfile

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

VerticalAnchor

ValueWhat y means (placeText / placeParagraph)
VerticalAnchor.BaselineThe (first) baseline: the historical default of placeText
VerticalAnchor.TopTop of the text: baseline lands ascent × size below y (legacy fixed-position layout); default of placeParagraph
VerticalAnchor.BottomBottom of the text: descender line rests on y; bottom-pins a paragraph
VerticalAnchor.LineTopTop of the layout line box (exact legacy layout engines line placement)
VerticalAnchor.LineBottomBottom of the layout line box; bottom-pins a paragraph with line-box leading

VerticalAlign

ValueLine placement inside a maskedText box
VerticalAlign.TopLine hangs from the top edge (top line-alignment in rectangle-based text APIs)
VerticalAlign.MiddleCap-height centering: the historical default
VerticalAlign.BottomDescender line rests on the bottom edge

StampSpace

ValueCoordinate space of the positioned primitives
StampSpace.VisibleDisplayed space, compensating /Rotate (default)
StampSpace.MediaRaw PDF user space (legacy layout semantics), ignoring /Rotate

ImageAnchor

ValueHow a rotated drawImage is anchored
ImageAnchor.CornerThe image's own lower-left corner sits at (x, y) (default)
ImageAnchor.BoundingBoxThe rotated image's bounding box lands with its lower-left at (x, y) (legacy layout engines)

Error handling

Every failing native call throws PdfError (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 (for example Serialize = 4 or Sign = 10) with the same “requires a valid license” message.

javascript
const { Document, PdfError } = require("rustpdf");

try {
  const doc = new Document();
  doc.pdfa().setInfo({ title: "x" });
  doc.addPage();
  doc.save("out.pdf");
  doc.close();
} catch (e) {
  if (e instanceof PdfError) {
    console.error("failed:", e.status, e.message);
    // e.g. PdfStatus=4 (Serialize): feature 'pdfa' requires a valid license
  }
}

Utilities

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