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

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

delphi
uses RustPdf;   // add bindings/delphi to the unit search path (-Fu / Project Options)

Build the native library and (optionally) point RUSTPDF_LIB at it:

shell
cargo build -p pdf-ffi                       # → target/debug/libpdf_ffi.{dylib,so,dll}
export RUSTPDF_LIB=/path/to/target/debug/libpdf_ffi.dylib   # optional

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

shell
fpc -Mdelphi -Fubindings/delphi hello.dpr

or with Delphi's command-line compiler:

shell
dcc64 -Ubindings/delphi hello.dpr

Verify the library loads:

delphi
Writeln(Pdf.Version);   // native library version, e.g. 0.1.0

Quick start

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

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

delphi
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;
UTF-8 source. Under Free Pascal, add {$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:

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:

delphi
Pdf.ActivateLicense(Token);   // raises ERustPdf 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

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.

delphi
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 free

Creates an empty document (A4 default page size). Always pair with try…finally Doc.Free end so the native handle is released.

MethodDescription
AddPageAppend 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.
PageCountNumber of pages so far.
ToBytesRender 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.

MethodDescription
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.
FillFill the current path with the fill color.
StrokeStroke the current path with the stroke color.
delphi
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): Integer
ShowText(Font, Size, X, Y, Text, HeadingLevel = 0)

HeadingLevel (1–6) tags the run as H1H6 in an accessible document (see Accessibility); leave 0 for ordinary text. Strings cross the boundary as UTF-8.

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

MethodDescription
AddImageFile(Path): IntegerLoad JPEG/PNG from a file; returns the image id.
AddImagePng(Data: TBytes): IntegerRegister a PNG from memory.
AddImageJpeg(Data: TBytes): IntegerRegister 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).
delphi
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)
delphi
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/A
PDF/A requires the title to be set for valid metadata: call SetInfo 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 H1H6, and Figure(…, Alt) for described images.

Tagged
delphi
Doc.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 = '')
delphi
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.

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

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

MethodDescription
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): TPdfEditableNew document containing just those pages.
PageCountCurrent page count.
delphi
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

MethodDescription
SetInfo(Key, Value)Set one info entry (e.g. 'Title').
GetInfo(Key): stringRead 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): BooleanFill an AcroForm text field; returns whether it was found.
delphi
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

MethodDescription
OptimizeDrop unreferenced objects, Flate-compress uncompressed streams, dedupe identical objects.
Compact(On = True)Pack objects into object streams + emit a cross-reference stream.
delphi
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)
delphi
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

MethodDescription
ToBytes: TBytesSerialize the manipulated document.
SaveToFile(Path)Serialize to a file.
ToBytesIncremental(Original: TBytes): TBytesAppend only changes to the original bytes (signature-safe, non-destructive).
delphi
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): TBytes
delphi
PdfBytes := ...;   // 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): TBytes
Pdf.Timestamp(PdfBytes, TsaKeyDer, TsaCertDer, Date = ''): TBytes
delphi
// 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 free
delphi
Data := ...;   // TBytes of report.pdf
Writeln(Pdf.ExtractText(Data));

Enums

TPdfaLevel

ValueLevel
palA1BPDF/A-1b (basic, PDF 1.4)
palA2BPDF/A-2b (basic): default of Pdfa
palA2APDF/A-2a (accessible: pair with Tagged)
palA3BPDF/A-3b (basic, allows attachments)
palA3APDF/A-3a (accessible + attachments)

TPdfAlign

ValueMeaning
paLeftLeft-aligned (default)
paRightRight-aligned
paCenterCentered
paJustifyJustified (space distributed between words)

TAFRelationship

ValueMeaning
afSourceSource data for the document (e.g. the invoice XML)
afDataData used to derive the visual content
afAlternativeAlternative representation
afSupplementSupplementary material
afUnspecifiedUnspecified relationship

TEncryption

ValueCipher
encRC4_128RC4-128 (legacy)
encAES128AES-128
encAES256AES-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.

delphi
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

FunctionDescription
Pdf.Version: stringNative library version string.
Pdf.ActivateLicense(Token)Activate a license token (raises on invalid/expired).
Looking for another language? The same API exists in Python, C#, Go, PHP, Ruby, Node and Java: browse all docs. They share one core, so behavior is identical.