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 + A-4/4e/4f), 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.Download
The binding ships as one versioned archive: the RustPdf.pas unit, the native library for each platform (lib/<os-arch>/), a sample and an INSTALL.txt. Basic PDF generation works immediately as a free trial; the corporate features unlock with a license token.
Prefer to build from source? make delphi-dist assembles the same archive. Using Boss? The repo is boss install-able. After unzipping, drop the matching native library next to your executable (matching the app's bitness on Windows). Full steps are in the bundled INSTALL.txt and the Installation section below.
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; the library next to the executable or in the current directory (the deployment layout); target/debug and target/release walking up from those dirs (the dev tree); then the bare platform name via the OS loader. 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.4.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, 3 → 2.0. |
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, provided the embedded font covers those characters). 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');#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 = 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, and A-4 (ISO 19005-4) is based on PDF 2.0.
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');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 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');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 plus Factur-X under veraPDF.
Facturx(Xml: TBytes; Profile: TFacturxProfile = fxEN16931)var Xml: TBytes;
…
Xml := TEncoding.UTF8.GetBytes(LoadXmlText); // your Cross-Industry Invoice XML
Doc.SetInfo('Invoice INV-2026-001', '', '', '', '');
F := Doc.AddFontFile('Roboto-Regular.ttf');
Doc.AddPage;
Doc.ShowText(F, 18, 72, 760, 'Invoice INV-2026-001');
Doc.Facturx(Xml, fxEN16931);
Doc.SaveToFile('einvoice.pdf'); // PDF/A-3 + Factur-X; needs a PDF/A licenseSee the TFacturxProfile enum for the conformance levels (fxMinimum to fxExtended).
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 and flatten fields later with TPdfEditable.
page does not exist, or whose rectangle is degenerate (x1 < x0 or zero area), is rejected when the document is serialized: ToBytes/Save raise ERustPdf ("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(R: TPdfRect; Uri: string) LinkToPage(R: TPdfRect; PageIndex: NativeUInt [; Top: Double])F := Doc.AddFontFile('Roboto-Regular.ttf');
Doc.AddPage;
Doc.ShowText(F, 14, 72, 760, 'Visit rustpdf.dev (see page 2)');
Doc.LinkUri(PdfRect(72, 756, 320, 776), 'https://rustpdf.dev/'); // web link
Doc.LinkToPage(PdfRect(330, 756, 430, 776), 1, 800); // jump to page 2
Doc.AddPage;
Doc.SaveToFile('links.pdf');Rectangles are PdfRect(x0, y0, x1, y1) in points; PageIndex is 0-based. The two-argument LinkToPage jumps without scrolling; the three-argument overload positions Top at the top of the view.
Bookmarks / outline free
Build a navigable document outline. A TPdfBookmark has a title, a target page and an optional Top; nest children with .Child(...). The object owns its children, so freeing the root frees the whole tree. A document with bookmarks opens with the outline pane shown.
TPdfBookmark.Create(Title; Page [; Top]) .Child(BM): TPdfBookmark AddBookmark(BM)var Root: TPdfBookmark;
…
F := Doc.AddFontFile('Roboto-Regular.ttf');
Doc.AddPage; Doc.AddPage; Doc.AddPage;
Root := TPdfBookmark.Create('Chapter 1', 0, 820);
try
Root.Child(TPdfBookmark.Create('Section 1.1', 1))
.Child(TPdfBookmark.Create('Section 1.2', 2));
Doc.AddBookmark(Root);
Doc.AddBookmark(TPdfBookmark.Create('Chapter 2', 2));
finally
Root.Free; // frees the whole tree
end;
Doc.SaveToFile('outline.pdf');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;Load does not raise in that case. Serializing a zero-page document raises ERustPdf ("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
| 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;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
| 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', 'Jane Doe');
Writeln('filled: ', Found, ' | title: ', Ed.GetInfo('Title'));
Ed.SaveToFile('filled.pdf');
finally
Ed.Free;
end;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: TPdfStringArray | 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 = True): 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. |
var Names: TPdfStringArray; I: Integer;
…
Ed := TPdfEditable.LoadFromFile('form.pdf');
try
Names := Ed.FieldNames; // ['applicant.name', 'agree', 'plan', …]
for I := 0 to High(Names) do
Writeln(Names[I]);
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.SaveToFile('filled.pdf');
finally
Ed.Free;
end;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; Size = 64; R = 0.5; G = 0.5; B = 0.5; Opacity = 0.30; RotationDeg = 45): TPdfEditableWatermarkImageFile(Path; Width, Height; Opacity = 0.30): TPdfEditableEd := TPdfEditable.LoadFromFile('report.pdf');
try
Ed.WatermarkText('CONFIDENTIAL', 64, 0.5, 0.5, 0.5, 0.25, 45);
Ed.SaveToFile('stamped.pdf');
finally
Ed.Free;
end;Redaction licensed
True redaction: the text and graphics whose origin falls inside a rectangle are removed from the content stream (not just covered), so the data is gone from the file and is no longer extractable. Opaque black boxes are then painted over the regions. Returns True when the page index was valid.
Redact(PageIndex: NativeUInt; const Rects: array of TPdfRect): BooleanEd := TPdfEditable.LoadFromFile('statement.pdf');
try
Ed.Redact(0, [PdfRect(60, 590, 400, 620), PdfRect(60, 540, 400, 570)]);
Ed.SaveToFile('redacted.pdf'); // raises ERustPdf without a license granting redaction
finally
Ed.Free;
end;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(Level: TPdfaLevel = palA2B): TPdfEditableEd := TPdfEditable.LoadFromFile('in.pdf');
try
Ed.ConvertToPdfa(palA2B); // raises ERustPdf if fonts aren't embedded
Ed.SaveToFile('archival.pdf'); // veraPDF: PDF/A-2b compliant
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', 'New York', 'Jane Doe', 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);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.
Object Pascal ships no bundled JSON parser, so the binding returns the report as the raw JSON array text: VerifySignaturesJson gives back the verbatim JSON string (a UTF8String) for you to parse with your preferred JSON unit. The string '[]' means the document is unsigned. Signature validation is a licensed feature: this call requires an active license (signatures) even when the document is unsigned — it is not available on the free tier.
Pdf.VerifySignaturesJson(PdfBytes: TBytes): UTF8StringEach array entry is an object with field_name, sub_filter, signer, covers_whole_document, digest_valid, signature_valid, is_valid and byte_range. field_name and signer may be empty when absent.
var Json: UTF8String;
…
Data := ...; // TBytes of contract.signed.pdf
Json := Pdf.VerifySignaturesJson(Data);
if Json = '[]' then
Writeln('document is unsigned')
else
Writeln(string(Json)); // hand the JSON to your parser of choiceText & 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. ExtractImagesToDir returns how many images were written.
Pdf.ExtractText(PdfBytes: TBytes): string freePdf.ExtractImagesToDir(PdfBytes: TBytes; Dir: string): NativeUInt freevar N: NativeUInt;
…
Data := ...; // TBytes of report.pdf
Writeln(Pdf.ExtractText(Data));
N := Pdf.ExtractImagesToDir(Data, 'out_images/'); // count of images written
Writeln(Format('wrote %d image(s)', [Int64(N)]));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.
Pdf.RenderPageToPng(const PdfBytes: TBytes; PageIndex: NativeUInt; Dpi: Double): TBytes licensedPdf.PageCount(const PdfBytes: TBytes): NativeUInt freeData := ReadBytes('report.pdf');
WriteLn(Pdf.PageCount(Data), ' page(s)');
Png := Pdf.RenderPageToPng(Data, 0, 150.0);
WriteBytes('page1.png', Png);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) |
palA4 | PDF/A-4 (ISO 19005-4, based on PDF 2.0) |
palA4E | PDF/A-4e (engineering) |
palA4F | PDF/A-4f (requires at least one embedded file) |
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 |
TFacturxProfile
| Value | Conformance level |
|---|---|
fxMinimum | Minimal header data only |
fxBasicWL | Basic, without line items |
fxBasic | Basic, with line items |
fxEN16931 | EN 16931 (Comfort): the interoperable core, default |
fxExtended | EN 16931 plus extensions |
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. 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.
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). |