The collection comprises 32 topics: 32 Delphi projects, 31 C++Builder projects and 32 Lazarus projects, or 95 native projects altogether. Each platform has 22 complete workflow demos in its main directory. The ten focused topics under Miscellaneous account for 29 native projects across the platforms; XfaLab is provided for Delphi and Lazarus
Open a main-directory workflow first: PDFViewer for a feature tour, CreatePDF for live authoring, ContentExtractionLab for an instant inventory, FormFill for editable forms, StandardsLab for validation and conversion, or IntakeWorkbenchCli for automated intake. These six entry points produce working results straight away; the focused samples below come into their own when you need to inspect a single API in detail
| Demo | Use it for | How it differs from related demos |
|---|---|---|
PDFViewer | General viewing and feature evaluation | The main viewer combines navigation, search, thumbnails, display modes, printing and a feature showcase |
MultiPageViewer | Continuous pages, spreads and cover-page layouts | A focused layout example using the built-in viewer selection APIs; use PDFViewer for the broader viewer experience |
SplitView | Comparing pages or documents in separate panes | Demonstrates independent view positions and routing commands to the active pane, rather than two-page layout in one view |
SearchAndSelect | Cross-page search results and selection | A result list and search options expose the search workflow more directly than a viewer toolbar |
PrintPDF | Preview and ordinary print jobs | Page ranges, scaling, progress and cancellation provide the main print workflow |
ContentExtractionLab | Inspecting several content categories together | TXT/JSON inventories describe objects and preview text; they do not replace complete text or image-file export |
ExtractImages | Exporting embedded image objects | Extracts image bitmaps rather than screenshots of whole pages; the built-in fixture contains two real image objects on each of two pages |
TableExtractionLab | Detecting tables and exporting CSV/JSON | Ruled and whitespace tables, merged cells, continuation groups and confidence controls are independent of plain-text extraction |
Attachment | Reading, adding and exporting embedded attachments | Attachment payload operations go beyond listing names and sizes in ContentExtractionLab |
BatchExport | Exporting page images from multiple documents | Streaming output, per-file status and isolated failures demonstrate batch processing rather than a single conversion |
CreatePDF | Creating page content with a live preview | All three native variants create searchable text, vector shapes, a real raster image, tables and a review annotation; options rebuild the preview before an explicit save |
ImageToPDF | Building pages from input image files | Image layout, fitting, ordering and JPEG handling provide a document conversion workflow |
MergePDF | Assembling pages from several PDFs | Demonstrates document import and ordered composition; splitting solves the inverse workflow |
SplitPDF | Writing selected page groups to separate PDFs | Output naming and page-group validation differ from merging or page-image rendering |
WatermarkStamp | Applying repeated overlays to existing pages | Placement and batch page stamping complement the basic authoring APIs |
FormFill | Interactive AcroForm entry and flattening | Field input and saved appearances differ from XFDF interchange and XFA-specific behaviour |
AccessibilityReader | Interactive reading and speech | The GUI reader demonstrates reading navigation and highlighting rather than a compliance audit |
AccessibilityWorkbenchCli | Batch accessibility review and report generation | Findings, summaries and review gates serve automation; findings are not a substitute for manual accessibility review |
StandardsLab | Standards-specific validation and conversion | The interactive lab exposes operations and options rather than only generating an aggregate report |
PreflightReport | Interactive standards reports | The GUI supports manual inspection and report export |
PreflightReportCli | Automated standards reports | Batch inputs, report formats and exit-code gates provide the automation counterpart to PreflightReport |
IntakeWorkbenchCli | Batch intake, triage and routing | Aggregate reports, failure gates and routing decisions make this a processing workflow beyond the SecurityAudit GUI |
Open these projects from Demo\Delphi\Miscellaneous, Demo\CBuilder\Miscellaneous or Demo\Lazarus\Miscellaneous. They keep useful API recipes and narrow export tasks to hand whilst the main directory concentrates on complete experiences. Basic Unicode write-and-reload checks are covered by the authoring and extraction workflows, so they no longer have separate demo projects
| Demo | Use it for | How it differs from related demos |
|---|---|---|
SecurityAudit | Inspecting security posture interactively | The GUI emphasises permissions, scripts, signatures and security details rather than file routing |
XfdfLab | Importing and exporting XFDF data | Interchange and an explicit save of the imported PDF demonstrate persistence; exporting XFDF alone does not save the modified PDF |
AccessibilityLab | Exploring the reader APIs in a console | A compact executable walkthrough exposes reading units, word boxes, structure and speech-script output |
ExtractText | Exporting complete text from selected pages | Text-layer output, page separators and cancellation are distinct from inventory previews and table detection; no OCR is performed |
FontProperties | Inspecting font and character metrics | Opens with the actual font and character metrics for the first text object; select another line to compare fonts without exporting document payloads |
NavigationLab | Bookmarks, destinations and navigation actions | The compact API walkthrough complements the visible navigation controls in PDFViewer |
PDFtoJpg | Rendering one document to page images | A compact single-document rendering example; Delphi exposes discrete page ranges, whilst BatchExport focuses on multiple input documents |
PrintPreferences | N-up sheets and PDF author print preferences | Copies, odd/even filtering and author-provided preferences are the teaching focus; this is a companion to PrintPDF |
ViewerInteractionLab | Diagnosing mouse, keyboard and coordinate interactions | The interaction log and page/device coordinate conversion make it an API diagnostics tool |
XfaLab | Static XFA packets and runtime capabilities | Opens the template and datasets packets with the ordinary runtime; interactive XFA rendering needs an XFA-enabled runtime, with Delphi and Lazarus variants |
PDFViewer opens its bundled PDFiumDemoShowcase.pdf, a six-page document carrying searchable text, vector charts, two embedded images, a table, four AcroForm fields, chapter bookmarks, links, a review note and two file attachments. The experience bar offers Restart showcase, Find live text, 2-page spread, Night reading, Inspect this PDF and Explore forms; every action works on the current document
CreatePDF opens a two-page live report preview. Edit its title and author, or toggle shapes, the table and the watermark, to rebuild the real page content; the Unicode option adds a third page. Nothing is written until you save, and the saved PDF remains searchable
MultiPageViewer, SearchAndSelect, ContentExtractionLab and FormFill open the same document for their own workflows, with search results, an extraction inventory and editable fields ready straight away. Miscellaneous\NavigationLab keeps its own local copy for a bookmark and destination API walkthrough
Attachment opens with two real embedded files. PrintPreferences opens with four pages and author preferences for two copies, selected page ranges, scaling and duplex; the current print workflow applies copies and page ranges, whilst scaling and duplex are inspected and reported. XfaLab opens with actual template and datasets packets for static XFA inspection; interactive XFA rendering needs a suitable runtime and is a separate capability
Each viewing, form, extraction and navigation project carries its own PDFiumDemoShowcase.pdf. Copy the project directory and keep this bundled input beside the distributed executable; Lazarus builds stage it automatically. The tour works offline, and the general viewers and extraction samples still fall back to generated content when the local PDF is unavailable
Every native demo carries its own sample implementation. The Delphi and Lazarus entry points for AccessibilityLab, AccessibilityWorkbenchCli, IntakeWorkbenchCli and PreflightReportCli include the full workflow in the project directory, compiler-specific initialisation and JPEG writing included. The attachment, print-preference and XFA examples also build their own feature-specific inputs locally. Only the library, the matching native runtime and the usual compiler framework dependencies are needed beyond the copied project
Choose image-object extraction when you need embedded bitmaps, page rendering when you need the visible page, and text extraction when the PDF already has a text layer. A report that lists these categories does not export their full payloads. These boundaries keep focused examples useful without expanding every demo into another general-purpose viewer or workbench
Pdf1.FileName := 'C:\MyDocument.pdf';
Pdf1.Active := True;
ShowMessage('Pages: ' + IntToStr(Pdf1.PageCount));
ShowMessage('Title: ' + Pdf1.Title);
ShowMessage('Author: ' + Pdf1.Author);
var
Stream: TMemoryStream;
begin
Stream := TMemoryStream.Create;
try
Stream.LoadFromFile('C:\MyDocument.pdf');
Pdf1.LoadDocument(Stream, True);
Pdf1.Active := True;
ShowMessage('PDF loaded from stream');
finally
Stream.Free;
end;
end;
Pdf1.CreateDocument;
Pdf1.Active := True;
Pdf1.AddPage(0, 612, 792);
Pdf1.PageNumber := 1;
Pdf1.AddText('Hello World!', 'Arial', 12, 100, 700, clBlack, $FF, 0.0);
Pdf1.UpdatePage;
Pdf1.SaveAs('C:\NewDocument.pdf');
Pdf1.Active := False;
var
PageText: WString;
RectText: WString;
begin
Pdf1.PageNumber := 1;
PageText := Pdf1.Text;
Memo1.Text := PageText;
RectText := Pdf1.TextInRectangle(100, 100, 500, 200);
ShowMessage('Text in rectangle: ' + RectText);
end;
var
Position: Integer;
begin
Position := Pdf1.FindFirst('Hello', [seCaseSensitive]);
while Position >= 0 do
begin
ShowMessage('Found at character: ' + IntToStr(Position));
Position := Pdf1.FindNext;
end;
end;
Pdf1.PageNumber := 1;
Pdf1.AddText('Document Title', 'Times New Roman', 16, 100, 750, clBlue);
Pdf1.AddText('This is the document content.', 'Arial', 12, 100, 700, clBlack);
Pdf1.UpdatePage;
var
I: Integer;
MetricSize: Single;
FontBytes: TBytes;
begin
Pdf1.PageNumber := 1;
for I := 0 to Pdf1.ObjectCount - 1 do
if Pdf1.ObjectType[I] = otText then
begin
MetricSize := 12.0;
FontBytes := Pdf1.FontData[I];
Memo1.Lines.Add('Object ' + IntToStr(I));
Memo1.Lines.Add('Base name: ' + Pdf1.FontBaseName[I]);
Memo1.Lines.Add('Family: ' + Pdf1.FontFamilyName[I]);
Memo1.Lines.Add('Embedded: ' + BoolToStr(Pdf1.FontIsEmbedded[I], True));
Memo1.Lines.Add('Weight: ' + IntToStr(Pdf1.FontWeight[I]));
Memo1.Lines.Add('Italic angle: ' + IntToStr(Pdf1.FontItalicAngle[I]));
Memo1.Lines.Add('Ascent: ' + FloatToStr(Pdf1.FontAscent[I, MetricSize]));
Memo1.Lines.Add('Descent: ' + FloatToStr(Pdf1.FontDescent[I, MetricSize]));
Memo1.Lines.Add('Font data bytes: ' + IntToStr(Length(FontBytes)));
end;
if Pdf1.CharacterCount > 0 then
begin
Memo1.Lines.Add('First character size: ' + FloatToStr(Pdf1.CharacterFontSize[0]));
Memo1.Lines.Add('First character weight: ' + IntToStr(Pdf1.CharacterFontWeight[0]));
Memo1.Lines.Add('First character angle: ' + FloatToStr(Pdf1.CharacterAngle[0]));
end;
end;
The Demo\Delphi\Miscellaneous\FontProperties, Demo\CBuilder\Miscellaneous\FontProperties, and Demo\Lazarus\Miscellaneous\FontProperties projects show the same metadata through both TPdf and TPdfView
The Demo\Delphi\ContentExtractionLab, Demo\CBuilder\ContentExtractionLab, and Demo\Lazarus\ContentExtractionLab projects turn content extraction into a single workflow. They open or create a PDF, let users choose metadata, page text, page objects, images, attachments, links, annotations, bookmarks, and font / character metrics, then generate a summary plus a detailed TXT or JSON report. The lab also includes all / none category selection, report copy-to-clipboard, and source-PDF save actions so users can keep the generated sample PDF beside its extraction report. ExtractImages and Attachment provide payload export workflows, whilst Miscellaneous\ExtractText and Miscellaneous\FontProperties retain focused API examples; ExtractText supports page-range and page-separator options to produce per-page text output across Delphi, C++Builder, and Lazarus, while ContentExtractionLab is the cross-cutting inventory sample
The Demo\Delphi\TableExtractionLab, Demo\CBuilder\TableExtractionLab, and Demo\Lazarus\TableExtractionLab projects turn table detection into a single workflow. The lab builds a sample document containing a ruled table with merged header cells, a whitespace-aligned table without ruling, and a long table that breaks across a page boundary, then runs ExtractTables for the current page or ExtractDocumentTables for the whole document and reports rows, columns, RowSpan and ColumnSpan values, header flags, detection mode, confidence, and continuation groups. Options for DetectRuledTables, DetectWhitespaceTables, DetectContinuations, DetectFilledRulings, and MinConfidence are editable in the UI, and the selected table exports through TPdfTable.ToCsv and TPdfTable.ToJson. The lab also batches a whole folder: Batch folder runs ExtractDocumentTables over every PDF, reports per-document table counts, flags scans that have no text layer, and retries empty documents with a compact-column preset; Export all CSV writes every detected table beside the source files. Each document opens in its own TPdf instance so one unreadable file cannot affect the rest of the batch
var
I: Integer;
FieldInfo: TPdfFormFieldInfo;
begin
Pdf1.FormField[0] := 'John';
for I := 0 to Pdf1.FormFieldCount - 1 do
begin
FieldInfo := Pdf1.FormFieldInfo[I];
ShowMessage(FieldInfo.Name + ': ' + Pdf1.FormField[I]);
end;
end;
// Without flattening, FormField[i] := value writes only the /V entry —
// the visible appearance comes from the /AP stream and may not refresh
// GenerateFormAppearances regenerates /AP for every widget, then
// FlattenAllPages bakes every page's annotations and widgets into
// permanent, non-editable content
Pdf1.FileName := 'C:\Application.pdf';
Pdf1.Active := True;
Pdf1.FormField[0] := 'John Smith';
Pdf1.FormField[1] := '[email protected]';
Pdf1.GenerateFormAppearances;
if Pdf1.FlattenAllPages then
Pdf1.SaveAs('C:\Application.flat.pdf');
Pdf1.Active := False;
var
I: Integer;
Bitmap: TBitmap;
begin
for I := 0 to Pdf1.BitmapCount - 1 do
begin
Bitmap := Pdf1.Bitmap[I];
try
Bitmap.SaveToFile('C:\ExtractedImage' + IntToStr(I) + '.bmp');
finally
Bitmap.Free;
end;
end;
end;
var
JpegStream: TFileStream;
begin
JpegStream := TFileStream.Create('C:\MyImage.jpg', fmOpenRead or fmShareDenyWrite);
try
Pdf1.AddJpegImage(JpegStream, 100, 500, 200, 150);
finally
JpegStream.Free;
end;
Pdf1.AddPicture(Image1.Picture, 100, 300, 150, 100);
Pdf1.UpdatePage;
end;
var
I: Integer;
Annotation: TPdfAnnotation;
begin
for I := 0 to Pdf1.AnnotationCount - 1 do
begin
Annotation := Pdf1.Annotation[I];
ShowMessage('Annotation ' + IntToStr(I) + ': ' + Annotation.ContentsText);
end;
end;
Pdf1.CreatePath(100, 100, 200, 150, fmAlternate, clYellow, $FF, True, clBlack, $FF, 2.0);
Pdf1.AddPath;
Pdf1.CreatePath(100, 100, fmNone, clBlack, $FF, True, clRed, $FF, 3.0);
Pdf1.LineTo(200, 100);
Pdf1.LineTo(150, 200);
Pdf1.ClosePath;
Pdf1.AddPath;
Pdf1.UpdatePage;
var
I: Integer;
Bookmarks: TBookmarks;
begin
Bookmarks := Pdf1.Bookmarks;
for I := 0 to Length(Bookmarks) - 1 do
ShowMessage(Bookmarks[I].Title + ' -> Page ' + IntToStr(Bookmarks[I].PageNumber));
end;
var
I: Integer;
Destination: TDestination;
begin
for I := 0 to Pdf1.DestinationCount - 1 do
begin
Destination := Pdf1.Destination[I];
if Destination.Name = 'Chapter1' then
begin
Pdf1.PageNumber := Destination.PageNumber;
Break;
end;
end;
end;
var
I: Integer;
AttachmentData: TBytes;
AttachmentName: WString;
FileStream: TFileStream;
begin
for I := 0 to Pdf1.AttachmentCount - 1 do
begin
AttachmentName := Pdf1.AttachmentName[I];
AttachmentData := Pdf1.Attachment[I];
FileStream := TFileStream.Create('C:\Extracted_' + string(AttachmentName), fmCreate);
try
if Length(AttachmentData) > 0 then
FileStream.WriteBuffer(AttachmentData[0], Length(AttachmentData));
finally
FileStream.Free;
end;
end;
if Pdf1.CreateAttachment('MyFile.txt') then
Pdf1.Attachment[Pdf1.AttachmentCount - 1] := TFile.ReadAllBytes('C:\MyFile.txt');
end;
Pdf1.AddPage(Pdf1.PageCount + 1, 612, 792);
Pdf1.AddPage(Pdf1.PageCount + 1, 595, 842);
Pdf1.AddPage(Pdf1.PageCount + 1, 400, 600);
var
SourcePdf: TPdf;
begin
SourcePdf := TPdf.Create(nil);
try
SourcePdf.FileName := 'C:\SourceDocument.pdf';
SourcePdf.Active := True;
Pdf1.ImportPages(SourcePdf, '1', Pdf1.PageCount + 1);
finally
SourcePdf.Free;
end;
end;
if Pdf1.PageCount > 1 then
Pdf1.DeletePage(Pdf1.PageCount);
// Create a compressed PDF document
Pdf1.CreateDocument;
Pdf1.Compressed := True; // ensure all streams are FlateDecode compressed (default)
Pdf1.Active := True;
Pdf1.AddPage(0, 595, 842); // A4 size
Pdf1.AddText('Hello World!', 'Arial', 24, 100, 700, clBlack, $FF, 0.0);
Pdf1.SaveAs('C:\Compressed.pdf');
Pdf1.Active := False;
// Save with options
Pdf1.SaveAs('C:\Output.pdf', saIncremental); // incremental save
Pdf1.SaveAs('C:\NoSecurity.pdf', saRemoveSecurity); // remove security
// Save to a specific PDF version target. The PDF version validator rejects
// any feature introduced after the chosen target (JBIG2Decode, MarkInfo,
// AES encryption, Polygon / Caret / Watermark / Redact annotations, ...)
if not Pdf1.SaveAs('C:\Legacy15.pdf', saNone, pv15) then
ShowMessage('Save rejected: document contains features newer than PDF 1.5');
// SaveAsPdfA post-processes the base save with an incremental update that
// injects an XMP metadata stream, an sRGB ICC OutputIntent, and an updated
// document catalog so the result is PDF/A-1b conformant. The bundled
// sRGB IEC61966-2.1 profile is used automatically when no custom profile
// is supplied
Pdf1.FileName := 'C:\Report.pdf';
Pdf1.Active := True;
if Pdf1.SaveAsPdfA('C:\Report.pdfa.pdf', pac1b) then
ShowMessage('Saved PDF/A-1b archive');
// PDF/A with a custom ICC profile (CMYK printing pipelines for example)
var
Opts: TPdfASaveOptions;
begin
Opts := Default(TPdfASaveOptions);
Opts.Conformance := pac1b;
Opts.IccProfileData := TFile.ReadAllBytes('C:\Profiles\Coated_GRACoL_2006.icc');
Pdf1.SaveAsPdfA('C:\Report.cmyk.pdfa.pdf', Opts);
end;
// In-memory PDF/A output
var
Stream: TMemoryStream;
begin
Stream := TMemoryStream.Create;
try
Opts := Default(TPdfASaveOptions);
Opts.Conformance := pac1b;
if Pdf1.SaveAsPdfAToStream(Stream, Opts) then
UploadToArchive(Stream);
finally
Stream.Free;
end;
end;
var
Validation: TPdfAValidationResult;
Issue: TPdfAValidationIssue;
begin
Pdf1.FileName := 'C:\Incoming\report.pdf';
Pdf1.Active := True;
// Cheap header-style probe — reads the conformance without iterating
// the full validator. Result: pacNone, pac1a, pac1b, pac2b, pac3b, or
// pacUnknown
case Pdf1.PdfAConformance of
pac1b: Status.Caption := 'PDF/A-1b';
pac2b: Status.Caption := 'PDF/A-2b';
pacNone: Status.Caption := 'Not PDF/A';
else
Status.Caption := 'Unknown conformance';
end;
// Full validator with per-issue diagnostics
Validation := Pdf1.ValidatePdfA;
Memo1.Lines.Add('Conformance: ' + GetEnumName(TypeInfo(TPdfAConformance),
Ord(Validation.Conformance)));
for Issue in Validation.Issues do
Memo1.Lines.Add('- ' + Issue.Description);
end;
The Demo\Delphi\StandardsLab, Demo\CBuilder\StandardsLab, and Demo\Lazarus\StandardsLab projects load or create a PDF, run ValidatePdfA, ValidatePdfUa, ValidatePdfE, ValidatePdfX, ValidatePdfR, and ValidatePdfVT, then show each detected conformance level and issue count in one grid. Their save buttons call SaveAsPdfA, SaveAsPdfUa, SaveAsPdfE, SaveAsPdfX, SaveAsPdfR, and SaveAsPdfVT so the marker-output workflow is visible without writing a custom harness
The FPdfPreflightReport unit wraps ValidatePdfA, ValidatePdfUa, ValidatePdfE, ValidatePdfX, ValidatePdfR, and ValidatePdfVT into one queryable report object with Info / Warning / Error severity, status and issue aggregation, lookup, search, filtering, and stable SHA-256 fingerprints. Reports can be emitted as text, HTML, Markdown, JSON, CSV, SARIF 2.1.0, JUnit XML, NDJSON, or native XML. Demo\Delphi\PreflightReport, Demo\Lazarus\PreflightReport, and Demo\CBuilder\PreflightReport provide interactive selection and preview workflows, while Demo\Delphi\PreflightReportCli adds scriptable input / output arguments, standard filtering, optional report attachment embedding, CI exit-code gating, batch list and directory processing, collision-safe routing, aggregate summaries, and CSV manifests. The generated report states that the built-in validators cover marker-level and selected file-level checks, so full content-level preflight can still be delegated to a dedicated validation engine
// LoadCustomDocument lets PDFium pull blocks from any seekable TStream on
// demand. The stream is NOT copied into memory, so multi-GB PDFs, remote
// HTTP body streams, and database-backed sources are all viable without
// an up-front buffer
var
FileStream: TFileStream;
begin
// Read-from-file streaming (no in-memory copy)
FileStream := TFileStream.Create('C:\Huge.pdf',
fmOpenRead or fmShareDenyWrite);
Pdf1.LoadCustomDocument(FileStream, True{AOwnsStream});
// ^^ ownership transferred — Pdf1 frees FileStream on UnloadDocument
Pdf1.Active := True;
ShowMessage('Pages: ' + IntToStr(Pdf1.PageCount));
end;
// Externally-owned stream (caller frees it after UnloadDocument)
var
Stream: TFileStream;
begin
Stream := TFileStream.Create('C:\Live.pdf',
fmOpenRead or fmShareDenyNone);
try
Pdf1.LoadCustomDocument(Stream, False);
Pdf1.Active := True;
DoWork;
Pdf1.Active := False;
finally
Stream.Free;
end;
end;
// FPdfAsync exposes IPdfCancellationToken /
// IPdfCancellationTokenSource for cancel-from-UI semantics that work
// across the long-running export and render paths
uses FPdfAsync;
var
CancelSource: IPdfCancellationTokenSource;
CancelToken: IPdfCancellationToken;
begin
CancelSource := PdfCancellationTokenSource;
CancelToken := CancelSource.Token;
// user clicks Cancel
CancelSource.Cancel;
// The worker periodically checks CancelToken.IsCancelled, or the render
// path polls it through PDFium's IFSDK_PAUSE callback
if CancelToken.IsCancelled then
Memo1.Lines.Add('Cancelled by user');
end;
// RenderPageProgressive lets a long high-DPI render abort mid-page when
// the user clicks Cancel, instead of blocking the caller until done
var
Status: TPdfProgressiveStatus;
Bitmap: TBitmap;
begin
Bitmap := TBitmap.Create;
try
Bitmap.PixelFormat := pf32bit;
Bitmap.SetSize(2480, 3508); // A4 @ 300 DPI
Status := Pdf1.RenderPageProgressive(Bitmap, 0, 0, Bitmap.Width,
Bitmap.Height, CancelToken, ro0, [reAnnotations], clWhite);
case Status of
prsDone: SaveBitmap(Bitmap);
prsCancelled: Memo1.Lines.Add('Render cancelled — partial result available');
prsFailed: Memo1.Lines.Add('PDFium reported FPDF_RENDER_FAILED');
end;
finally
Bitmap.Free;
end;
end;
// TPdfFuture<T> spins up a worker on a background thread and posts a
// TPdfFutureResult<T> envelope back to the main thread
//
// Important: TPdf serializes render calls per instance, but the worker still
// owns dispatch and document lifetime. Do not mutate a TPdf while a render is
// in flight; batch export usually keeps one TPdf per file
type
TRenderResult = record
Bitmap : TBitmap;
PageNo : Integer;
end;
var
Future: TPdfFuture<TRenderResult>;
begin
Future := TPdfFuture<TRenderResult>.Create(
function (const ACancelToken: IPdfCancellationToken): TRenderResult
var
LocalPdf: TPdf;
begin
LocalPdf := TPdf.Create(nil);
try
LocalPdf.FileName := 'C:\Huge.pdf';
LocalPdf.Active := True;
LocalPdf.PageNumber := 1;
Result.Bitmap := LocalPdf.RenderPage(0, 0, 2480, 3508);
Result.PageNo := 1;
finally
LocalPdf.Free;
end;
end,
procedure (const AResult: TPdfFutureResult<TRenderResult>)
begin
case AResult.State of
pfsSuccess: PaintBitmapToView(AResult.Value.Bitmap);
pfsCancelled: Log.Add('Cancelled');
pfsFailed: Log.Add('Worker failed: ' + AResult.ErrorMessage);
end;
end);
end;
// FPdfMatrix wraps PDFium's FS_MATRIX so callers can build a transform
// declaratively and hand it to FPDFPageObj_SetMatrix
uses FPdfMatrix, FPdfView;
var
M: TPdfMatrix;
RawMatrix: FS_MATRIX;
PageObj: FPDF_PAGEOBJECT;
begin
M := TPdfMatrix.Create;
try
M.Translate(72, 200); // move 1in right, ~2.8in up
M.Scale(0.5, 0.5); // half-size stamp
M.Rotate(15); // 15-degree CCW rotation
// M.HorizontalFlip;
// M.VerticalFlip;
// M.CentralFlip;
// M.Skew(10, 5);
// M.Multiply(Other); // post-multiply with another matrix
// Apply to any PDFium page object (text / path / image / form XObject)
RawMatrix := M.Handle;
FPDFPageObj_SetMatrix(PageObj, RawMatrix);
finally
M.Free;
end;
end;
// ImportPagesByIndex copies an explicit zero-based index array from a
// source PDF into this document. InsertAt = 0 inserts before page 1;
// PageCount appends
var
Source: TPdf;
begin
Source := TPdf.Create(nil);
try
Source.FileName := 'C:\Report.pdf';
Source.Active := True;
// Import pages 0, 2, 4 (one-based: pages 1, 3, 5) from Source
// and append them at the end of Pdf1
Pdf1.ImportPagesByIndex(Source, [0, 2, 4], Pdf1.PageCount);
// Empty array imports every source page
// Pdf1.ImportPagesByIndex(Source, [], 0);
finally
Source.Free;
end;
end;
// ImportNPagesToOne returns a brand-new TPdf whose pages are NumX*NumY
// composites of THIS document. OutputWidth/Height are PDF user units
// (1 unit = 1/72 in)
var
Composite: TPdf;
begin
Pdf1.FileName := 'C:\Slides.pdf';
Pdf1.Active := True;
// 4-up A4 landscape: 842 x 595 pt, 2 columns x 2 rows
Composite := Pdf1.ImportNPagesToOne(842, 595, 2, 2);
try
if Composite <> nil then
Composite.SaveAs('C:\Slides_4up.pdf');
finally
Composite.Free;
end;
end;
// MovePages takes the zero-based indices of the pages to move plus the
// destination index for the first moved page after the move completes
// Other pages shift around the moved block so the document length stays
// the same
//
// Example: in a 5-page document with pages numbered [0,1,2,3,4],
// MovePages([2, 3], 0) reorders to [2, 3, 0, 1, 4]
if not Pdf1.MovePages([2, 3], 0) then
ShowMessage('Invalid indices passed to MovePages');
// CreateXObjectFromPage manufactures a reusable Form XObject handle from
// any page of any other TPdf. Stamp that handle onto as many pages of
// THIS document as you want — each stamp is a single PDFium page object
// that can be positioned, scaled, and rotated through the matrix API
uses FPdfMatrix, FPdfView;
var
WatermarkSource: TPdf;
XObj: TPdfXObject;
PageObj: FPDF_PAGEOBJECT;
M: TPdfMatrix;
RawMatrix: FS_MATRIX;
I: Integer;
begin
WatermarkSource := TPdf.Create(nil);
try
WatermarkSource.FileName := 'C:\Watermarks\Confidential.pdf';
WatermarkSource.Active := True;
// Wrap page 0 of WatermarkSource as a reusable XObject
XObj := Pdf1.CreateXObjectFromPage(WatermarkSource, 0);
try
for I := 1 to Pdf1.PageCount do
begin
Pdf1.PageNumber := I;
PageObj := Pdf1.InsertFormObjectFromXObject(XObj);
if PageObj = nil then Continue;
// Centre the watermark on the page
M := TPdfMatrix.Create;
try
M.Scale(0.5, 0.5);
M.Translate(0.25 * Pdf1.PageWidth, 0.5 * Pdf1.PageHeight);
RawMatrix := M.Handle;
FPDFPageObj_SetMatrix(PageObj, RawMatrix);
finally
M.Free;
end;
Pdf1.UpdatePage;
end;
finally
XObj.Free; // closes the FPDF_XOBJECT handle
end;
Pdf1.SaveAs('C:\Watermarked.pdf');
finally
WatermarkSource.Free;
end;
end;
The Demo\Delphi\WatermarkStamp, Demo\Lazarus\WatermarkStamp, and Demo\CBuilder\WatermarkStamp projects turn this pattern into a runnable workflow: they create a target PDF and a reusable stamp PDF, wrap the stamp page with CreateXObjectFromPage, insert the same Form XObject on every target page, apply TPdfMatrix transforms for centred watermarks and top-right stamps, and add optional page number labels before saving the result
// AddImage(FileName) accepts any format registered with the VCL or LCL
// graphics units (BMP, PNG via PngImage, JPG, ...)
Pdf1.AddImage('C:\Photo.png', 100, 500, 200, 150);
// AddImage(TBitmap) skips the TPicture intermediary, ideal when the
// bitmap already comes from rendering or batch generation work
var
Bitmap: TBitmap;
begin
Bitmap := Pdf1.RenderPage(0, 0, 595, 842);
try
Pdf1.PageNumber := 2;
Pdf1.AddImage(Bitmap, 50, 50, 250, 350);
Pdf1.UpdatePage;
finally
Bitmap.Free;
end;
end;
The Demo\Delphi\ImageToPDF, Demo\CBuilder\ImageToPDF, and Demo\Lazarus\ImageToPDF projects turn this into a full batch workflow: select multiple image files, preview the selected item, create one PDF page per image, scale each image into an A4 portrait or landscape page, save the result, and open the generated PDF
var
MatchCount: Integer;
begin
// Paint a clYellow mask over every match on the view's current page
// Switching to a different page clears the highlight automatically
PdfView1.HighlightColor := clYellow;
MatchCount := PdfView1.HighlightSearchText('invoice', False{Case}, True{Word});
Status.Caption := Format('%d match(es) on this page', [MatchCount]);
// Programmatically remove the overlay before the page changes
PdfView1.ClearHighlight;
end;
// FitMode keeps long documents framed during Resize or page changes
// Setting Zoom directly cancels FitMode (it reverts to pfmNone)
PdfView1.FitMode := pfmFitPage; // whole page in viewport
// PdfView1.FitMode := pfmFitWidth; // page width matches viewport width
// PdfView1.FitMode := pfmActualSize; // 100%
// PageColor decouples the rendered PDF page from the host control's Color
// — typical dark-mode viewer setup keeps the scroll area dark while the
// PDF page stays paper-white
PdfView1.Color := clBlack; // scroll area
PdfView1.PageColor := clWhite; // PDF page background
// Optional drop shadow and page border to lift the page off a dark bg
PdfView1.PageShadowSize := 6;
PdfView1.PageShadowColor := $00404040;
PdfView1.PageBorderColor := clGray;
The Demo\Delphi\Miscellaneous\ViewerInteractionLab, Demo\CBuilder\Miscellaneous\ViewerInteractionLab, and Demo\Lazarus\Miscellaneous\ViewerInteractionLab projects keep the modern TPdfView interaction surface in one compact form: open or create a sample PDF, switch DisplayMode, apply FitMode, change PageColor, rotate pages, highlight current-page search hits with HighlightSearchText, enable or disable user text selection, inspect SelectedText, call SelectAll, CopySelectionToClipboard, and ClearSelection, and watch DeviceToPage coordinates update in the status bar while the mouse moves over the page
The Demo\Delphi\SearchAndSelect, Demo\CBuilder\SearchAndSelect, and Demo\Lazarus\SearchAndSelect projects turn the search-and-selection viewer workflow into standalone samples. They open or create a PDF, list all-page search results with page number, character index, and preview text, support Previous / Next navigation and double-click result jumping, highlight current-page matches with HighlightSearchText, expose match-case and whole-word options, toggle AllowUserTextSelection, preview SelectedText, drive SelectAll, CopySelectionToClipboard, and ClearSelection from buttons, and let users switch between single and spread display modes
The Demo\Delphi\Miscellaneous\PrintPreferences, Demo\CBuilder\Miscellaneous\PrintPreferences, and Demo\Lazarus\Miscellaneous\PrintPreferences projects read author print preferences before printing. They display PrintCopies, PrintPageRanges, PrintScaling, and PrintPaperHandling, can copy author-provided copies and ranges into the print inputs, and show how SetPdfPrintPaperHandlingDevMode maps PDF duplex intent into a Windows DEVMODE
The Demo\Delphi\PrintPDF, Demo\CBuilder\PrintPDF, and Demo\Lazarus\PrintPDF projects show the standard print workflow: open a PDF, preview pages, choose page ranges and copies in the platform print dialogue, honour collate mode, render each page through TPdf.RenderPage, and expose print progress with cancellation. The Delphi sample also accepts dropped PDF files for quick loading
The Demo\Delphi\MultiPageViewer, Demo\CBuilder\MultiPageViewer, and Demo\Lazarus\MultiPageViewer projects exercise continuous, spread, and cover-aware viewing with regular page navigation and zoom workflows. They are also useful for testing open-to-first-page speed and rendering responsiveness on very large PDFs: in large-file tests, 2.x GB documents that can make Acrobat or Foxit spend 20-30 seconds before the first view can become usable almost immediately through the PDFium Delphi Component viewer path
The Demo\Delphi\SplitView, Demo\CBuilder\SplitView, and Demo\Lazarus\SplitView projects show side-by-side PDF review. The C++Builder and Lazarus samples focus on two- or three-pane comparison, active-view selection, optional synchronised navigation commands, shared zoom presets, and rotation for the selected view or all loaded views. Use these samples to compare large documents while keeping the first usable view and subsequent page rendering responsive; the same 2.x GB PDF files that may take Acrobat or Foxit 20-30 seconds to open can become usable almost immediately in the PDFium Delphi Component viewer path
The Demo\Delphi\Miscellaneous\SecurityAudit, Demo\CBuilder\Miscellaneous\SecurityAudit and Demo\Lazarus\Miscellaneous\SecurityAudit projects inspect PDFs read-only, listing permissions, embedded attachments, document JavaScript actions, URI/Launch link annotations, web links, signature summaries, XFA status, V8 helper availability and unsupported-feature callbacks. Summary and finding rows can be saved or copied as a TXT audit report, or saved as structured JSON. These samples provide risk panels for host applications rather than malware detection
// LinkOptions controls automatic handling of the four PDF link action
// types. Defaults enable goto and URI only; launch (run a program) and
// embedded GotoR (jump to another file) stay OFF so a stray click cannot
// run arbitrary code
PdfView1.LinkOptions :=
[loAutoGoto, loAutoOpenURI]; // safe defaults
// OnAnnotationLinkClick fires for in-document destinations (page jumps,
// named destinations, action chains). Mark Handled = True to prevent the
// view from auto-following the link
procedure TForm1.PdfView1AnnotationLinkClick(Sender: TObject;
LinkIndex: Integer; const Action: TPdfAction;
var Handled: Boolean);
begin
if Action.Kind = paUri then
begin
if MessageDlg('Open ' + Action.Uri + '?', mtConfirmation, [mbYes, mbNo], 0)
<> mrYes then
Handled := True; // suppress auto-handling
end;
end;
// OnWebLinkClick fires for URI strings discovered by PDFium's web-link
// scanner (auto-detected URLs in the text stream, even without an
// /A <</S/URI...>> annotation)
procedure TForm1.PdfView1WebLinkClick(Sender: TObject;
WebLinkIndex: Integer; const Url: WString;
var Handled: Boolean);
begin
Log.Add('Web link clicked: ' + string(Url));
end;
// On the focused AcroForm widget, six new methods drive PDFium's form
// edit history. All six short-circuit safely if no widget is focused or
// the document carries no AcroForm
procedure TForm1.btnSelectAllClick(Sender: TObject);
begin
PdfView1.SelectAllFormText;
EditMenuCopy.Caption := 'Copy: ' + string(PdfView1.GetSelectedFormText);
end;
procedure TForm1.btnUndoClick(Sender: TObject);
begin
btnUndo.Enabled := PdfView1.FormCanUndo;
btnRedo.Enabled := PdfView1.FormCanRedo;
if PdfView1.FormCanUndo then
PdfView1.FormUndo;
end;
procedure TForm1.btnRedoClick(Sender: TObject);
begin
if PdfView1.FormCanRedo then
PdfView1.FormRedo;
end;
// All three default to True so existing apps upgrade with no behaviour
// change. Flip them False for kiosks, preview panes, or read-only
// embeddings without subclassing TPdfView
PdfView1.AllowUserPageChange := False; // PgUp/PgDn/Ctrl+Home/End
PdfView1.ChangePageOnMouseScrolling := False; // wheel up/down jumps
PdfView1.AllowUserTextSelection := False; // mouse/keyboard text selection
// Ctrl+wheel is reserved for zoom and is skipped by the navigation
// handler regardless of ChangePageOnMouseScrolling.
// TPdfThumbnailView (unit FPdfThumbnail) renders one page thumbnail per
// row in a scrollable side panel
uses FPdfThumbnail;
procedure TForm1.FormCreate(Sender: TObject);
begin
ThumbView.Pdf := Pdf1;
ThumbView.ThumbnailWidth := 120;
ThumbView.ThumbnailHeight := 160;
ThumbView.SelectionColor := $00CFA85F; // warm highlight
ThumbView.OnPageClick := ThumbViewPageClick;
end;
procedure TForm1.ThumbViewPageClick(Sender: TObject; PageIndex: Integer);
begin
PdfView1.PageNumber := PageIndex + 1; // PageIndex is zero-based
end;
procedure TForm1.PdfView1PageNumberChanged(Sender: TObject);
begin
ThumbView.CurrentPageIndex := PdfView1.PageNumber - 1;
end;
// Pattern used by Demo\Delphi\BatchExport, Demo\CBuilder\BatchExport,
// and Demo\Lazarus\BatchExport. One TPdf per file, IPdfCancellationToken
// for cancel-from-UI, TPdfStreamAdapter for buffered file load
uses FPdfAsync;
procedure ExportBatch(const Files: TArray<string>;
const OutDir: string; DPI: Integer; Quality: Integer;
const ACancelToken: IPdfCancellationToken);
var
I, J: Integer;
Job: TPdf;
Bmp: TBitmap;
Jpeg: TJPEGImage;
begin
for I := 0 to High(Files) do
begin
if ACancelToken.IsCancelled then Break;
OverallProgress.Position := I;
Job := TPdf.Create(nil);
try
Job.FileName := Files[I];
Job.Active := True;
PageProgress.Max := Job.PageCount;
for J := 1 to Job.PageCount do
begin
if ACancelToken.IsCancelled then Break;
PageProgress.Position := J;
Job.PageNumber := J;
Bmp := Job.RenderPage(
0, 0,
Round(Job.PageWidth * DPI / 72),
Round(Job.PageHeight * DPI / 72));
try
Jpeg := TJPEGImage.Create;
try
Jpeg.CompressionQuality := Quality;
Jpeg.Assign(Bmp);
Jpeg.SaveToFile(Format('%s\%s.p%.3d.jpg',
[OutDir, ExtractFileName(Files[I]), J]));
finally
Jpeg.Free;
end;
finally
Bmp.Free;
end;
end;
finally
Job.Free;
end;
end;
end;
procedure FitCurrentPage;
begin
PdfView1.Zoom := PdfView1.PageZoom[PdfView1.PageNumber];
end;
procedure InspectViewerPoint(X, Y: Integer);
var
PageNo: Integer;
PageX, PageY: Double;
CharIndex: Integer;
Ch: WString;
begin
PageNo := PdfView1.PageNumber;
if not PdfView1.DeviceToPage(X, Y, PageNo, PageX, PageY) then
Exit;
CharIndex := PdfView1.CharacterIndexAtPos(X, Y, 8, 8);
if CharIndex >= 0 then
begin
Ch := PdfView1.Text(CharIndex, 1);
ShowMessage(Format('Page %d, X %.2f, Y %.2f, character %s',
[PageNo, PageX, PageY, string(Ch)]));
end;
end;
function LoadPdfSafely(const FileName: string): Boolean;
begin
Result := False;
if not FileExists(FileName) then
Exit;
try
Pdf1.FileName := FileName;
Pdf1.Active := True;
Result := Pdf1.Active;
except
Result := False;
end;
end;
function ExtractTextSafely(PageNo: Integer): WString;
begin
Result := '';
if not Pdf1.Active then
Exit;
if (PageNo < 1) or (PageNo > Pdf1.PageCount) then
Exit;
Pdf1.PageNumber := PageNo;
Result := Pdf1.Text;
end;
// IsTagged is a cheap catalog probe — True when /StructTreeRoot exists
// StructureElements materialises the tree into a flat array of
// TPdfStructureElement records with type, title, alternate text, actual
// text, expansion text, language, level, parent index, and child / marked
// content / attribute counts
var
Elements: TPdfStructureElements;
I: Integer;
begin
if not Pdf1.IsTagged then
begin
Log.Add('Document is NOT tagged — accessibility tools may struggle.');
Exit;
end;
if Pdf1.Language = '' then
Log.Add('Tagged PDF without /Lang — fails PDF/UA.');
Elements := Pdf1.StructureElements;
for I := 0 to High(Elements) do
Log.Add(StringOfChar(' ', Elements[I].Level * 2)
+ Elements[I].StructType + ' "' + Elements[I].Title + '"');
end;
// SetPdfPrintMode is a global function that maps to PDFium's
// FPDF_SetPrintMode. Use the matching TPdfPrintMode value for the
// printer driver in use
SetPdfPrintMode(pmEmf); // GDI EMF (default)
// SetPdfPrintMode(pmTextOnly); // text-only rendering
// SetPdfPrintMode(pmPostScript2); // PostScript Level 2
// SetPdfPrintMode(pmPostScript3); // PostScript Level 3
// SetPdfPrintMode(pmPostScript2PassThrough);
// SetPdfPrintMode(pmPostScript3PassThrough);
// SetPdfPrintMode(pmEmfImageMasks); // EMF + image masks
// SetPdfPrintMode(pmPostScript3Type42); // PostScript 3 + Type 42 fonts
// PrintCopies, PrintPageRanges, PrintScaling, and PrintPaperHandling are
// the viewer-preference dictionary entries — they encode what the
// document author wanted PDF readers to default to
var
Ranges: TPrintPageRanges;
Range: TPrintPageRange;
begin
ShowMessage('Suggested copies: ' + IntToStr(Pdf1.PrintCopies));
ShowMessage('Print scaling: ' + GetEnumName(TypeInfo(TPrintScaling),
Ord(Pdf1.PrintScaling)));
Ranges := Pdf1.PrintPageRanges; // one-based ranges
for Range in Ranges do
Log.Add(Format('Pages %d..%d', [Range.First, Range.Last]));
if SetPdfPrintPaperHandlingDevMode(PrinterDevMode, Pdf1.PrintPaperHandling) then
ApplyPrinterDevMode(PrinterDevMode);
end;
// Document-level JavaScript is stored in the /Names /JavaScript name
// tree. JavaScriptAction[i] returns one entry; JavaScriptActions returns
// the full array
var
I: Integer;
Action: TPdfJavaScriptAction;
begin
for I := 0 to Pdf1.JavaScriptActionCount - 1 do
begin
Action := Pdf1.JavaScriptAction[I];
Memo1.Lines.Add('Name: ' + string(Action.Name));
Memo1.Lines.Add('Script: ' + string(Action.Script));
Memo1.Lines.Add('---');
end;
end;
procedure ProcessMultiplePages;
var
I: Integer;
PageText: WString;
begin
for I := 1 to Pdf1.PageCount do
begin
Pdf1.PageNumber := I;
PageText := Pdf1.Text;
ProcessPageText(PageText);
end;
end;
procedure SetOptimalRenderOptions;
begin
PdfView1.Options := [reAnnotations, reLcd];
end;
// Zero-copy render path (default since v1.21.0): RenderPage writes
// directly into the destination TBitmap's DIB buffer when callers pass a
// pre-allocated bitmap, avoiding one width * height * 4 bytes copy
procedure RenderIntoCallerBitmap(Bitmap: TBitmap);
begin
Bitmap.PixelFormat := pf32bit;
Bitmap.SetSize(2480, 3508); // A4 @ 300 DPI
Pdf1.RenderPage(Bitmap, 0, 0, Bitmap.Width, Bitmap.Height);
end;