Java
Last updated: 2026-07-02
The dev.rustpdf:rustpdf Maven artifact wraps the rust-pdf C core with idiomatic, chainable classes over pure-Java FFI (JNA), so there is no native build step on your side. 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 published artifact is a fat JAR that bundles the native library for every supported platform.
Document authors a new PDF; EditableDoc loads and manipulates an existing one. Both implement AutoCloseable and hold a native handle, so use them with try-with-resources to free it promptly. Using a Document or EditableDoc after it has been closed throws IllegalStateException (never a crash); calling close() more than once is safe.Installation
Add the dependency from Maven Central. The native library (libpdf_ffi) is bundled inside the JAR as a JNA classpath resource for every supported platform (darwin-aarch64, linux-x86-64, linux-aarch64 and win32-x86-64); JNA extracts the matching one at runtime, so there is nothing to compile.
<dependency>
<groupId>dev.rustpdf</groupId>
<artifactId>rustpdf</artifactId>
<version>0.4.8</version>
</dependency>implementation("dev.rustpdf:rustpdf:0.4.8")Requires Java 17+. Verify it loaded:
import dev.rustpdf.Pdf;
System.out.println(Pdf.version()); // native library versionRUSTPDF_LIB can point at an explicit shared-library path to override resolution (handy in a dev tree, where the loader also picks up target/debug/ or target/release/).Quick start
A one-page document with a filled rectangle, saved to disk:
import dev.rustpdf.Document;
try (Document 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");
}Most methods return the document, so calls chain:
try (Document doc = new Document()) {
int 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é");
byte[] data = doc.toBytes(); // a byte[] instead of a file
}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 PdfException and produce no output.
Activation needs no rebuild. Easiest is an environment variable, auto-activated the first time a corporate feature is used:
export RUSTPDF_LICENSE="010f0000…" # the token we email you
# or point at a file:
export RUSTPDF_LICENSE_FILE=/etc/rustpdf/license.txtOr activate explicitly in code:
import dev.rustpdf.Pdf;
Pdf.activateLicense(token); // throws PdfException if forged / expired / malformedCoordinate system
- Units are points (1 pt = 1/72 inch). A4 is
595 × 842, US Letter612 × 792. - The origin
(0, 0)is the bottom-left corner;ygrows upward. - For text,
(x, y)is the baseline of the first glyph. - Drawing/text always targets the most recently added page.
Threading & concurrency
Every native call is synchronous and the core is Send but not Sync: a single handle must never be touched by two threads at once.
- Documents are independent. Each
Document/EditableDocshares no state, so they are safe to build in parallel tasks or threads, one handle per task. - Do not share a live handle across threads; give each its own.
- Errors are per-thread. The native last-error is thread-local, so a failure on one thread never clobbers another's, and
PdfExceptionsurfaces on the calling side. - License is process-global.
Pdf.activateLicense(or the env var) applies to every thread; activate once at startup.
// one document per task, no shared handle
List<Callable<byte[]>> jobs = IntStream.range(0, 8).<Callable<byte[]>>mapToObj(i -> () -> {
try (Document doc = new Document()) {
doc.addPage().setFillRgb(0.1, 0.1, 0.12).rect(72, 700, 200, 80).fill();
return doc.toBytes();
}
}).toList();
List<Future<byte[]>> pdfs = Executors.newFixedThreadPool(4).invokeAll(jobs);Authoring: create & save
new Document() freeCreates an empty document (A4 default page size). Use try-with-resources (or call close()) to free the native handle.
| Member | Description |
|---|---|
addPage() / addPage(w, h) | Append a page; optional size 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). |
pageCount() | Number of pages so far. |
toBytes() | Render the document to a byte[]. |
save(path) | Render and write to a file. |
close() | Free the native handle. |
Pages & vector graphics
Graphics state and path operators mirror PDF's content-stream model. Colors are RGB in 0.0–1.0.
| Method | Description |
|---|---|
setFillRgb(r, g, b) | 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. |
try (Document 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");
}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) → int addFont(byte[] data) → intshowText(font, size, x, y, text, headingLevel = 0)headingLevel (1–6) tags the run as H1–H6 in an accessible document (see Accessibility); leave it as 0 for ordinary text (the no-heading overload omits it).
try (Document doc = new Document()) {
int regular = doc.addFontFile("Roboto-Regular.ttf");
// …or from bytes you already have in memory:
// int regular = doc.addFont(Files.readAllBytes(Path.of("Roboto-Regular.ttf")));
doc.addPage();
doc.showText(regular, 28, 72, 760, "Invoice #1024");
doc.showText(regular, 12, 72, 720, "日本語 · Ελληνικά · العربية");
doc.save("text.pdf");
}\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.Paragraphs
The paragraph layer wraps, aligns and justifies text inside a fixed width (greedy line breaking using shaped glyph widths).
paragraph(font, size, x, y, width, text, Align align)import dev.rustpdf.*;
String intro =
"A long paragraph that wraps to the given width and is justified "
+ "automatically; extra space is distributed between words.";
try (Document doc = new Document()) {
int f = doc.addFontFile("Roboto-Regular.ttf");
doc.addPage();
doc.paragraph(f, 12, 72, 700, 451, intro, Align.JUSTIFY);
doc.save("paragraph.pdf");
}See the Align enum for the alignment options.
Images
JPEGs are embedded verbatim (DCTDecode, no re-encode). PNGs are decoded and re-encoded (FlateDecode); alpha becomes an /SMask, palette becomes an Indexed color space. Register an image once, draw it many times.
| Method | Description |
|---|---|
addImageFile(path) → int | Load JPEG/PNG from a file; returns the image id. |
addImagePng(byte[] data) → int | Register a PNG from memory. |
addImageJpeg(byte[] data) → int | Register 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). |
try (Document doc = new Document()) {
int 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; 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() pdfa(PdfaLevel level)import dev.rustpdf.*;
try (Document doc = new Document()) {
doc.pdfa(PdfaLevel.A2B).setInfo("Q3 Report", "Acme Inc.", null, null, null);
int f = doc.addFontFile("Roboto-Regular.ttf");
doc.addPage();
doc.showText(f, 20, 72, 760, "Archival report");
doc.save("report_pdfa.pdf"); // throws PdfException without a license granting PDF/A
}setTitle(…) (or setInfo with a title).Accessibility (Tagged PDF / PDF/UA) licensed
tagged() builds a logical structure tree (PDF/UA-1). Combine with pdfa(PdfaLevel.A2A) for archival and accessible output. Use headingLevel on showText for H1–H6, and figure(..., alt) for described images.
tagged()import dev.rustpdf.*;
try (Document doc = new Document()) {
doc.pdfa(PdfaLevel.A2A).tagged().setTitle("Accessible report");
int 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…");
int chart = doc.addImageFile("chart.png");
doc.figure(chart, 72, 520, 300, 150, "Revenue grew 18% year over year");
doc.save("accessible.pdf");
}figure() only produces an accessible, alt-texted figure inside a tagged/accessible document; on a plain document the alt text has no effect.Attachments (PDF/A-3) licensed
PDF/A-3 allows embedding arbitrary source files (e.g. the XML behind an e-invoice). Each attachment carries a MIME type and an AFRelationship.
attachFile(name, mime, byte[] data, AFRelationship relationship, description)import dev.rustpdf.*;
import java.nio.file.*;
byte[] xml = Files.readAllBytes(Path.of("invoice.xml"));
try (Document doc = new Document()) {
doc.pdfa(PdfaLevel.A3B).setTitle("E-invoice 1024");
int 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");
}attachFile before serializing.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(byte[] xml, FacturxProfile profile)import dev.rustpdf.*;
import java.nio.file.*;
byte[] xml = Files.readAllBytes(Path.of("factur-x.xml")); // your Cross-Industry Invoice XML
try (Document doc = new Document()) {
doc.setTitle("Invoice INV-2026-001");
int 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
}See the FacturxProfile enum for the conformance levels (Minimum to Extended).
AcroForm fields
Build interactive forms with generated appearance streams (no NeedAppearances). Rectangles are double[]{x0, y0, x1, y1}; page is a 0-based page index. Dotted names ("a.b.c") create hierarchical fields.
| Method | Description |
|---|---|
textField(name, page, rect, value, size) | Text input (size = 0 → auto font size). |
checkbox(name, page, rect, checked) | Checkbox (boolean checked). |
dropdown(name, page, rect, options, selected, size) | Combo box from a List<String>; selected 0-based index or -1. |
radioGroup(name, page, rects, exports, selected) | rects = double[][], exports = String[]; selected 0-based or -1. |
import dev.rustpdf.*;
import java.util.List;
try (Document doc = new Document()) {
doc.addPage();
doc.textField("applicant.name", 0, new double[]{72, 700, 320, 720}, "", 0);
doc.checkbox("agree", 0, new double[]{72, 660, 88, 676}, false);
doc.dropdown("plan", 0, new double[]{72, 620, 240, 640},
List.of("Starter", "Pro", "Enterprise"), 1, 0);
doc.radioGroup("billing", 0,
new double[][]{ {72, 580, 88, 596}, {140, 580, 156, 596} },
new String[]{ "monthly", "annual" }, 1);
doc.save("form.pdf");
}Fill and flatten fields later with EditableDoc.
page does not exist, or whose rectangle is degenerate (x1 < x0 or zero area), is rejected when the document is serialized: toBytes()/save() throw PdfException ("targets page index N…" / "degenerate rectangle…") instead of producing an invisible, never-appearing widget. Pass a 0-based page index that exists and a rectangle with x1 > x0 and y1 > y0.Hyperlinks free
Add clickable link rectangles to the current page: a web link opens a URL; an internal link jumps to another page (optionally scrolling so a given top y-coordinate sits at the top of the view).
linkUri(rect, uri) linkToPage(rect, pageIndex) linkToPage(rect, pageIndex, top)try (Document doc = new Document()) {
int f = doc.addFontFile("Roboto-Regular.ttf");
doc.addPage();
doc.showText(f, 14, 72, 760, "Visit rustpdf.dev (see page 2)");
doc.linkUri(new double[]{72, 756, 320, 776}, "https://rustpdf.dev/"); // web link
doc.linkToPage(new double[]{330, 756, 430, 776}, 1, 800); // jump to page 2
doc.addPage();
doc.save("links.pdf");
}Rectangles are double[]{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) new Bookmark(title, page, Double top) .child(bookmark) addBookmark(bookmark)import dev.rustpdf.*;
try (Document doc = new Document()) {
int f = doc.addFontFile("Roboto-Regular.ttf");
for (int i = 0; i < 3; i++)
doc.addPage();
doc.addBookmark(
new Bookmark("Chapter 1", 0, 820.0)
.child(new Bookmark("Section 1.1", 1))
.child(new Bookmark("Section 1.2", 2)));
doc.addBookmark(new Bookmark("Chapter 2", 2));
doc.save("outline.pdf");
}Metadata
setInfo(title, author, subject, keywords, creator) setTitle(title)Sets the document information dictionary (and, for PDF/A, the matching XMP). Pass null for any entry you do not need, or use setTitle for the common case.
doc.setInfo("Q3 Report", "Acme Inc.",
"Quarterly results", "finance, q3", null);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(byte[] data) EditableDoc.load(byte[] data, password)EditableDoc.loadFile(Path path) EditableDoc.loadFile(Path path, password)import dev.rustpdf.EditableDoc;
import java.nio.file.Path;
try (EditableDoc ed = EditableDoc.loadFile(Path.of("in.pdf"))) {
System.out.println(ed.pageCount());
}
// encrypted input:
try (EditableDoc sec = EditableDoc.loadFile(Path.of("secured.pdf"), "user-or-owner-pw")) {
sec.save("plain.pdf");
}load does not throw in that case. Serializing a zero-page document throws PdfException ("document has no pages") rather than writing an invalid file, but you should check pageCount() > 0 after loading untrusted input before relying on it.Pages: merge, split, reorder, rotate
| Method | Description |
|---|---|
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(int[] order) | Reorder with a full permutation list of indices. |
extractPages(int[] indices) → EditableDoc | New document containing just those pages. |
pageCount() | Current page count. |
try (EditableDoc a = EditableDoc.loadFile(Path.of("a.pdf"))) {
try (EditableDoc b = EditableDoc.loadFile(Path.of("b.pdf"))) {
a.merge(b); // a now has a's pages followed by b's
}
a.rotatePage(0, 90);
a.reorderPages(new int[]{2, 1, 0}); // a full permutation of every index
a.save("merged.pdf");
try (EditableDoc subset = a.extractPages(new int[]{0, 2})) { // pages 1 and 3
subset.save("subset.pdf");
}
}reorderPages requires a true permutation of every page index (each used exactly once); an invalid argument (wrong length, a repeated index, or out-of-range) is rejected and leaves the page order unchanged. That call is a silent no-op that raises no error, so an invalid reorder cannot be detected from a return value.Metadata, overlay & form fill
| Method | Description |
|---|---|
setInfo(key, value) | Set one info entry (e.g. "Title"). |
getInfo(key) → String | Read an info entry. |
setXmp(byte[] xml) | Replace the XMP metadata stream. |
overlayPage(index, byte[] content) | Overlay a content-stream fragment onto a page (stamps/watermarks). |
fillTextField(name, value) → boolean | Fill an AcroForm text field; returns whether it was found. |
try (EditableDoc ed = EditableDoc.loadFile(Path.of("form.pdf"))) {
ed.setInfo("Title", "Filled form");
boolean found = ed.fillTextField("applicant.name", "Jane Doe");
System.out.println("filled: " + found + " | title: " + ed.getInfo("Title"));
ed.save("filled.pdf");
}Form fill & flatten free
Fill the fields of an existing AcroForm and (optionally) flatten them: filling generates a fresh appearance stream (no NeedAppearances), and flattening bakes every widget's appearance into the page content and removes the interactive form entirely.
| Method | Description |
|---|---|
fieldNames() → List<String> | Fully-qualified names of every terminal field. |
fillTextField(name, value) → boolean | Set a text (or text-style choice) field; returns whether it matched. |
setCheckbox(name, checked) → boolean | Check/uncheck a checkbox. |
setRadio(name, exportValue) → boolean | Select a radio button by its export value. |
setChoice(name, value) → boolean | Set a dropdown / list-box value. |
flattenForms() | Bake all fields into static content and drop the /AcroForm. |
try (EditableDoc ed = EditableDoc.loadFile(Path.of("form.pdf"))) {
System.out.println(String.join(", ", 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");
}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".
watermarkText(text) watermarkText(text, size, r, g, b, opacity, rotationDeg) watermarkText(text, size, r, g, b, opacity, rotationDeg, boolean opaqueBackground)watermarkImageFile(path, width, height, opacity) watermarkImageFile(path, width, height, opacity, rotationDeg)The final opaqueBackground argument draws the text over an opaque filled box (a white-out stamp, e.g. a redaction label) instead of letting the page show through. watermarkImageFile takes an optional rotationDeg to rotate the image stamp.
try (EditableDoc ed = EditableDoc.loadFile(Path.of("report.pdf"))) {
ed.watermarkText("CONFIDENTIAL", 64, 0.7, 0.1, 0.1, 0.25, 45); // translucent diagonal
ed.watermarkText("VOID", 48, 0, 0, 0, 1.0, 0, true); // opaque white-out stamp
ed.watermarkImageFile("stamp.png", 180, 180, 0.4, 30); // rotated image stamp
ed.save("stamped.pdf");
}Positioned drawing free
Stamp a single page in place: fill a rectangle and/or drop a line of text at exact coordinates. 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; rotationDeg rotates the text counter-clockwise about its anchor. Text uses the standard Helvetica font (keep it WinAnsi / Latin-1). Both return whether the page existed.
boolean fillRect(int pageIndex, double x, double y, double width, double height, double r, double g, double b, double opacity)boolean placeText(int pageIndex, double x, double y, String text, double size, double r, double g, double b, double rotationDeg)boolean placeText(int pageIndex, double x, double y, String text, double size, double r, double g, double b, double rotationDeg, Align align)boolean drawImage(int pageIndex, byte[] image, double x, double y, double width, double height, double rotationDeg)A typical use is masking a spot with an opaque white box, then writing a value over it. drawImage takes raw JPEG/PNG image bytes and places the image with its lower-left corner at (x, y) scaled to width × height points (an overload drops the trailing rotationDeg). The placeText overload taking an Align (LEFT/RIGHT/CENTER/JUSTIFY) shifts the start point back along the baseline by the measured text width, so the anchor (x, y) becomes the right edge or centre instead of the left.
byte[] data = Files.readAllBytes(Path.of("invoice.pdf"));
PageGeometry g = Pdf.measurePage(data, 0); // read the page geometry first
try (EditableDoc ed = EditableDoc.load(data)) {
ed.fillRect(0, 400, g.height() - 60, 120, 18, 1, 1, 1, 1); // mask: opaque white
ed.placeText(0, 404, g.height() - 56, "PAID", 12, 0, 0.5, 0, 0);
ed.placeText(0, 520, g.height() - 56, "R$ 1.200,00", 12, 0, 0, 0, 0, Align.RIGHT); // right-aligned
ed.drawImage(0, Files.readAllBytes(Path.of("logo.png")), 404, g.height() - 120, 120, 48);
ed.save("stamped.pdf");
}For the common mask-then-stamp pattern, maskedText does it in one call: it fills an opaque background box [x, y, x+width, y+height], then writes the text horizontally aligned per align and vertically centred within the box, with no need to hand-compute the baseline. Text defaults to black on a white box; pass {r, g, b} arrays (each 0..1) to override.
boolean maskedText(int pageIndex, double x, double y, double width, double height, String text, double size, double[] textColor, double[] bgColor, Align align)boolean maskedText(int pageIndex, double x, double y, double width, double height, String text, double size)try (EditableDoc ed = EditableDoc.load(data)) {
// white-out the placeholder box and stamp the value centred over it
ed.maskedText(0, 400, g.height() - 62, 130, 20, "APPROVED", 12,
new double[]{0, 0.4, 0}, new double[]{1, 1, 1}, Align.CENTER);
ed.save("stamped.pdf");
}Stamping: fonts, anchors & paragraphs free
The positioned-drawing primitives above have overloads that unlock embedded fonts, precise vertical anchoring and wrapped paragraphs: the toolkit for filling templates coming from legacy layout engines or legacy PDF libraries coordinates. See the Interactive positioning guide to explore each anchor visually.
Custom fonts
int addFontFile(String path) int addFont(byte[] data)Register a TrueType/OpenType font on the EditableDoc and pass the returned id as the fontId argument of placeText / maskedText / placeParagraph. The font is embedded as a subset, so the stamp renders with the real glyphs and metrics (full Unicode, no WinAnsi limit). fontId = -1 keeps the built-in Helvetica.
Vertical anchors
boolean placeText(int pageIndex, double x, double y, String text, double size, double r, double g, double b, double rotationDeg, Align align, int fontId)boolean placeText(…, Align align, int fontId, VerticalAnchor anchor)The anchor says what y means: VerticalAnchor.BASELINE (the default and historical behavior), TOP (text hangs from y: the baseline lands ascent × size below it), BOTTOM (the descender line rests on y), and LINE_TOP/LINE_BOTTOM, the top/bottom of the layout line box (font line metrics plus a fixed half-leading). Ascent/descent come from the selected font's metrics.
VerticalAnchor.LINE_BOTTOM reproduces fixed-position layout(x, y, width) exactly: such engines position an element by the bottom of its line box, so a legacy y passed unchanged with LINE_BOTTOM lands the text on the same spot. For rotated (scanned) pages also switch to StampSpace.MEDIA below.maskedText: vertical alignment & padding
boolean maskedText(int pageIndex, double x, double y, double width, double height, String text, double size, double[] textColor, double[] bgColor, Align align, int fontId, VerticalAlign valign)boolean maskedText(…, VerticalAlign valign, double padding)valign aligns the line inside the box: VerticalAlign.MIDDLE (the default: cap-height centering), TOP (baseline at y + height − ascent × size, top line-alignment in rectangle-based text APIs) or BOTTOM. padding is the horizontal edge inset (points) for Align.LEFT/Align.RIGHT: a negative value keeps the historical min(0.15 × size, width / 4); pass 0 to start flush with the box edge like rectangle-based DrawString APIs.
Wrapped paragraphs
boolean placeParagraph(int pageIndex, double x, double y, double width, String text)boolean placeParagraph(int pageIndex, double x, double y, double width, String text, double size, double r, double g, double b, Align align, int fontId, double maxHeight, double lineHeight, VerticalAnchor anchor, double rotationDeg)PlaceParagraphResult placeParagraphMeasured(…) // lines() + height()Stamps a paragraph with automatic word wrapping: text is broken into lines that fit width points (\n forces a break) and drawn from the anchor (x, y). With the default VerticalAnchor.TOP the first baseline lands ascent × size below y (legacy fixed-position layout) and each further line steps down by size × 1.2 × lineHeight. The bottom-pinned anchors (BOTTOM/LINE_BOTTOM) rest the block's bottom on y and grow it upward by its real content height. maxHeight > 0 is a ceiling: lines that would overflow it are cut (<= 0 = unlimited); Align.JUSTIFY stretches the word gaps of every line but the last of each paragraph. placeParagraphMeasured additionally returns a PlaceParagraphResult with how many lines() were drawn (detects truncation) and the consumed block height() in points, so you can stack blocks without re-measuring.
Coordinate space (Visible vs Media)
EditableDoc setStampSpace(StampSpace space)Sets the coordinate space of all subsequent stamping 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. Use it to reproduce coordinates computed for legacy PDF libraries on rotated (scanned) pages. Watermarks and redaction are unaffected.
Image anchor
boolean drawImage(int pageIndex, byte[] image, double x, double y, double width, double height, double rotationDeg, ImageAnchor anchor)The anchor controls how a rotated image is anchored at (x, y): ImageAnchor.CORNER (default) rotates the image about its own lower-left corner, sweeping it around the point; ImageAnchor.BOUNDING_BOX lands the rotated image's bounding box with its lower-left at (x, y) (bounding-box layout semantics; e.g. a 90° image occupies [x, x+height] × [y, y+width]).
import dev.rustpdf.*;
import java.nio.file.Files;
import java.nio.file.Path;
try (EditableDoc ed = EditableDoc.loadFile(Path.of("template.pdf"))) {
int times = ed.addFontFile("times.ttf"); // embedded subset
// hang the text from y like legacy PDF engines fixed-position layout
ed.placeText(0, 72, 700, "Náme with accents ✓", 12, 0, 0, 0, 0,
Align.LEFT, times, VerticalAnchor.TOP);
// mask a box; text flush with the edge and hung from the top
ed.maskedText(0, 400, 680, 140, 18, "R$ 1.234,56", 11,
null, null, Align.LEFT, times, VerticalAlign.TOP, 0);
// wrapped paragraph pinned by its bottom, with a height ceiling
PlaceParagraphResult r = ed.placeParagraphMeasured(
0, 72, 120, 300, "Terms and conditions… ".repeat(12), 9, 0, 0, 0,
Align.JUSTIFY, -1, 140, 1.0, VerticalAnchor.BOTTOM, 0);
System.out.println(r.lines() + " lines, " + r.height() + " pt used");
// reproduce legacy raw-space coordinates on a rotated scan
ed.setStampSpace(StampSpace.MEDIA);
ed.placeText(0, 50, 50, "legacy layout engines-space stamp", 10, 0, 0, 0, 0);
ed.setStampSpace(StampSpace.VISIBLE);
// rotated image anchored by its bounding box (bounding-box layout)
ed.drawImage(0, Files.readAllBytes(Path.of("sig.png")), 420, 100, 120, 40,
90.0, ImageAnchor.BOUNDING_BOX);
ed.save("filled.pdf");
}fontId, no anchor) behave exactly as before: built-in Helvetica, VerticalAnchor.BASELINE, VerticalAlign.MIDDLE, the historical padding, ImageAnchor.CORNER and StampSpace.VISIBLE. Existing code keeps compiling and rendering unchanged.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, double[][] rects) → booleantry (EditableDoc ed = EditableDoc.loadFile(Path.of("statement.pdf"))) {
// rects = double[][] of {x0, y0, x1, y1} on that page
ed.redact(0, new double[][]{
{60, 590, 400, 620},
{60, 540, 400, 570},
});
ed.save("redacted.pdf"); // throws PdfException without a license granting redaction
}Pdf.extractText on the output no longer returns it.Convert to PDF/A licensed
Convert an existing PDF to archival PDF/A (a basic profile: A-1b, A-2b or A-3b). An sRGB output intent, PDF/A XMP metadata (synced with /Info) and a document /ID are added. Fails if any font is not embedded (PDF/A requires every font embedded) or a level-A profile is requested.
convertToPdfa(PdfaLevel level)import dev.rustpdf.*;
import java.nio.file.Path;
try (EditableDoc ed = EditableDoc.loadFile(Path.of("in.pdf"))) {
ed.convertToPdfa(PdfaLevel.A2B); // throws PdfException if fonts aren't embedded
ed.save("archival.pdf"); // veraPDF: PDF/A-2b compliant
}Optimize & compact
| Method | Description |
|---|---|
optimize() | Drop unreferenced objects, Flate-compress uncompressed streams, dedupe identical objects. |
compact(on) | Pack objects into object streams + emit a cross-reference stream (boolean on). |
try (EditableDoc ed = EditableDoc.loadFile(Path.of("big.pdf"))) {
ed.optimize().compact(true);
ed.save("small.pdf");
}Encryption licensed
Apply standard-handler encryption at output. AES-256 (V5/R6) uses OS-CSPRNG keys/IVs.
encrypt(user, owner, Encryption method, readOnly)import dev.rustpdf.*;
import java.nio.file.Path;
try (EditableDoc ed = EditableDoc.loadFile(Path.of("in.pdf"))) {
ed.encrypt("", "owner-secret", Encryption.AES256, true);
ed.save("secured.pdf"); // throws PdfException without an Encryption license
}See the Encryption enum for RC4 / AES-128 / AES-256.
readOnly 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 readOnly as a hint to well-behaved viewers, not as an access control.Output & incremental update
| Method | Description |
|---|---|
toBytes() → byte[] | Serialize the manipulated document. |
save(path) | Serialize to a file. |
toBytesIncremental(byte[] original) → byte[] | Append only changes to the original bytes (signature-safe, non-destructive). |
byte[] original = Files.readAllBytes(Path.of("in.pdf"));
try (EditableDoc ed = EditableDoc.load(original)) {
ed.setInfo("Subject", "reviewed");
byte[] incremental = ed.toBytesIncremental(original); // original bytes preserved verbatim
Files.write(Path.of("reviewed.pdf"), incremental);
}Normalize & downgrade free
Drop PDF/A conformance and downgrade the PDF version, turning an archival or newer file into a plain, broadly compatible PDF. setVersion rewrites only the header/catalog version; stripPdfa removes the PDF/A markers (the catalog /OutputIntents, the XMP pdfaid block and /Version); normalize does both in one call. Version codes match Document.setVersion: 0 → 1.4, 1 → 1.5, 2 → 1.7, 3 → 2.0.
| Method | Description |
|---|---|
setVersion(version) | Set the output PDF version (header + catalog). |
stripPdfa() | Remove PDF/A conformance so the file becomes a plain PDF. |
normalize(version) | Strip PDF/A then set the version (the common case). |
try (EditableDoc ed = EditableDoc.loadFile(Path.of("archival.pdf"))) {
ed.normalize(2); // strip PDF/A, downgrade to PDF 1.7
ed.save("plain.pdf");
}
// or step by step:
try (EditableDoc ed = EditableDoc.loadFile(Path.of("v2.pdf"))) {
ed.stripPdfa();
ed.setVersion(0); // PDF 1.4
ed.save("compat.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 byte[]. pades = true switches to PAdES-B-B.
Pdf.sign(pdf, keyDer, certDer, reason, location, name, pades) → byte[]import dev.rustpdf.Pdf;
import java.nio.file.*;
byte[] pdf = Files.readAllBytes(Path.of("contract.pdf"));
byte[] keyDer = Files.readAllBytes(Path.of("signing-key.pkcs8.der")); // PKCS#8 private key (DER)
byte[] certDer = Files.readAllBytes(Path.of("signing-cert.der")); // X.509 certificate (DER)
byte[] signed = Pdf.sign(pdf, keyDer, certDer,
"Approved", "New York", "Jane Doe", true);
Files.write(Path.of("contract.signed.pdf"), signed);
// Verify in a shell: pdfsig contract.signed.pdf → "Signature is Valid."Deferred / HSM signing licensed
Sign without ever handing the library a private key. When the key lives in an HSM, a cloud KMS, a smartcard or a PKI token (any PKI: eIDAS, AATL, a national CA), you supply the raw RSA signature and the library builds and embeds the CMS / PKCS#7 container. There are two flavours: a synchronous remote signer callback, and a two-phase begin / complete flow for asynchronous or out-of-band signing.
Model A: remote signer callback
The library assembles the signed attributes and calls back for the raw RSA PKCS#1 v1.5 signature over their SHA-256 digest. The signer certificate is passed as DER; intermediate certificates go in chain (also DER), supplied independently of the key, which stays inside your HSM. The callback is a Function<byte[], byte[]>.
Pdf.signWith(pdf, certDer, Function<byte[],byte[]> signHash, List<byte[]> chain, SigningOptions options) → byte[]import dev.rustpdf.*;
import java.nio.file.*;
import java.util.List;
byte[] pdf = Files.readAllBytes(Path.of("contract.pdf"));
byte[] certDer = Files.readAllBytes(Path.of("signer-cert.der")); // X.509 (DER); key stays remote
SigningOptions options = new SigningOptions();
options.reason = "Approved";
options.pades = true;
byte[] signed = Pdf.signWith(pdf, certDer,
dataToSign -> hsm.signRsaPkcs1Sha256(dataToSign), // your HSM / KMS / token
List.of(intermediateDer), options);
Files.write(Path.of("contract.signed.pdf"), signed);signHash callback receives the bytes to sign and must return the raw RSA PKCS#1 v1.5 signature over their SHA-256 digest. The private key never reaches this library, so the same code path works for a cloud KMS, a smartcard or a PKI token. Return null (or throw) from the callback to abort signing.Model B: two-phase begin / complete
For an asynchronous HSM or a detached workflow, phase 1 prepares the PDF with a placeholder and returns the exact bytes the signature covers; you sign the digest out of band and build the CMS container; phase 2 embeds it.
Pdf.beginSigning(pdf, SigningOptions options) → SigningSessionPdf.completeSignature(document, container) → byte[]A SigningSession exposes document() (the prepared PDF), bytes() (the bytes covered by the signature), hash() (their SHA-256, the value an HSM signs) and complete(container), which embeds a finished DER CMS / PKCS#7 container and returns the final signed PDF.
SigningOptions options = new SigningOptions();
options.name = "Jane Doe";
options.containerSize = 16384;
SigningSession session = Pdf.beginSigning(pdf, options);
byte[] cmsDer = buildCmsWithRemoteSignature(session.hash()); // sign the digest remotely
byte[] signed = session.complete(cmsDer);
// equivalently: Pdf.completeSignature(session.document(), cmsDer)
Files.write(Path.of("contract.signed.pdf"), signed);Inspect existing signature fields
Before signing, list the signature fields already present (a classic pre-sign signature-field inventory). An empty list means the document is unsigned. Each SignatureField is a record with name() and signed().
Pdf.listSignatures(pdf) → List<SignatureField>for (SignatureField f : Pdf.listSignatures(pdf))
System.out.println(f.name() + " signed: " + f.signed());SigningOptions
Both models accept a SigningOptions with public fields:
| Field | Meaning |
|---|---|
reason, location, name | Signature metadata (all String, may be null). |
pades | true produces a PAdES-B-B signature (ETSI.CAdES.detached). |
certify | Certify the document (DocMDP); use only on the first signature. See Certify below. |
containerSize | Reserved /Contents bytes; 0 uses the default (8192). Raise it for large cloud-HSM CMS containers. |
policy | A SignaturePolicy identifier (PAdES-EPES); null for none. |
visible | true draws a visible signature appearance using the fields below. |
visiblePage, visibleRect | 0-based page index and rectangle double[]{x0, y0, x1, y1} (points) for the appearance. |
visibleText | Appearance text lines (separated by '\n'); null for none. |
visibleImage | PNG/JPEG byte[] (e.g. a handwritten-signature scan) drawn aspect-fit behind any text; null for none. |
Set visible = true and supply a rectangle to draw the appearance; visibleImage embeds a PNG or JPEG (scaled aspect-fit) behind visibleText.
SigningOptions options = new SigningOptions();
options.visible = true;
options.visiblePage = 0;
options.visibleRect = new double[]{72, 96, 272, 156};
options.visibleText = "Jane Doe\nApproved";
options.visibleImage = Files.readAllBytes(Path.of("signature.png")); // drawn aspect-fitCertify
| Value | DocMDP level |
|---|---|
Certify.NONE | Not a certifying signature (default). |
Certify.LOCKED | /P 1: no changes permitted after signing. |
Certify.FORMS | /P 2: form-filling and signing permitted. |
Certify.FORMS_AND_ANNOTATIONS | /P 3: form-filling, signing and annotations permitted. |
SignaturePolicy
A PAdES-EPES signature-policy identifier (public fields). oid is the policy OID (dotted-decimal); hash is the policy document hash (byte[]); hashAlgorithmOid is the hash algorithm OID (null means SHA-256); uri is an optional SPURI qualifier pointing to where the policy can be retrieved.
Timestamp & DSS (PAdES LTV) licensed
Build long-term-validation signatures offline. Pdf.addDss appends a Document Security Store (/DSS with certs/CRLs, PAdES-B-LT); Pdf.timestamp appends an RFC 3161 document timestamp (/DocTimeStamp, PAdES-B-LTA).
Pdf.addDss(pdf, List<byte[]> certs, List<byte[]> crls) → byte[]Pdf.timestamp(pdf, tsaKeyDer, tsaCertDer, date) → byte[]byte[] signed = Files.readAllBytes(Path.of("contract.signed.pdf"));
// B-LT: embed validation material (caller supplies DER certs/CRLs)
byte[] lt = Pdf.addDss(signed, List.of(certDer), List.of(crlDer));
// B-LTA: add a document timestamp signed by a TSA key/cert
byte[] lta = Pdf.timestamp(lt, tsaKeyDer, tsaCertDer, null);
Files.write(Path.of("contract.lta.pdf"), lta);Network timestamp (AD-RT)
To stamp with a real network RFC 3161 TSA, use the transport-agnostic helpers and do the HTTP POST yourself. Phase 1 beginTimestamp prepares the PDF and returns a SigningSession; build the request from its hash() with timestampRequest, POST it to the TSA, extract the token from the response with timestampTokenFromResponse, then embed it via session.complete(token). Free public TSAs work (FreeTSA https://freetsa.org/tsr, DigiCert http://timestamp.digicert.com).
Pdf.beginTimestamp(pdf) → SigningSession Pdf.timestampRequest(imprint[, nonce, certReq]) → byte[] Pdf.timestampTokenFromResponse(response) → byte[]SigningSession session = Pdf.beginTimestamp(signed);
byte[] tsq = Pdf.timestampRequest(session.hash()); // RFC 3161 TimeStampReq (DER)
// POST tsq to the TSA (Content-Type: application/timestamp-query) — your HTTP client:
byte[] tsr = httpPost("https://freetsa.org/tsr", "application/timestamp-query", tsq);
byte[] token = Pdf.timestampTokenFromResponse(tsr); // the CMS TimeStampToken
byte[] lta = session.complete(token);
Files.write(Path.of("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.
Pdf.verifySignatures(byte[] pdf) → List<SignatureReport>Each SignatureReport is a record with fieldName(), subFilter(), signer(), coversWholeDocument(), digestValid(), signatureValid(), isValid() and byteRange(). It also carries the signer-certificate and timestamp details: issuer() (RFC 4514 DN), serialNumber() (uppercase hex), validFrom() / validTo() (ISO-8601 certificate validity), algorithm() (e.g. SHA256withRSA), signingTime() (claimed signing time, ISO-8601), certCount() (number of certificates in the CMS) and hasTimestamp() (whether an embedded timestamp is present). The string accessors may be null when absent. An empty list 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.
byte[] data = Files.readAllBytes(Path.of("contract.signed.pdf"));
for (SignatureReport sig : Pdf.verifySignatures(data)) {
System.out.println(sig.signer() + " valid: " + sig.isValid()
+ " covers whole doc: " + sig.coversWholeDocument());
System.out.println(" issuer: " + sig.issuer()
+ " serial: " + sig.serialNumber()
+ " algorithm: " + sig.algorithm());
System.out.println(" valid " + sig.validFrom() + " .. " + sig.validTo()
+ " signed: " + sig.signingTime()
+ " certs: " + sig.certCount()
+ " timestamped: " + sig.hasTimestamp());
}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.
Pdf.extractText(byte[] data) → String freePdf.extractPageText(byte[] data, int pageIndex) → String freePdf.extractImagesToDir(byte[] data, String outDir) → long freeextractPageText pulls the text of a single page (0-based) without building an intermediate one-page document, the fast path when you only need one page. It throws PdfException if the page is out of range.
byte[] data = Files.readAllBytes(Path.of("report.pdf"));
System.out.println(Pdf.extractText(data));
System.out.println(Pdf.extractPageText(data, 0)); // just the first page
long n = Pdf.extractImagesToDir(data, "out_images/"); // returns how many were written
System.out.println("wrote " + n + " image(s)");Positional text search free
Find every occurrence of a query string and get back its position on the page, not just the text. Each match is a TextHit with a 0-based page() and a bounding box (x(), y(), width(), height()) in PDF points, origin at the lower-left. Search is case-insensitive by default; pass a flag to make it case-sensitive. Useful for highlighting, redaction targeting or click-to-locate. See the searching text in a PDF concept guide.
Pdf.findText(byte[] pdf, String query) → List<TextHit> Pdf.findText(byte[] pdf, String query, boolean caseSensitive) → List<TextHit>byte[] data = Files.readAllBytes(Path.of("invoice.pdf"));
for (TextHit hit : Pdf.findText(data, "Total"))
System.out.printf("page %d box [%.1f %.1f %.1f %.1f] %s%n",
hit.page(), hit.x(), hit.y(), hit.width(), hit.height(), hit.text());
// case-sensitive
List<TextHit> exact = Pdf.findText(data, "INV-2026-001", true);Page geometry free
Read each page's size, rotation and boxes without modifying the file. width()/height() ignore /Rotate; rotatedWidth()/rotatedHeight() are the dimensions a viewer shows. Sizes are in PDF points (72 per inch).
List<PageGeometry> Pdf.measurePages(byte[] pdf)PageGeometry Pdf.measurePage(byte[] pdf, int pageIndex)Each PageGeometry record exposes page() (0-based), width(), height(), rotation() (0/90/180/270), rotatedWidth(), rotatedHeight(), and the mediaBox()/cropBox() as a PdfRect (x0(), y0(), x1(), y1() with width()/height() helpers).
byte[] data = Files.readAllBytes(Path.of("report.pdf"));
PageGeometry g = Pdf.measurePage(data, 0);
System.out.printf("page %d: %.1f x %.1f pt, rotate %d%n", g.page(), g.width(), g.height(), g.rotation());
System.out.printf("visible: %.1f x %.1f pt%n", g.rotatedWidth(), g.rotatedHeight());Document inspection free
Get a non-mutating overview of a PDF, its version, PDF/A level (if any) and encryption posture, without decrypting or rewriting it. Handy for triage before deciding how to process a file.
PdfOverview Pdf.inspect(byte[] pdf)PdfOverview carries version() (e.g. "1.7"), pdfaLevel() (e.g. "2b", or null if not PDF/A), encrypted(), encryption() ("None", "RC4", "AES-128" or "AES-256"), requiresPassword(), and pageCount().
byte[] data = Files.readAllBytes(Path.of("report.pdf"));
PdfOverview o = Pdf.inspect(data);
System.out.printf("PDF %s, %d page(s)%n", o.version(), o.pageCount());
System.out.printf("PDF/A: %s, encryption: %s%n", o.pdfaLevel() == null ? "no" : o.pdfaLevel(), o.encryption());
if (o.requiresPassword()) System.out.println("needs a password to open");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; pageCount is free.
Pdf.renderPageToPng(byte[] pdf, int pageIndex, double dpi) → byte[] licensedPdf.pageCount(byte[] pdf) → long freebyte[] data = Files.readAllBytes(Path.of("report.pdf"));
System.out.println(Pdf.pageCount(data) + " page(s)");
byte[] png = Pdf.renderPageToPng(data, 0, 150.0);
Files.write(Path.of("page1.png"), png);Enums
PdfaLevel
| Value | Level |
|---|---|
PdfaLevel.A1B | PDF/A-1b (basic, PDF 1.4) |
PdfaLevel.A2B | PDF/A-2b (basic): default of pdfa() |
PdfaLevel.A2A | PDF/A-2a (accessible: pair with tagged()) |
PdfaLevel.A3B | PDF/A-3b (basic, allows attachments) |
PdfaLevel.A3A | PDF/A-3a (accessible + attachments) |
PdfaLevel.A4 | PDF/A-4 (ISO 19005-4, based on PDF 2.0) |
PdfaLevel.A4E | PDF/A-4e (engineering) |
PdfaLevel.A4F | PDF/A-4f (requires at least one embedded file) |
Align
| Value | Meaning |
|---|---|
Align.LEFT | Left-aligned (default) |
Align.RIGHT | Right-aligned |
Align.CENTER | Centered |
Align.JUSTIFY | Justified (space distributed between words) |
VerticalAnchor
| Value | Meaning of y |
|---|---|
VerticalAnchor.BASELINE | Text baseline (default, historical behavior) |
VerticalAnchor.TOP | Text hangs from y (baseline at y − ascent × size) |
VerticalAnchor.BOTTOM | Descender line rests on y |
VerticalAnchor.LINE_TOP | Top of the layout line box |
VerticalAnchor.LINE_BOTTOM | Bottom of the layout line box (matches legacy fixed-position layout) |
VerticalAlign
| Value | Line position inside a maskedText box |
|---|---|
VerticalAlign.TOP | Hangs from the top edge (top line-alignment in rectangle-based text APIs) |
VerticalAlign.MIDDLE | Cap-height centering (default, historical behavior) |
VerticalAlign.BOTTOM | Descender line rests on the bottom edge |
StampSpace
| Value | Coordinate space of stamping calls |
|---|---|
StampSpace.VISIBLE | Displayed space, compensating /Rotate (default) |
StampSpace.MEDIA | Raw PDF user space (legacy layout semantics), ignores /Rotate |
ImageAnchor
| Value | Rotation anchor at (x, y) |
|---|---|
ImageAnchor.CORNER | The image's own lower-left corner (default) |
ImageAnchor.BOUNDING_BOX | Lower-left of the rotated image's bounding box (bounding-box layout) |
AFRelationship
| Value | Meaning |
|---|---|
AFRelationship.SOURCE | Source data for the document (e.g. the invoice XML) |
AFRelationship.DATA | Data used to derive the visual content |
AFRelationship.ALTERNATIVE | Alternative representation |
AFRelationship.SUPPLEMENT | Supplementary material |
AFRelationship.UNSPECIFIED | Unspecified relationship |
Encryption
| Value | Cipher |
|---|---|
Encryption.RC4 | RC4 (legacy) |
Encryption.AES128 | AES-128 |
Encryption.AES256 | AES-256 (V5/R6): recommended |
FacturxProfile
| Value | Conformance level |
|---|---|
FacturxProfile.MINIMUM | Minimal header data only |
FacturxProfile.BASIC_WL | Basic, without line items |
FacturxProfile.BASIC | Basic, with line items |
FacturxProfile.EN16931 | EN 16931 (Comfort): the interoperable core, default |
FacturxProfile.EXTENDED | EN 16931 plus extensions |
Error handling
Every failing native call throws PdfException (an unchecked RuntimeException) carrying the status() (PdfStatus) code and the library's last-error message (via getMessage()). License failures (missing/expired/forged token, or a feature the token does not 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.
import dev.rustpdf.*;
try (Document doc = new Document()) {
doc.pdfa().setTitle("x");
doc.addPage();
doc.save("out.pdf");
} catch (PdfException e) {
System.err.println("failed: " + e.status() + " " + e.getMessage());
// e.g. PdfStatus=4 (Serialize): feature 'pdfa' requires a valid license
}Utilities
| Member | Description |
|---|---|
Pdf.version() → String | Native library version string. |
Pdf.activateLicense(token) | Activate a license token (throws on invalid/expired). |