Delphi / Free Pascal
The RustPdf unit wraps the rust-pdf C core with idiomatic, handle-owning classes. It covers the whole product surface: vector graphics, embedded/subset fonts & Unicode text, paragraphs, images, PDF/A (1b–3a), tagged/accessible output, attachments, AcroForm fields, manipulation, text extraction, encryption and digital signatures. One unit compiles under both Delphi (10.x+, Windows/macOS/Linux) and Free Pascal (3.2+).
TPdfDocument authors a new PDF; TPdfEditable loads and manipulates an existing one. Both own a native handle — free them with .Free (a try…finally is the idiom). Stateless calls (version, licensing, extraction, signing) live on the Pdf record.Installation
The binding is a single source unit, RustPdf.pas — no native build step, no headers to install. It loads the shared library (libpdf_ffi) built from the Rust core at run time, so there is no fixed link name. Add the binding directory to your unit search path:
uses RustPdf; // add bindings/delphi to the unit search path (-Fu / Project Options)Build the native library and (optionally) point RUSTPDF_LIB at it:
cargo build -p pdf-ffi # → target/debug/libpdf_ffi.{dylib,so,dll}
export RUSTPDF_LIB=/path/to/target/debug/libpdf_ffi.dylib # optionalThe loader searches, in order: $RUSTPDF_LIB, then target/debug and target/release walking up from the executable and the current directory, then the bare platform name via the OS loader (install-name / PATH / LD_LIBRARY_PATH). Compile a console program with Free Pascal:
fpc -Mdelphi -Fubindings/delphi hello.dpror with Delphi's command-line compiler:
dcc64 -Ubindings/delphi hello.dprVerify the library loads:
Writeln(Pdf.Version); // native library version, e.g. 0.1.0Quick start
A one-page document with a filled rectangle, saved to disk:
program hello;
{$IFDEF FPC}{$MODE DELPHI}{$H+}{$CODEPAGE UTF8}{$ENDIF}
uses RustPdf;
var
Doc: TPdfDocument;
begin
Doc := TPdfDocument.Create; // A4 by default
try
Doc.AddPage;
Doc.FillRgb(0.86, 0.20, 0.18);
Doc.Rect(72, 640, 200, 120); // x, y, width, height (points)
Doc.Fill;
Doc.SaveToFile('out.pdf');
finally
Doc.Free;
end;
end.Most methods return the document, so calls chain:
var
Doc: TPdfDocument;
Font: Integer;
Data: TBytes;
begin
Doc := TPdfDocument.Create;
try
Font := Doc.AddFontFile('Roboto-Regular.ttf');
Doc.AddPage
.FillRgb(0.1, 0.1, 0.12).Rect(0, 800, 595, 42).Fill
.ShowText(Font, 24, 72, 740, 'Olá, açúcar — café');
Data := Doc.ToBytes; // in-memory bytes instead of a file
finally
Doc.Free;
end;
end;{$CODEPAGE UTF8} so accented string literals reach the engine as proper UTF-8. Delphi handles UTF-8 source natively.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 raise ERustPdf 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:
Pdf.ActivateLicense(Token); // raises ERustPdf 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
The core is Send but not Sync: you can build many documents in parallel, but a single handle must never be touched by two threads at once.
- Generate in parallel. Give each thread its own
TPdfDocument/TPdfEditable— independent documents share no state and run truly concurrently. - Move between threads. A handle may be created on one thread and used on another.
- Never share a live handle. Two threads calling into the same
TPdfDocumentat the same time is unsupported; protect it with aTCriticalSectionif you really must. - Errors are per-thread. The native last-error is thread-local, so a failure on one thread never clobbers another's —
ERustPdfis always raised on the calling thread. - License is process-global.
Pdf.ActivateLicense(or the env var) applies to every thread; activate once at startup.
type
TRenderThread = class(TThread)
protected
procedure Execute; override;
end;
procedure TRenderThread.Execute;
var
Doc: TPdfDocument;
begin
Doc := TPdfDocument.Create; // one document per thread
try
Doc.AddPage.FillRgb(0.1, 0.1, 0.12).Rect(72, 700, 200, 80).Fill;
Doc.SaveToFile(Format('out-%d.pdf', [ThreadID]));
finally
Doc.Free;
end;
end;Authoring: create & save
TPdfDocument.Create freeCreates an empty document (A4 default page size). Always pair with try…finally Doc.Free end so the native handle is released.
| Method | Description |
|---|---|
AddPage | Append a page using the default size. |
AddPage(Width, Height) | Append a page with an explicit size in points. |
DefaultSize(W, H) | Default size for subsequently added pages. |
SetVersion(V) | PDF header version: 0 → 1.4, 1 → 1.5, 2 → 1.7. |
PageCount | Number of pages so far. |
ToBytes | Render the document to TBytes. |
SaveToFile(Path) | Render and write to a file. |
Pages & vector graphics
Graphics state and path operators mirror PDF's content-stream model. Colors are RGB in 0.0–1.0.
| Method | Description |
|---|---|
FillRgb(R, G, B) | Fill color. |
StrokeRgb(R, G, B) | Stroke color. |
LineWidth(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. |
Doc.AddPage;
Doc.StrokeRgb(0.10, 0.45, 0.90).LineWidth(3);
Doc.Rect(72, 600, 300, 160).Stroke;
Doc.FillRgb(0.95, 0.77, 0.06);
Doc.Rect(120, 640, 120, 80).Fill;
Doc.SaveToFile('shapes.pdf');Fonts & text
Fonts are embedded and subsetted, with HarfBuzz-quality shaping, kerning and full Unicode (Type0/CIDFontType2 with ToUnicode, so text extracts and copies correctly). Register a font once, then reference it by its integer id.
AddFontFile(Path): Integer AddFont(Data: TBytes): IntegerShowText(Font, Size, X, Y, Text, HeadingLevel = 0)HeadingLevel (1–6) tags the run as H1–H6 in an accessible document (see Accessibility); leave 0 for ordinary text. Strings cross the boundary as UTF-8.
var Regular: Integer;
…
Regular := Doc.AddFontFile('Roboto-Regular.ttf');
Doc.AddPage;
Doc.ShowText(Regular, 28, 72, 760, 'Invoice #1024');
Doc.ShowText(Regular, 12, 72, 720, '日本語 · Ελληνικά · العربية');
Doc.SaveToFile('text.pdf');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 = paLeft)const
Intro = 'A long paragraph that wraps to the given width and is justified ' +
'automatically; extra space is distributed between words.';
…
F := Doc.AddFontFile('Roboto-Regular.ttf');
Doc.AddPage;
Doc.Paragraph(F, 12, 72, 700, 451, Intro, paJustify);
Doc.SaveToFile('paragraph.pdf');See the TPdfAlign 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): Integer | Load JPEG/PNG from a file; returns the image id. |
AddImagePng(Data: TBytes): Integer | Register a PNG from memory. |
AddImageJpeg(Data: TBytes): Integer | 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). |
Logo := Doc.AddImageFile('logo.png');
Doc.AddPage;
Doc.DrawImage(Logo, 72, 680, 160, 90);
Doc.SaveToFile('with_image.pdf');PDF/A licensed
Produce archival-grade output. Pdfa defaults to A-2b; pass a TPdfaLevel 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.
Pdfa Pdfa(Level: TPdfaLevel)Doc.Pdfa(palA2B).SetInfo('Q3 Report', 'Acme Inc.', '', '', '');
F := Doc.AddFontFile('Roboto-Regular.ttf');
Doc.AddPage;
Doc.ShowText(F, 20, 72, 760, 'Archival report');
Doc.SaveToFile('report_pdfa.pdf'); // raises ERustPdf without a license granting PDF/ASetInfo with a non-empty title.Accessibility (Tagged PDF / PDF/UA) licensed
Tagged builds a logical structure tree (PDF/UA-1). Combine with Pdfa(palA2A) for archival and accessible output. Use HeadingLevel on ShowText for H1–H6, and Figure(…, Alt) for described images.
TaggedDoc.Pdfa(palA2A).Tagged.SetInfo('Accessible report', '', '', '', '');
F := Doc.AddFontFile('Roboto-Regular.ttf');
Doc.AddPage;
Doc.ShowText(F, 26, 72, 760, 'Annual report', 1); // H1
Doc.ShowText(F, 14, 72, 720, 'Overview', 2); // H2
Doc.ShowText(F, 11, 72, 690, 'Body paragraph of the section…');
Chart := Doc.AddImageFile('chart.png');
Doc.Figure(Chart, 72, 520, 300, 150, 'Revenue grew 18% year over year');
Doc.SaveToFile('accessible.pdf');Attachments (PDF/A-3) licensed
PDF/A-3 allows embedding arbitrary source files (e.g. the XML behind an e-invoice). Each attachment carries a MIME type and an TAFRelationship.
AttachFile(Name, Mime, Data, Relationship = afSource, Description = '')Xml := ...; // TBytes of invoice.xml
Doc.Pdfa(palA3B).SetInfo('E-invoice 1024', '', '', '', '');
F := Doc.AddFontFile('Roboto-Regular.ttf');
Doc.AddPage;
Doc.ShowText(F, 18, 72, 760, 'Invoice 1024');
Doc.AttachFile('invoice.xml', 'text/xml', Xml, afSource, 'Structured invoice data');
Doc.SaveToFile('einvoice.pdf');AcroForm fields
Build interactive forms with generated appearance streams (no NeedAppearances). Rectangles are PdfRect(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 = 0) | Text input (Size = 0 → auto font size). |
Checkbox(Name, Page, Rect, Checked) | Checkbox (Checked: Boolean). |
Dropdown(Name, Page, Rect, Options, Selected = -1, Size = 0) | Combo box from an array of strings. |
RadioGroup(Name, Page, Buttons, Selected = -1) | Buttons: TRadioButtons — each has a Rect and ExportValue. |
var Buttons: TRadioButtons;
…
Doc.AddPage;
Doc.TextField('applicant.name', 0, PdfRect(72, 700, 320, 720));
Doc.Checkbox('agree', 0, PdfRect(72, 660, 88, 676), False);
Doc.Dropdown('plan', 0, PdfRect(72, 620, 240, 640), ['Starter', 'Pro', 'Enterprise'], 1);
SetLength(Buttons, 2);
Buttons[0].Rect := PdfRect(72, 580, 88, 596); Buttons[0].ExportValue := 'monthly';
Buttons[1].Rect := PdfRect(140, 580, 156, 596); Buttons[1].ExportValue := 'annual';
Doc.RadioGroup('billing', 0, Buttons, 1);
Doc.SaveToFile('form.pdf');Fill fields later with TPdfEditable.FillTextField.
Metadata
SetInfo(Title, Author, Subject, Keywords, Creator)Sets the document information dictionary (and, for PDF/A, the matching XMP). Pass an empty string '' for any field you want to leave unset.
Doc.SetInfo('Q3 Report', 'Acme Inc.', 'Quarterly results', 'finance, q3', '');Manipulation: load an existing PDF
TPdfEditable 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.
TPdfEditable.Load(Data: TBytes) TPdfEditable.Load(Data, Password)TPdfEditable.LoadFromFile(Path) TPdfEditable.LoadFromFile(Path, Password)var Ed: TPdfEditable;
…
Ed := TPdfEditable.LoadFromFile('in.pdf');
try
Writeln(Ed.PageCount);
finally
Ed.Free;
end;
// encrypted input:
Ed := TPdfEditable.LoadFromFile('secured.pdf', 'user-or-owner-pw');
try
Ed.SaveToFile('plain.pdf');
finally
Ed.Free;
end;Pages: merge, split, reorder, rotate
| Method | Description |
|---|---|
Merge(Other) | Append all pages of another TPdfEditable (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): TPdfEditable | New document containing just those pages. |
PageCount | Current page count. |
A := TPdfEditable.LoadFromFile('a.pdf');
B := TPdfEditable.LoadFromFile('b.pdf');
try
A.Merge(B); // a now has a's pages followed by b's
A.RotatePage(0, 90);
A.ReorderPages([2, 1, 0]);
A.SaveToFile('merged.pdf');
finally
A.Free; B.Free;
end;Metadata, overlay & form fill
| Method | Description |
|---|---|
SetInfo(Key, Value) | Set one info entry (e.g. 'Title'). |
GetInfo(Key): string | Read an info entry. |
SetXmp(Xml: TBytes) | Replace the XMP metadata stream. |
OverlayPage(Index, Content: TBytes) | Overlay a content-stream fragment onto a page (stamps/watermarks). |
FillTextField(Name, Value): Boolean | Fill an AcroForm text field; returns whether it was found. |
Ed := TPdfEditable.LoadFromFile('form.pdf');
try
Ed.SetInfo('Title', 'Filled form');
Found := Ed.FillTextField('applicant.name', 'Edivan Teixeira');
Writeln('filled: ', Found, ' | title: ', Ed.GetInfo('Title'));
Ed.SaveToFile('filled.pdf');
finally
Ed.Free;
end;Optimize & compact
| Method | Description |
|---|---|
Optimize | Drop unreferenced objects, Flate-compress uncompressed streams, dedupe identical objects. |
Compact(On = True) | Pack objects into object streams + emit a cross-reference stream. |
Ed := TPdfEditable.LoadFromFile('big.pdf');
try
Ed.Optimize.Compact(True);
Ed.SaveToFile('small.pdf');
finally
Ed.Free;
end;Encryption licensed
Apply standard-handler encryption at output. AES-256 (V5/R6) uses OS-CSPRNG keys/IVs.
Encrypt(Method = encAES256, User = '', Owner = '', ReadOnlyPerms = False)Ed := TPdfEditable.LoadFromFile('in.pdf');
try
Ed.Encrypt(encAES256, '', 'owner-secret', True);
Ed.SaveToFile('secured.pdf'); // raises ERustPdf without an Encryption license
finally
Ed.Free;
end;See the TEncryption enum for RC4 / AES-128 / AES-256.
Output & incremental update
| Method | Description |
|---|---|
ToBytes: TBytes | Serialize the manipulated document. |
SaveToFile(Path) | Serialize to a file. |
ToBytesIncremental(Original: TBytes): TBytes | Append only changes to the original bytes (signature-safe, non-destructive). |
Original := ...; // TBytes of in.pdf
Ed := TPdfEditable.Load(Original);
try
Ed.SetInfo('Subject', 'reviewed');
Incremental := Ed.ToBytesIncremental(Original); // original bytes preserved verbatim
finally
Ed.Free;
end;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 TBytes. Pades = True switches to PAdES-B-B.
Pdf.Sign(PdfBytes, KeyDer, CertDer, Reason = '', Location = '', Name = '', Pades = False): TBytesPdfBytes := ...; // TBytes of contract.pdf
KeyDer := ...; // PKCS#8 private key (DER)
CertDer := ...; // X.509 certificate (DER)
Signed := Pdf.Sign(PdfBytes, KeyDer, CertDer, 'Approved', 'São Paulo', 'Edivan Teixeira', True);
// Verify in a shell: pdfsig contract.signed.pdf → "Signature is Valid."Timestamp & DSS (PAdES LTV) licensed
Build long-term-validation signatures offline. AddDss appends a Document Security Store (/DSS with certs/CRLs, PAdES-B-LT); Timestamp appends an RFC 3161 document timestamp (/DocTimeStamp, PAdES-B-LTA).
Pdf.AddDss(PdfBytes, Certs, Crls): TBytesPdf.Timestamp(PdfBytes, TsaKeyDer, TsaCertDer, Date = ''): TBytes// B-LT: embed validation material (caller supplies DER certs/CRLs)
Lt := Pdf.AddDss(Signed, [CertDer], [CrlDer]);
// B-LTA: add a document timestamp signed by a TSA key/cert
Lta := Pdf.Timestamp(Lt, TsaKeyDer, TsaCertDer);Text extraction
Extract a document's text, mapping shown glyph codes back to Unicode through each font's ToUnicode map, with space/line inference.
Pdf.ExtractText(PdfBytes: TBytes): string freeData := ...; // TBytes of report.pdf
Writeln(Pdf.ExtractText(Data));Enums
TPdfaLevel
| Value | Level |
|---|---|
palA1B | PDF/A-1b (basic, PDF 1.4) |
palA2B | PDF/A-2b (basic): default of Pdfa |
palA2A | PDF/A-2a (accessible: pair with Tagged) |
palA3B | PDF/A-3b (basic, allows attachments) |
palA3A | PDF/A-3a (accessible + attachments) |
TPdfAlign
| Value | Meaning |
|---|---|
paLeft | Left-aligned (default) |
paRight | Right-aligned |
paCenter | Centered |
paJustify | Justified (space distributed between words) |
TAFRelationship
| Value | Meaning |
|---|---|
afSource | Source data for the document (e.g. the invoice XML) |
afData | Data used to derive the visual content |
afAlternative | Alternative representation |
afSupplement | Supplementary material |
afUnspecified | Unspecified relationship |
TEncryption
| Value | Cipher |
|---|---|
encRC4_128 | RC4-128 (legacy) |
encAES128 | AES-128 |
encAES256 | AES-256 (V5/R6): recommended |
Error handling
Every failing native call raises ERustPdf (a Exception) carrying the Status (a TPdfStatus) and the library's last-error message. License failures (missing/expired/forged token, or a feature the token doesn't grant) surface here too.
try
Doc.Pdfa.SetInfo('x', '', '', '', '');
Doc.AddPage;
Doc.SaveToFile('out.pdf');
except
on E: ERustPdf do
Writeln('failed: ', E.Message, ' (status ', Ord(E.Status), ')');
end;Utilities
| Function | Description |
|---|---|
Pdf.Version: string | Native library version string. |
Pdf.ActivateLicense(Token) | Activate a license token (raises on invalid/expired). |