A new PDFium project in Delphi often stalls before a single page appears: Active := True leaves the document closed because pdfium.dll is missing or built for the other bitness. Put the Win32 or Win64 pdfium.dll that matches your executable where PDFium Component searches, and TPdf and TPdfView open, render and display PDFs the same way in Delphi and Lazarus
Where does PDFium Component look for pdfium.dll?
PDFium Component binds pdfium.dll lazily, on the first document load, and checks a fixed list of places before handing the bare file name to Windows. The order comes straight from the loader in PDFium.pas, and it turns most "module not found" reports into a quick fix:
- If the global
LibraryNamevariable contains a path, that exact file is loaded and nothing else is tried, so a wrong path stays a visible error instead of silently picking up some other copy DLLs\Win32\pdfium.dllorDLLs\Win64\pdfium.dllunder the executable's folder, then the sameDLLssubfolder under each parent folder, up to eight levels up- A
Win32orWin64subfolder directly beside the executable pdfium.dllin the executable's own folder- The system folder for the process bitness:
System32for a 64-bit process,SysWOW64and thenSystem32for a 32-bit one - Failing all of that, the plain file name, resolved by the standard Windows DLL search order
The installer places prebuilt binaries compiled from the open-source PDFium project in DLLs\Win32 and DLLs\Win64. Each folder holds the plain pdfium.dll next to pdfium.v8.dll, the build that carries the V8 engine for XFA forms and form JavaScript and is selected by the global EnableV8Engine flag. Bitness follows the process, not Windows: a Win32 executable on 64-bit Windows still needs the 32-bit DLL. Get it wrong and Windows reports ERROR_BAD_EXE_FORMAT (193), to which the component appends a note that the DLL was found but its architecture does not match the host process. The file is there. Only its bitness is wrong
The upward walk in step 2 explains why the bundled demos start without complaint while a fresh project under C:\Projects does not: the demo executables sit inside the installation folder, below the shipped DLLs tree, and the new project has no such ancestor. Give each project its own copy. Running DLLs\CopyDLLs.bat <YourProject>\DLLs creates Win32 and Win64 subfolders there, and an executable built into <YourProject>\Win32\Debug finds them by walking up. Started without arguments, the same script offers to copy into System32 and SysWOW64 instead; avoid that on customer machines, because a system folder holds a single pdfium.dll for every program on the computer, so the plain build, the V8 build and differing versions overwrite one another. Missing exports, version skew and threaded loading are covered in deploying the PDFium DLL and diagnosing load failures
Render a PDF page to PNG in Delphi
The smallest useful TPdf program opens a file, reads the page count and turns one page into an image. TPdf is a non-visual TComponent, so the function below needs no form and runs from a button handler, a service or a command-line tool alike. It reports load failures with the real reason, and the notes after the code explain the lines that most often trip people up:
uses
System.SysUtils, Vcl.Graphics, Vcl.Imaging.pngimage, PDFium;
// Renders page PageNo (1-based) of PdfFile to PngFile at Dpi and
// returns the document's page count
function ExportPageAsPng(const PdfFile, PngFile: string;
PageNo, Dpi: Integer): Integer;
var
Pdf: TPdf;
Bmp: TBitmap;
Png: TPngImage;
W, H: Integer;
begin
Pdf := TPdf.Create(nil);
try
Pdf.FileName := PdfFile;
Pdf.Active := True; // never raises, so check the result
if not Pdf.Active then
raise Exception.CreateFmt('Cannot open %s: %s',
[PdfFile, Pdf.LastLoadReport.ErrorMessage]);
Result := Pdf.PageCount;
Pdf.PageNumber := PageNo; // default 0 means no page is loaded
W := Round(PointsToPixels(Pdf.PageWidth, Dpi)); // 1 point = 1/72 inch
H := Round(PointsToPixels(Pdf.PageHeight, Dpi));
Bmp := Pdf.RenderPage(0, 0, W, H, ro0, [reAnnotations], clWhite);
try
Png := TPngImage.Create;
try
Png.Assign(Bmp);
Png.SaveToFile(PngFile);
finally
Png.Free;
end;
finally
Bmp.Free; // the function form returns a new bitmap
end;
finally
Pdf.Free;
end;
end;
Pdf.Active := Truenever raises. The setter catches every load error and leavesActiveatFalse, so atry/exceptaround it stays quiet and the next line fails instead, withPageCountreporting "Cannot perform this operation on an inactive Pdf1 component" for a component dropped on a form asPdf1;LastLoadReport.ErrorMessageholds the actual causeTPdf.PageNumberstarts at 0, which means no page is loaded, so callingRenderPagestraight after opening raises "Cannot perform this operation on an inactive page". Valid values run from 1 toPageCount, and anything else raises "Incorrect page number" right awayPageWidthandPageHeightare in PDF points of 1/72 inch, andPointsToPixelsconverts them for the target resolution: an A4 page (595.28 by 841.89 points) at 150 DPI becomes a 1240 by 1754 pixel bitmap- An empty options set leaves out annotation appearances, so highlights, stamps and sticky-note icons vanish from the export.
reAnnotationsmaps to PDFium'sFPDF_ANNOTflag and is the option worth turning on for any image a person will look at - The function form of
RenderPageallocates theTBitmapand the caller frees it, while the overload that takes aTBitmapparameter renders into one you already own. JPEG output, quality settings and whole-document batches are covered in rendering PDF pages to JPEG with PDFium Component
Build a Delphi PDF viewer form with TPdfView
A working Delphi PDF viewer needs one TPdf, one TPdfView linked to it, and a few commands on top. The form below creates everything in code, so it pastes into a new VCL Forms Application without touching the designer; assign FormCreate to the form's OnCreate event and it runs. Dropping both components from the losLab palette works just as well, with the same property assignments made in the Object Inspector:
unit ViewerMain;
interface
uses
System.SysUtils, System.Classes, Vcl.Controls, Vcl.Forms, Vcl.StdCtrls,
Vcl.ExtCtrls, Vcl.Dialogs, PDFium, FPdfView;
type
TFormViewer = class(TForm)
procedure FormCreate(Sender: TObject);
private
FPdf: TPdf;
FView: TPdfView;
FBar: TPanel;
procedure AddButton(const ACaption: string; AOnClick: TNotifyEvent);
procedure OpenClick(Sender: TObject);
procedure FitWidthClick(Sender: TObject);
procedure ZoomInClick(Sender: TObject);
procedure ZoomOutClick(Sender: TObject);
procedure ViewPageChange(Sender: TObject);
procedure OpenPdf(const FileName: string);
end;
var
FormViewer: TFormViewer;
implementation
{$R *.dfm}
procedure TFormViewer.FormCreate(Sender: TObject);
begin
FBar := TPanel.Create(Self);
FBar.Parent := Self;
FBar.Align := alTop;
AddButton('Open...', OpenClick);
AddButton('Fit width', FitWidthClick);
AddButton('Zoom +', ZoomInClick);
AddButton('Zoom -', ZoomOutClick);
FPdf := TPdf.Create(Self);
FView := TPdfView.Create(Self);
FView.Parent := Self;
FView.Align := alClient;
FView.Pdf := FPdf; // allowed only while the view is inactive
FView.OnPageChange := ViewPageChange;
end;
procedure TFormViewer.AddButton(const ACaption: string; AOnClick: TNotifyEvent);
var
Button: TButton;
begin
Button := TButton.Create(Self);
Button.Parent := FBar;
Button.Left := FBar.ControlCount * 100; // keep creation order left to right
Button.Align := alLeft;
Button.Caption := ACaption;
Button.OnClick := AOnClick;
end;
procedure TFormViewer.OpenPdf(const FileName: string);
var
Password: string;
begin
FView.Active := False;
FPdf.Active := False;
FPdf.FileName := FileName;
FPdf.Password := '';
FView.PageNumber := 1;
FView.Active := True; // opens FPdf as well and never raises
if (not FView.Active) and
(FPdf.LastLoadReport.NativeErrorCode = FPDF_ERR_PASSWORD) and
InputQuery('Protected PDF', 'Open password:', Password) then
begin
FPdf.Password := Password; // must be set while FPdf is inactive
FView.Active := True;
end;
if not FView.Active then
raise Exception.CreateFmt('Cannot open %s: %s',
[FileName, FPdf.LastLoadReport.ErrorMessage]);
FView.FitMode := pfmFitWidth;
FView.SetFocus; // PgUp, PgDn, Ctrl+Home, Ctrl+End
end;
procedure TFormViewer.OpenClick(Sender: TObject);
var
Dialog: TOpenDialog;
begin
Dialog := TOpenDialog.Create(nil);
try
Dialog.Filter := 'PDF files (*.pdf)|*.pdf';
if Dialog.Execute then
OpenPdf(Dialog.FileName);
finally
Dialog.Free;
end;
end;
procedure TFormViewer.FitWidthClick(Sender: TObject);
begin
FView.FitMode := pfmFitWidth; // re-applied on resize and page change
end;
procedure TFormViewer.ZoomInClick(Sender: TObject);
begin
if FView.Active then
FView.Zoom := FView.Zoom * 1.25; // 1.0 = 100%; resets FitMode to pfmNone
end;
procedure TFormViewer.ZoomOutClick(Sender: TObject);
begin
if FView.Active then
FView.Zoom := FView.Zoom / 1.25;
end;
procedure TFormViewer.ViewPageChange(Sender: TObject);
begin
Caption := Format('Page %d of %d', [FView.PageNumber, FPdf.PageCount]);
end;
end.
Activation goes through the view. TPdfView has its own Active property and paints a page only once that property is True; setting it opens the linked TPdf too, so OpenPdf activates the view, and code that opens only the document is left with an empty control. The Pdf link can be assigned only while the view is inactive, which is why the form sets it once in FormCreate and closes the view before switching files. OnPageChange fires each time the view loads a page, including the first page at activation, so the caption always shows the current position
Password handling hinges on one field. After a failed activation, LastLoadReport.NativeErrorCode equals FPDF_ERR_PASSWORD from the FPdfView unit only when the file is encrypted with an open password the component did not receive. That lets the viewer prompt on a protected file and report a damaged one straight away, rather than asking for a password that does not exist. Password must be assigned while the document is closed, because its setter refuses changes to an open TPdf
Navigation and zoom live on the view. TPdf.PageNumber and TPdfView.PageNumber are independent properties, a viewer drives the view's, and out-of-range values assigned to it are ignored. With the view focused, Page Up and Page Down step through pages and Ctrl+Home and Ctrl+End jump to the ends, governed by AllowUserPageChange, which defaults to True. Zoom is a scale factor, so 1.5 means 150%. Assigning it cancels the active fit mode, the convention desktop readers follow when you type a zoom value, while an active FitMode re-applies itself whenever the window is resized or the page changes
Lazarus and Free Pascal: package, mode and bitness
On Lazarus the components and their API are the same, and the work shifts to package setup and project options. Tick Lazarus on the installer's optional-content page, open Lib\FPC\PDFiumLaz.lpk in the IDE, compile it, then install it and let Lazarus rebuild itself; TPdf, TPdfView and TPdfThumbnailView then appear on a losLab palette page, exactly as in Delphi. The package depends on LCL and IDEIntf, compiles its units into Lib\FPC\i386-win32 or Lib\FPC\x86_64-win64 depending on the target, and on Windows uses the same DLLs\Win32 and DLLs\Win64 search as Delphi. The project file below adds a habit that pays off in both IDEs, pinning the engine and loading it at startup so a deployment mistake surfaces as a clear message before the user opens anything, and the remaining differences follow it:
program PdfViewerLaz;
{$MODE Delphi}
uses
Interfaces, Forms, Dialogs, SysUtils, PDFium, ViewerMain;
{$R *.res}
begin
Application.Initialize;
// Optional: pin an explicit engine location. A value with a path is
// loaded as-is in either engine mode and disables the DLLs-tree search
{$IFDEF WIN64}
LibraryName := ExtractFilePath(ParamStr(0)) + 'engine\x64\pdfium.dll';
{$ELSE}
LibraryName := ExtractFilePath(ParamStr(0)) + 'engine\x86\pdfium.dll';
{$ENDIF}
try
PDFium.LoadLibrary; // qualified: Windows has a LoadLibrary too
except
on E: EPdfError do
begin
ShowMessage('PDF engine did not start: ' + E.Message);
Exit;
end;
end;
Application.CreateForm(TFormViewer, FormViewer);
Application.Run;
end.
- Keep
{$MODE Delphi}in units that use the component. The package compiles in Delphi syntax mode, and the viewer unit above passes method pointers Delphi-style (AddButton('Open...', OpenClick)), which under{$MODE ObjFPC}must be written@OpenClick - Unit and resource names change:
SysUtils,FormsandExtCtrlsdrop theSystem.andVcl.prefixes, the form resource becomes{$R *.lfm}, and LCL'sTPortableNetworkGraphicreplacesTPngImagein the exporter - Text APIs return
WString, which isstringon Delphi andWideStringon Free Pascal, so extracted text bound for an LCL control goes through the usual UTF-16 to UTF-8 conversion - A 64-bit Lazarus install builds
x86_64-win64executables unless the project's target CPU says otherwise, so those need the Win64 DLL; in Delphi, the Target Platforms node in the Projects window shows which platform the build uses - A pinned
LibraryNameis used exactly as given, so point it atpdfium.v8.dllwhen the application needs XFA - Outside Windows, set
LibraryNameto thelibpdfium.soorlibpdfium.dylibbuilt for that target. String handling, conditional code and other Free Pascal differences are covered in building a PDF viewer for Lazarus and Free Pascal with PDFium
Which Delphi versions does PDFium Component support?
PDFium Component supports Delphi and C++Builder from RAD Studio XE5 through 13.2 Florence, with Win32 and Win64 packages for each release in folders named by the IDE's internal BDS number, from Lib\12.0 (XE5) to Lib\37.0 (13), plus the Lazarus packages under Lib\FPC. People regularly search for a Delphi 2007 build, and the answer follows from the source as much as from packaging: on Delphi the component's text type WString is the Unicode string that arrived in Delphi 2009, and the cancellable async helpers are generic classes such as TPdfFuture<T>, a language feature from the same release. Delphi 2007 (BDS 5.0) predates both, and the installer ships no package folder for it. A Delphi 2007 application that needs PDFium rendering is better served by moving that project to XE5 or later, or by building the PDF part with Lazarus, which compiles the same TPdf and TPdfView source
Why does a page render blank or a file refuse to open?
Almost every open or render failure at this stage traces back to the environment or to the input file, and LastLoadReport.ErrorMessage names which one after any failed Active := True. The messages and symptoms map to causes like this:
- Windows' "module could not be found" text followed by a DLL path: none of the search locations held the file, so copy the
DLLstree beside the executable - The same text plus the note that the DLL architecture does not match the host process: the file exists but has the other bitness, typically a Win64 build target sitting next to the 32-bit DLL
- "Password required or incorrect password": the PDF is encrypted with an open password, and
Passwordhas to be set before activation - "File not in PDF format or corrupted": usually not a PDF at all, such as an HTML error page saved under a
.pdfname or a truncated download; compare the first bytes of the file with%PDF- - The document opens but the viewer stays empty: the view was never activated, or the code activated only the
TPdf - An exported image is missing highlights, stamps or comment icons: the render call omitted
reAnnotations - An XFA form shows only a "Please wait" placeholder page: the document needs the V8 build. The component switches to
pdfium.v8.dllby itself when it detects XFA markers before the engine is loaded, but a process that already loaded the plain DLL firesOnXfaRuntimeMissinginstead, so applications that expect XFA should setEnableV8Engine := Trueat startup
Run the exporter against a handful of your own PDFs before wiring up the viewer. Real files surface a misplaced DLL, an encrypted document or a mislabeled download much sooner than the bundled samples do. The trial installer includes TPdfView, so you can build this viewer before buying a license, and it is available from the PDFium Component product page