Pdfviewer
A complete, runnable delphi program — 1074 lines, shipped in your download.
Demonstrates Rendering & Viewer
Same example, other languages: activex_cpp c cpp cpp_linux csharp python vbnet
examples/delphi/pdfviewer/pdfcontrol.pas
1074 lines
unit pdfcontrol;
interface
{$FINITEFLOAT OFF}
uses
Windows, Messages, Graphics, Forms, Math, SysUtils, Classes, Controls, ExtCtrls, LumasPdfApi;
type TOnAfterUpdateScreen = procedure(Sender: TObject; DC: HDC; var Area: TIntRect) of object;
type TOnNewPDFPage = procedure(Sender: TObject; NewPage: Integer) of object;
type TOnPDFPaint = procedure(Sender: TObject; DC: HDC; CurrPage: Integer) of object;
type TPDFScrollEvent = procedure(Sender: TObject; Vertical: Boolean; ScrollCode, NewPos, NewPageNum: Integer) of object;
// Cursors
const crHandNormal = 1;
const crHandClosed = 2;
// Private error message
const WM_DISPLAYERRORS = WM_USER + 1;
{
The page cache is the owner of the main PDF instance that loads a PDF file. It is safe to access this instance at runtime since
it is only used to caluculate the page size and orientation. The corresponding code runs in the main process so that you can safely
access it without side effects. From the view of the caller this instance is single threaded.
The main PDF instance has the same life time as the PDFCanvas. It is safe to store a reference of the main instance in a variable in
the FormCreate event and to use it until the application becomes terminated.
The page cache doesn't load pages into the main instance, so, the page count of this instance is always zero. The rendering thread(s)
use their own PDF instance that cannot be accessed from outside.
When it is required to modify or to store the PDF file on disk then use another PDF instance for this purpose.
}
type
TPDFCanvas = class(TWinControl)
private
FAutoScroll: Boolean;
FBackColor: TColor;
FCache: TPDFPageCache;
FColorManagement: Boolean;
FErrMaxCount: Integer;
FErrors: TStringList;
FFirstPage: Integer;
FHandle: HWND;
FHavePos: Boolean;
FHeight: Integer;
FInitError: String;
FInitialized: Boolean;
FOldPage: Integer;
FOnAfterUpdateScreen: TOnAfterUpdateScreen;
FOnError: TNotifyEvent;
FOnMouseDown: TMouseEvent;
FOnMouseMove: TMouseMoveEvent;
FOnMouseUp: TMouseEvent;
FOnMouseWheel: TMouseWheelEvent;
FOnNewPage: TOnNewPDFPage;
FOnPaint: TOnPDFPaint;
FOnResize: TNotifyEvent;
FOnScroll: TPDFScrollEvent;
FPageCount: Integer;
FPDF: TPDF;
FRedraw: Boolean;
FResolution: Integer;
FScrollLine: Integer;
FScrollVMax: Integer;
FWidth: Integer;
FScrollWindow: Boolean;
FWheelLines: Integer;
FZoomMode: Boolean;
protected
procedure CreateParams(var Params: TCreateParams); override;
function GetDefPageLayout(): TPageLayout;
function GetPageLayout(): TPageLayout;
function GetPageScale(): TPDFPageScale;
function Scroll(Vertical: Boolean; ScrollCode: Integer; var ScrollPos, NewPageNum: Integer): Boolean;
procedure SetAutoScroll(Value: Boolean);
procedure SetBackColor(Color: TColor);
procedure SetDefPageLayout(Value: TPageLayout);
procedure SetPageLayout(Value: TPageLayout);
procedure SetPageScale(Value: TPDFPageScale);
procedure UpdateScrollBars;
procedure UpdateScrollBarsEx;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
procedure AddError(ErrMessage: String);
property AlignDisabled;
property CacheInstance: TPDFPageCache read FCache;
procedure CloseFile;
property DefPageLayout: TPageLayout read GetDefPageLayout write SetDefPageLayout;
procedure DisableScrollBars;
procedure DisplayFirstPage;
property ErrorLog: TStringList read FErrors;
function ExecBookmark(Index: Cardinal): TUpdBmkAction;
property FirstPage: Integer read FFirstPage;
function GetDC: HDC;
// GetPageMatrix() returns the page number at the curser position on success, as well the exact position and size of the rendered page.
// You must check whether the return value is greater 0. Otherwise there is no page at the cursor coordinates or the page was not loaded yet.
// The x-coordinate of the cursor is currently not taken into account because only one page can occur in horizontal direction at this time.
// The matrix transforms PDF space to device space. DestX/Y must be added to get final device coordinates.
function GetPageMatrix(CursorX, CursorY: Integer; var DestX, DestY, Width, Height: Integer; var Matrix: TCTM): Integer;
function GetRotate: Integer;
function GetScrollLineDelta(Vertical: Boolean): Cardinal;
function GetScrollPos(Vertical: Boolean): Integer;
property Handle: HWND read FHandle;
property Height: Integer read FHeight;
function InitBaseObjects(Flags: TInitCacheFlags): Boolean;
property InitError: String read FInitError;
procedure InitScrollBar(Vertical: Boolean; Max, SmallChange, LargeChange: Integer);
procedure Loaded; override;
procedure Lock;
property PageCount: Integer read FPageCount;
property PageLayout: TPageLayout read GetPageLayout write SetPageLayout;
property PageScale: TPDFPageScale read GetPageScale write SetPageScale;
property PDFInstance: TPDF read FPDF;
procedure ProcessErrors(UpdateWindow: Boolean);
procedure Redraw;
property Resolution: Integer read FResolution;
procedure ScrollTo(PageNum: Integer);
function SetOCGState(Handle: Cardinal; Visible, SaveState: Boolean): Boolean;
procedure SetRotate(Value: Integer);
function SetScrollLineDelta(Vertical: Boolean; Value: Cardinal): Boolean;
procedure SetScrollPos(Vertical: Boolean; NewPos: Integer; RedrawScrollBar: Boolean);
procedure SetThreadPriority(UpdateThread, RenderThread: TPDFThreadPriority);
procedure UnLock;
procedure UpdateCache(PageNum: Integer);
property Width: Integer read FWidth;
procedure Zoom(Value: Single);
property ZoomMode: Boolean read FZoomMode;
procedure WMButtonLDown(var Msg: TWMMouse); message WM_LBUTTONDOWN;
procedure WMButtonLUp(var Msg: TWMMouse); message WM_LBUTTONUP;
procedure WMError(var Msg: TMessage); message WM_DISPLAYERRORS;
procedure WMEraseBackground(var Msg: TMessage); message WM_ERASEBKGND;
procedure WMHandleDlgCode(var Msg: TMessage); message WM_GETDLGCODE;
procedure WMKeyDown(var Msg: TWMKey); message WM_KEYDOWN;
procedure WMMouseMove(var Msg: TWMMouseMove); message WM_MOUSEMOVE;
procedure WMMouseWheel(var Msg: TCMMouseWheel); message WM_MOUSEWHEEL;
procedure WMPaint(var Msg: TMessage); message WM_PAINT;
procedure WMResize(var Msg: TMessage); message WM_SIZE;
procedure WMScrollHorz(var Msg: TMessage); message WM_HSCROLL;
procedure WMScrollVert(var Msg: TMessage); message WM_VSCROLL;
published
property Align;
property AutoScroll: Boolean read FAutoScroll write SetAutoScroll default true;
property BackColor: TColor read FBackColor write SetBackColor default clAppWorkSpace;
property ColorManagement: Boolean read FColorManagement write FColorManagement default true;
property Constraints;
property Enabled;
property MaxErrCount: Integer read FErrMaxCount write FErrMaxCount default 100;
property OnAfterUpdateScreen: TOnAfterUpdateScreen read FOnAfterUpdateScreen write FOnAfterUpdateScreen;
property OnEnter;
property OnError: TNotifyEvent read FOnError write FOnError;
property OnExit;
property OnKeyDown;
property OnKeyPress;
property OnKeyUp;
property OnPaint: TOnPDFPaint read FOnPaint write FOnPaint;
property OnMouseDown: TMouseEvent read FOnMouseDown write FOnMouseDown;
property OnMouseUp: TMouseEvent read FOnMouseUp write FOnMouseUp;
property OnMouseMove: TMouseMoveEvent read FOnMouseMove write FOnMouseMove;
property OnMouseWheel: TMouseWheelEvent read FOnMouseWheel write FOnMouseWheel;
property OnNewPage: TOnNewPDFPage read FOnNewPage write FOnNewPage;
property OnResize: TNotifyEvent read FOnResize write FOnResize;
property OnScroll: TPDFScrollEvent read FOnScroll write FOnScroll;
property TabOrder;
property TabStop;
property UseScrollWindow: Boolean read FScrollWindow write FScrollWindow default true;
property WheelLines: Integer read FWheelLines;
end;
procedure Register;
implementation
procedure Register;
begin
RegisterComponents('PDF', [TPDFCanvas]);
end;
{ TPDFCanvas }
{
The error callback function works differently with the page cache. It does not return error messages since all error
messages are stored in the error log. The error callback function is called when it is safe to access the error log
and if new messages are available.
The parameter ErrCode is set to the page number that produced the error or to -1 if the error occurred during loading
the top level objects.
The rendering thread (if running) waits until the error callback function returns. So, it is not required to synchronize
anything since no competing threads are running.
}
function PDFErrProc(const Data: Pointer; ErrCode: Integer; const ErrMessage: PAnsiChar; ErrType: Integer): Integer; stdcall;
var i, count: Integer; canvas: TPDFCanvas; list: TStringList; pdf: TPDF; msg: TPDFError;
begin
Result := 0;
canvas := TPDFCanvas(Data);
pdf := canvas.PDFInstance;
list := canvas.ErrorLog;
count := pdf.GetErrLogMessageCount;
if list.Count + count > canvas.MaxErrCount then
count := canvas.MaxErrCount - list.Count;
if count < 1 then Exit;
msg.StructSize := sizeof(msg);
try
for i := 0 to count - 1 do begin
if pdf.GetErrLogMessage(i, msg) then begin
// ErrCode is set to the page number if the errors occurred during rendering a page!
if ErrCode > 0 then
list.Add(Format('Page %.5d: %s, ObjNum: %d, Offset: %d', [ErrCode, String(msg.Msg), msg.ObjNum, msg.Offset]))
else
list.Add(Format('%s, ObjNum: %d, Offset: %d', [String(msg.Msg), msg.ObjNum, msg.Offset]));
end;
end;
// Clear the error log so that we don't receive the same messages again
pdf.ClearErrorLog;
// Generate an error event so that the application can process the error messages.
// Let the function return and don't use SendMessage() to generate the error event.
Windows.PostMessage(canvas.Handle, WM_DISPLAYERRORS, 0, 0);
except
Result := -1;
end;
end;
procedure TPDFCanvas.CreateParams(var Params: TCreateParams);
begin
inherited CreateParams(Params);
Params.Style := Params.Style or WS_HSCROLL or WS_VSCROLL;
Params.WindowClass.style := Params.WindowClass.style and not (CS_PARENTDC or CS_CLASSDC) or CS_OWNDC or CS_VREDRAW or CS_HREDRAW; // We need a private dc and we need resize events
end;
{
// In the FormCreate event the application must check whether the InitError string is set. If this is the case then
// we have no valid PDF instance in memory and the application should either be terminated or all PDF related functions
// must be be disabled.
// Example:
if PDFCanvas.InitError <> '' then begin
// Display the error messaage and terminate
MessageDlg(PDFCanvas.InitError, mtError, [mbOK], 0);
Application.Terminate;
Exit;
end;
}
constructor TPDFCanvas.Create(AOwner: TComponent);
begin
inherited;
FAutoScroll := true;
BevelOuter := bvNone;
FBackColor := $303030;
FColorManagement := true;
FErrMaxCount := 100;
FRedraw := true;
FScrollWindow := true;
Screen.Cursors[crHandNormal] := LoadCursor(hInstance,'HAND_NORMAL');
Screen.Cursors[crHandClosed] := LoadCursor(hInstance,'HAND_CLOSED');
Cursor := crArrow;
SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, FWheelLines, 0);
if FWheelLines < 1 then FWheelLines := 3;
// Delphi enables floating point exceptions and this conflicts with the C++ exception handling of LumasPDF.
SetExceptionMask([exInvalidOp, exDenormalized, exZeroDivide, exOverflow, exUnderflow, exPrecision]);
Set8087CW($133f);
end;
procedure TPDFCanvas.AddError(ErrMessage: String);
begin
if FErrors.Count + 1 <= MaxErrCount then begin
FErrors.Add(ErrMessage);
if Assigned(FOnError) then FOnError(Self);
end;
end;
procedure TPDFCanvas.CloseFile;
begin
FCache.CloseFile;
FErrors.Clear;
Cursor := crArrow;
FFirstPage := 0;
FPageCount := 0;
DisableScrollBars;
InvalidateRect(FHandle, nil, false);
end;
destructor TPDFCanvas.Destroy;
begin
if FCache <> nil then FreeAndNil(FCache);
if FErrors <> nil then FreeAndNil(FErrors);
if FPDF <> nil then FreeAndNil(FPDF);
inherited;
end;
procedure TPDFCanvas.DisableScrollBars;
var si: TScrollInfo;
begin
FillChar(si, sizeof(si), 0);
si.cbSize := sizeof(si);
si.fMask := SIF_ALL;
SetScrollPos(true, 0, false);
SetScrollPos(false, 0, false);
SetScrollInfo(FHandle, SB_HORZ, si, true);
SetScrollInfo(FHandle, SB_VERT, si, true);
ShowScrollBar(FHandle, SB_VERT, true);
ShowScrollBar(FHandle, SB_HORZ, true);
EnableScrollBar(FHandle, SB_VERT, ESB_DISABLE_BOTH);
EnableScrollBar(FHandle, SB_HORZ, ESB_DISABLE_BOTH);
end;
procedure TPDFCanvas.DisplayFirstPage;
begin
ScrollTo(FFirstPage);
end;
function TPDFCanvas.ExecBookmark(Index: Cardinal): TUpdBmkAction;
var x, y, max, small, large: Integer; z: Single; ps: TPDFPageScale;
begin
FOldPage := 0;
Result := FCache.ExecBookmark(Index, x, y, z, ps, nil);
if (Result and ubaOpenPage) = ubaOpenPage then begin
if (Result and ubaZoom) = ubaZoom then begin
FCache.Zoom(z * 72.0 / FResolution, x, y);
FZoomMode := true;
FCache.GetScrollRange(true, max, small, large);
SetScrollPos(true, y, true);
if FCache.GetScrollRange(false, max, small, large) then begin
InitScrollBar(false, max, small, large);
SetScrollPos(false, x, true);
end else begin
SetScrollPos(false, 0, true);
EnableScrollBar(FHandle, SB_HORZ, ESB_DISABLE_BOTH);
end;
end else begin
if (Result and ubaPageScale) = ubaPageScale then begin
if FZoomMode or (ps <> FCache.GetPageScale) then begin
FZoomMode := false;
FCache.SetPageScale(ps);
UpdateScrollBarsEx;
end;
end;
SetScrollPos(true, y, true);
SetScrollPos(false, x, true);
end;
end;
InvalidateRect(FHandle, nil, false);
end;
function TPDFCanvas.GetDC: HDC;
begin
Result := Windows.GetDC(FHandle);
end;
function TPDFCanvas.GetRotate: Integer;
begin
Result := FCache.GetRotate;
end;
function TPDFCanvas.GetDefPageLayout: TPageLayout;
begin
Result := FCache.GetDefPageLayout;
end;
function TPDFCanvas.GetPageLayout: TPageLayout;
begin
Result := FCache.GetPageLayout;
end;
function TPDFCanvas.GetPageMatrix(CursorX, CursorY: Integer; var DestX, DestY, Width, Height: Integer; var Matrix: TCTM): Integer;
begin
Result := FCache.GetPageMatrix(CursorX, CursorY, DestX, DestY, Width, Height, Matrix);
end;
function TPDFCanvas.GetPageScale: TPDFPageScale;
begin
Result := FCache.GetPageScale;
end;
function TPDFCanvas.GetScrollLineDelta(Vertical: Boolean): Cardinal;
begin
Result := FCache.GetScrollLineDelta(Vertical);
end;
function TPDFCanvas.GetScrollPos(Vertical: Boolean): Integer;
var si: TScrollInfo;
begin
si.cbSize := sizeof(si);
si.fMask := SIF_POS;
si.nPos := 0;
if Vertical then
GetScrollInfo(FHandle, SB_VERT, si)
else
GetScrollInfo(FHandle, SB_HORZ, si);
Result := si.nPos;
end;
function TPDFCanvas.InitBaseObjects(Flags: TInitCacheFlags): Boolean;
var max, small, large: Integer;
begin
Result := false;
FHavePos := false;
if FCache = nil then Exit;
FErrors.Clear;
FOldPage := 0;
FZoomMode := false;
FPageCount := FPDF.GetInPageCount;
FFirstPage := FCache.InitBaseObjects(FWidth, FHeight, Flags);
FScrollLine := 120 * FWheelLines * 96 div FResolution;
if (FFirstPage < 1) or (FPageCount < 1) then Exit;
FCache.SetScrollLineDelta(true, FScrollLine);
if FAutoScroll then begin
FCache.GetScrollRange(true, max, small, large);
InitScrollBar(true, max, small, large);
InitScrollBar(false, 0, 0, 0);
small := FCache.GetScrollPos(true, FFirstPage);
SetScrollPos(true, small, true);
end;
Cursor := crHandNormal;
Result := true;
end;
procedure TPDFCanvas.InitScrollBar(Vertical: Boolean; Max, SmallChange, LargeChange: Integer);
var si: TScrollInfo;
begin
si.cbSize := sizeof(si);
si.fMask := SIF_POS or SIF_RANGE or SIF_PAGE or SIF_DISABLENOSCROLL;
si.nMin := 0;
si.nMax := Max;
si.nPage := LargeChange;
si.nPos := 0;
if Vertical then begin
FScrollVMax := Max - LargeChange + 1;
SetScrollInfo(Handle, SB_VERT, si, false)
end else
SetScrollInfo(Handle, SB_HORZ, si, false);
end;
procedure TPDFCanvas.Loaded;
var dc: HDC;
begin
inherited;
if (FPDF <> nil) or (csDesigning in ComponentState) then Exit;
try
HandleNeeded;
FHandle := WindowHandle;
FPDF := TPDF.Create;
FPDF.SetErrorMode(FPDF.GetErrorMode() or emUseErrLog);
// Min version 4.0.53.147
if FPDF.GetDynaPDFVersionInt() < 40530147 then raise Exception.Create(Format('Wrong LumasPdf.dll version!'#10'Found %s'#10'Require 4.0.53.147 or higher', [FPDF.GetDynaPDFVersion]));
FPDF.SetOnErrorProc(Self, @PDFErrProc);
dc := GetDC;
FResolution := GetDeviceCaps(dc, LOGPIXELSX);
ReleaseDC(FHandle, dc);
// Palette colors like clBackground are not supported!
FCache := TPDFPageCache.Create(FPDF, pxfBGRA, 4, 8, Cardinal(FBackColor), ttpLowest);
FErrors := TStringList.Create;
except
on E: Exception do begin
FInitError := E.Message;
Exit;
end;
end;
if FResolution < 72 then FResolution := 72;
end;
procedure TPDFCanvas.Lock;
begin
if FCache = nil then Exit;
FCache.Lock;
end;
{
This callback is excuted from the rendering thread whenever the screen was updated. The rendering
thread waits until the function returns.
However, note that the rendering thread calls this function and not the main thread! Be careful
what you do here since most Delphi components are not necessarily thread-safe.
If you want to draw additional controls on the canvas then draw them in the OnAfterUpdateScreen event.
The provided dc is the one of the PDFCanvas. This is a private device context. Changes made on this dc
are not restored among paint events! So, make sure that you restore important changes, e.g. the coordinate
system or clipping region if necessary.
}
function OnUpdateWindow(const Data: Pointer; var Area: TIntRect): Integer; stdcall;
var canvas: TPDFCanvas;
begin
Result := 0;
canvas := TPDFCanvas(Data);
if Assigned(canvas.OnAfterUpdateScreen) then
canvas.OnAfterUpdateScreen(canvas, canvas.GetDC(), Area);
end;
procedure TPDFCanvas.ProcessErrors(UpdateWindow: Boolean);
begin
// If we have errors in the pipe then ProcessErrors() stops the rendering thread and calls then
// the error callback function so that we can safely access the error log. If the return value
// is true then the error callback function was called and the rendering thread was stopped.
if FCache.ProcessErrors and UpdateWindow then
InvalidateRect(FHandle, nil, false);
end;
procedure TPDFCanvas.Redraw;
begin
InvalidateRect(FHandle, nil, false);
end;
function TPDFCanvas.Scroll(Vertical: Boolean; ScrollCode: Integer; var ScrollPos, NewPageNum: Integer): Boolean;
var x, y, max, small, large: Integer; retval: TUpdScrollbar;
begin
if Vertical then begin
x := GetScrollPos(false);
y := ScrollPos;
end else begin
x := ScrollPos;
y := GetScrollPos(true);
end;
retval := FCache.Scroll(Vertical, ScrollCode, x, y);
if (retval and usbVertRange) <> 0 then begin
FCache.GetScrollRange(true, max, small, large);
InitScrollBar(true, max, small, large);
end;
if (retval and usbHorzRange) <> 0 then begin
FCache.GetScrollRange(false, max, small, large);
InitScrollBar(false, max, small, large);
end;
if (Vertical) then
ScrollPos := y
else
ScrollPos := x;
NewPageNum := FCache.GetPageAt(x, y, 0, 0);
Result := retval <> 0;
end;
procedure TPDFCanvas.ScrollTo(PageNum: Integer);
var y: Integer;
begin
if FCache = nil then Exit;
y := FCache.GetScrollPos(true, pageNum);
FCache.ResetMousePos;
SetScrollPos(true, y, true);
InvalidateRect(FHandle, nil, false);
end;
procedure TPDFCanvas.SetRotate(Value: Integer);
var pos, max, small, large: Integer;
begin
// We must update the scroll bars when we rotate the pages
FOldPage := 0; // Make sure that a NewPage event is raised
pos := FCache.GetScrollPos(true, FCache.GetCurrPage);
FCache.SetRotate(Value);
FCache.GetScrollRange(true, max, small, large);
FCache.ResetMousePos;
InitScrollBar(true, max, small, large);
FCache.GetScrollRange(false, max, small, large);
InitScrollBar(false, max, small, large);
SetScrollPos(true, pos, true);
InvalidateRect(FHandle, nil, false);
end;
procedure TPDFCanvas.SetAutoScroll(Value: Boolean);
begin
FAutoScroll := Value;
if not FAutoScroll then begin
ShowScrollBar(FHandle, SB_VERT, false);
ShowScrollBar(FHandle, SB_HORZ, false);
end else begin
ShowScrollBar(FHandle, SB_VERT, true);
ShowScrollBar(FHandle, SB_HORZ, true);
end;
EnableScrollBar(FHandle, SB_VERT, ESB_DISABLE_BOTH);
EnableScrollBar(FHandle, SB_HORZ, ESB_DISABLE_BOTH);
end;
procedure TPDFCanvas.SetBackColor(Color: TColor);
begin
FBackColor := Color;
if FCache <> nil then FCache.ChangeBackColor(Cardinal(Color));
InvalidateRect(FHandle, nil, false);
end;
procedure TPDFCanvas.SetDefPageLayout(Value: TPageLayout);
begin
FCache.SetDefPageLayout(Value);
end;
function TPDFCanvas.SetOCGState(Handle: Cardinal; Visible, SaveState: Boolean): Boolean;
begin
Result := FCache.SetOCGState(Handle, Visible, SaveState);
if Result then InvalidateRect(FHandle, nil, false);
end;
procedure TPDFCanvas.SetPageLayout(Value: TPageLayout);
var pos: Integer;
begin
if Value <> FCache.GetPageLayout then begin
FCache.SetPageLayout(Value);
if Value = plSinglePage then begin
FCache.ResetMousePos;
pos := FCache.GetScrollPos(true, FCache.GetCurrPage);
SetScrollPos(true, pos, true);
end;
InvalidateRect(FHandle, nil, false);
end;
end;
procedure TPDFCanvas.SetPageScale(Value: TPDFPageScale);
begin
if FZoomMode or (Value <> FCache.GetPageScale) then begin
FOldPage := 0;
FZoomMode := false;
FCache.SetPageScale(Value);
FCache.ResetMousePos;
UpdateScrollBars;
end;
end;
function TPDFCanvas.SetScrollLineDelta(Vertical: Boolean; Value: Cardinal): Boolean;
var y, max, small, large: Integer;
begin
y := GetScrollPos(true);
FScrollLine := Integer(Value) * FWheelLines * FResolution div 120;
Result := FCache.SetScrollLineDelta(Vertical, FScrollLine);
if Result then begin
FCache.GetScrollRange(true, max, small, large);
InitScrollbar(true, max, small, large);
SetScrollPos(true, y, true);
end;
end;
procedure TPDFCanvas.SetScrollPos(Vertical: Boolean; NewPos: Integer; RedrawScrollBar: Boolean);
var si: TScrollInfo;
begin
si.cbSize := sizeof(si);
si.fMask := SIF_POS;
si.nPos := NewPos;
if Vertical then
SetScrollInfo(FHandle, SB_VERT, si, RedrawScrollBar)
else
SetScrollInfo(FHandle, SB_HORZ, si, RedrawScrollBar);
FRedraw := true;
end;
procedure TPDFCanvas.SetThreadPriority(UpdateThread, RenderThread: TPDFThreadPriority);
begin
if FCache <> nil then FCache.SetThreadPriority(UpdateThread, RenderThread);
end;
procedure TPDFCanvas.UnLock;
begin
FCache.UnLock;
InvalidateRect(FHandle, nil, false);
end;
procedure TPDFCanvas.UpdateCache(PageNum: Integer);
begin
FCache.Update(PageNum);
InvalidateRect(FHandle, nil, false);
end;
procedure TPDFCanvas.UpdateScrollBars;
var pageNum: Integer;
begin
pageNum := FCache.GetCurrPage;
UpdateScrollBarsEx;
SetScrollPos(false, 0, true);
ScrollTo(pageNum);
end;
procedure TPDFCanvas.UpdateScrollBarsEx;
var max, small, large: Integer;
begin
FCache.GetScrollRange(true, max, small, large);
InitScrollBar(true, max, small, large);
FCache.GetScrollRange(false, max, small, large);
InitScrollBar(false, max, small, large);
end;
procedure TPDFCanvas.WMButtonLDown(var Msg: TWMMouse);
begin
SetFocus;
if FPageCount = 0 then
Cursor := crArrow
else begin
FHavePos := true;
case FCache.MouseDown(Msg.XPos, Msg.YPos) of
pcrHandClosed: Cursor := crHandClosed;
pcrHandPoint: Cursor := crHandPoint;
end;
end;
if Assigned(FOnMouseDown) then
FOnMouseDown(Self, mbLeft, KeysToShiftState(Msg.Keys), Msg.XPos, Msg.YPos)
end;
procedure TPDFCanvas.WMButtonLUp(var Msg: TWMMouse);
begin
FHavePos := false;
if FPageCount = 0 then
Cursor := crArrow
else
Cursor := crHandNormal;
if Assigned(FOnMouseUp) then
FOnMouseUp(Self, mbLeft, KeysToShiftState(Msg.Keys), Msg.XPos, Msg.YPos)
end;
procedure TPDFCanvas.WMEraseBackground(var Msg: TMessage);
begin
FRedraw := true;
Msg.Result := 1;
end;
procedure TPDFCanvas.WMError(var Msg: TMessage);
begin
if Assigned(FOnError) then begin
if FErrors.Count > 0 then FOnError(Self);
end;
end;
procedure TPDFCanvas.WMHandleDlgCode(var Msg: TMessage);
var M: PMsg;
begin
Msg.Result := DLGC_WANTARROWS;
if Msg.lParam <> 0 then begin
M := PMsg(Msg.lParam);
case M.message of
WM_KEYDOWN, WM_KEYUP, WM_CHAR: begin
Perform(M.message, M.wParam, M.lParam);
Msg.Result := Msg.Result or DLGC_WANTMESSAGE or CM_COLORCHANGED;
end;
end;
end else
Msg.Result := Msg.Result or DLGC_WANTMESSAGE;
end;
procedure TPDFCanvas.WMKeyDown(var Msg: TWMKey);
var pos: Integer; shiftState: TShiftState; scale: single;
begin
if FCache = nil then Exit;
shiftState := KeyDataToShiftState(Msg.KeyData);
if shiftState = [] then begin
case Msg.CharCode of
VK_DOWN: SendMessage(FHandle, WM_VSCROLL, SB_LINEDOWN, 0);
VK_UP: SendMessage(FHandle, WM_VSCROLL, SB_LINEUP, 0);
VK_RIGHT: SendMessage(FHandle, WM_HSCROLL, SB_LINELEFT, 0);
VK_LEFT: SendMessage(FHandle, WM_HSCROLL, SB_LINERIGHT, 0);
// Note that the scroll messages SB_PAGEUP or SB_PAGEDOWN scroll maybe more than one page up or down. So, we use
// the scroll message only if the file contains one page. Otherwise we calculate the scroll position and scroll
// one page forward or backward as needed.
VK_PRIOR: begin
if FCache.GetCurrPage > 1 then begin
pos := FCache.GetScrollPos(true, FCache.GetCurrPage - 1);
SetScrollPos(true, pos, true);
InvalidateRect(FHandle, nil, false);
end else
SendMessage(FHandle, WM_VSCROLL, SB_PAGEUP, 0);
end;
VK_NEXT: begin
if FCache.GetCurrPage < FPageCount then begin
pos := FCache.GetScrollPos(true, FCache.GetCurrPage + 1);
SetScrollPos(true, pos, true);
InvalidateRect(FHandle, nil, false);
end else
SendMessage(FHandle, WM_VSCROLL, SB_PAGEDOWN, 0);
end;
VK_HOME: begin
FCache.ResetMousePos;
pos := FCache.GetScrollPos(true, 1);
SetScrollPos(true, pos, true);
InvalidateRect(FHandle, nil, false);
end;
VK_END: begin
FCache.ResetMousePos;
if FPageCount > 1 then
pos := FCache.GetScrollPos(true, FPageCount)
else
pos := FScrollVMax;
SetScrollPos(true, pos, true);
InvalidateRect(FHandle, nil, false);
end;
VK_OEM_PLUS: begin
scale := FCache.GetCurrZoom * 72.0 / Resolution * 1.25;
Zoom(scale * Resolution / 72.0);
end;
VK_OEM_MINUS: begin
scale := FCache.GetCurrZoom * 72.0 / Resolution * 0.75;
Zoom(scale * Resolution / 72.0);
end;
end;
Msg.Result := 1;
if Assigned(OnKeyDown) then
OnKeyDown(Self, Msg.CharCode, shiftState)
end else begin
if Assigned(OnKeyDown) then begin
Msg.Result := 1;
OnKeyDown(Self, Msg.CharCode, shiftState);
end;
end;
end;
procedure TPDFCanvas.WMMouseMove(var Msg: TWMMouseMove);
var sx, sy, max, small, large: Integer; retval: TUpdScrollbar;
begin
Msg.Result := 0;
if (FPageCount > 0) and FHavePos then begin
if Msg.Keys = 0 then begin
sx := GetScrollPos(false);
sy := GetScrollPos(true);
retval := FCache.MouseMove(0, false, sx, sy, Msg.XPos, Msg.YPos);
case (retval and usbCursorMask) of
usbCursorHandNormal: Cursor := crHandNormal;
usbCursorHandClosed: Cursor := crHandClosed;
usbCursorHandPoint: Cursor := crHandPoint;
usbCursorIBeam: Cursor := crIBeam;
end;
end else if Msg.Keys = MK_LBUTTON then begin
sx := GetScrollPos(false);
sy := GetScrollPos(true);
if FScrollWindow then
retval := FCache.MouseMove(FHandle, true, sx, sy, Msg.XPos, Msg.YPos)
else
retval := FCache.MouseMove(0, true, sx, sy, Msg.XPos, Msg.YPos);
if (retval and usbVertRange) <> 0 then begin
FCache.GetScrollRange(true, max, small, large);
InitScrollBar(true, max, small, large);
end;
if (retval and usbHorzRange) <> 0 then begin
FCache.GetScrollRange(false, max, small, large);
InitScrollBar(false, max, small, large);
end;
case (retval and usbCursorMask) of
usbCursorHandNormal: Cursor := crHandNormal;
usbCursorHandClosed: Cursor := crHandClosed;
usbCursorHandPoint: Cursor := crHandPoint;
usbCursorIBeam: Cursor := crIBeam;
end;
if not FScrollWindow then InvalidateRect(FHandle, nil, false);
SetScrollPos(false, sx, true);
SetScrollPos(true, sy, true);
end;
end;
if Assigned(FOnMouseMove) then
FOnMouseMove(Self, KeysToShiftState(Msg.Keys), Msg.XPos, Msg.YPos)
end;
procedure TPDFCanvas.WMMouseWheel(var Msg: TCMMouseWheel);
var x, y, gap, max, smallChange, largeChange, newX, newY: Integer; scale: Single; update: TUpdScrollbar;
begin
if FCache = nil then Exit;
if ssCtrl in Msg.ShiftState then begin
scale := FCache.GetCurrZoom;
if Msg.WheelDelta < 0 then
scale := scale / -(Msg.WheelDelta div FResolution)
else
scale := scale * (Msg.WheelDelta div FResolution);
if FCache.Zoom(scale, newX, newY) then begin
FZoomMode := true;
FCache.GetScrollRange(true, max, smallChange, largeChange);
InitScrollBar(true, max, smallChange, largeChange);
SetScrollPos(true, newY, true);
if FCache.GetScrollRange(false, max, smallChange, largeChange) then begin
InitScrollBar(false, max, smallChange, largeChange);
SetScrollPos(false, newX, true);
end else
EnableScrollBar(FHandle, SB_HORZ, ESB_DISABLE_BOTH);
end;
end else begin
if (FCache.GetPageScale = psFitBest) and (FCache.GetPageLayout = plSinglePage) then begin
FCache.GetScrollRange(true, max, smallChange, largeChange);
gap := smallChange;
x := GetScrollPos(false);
if Msg.WheelDelta < 0 then
y := GetScrollPos(true) + gap
else
y := GetScrollPos(true) - gap;
end else begin
scale := 1.5 / FCache.GetCurrZoom;
gap := Trunc((Msg.WheelDelta * FScrollLine div 120) * scale);
x := GetScrollPos(false);
y := GetScrollPos(true) - gap;
end;
if y < 0 then y := 0;
update := FCache.Scroll(true, SB_THUMBPOSITION, x, y);
if update and usbVertRange = usbVertRange then begin
FCache.GetScrollRange(true, max, smallChange, largeChange);
InitScrollBar(true, max, smallChange, largeChange);
end;
if update and usbHorzRange = usbHorzRange then begin
FCache.GetScrollRange(false, max, smallChange, largeChange);
InitScrollBar(false, max, smallChange, largeChange);
end;
SetScrollPos(true, y, update <> 0);
end;
InvalidateRect(FHandle, nil, false);
end;
procedure TPDFCanvas.WMPaint(var Msg: TMessage);
var x, y, max, small, large, pageNum: Integer; retval: TUpdScrollbar; dc: HDC; ps: TPaintStruct; r: TRect;
begin
{
The drawing speed on Windows 7 is very limited because Microsoft changed the way how SetDIBitsToDevice()
copies a bitmap into the video buffer. The function works now mostly without hardware acceleration and
this makes it of course incredible slow. The new way to copy a bitmap into the video buffer is Direct2D.
The usage is more than complicated and a lot of compatibility problems must be taken into account. Maybe
Direct2D will be supported in future, or LumasPDF will provide API functions to enable the usage of arbitrary
blend functions.
}
if FCache <> nil then begin
FillChar(ps, sizeof(ps), 0);
dc := BeginPaint(FHandle, ps);
if ps.fErase or FRedraw then begin
x := GetScrollPos(false);
y := GetScrollPos(true);
retval := FCache.Paint(dc, x, y);
pageNum := FCache.GetCurrPage;
if retval <> usbNoUpdate then begin
if (retval and usbVertRange) <> 0 then begin
FCache.GetScrollRange(true, max, small, large);
InitScrollBar(true, max, small, large);
end;
if (retval and usbHorzRange) <> 0 then begin
FCache.GetScrollRange(false, max, small, large);
InitScrollBar(false, max, small, large);
SetScrollPos(false, x, true);
end else begin
SetScrollPos(false, 0, false);
EnableScrollBar(FHandle, SB_HORZ, ESB_DISABLE_BOTH);
end;
SetScrollPos(true, y, true);
end;
EndPaint(FHandle, ps);
if Assigned(FOnPaint) then FOnPaint(Self, dc, pageNum);
if FOldPage <> pageNum then begin
FOldPage := pageNum;
if Assigned(FOnNewPage) then FOnNewPage(Self, pageNum);
end;
end else
EndPaint(FHandle, ps);
end else begin
dc := BeginPaint(FHandle, ps);
if ps.fErase or FRedraw then begin
SetBkColor(dc, Cardinal(FBackColor));
r := ClientRect;
ExtTextOut(dc, 0, 0, ETO_OPAQUE, @r, nil, 0, nil);
if Assigned(FOnPaint) then FOnPaint(Self, dc, 0);
end;
EndPaint(FHandle, ps);
end;
FRedraw := false;
end;
procedure TPDFCanvas.WMResize(var Msg: TMessage);
var max, small, large, page, pos: Integer; saved: TPDFPageCache;
begin
FWidth := LOWORD(Msg.lParam);
FHeight := HIWORD(Msg.lParam);
if FHeight > 0 then begin
FHandle := WindowHandle;
if not FInitialized then begin
FInitialized := true;
if FAutoScroll then begin
ShowScrollBar(FHandle, SB_VERT, true);
ShowScrollBar(FHandle, SB_HORZ, true);
EnableScrollBar(FHandle, SB_VERT, ESB_DISABLE_BOTH);
EnableScrollBar(FHandle, SB_HORZ, ESB_DISABLE_BOTH);
end else begin
// Hidding the scrollbars causes two new resize events.
saved := FCache;
FCache := nil;
ShowScrollBar(FHandle, SB_VERT, false);
ShowScrollBar(FHandle, SB_HORZ, false);
FCache := saved;
end;
end;
if FCache <> nil then begin
FOldPage := 0;
page := FCache.GetCurrPage;
FCache.Resize(FWidth, FHeight);
// If AutoScroll is disabled then you must initialize your scroll bars in the very same way in
// the OnResize event.
if FAutoScroll then begin
pos := FCache.GetScrollPos(true, page);
FCache.GetScrollRange(true, max, small, large);
InitScrollBar(true, max, small, large);
SetScrollPos(true, pos, true);
FCache.GetScrollRange(false, max, small, large);
InitScrollBar(false, max, small, large);
end;
if Assigned(FOnResize) then
FOnResize(Self);
end;
end else if csDesigning in ComponentState then begin
FHandle := WindowHandle;
SetAutoScroll(FAutoScroll);
end;
end;
procedure TPDFCanvas.WMScrollHorz(var Msg: TMessage);
var code, pos, pageNum: Integer; si: TScrollInfo; update: Boolean;
begin
if FCache = nil then Exit;
if FAutoScroll then begin
pos := 0;
si.cbSize := sizeof(si);
si.fMask := SIF_ALL;
si.nTrackPos := 0;
code := LOWORD(Msg.wParam);
GetScrollInfo(FHandle, SB_HORZ, si);
case code of
SB_BOTTOM: pos := si.nMax - (Integer(si.nPage) - 1);
SB_TOP: pos := 0;
SB_LINEDOWN: pos := si.nPos;
SB_LINEUP: pos := si.nPos;
SB_PAGEDOWN: pos := si.nPos;
SB_PAGEUP: pos := si.nPos;
SB_THUMBTRACK: pos := si.nTrackPos;
SB_THUMBPOSITION: pos := si.nPos;
SB_ENDSCROLL: Exit; // can be ignored
else
pos := si.nPos;
end;
update := Scroll(false, code, pos, pageNum);
SetScrollPos(false, pos, update);
if Assigned(FOnScroll) then
FOnScroll(Self, false, code, pos, pageNum);
InvalidateRect(FHandle, nil, false);
end;
end;
procedure TPDFCanvas.WMScrollVert(var Msg: TMessage);
var code, pos, pageNum: Integer; si: TScrollInfo; update: Boolean;
begin
if FCache = nil then Exit;
if FAutoScroll then begin
pos := 0;
si.cbSize := sizeof(si);
si.fMask := SIF_ALL;
si.nTrackPos := 0;
code := LOWORD(Msg.wParam);
GetScrollInfo(FHandle, SB_VERT, si);
case code of
SB_BOTTOM: pos := si.nMax - (Integer(si.nPage) - 1);
SB_TOP: pos := 0;
SB_LINEDOWN: pos := si.nPos;
SB_LINEUP: pos := si.nPos;
SB_PAGEDOWN: pos := si.nPos;
SB_PAGEUP: pos := si.nPos;
SB_THUMBTRACK: pos := si.nTrackPos;
SB_THUMBPOSITION: pos := si.nPos;
SB_ENDSCROLL: pos := si.nPos;
else
pos := si.nPos;
end;
update := Scroll(true, code, pos, pageNum);
SetScrollPos(true, pos, update);
if Assigned(FOnScroll) then
FOnScroll(Self, true, code, pos, pageNum);
InvalidateRect(FHandle, nil, false);
end;
end;
procedure TPDFCanvas.Zoom(Value: Single);
var x, y, max, small, large: Integer;
begin
x := GetScrollPos(false);
y := GetScrollPos(true);
FCache.Zoom(Value, x, y);
FCache.GetScrollRange(true, max, small, large);
InitScrollBar(true, max, small, large);
SetScrollPos(true, y, true);
if FCache.GetScrollRange(false, max, small, large) then begin
InitScrollBar(false, max, small, large);
SetScrollPos(false, x, true);
end else begin
SetScrollPos(false, 0, true);
EnableScrollBar(FHandle, SB_HORZ, ESB_DISABLE_BOTH);
end;
FOldPage := 0;
FZoomMode := true;
InvalidateRect(FHandle, nil, false);
end;
end.
This file is in the SDK at examples/delphi/pdfviewer/pdfcontrol.pas.
The build fails if this page and that file ever differ.