4521 lines
129 KiB
Plaintext
4521 lines
129 KiB
Plaintext
unit PdfiumCore;
|
|
|
|
{$IFDEF FPC}
|
|
{$MODE DelphiUnicode}
|
|
{$ENDIF FPC}
|
|
|
|
{$IFNDEF FPC}
|
|
{$A8,B-,E-,F-,G+,H+,I+,J-,K-,M-,N-,P+,Q-,R-,S-,T-,U-,V+,X+,Z1}
|
|
{$STRINGCHECKS OFF}
|
|
{$ENDIF ~FPC}
|
|
|
|
interface
|
|
|
|
{.$UNDEF MSWINDOWS}
|
|
|
|
uses
|
|
{$IFDEF MSWINDOWS}
|
|
Windows, //WinSpool,
|
|
{$ELSE}
|
|
{$IFDEF FPC}
|
|
LCLType,
|
|
{$ENDIF FPC}
|
|
ExtCtrls, // for TTimer
|
|
{$ENDIF MSWINDOWS}
|
|
Types, SysUtils, Classes, Contnrs,
|
|
PdfiumLib;
|
|
|
|
const
|
|
// DIN A4
|
|
PdfDefaultPageWidth = 595;
|
|
PdfDefaultPageHeight = 842;
|
|
|
|
type
|
|
EPdfException = class(Exception);
|
|
EPdfUnsupportedFeatureException = class(EPdfException);
|
|
EPdfArgumentOutOfRange = class(EPdfException);
|
|
|
|
TPdfUnsupportedFeatureHandler = procedure(nType: Integer; const Typ: string) of object;
|
|
|
|
TPdfDocument = class;
|
|
TPdfPage = class;
|
|
TPdfAttachmentList = class;
|
|
TPdfAnnotationList = class;
|
|
TPdfFormField = class;
|
|
TPdfAnnotation = class;
|
|
|
|
TPdfPoint = record
|
|
X, Y: Double;
|
|
procedure Offset(XOffset, YOffset: Double);
|
|
class function Empty: TPdfPoint; static;
|
|
end;
|
|
|
|
TPdfRect = record
|
|
private
|
|
function GetHeight: Double; inline;
|
|
function GetWidth: Double; inline;
|
|
procedure SetHeight(const Value: Double); inline;
|
|
procedure SetWidth(const Value: Double); inline;
|
|
public
|
|
property Width: Double read GetWidth write SetWidth;
|
|
property Height: Double read GetHeight write SetHeight;
|
|
procedure Offset(XOffset, YOffset: Double);
|
|
function PtIn(const Pt: TPdfPoint): Boolean;
|
|
|
|
class function New(Left, Top, Right, Bottom: Double): TPdfRect; static;
|
|
class function Empty: TPdfRect; static;
|
|
public
|
|
case Integer of
|
|
0: (Left, Top, Right, Bottom: Double);
|
|
1: (TopLeft: TPdfPoint; BottomRight: TPdfPoint);
|
|
end;
|
|
|
|
TPdfRectArray = array of TPdfRect;
|
|
TPdfFloatArray = array of FS_FLOAT;
|
|
|
|
TPdfDocumentCustomReadProc = function(Param: Pointer; Position: LongWord; Buffer: PByte; Size: LongWord): Boolean;
|
|
|
|
TPdfNamedActionType = (
|
|
naPrint,
|
|
naNextPage,
|
|
naPrevPage,
|
|
naFirstPage,
|
|
naLastPage
|
|
);
|
|
|
|
TPdfPageRenderOptionType = (
|
|
proAnnotations, // Set if annotations are to be rendered.
|
|
proLCDOptimized, // Set if using text rendering optimized for LCD display.
|
|
proNoNativeText, // Don't use the native text output available on some platforms
|
|
proNoCatch, // Set if you don't want to catch exception.
|
|
proLimitedImageCacheSize, // Limit image cache size.
|
|
proForceHalftone, // Always use halftone for image stretching.
|
|
proPrinting, // Render for printing.
|
|
proReverseByteOrder // Set whether render in a reverse Byte order, this flag only enable when render to a bitmap.
|
|
);
|
|
TPdfPageRenderOptions = set of TPdfPageRenderOptionType;
|
|
|
|
TPdfPageRotation = (
|
|
prNormal = 0,
|
|
pr90Clockwise = 1,
|
|
pr180 = 2,
|
|
pr90CounterClockwide = 3
|
|
);
|
|
|
|
TPdfDocumentSaveOption = (
|
|
dsoIncremental = 1,
|
|
dsoNoIncremental = 2,
|
|
dsoRemoveSecurity = 3
|
|
);
|
|
|
|
TPdfDocumentLoadOption = (
|
|
dloDefault, // load the file by using PDFium's file load mechanism (file stays open)
|
|
dloMemory, // load the whole file into memory
|
|
dloMMF, // load the file by using a memory mapped file (file stays open)
|
|
dloOnDemand // load the file using the custom load function (file stays open)
|
|
);
|
|
|
|
TPdfDocumentPageMode = (
|
|
dpmUnknown = -1, // Unknown value
|
|
dpmUseNone = 0, // Neither document outline nor thumbnail images visible
|
|
dpmUseOutlines = 1, // Document outline visible
|
|
dpmUseThumbs = 2, // Thumbnial images visible
|
|
dpmFullScreen = 3, // Full-screen mode, with no menu bar, window controls, or any other window visible
|
|
dpmUseOC = 4, // Optional content group panel visible
|
|
dpmUseAttachments = 5 // Attachments panel visible
|
|
);
|
|
|
|
TPdfPrintMode = (
|
|
pmEMF = FPDF_PRINTMODE_EMF,
|
|
pmTextMode = FPDF_PRINTMODE_TEXTONLY,
|
|
pmPostScript2 = FPDF_PRINTMODE_POSTSCRIPT2,
|
|
pmPostScript3 = FPDF_PRINTMODE_POSTSCRIPT3,
|
|
pmPostScriptPassThrough2 = FPDF_PRINTMODE_POSTSCRIPT2_PASSTHROUGH,
|
|
pmPostScriptPassThrough3 = FPDF_PRINTMODE_POSTSCRIPT3_PASSTHROUGH,
|
|
pmEMFImageMasks = FPDF_PRINTMODE_EMF_IMAGE_MASKS,
|
|
pmPostScript3Type42 = FPDF_PRINTMODE_POSTSCRIPT3_TYPE42,
|
|
pmPostScript3Type42PassThrough = FPDF_PRINTMODE_POSTSCRIPT3_TYPE42_PASSTHROUGH
|
|
);
|
|
|
|
TPdfFileIdType = (
|
|
pfiPermanent = 0,
|
|
pfiChanging = 1
|
|
);
|
|
|
|
TPdfBitmapFormat = (
|
|
bfGrays = FPDFBitmap_Gray, // Gray scale bitmap, one byte per pixel.
|
|
bfBGR = FPDFBitmap_BGR, // 3 bytes per pixel, byte order: blue, green, red.
|
|
bfBGRx = FPDFBitmap_BGRx, // 4 bytes per pixel, byte order: blue, green, red, unused.
|
|
bfBGRA = FPDFBitmap_BGRA // 4 bytes per pixel, byte order: blue, green, red, alpha.
|
|
);
|
|
|
|
TPdfFormFieldType = (
|
|
fftUnknown = FPDF_FORMFIELD_UNKNOWN,
|
|
fftPushButton = FPDF_FORMFIELD_PUSHBUTTON,
|
|
fftCheckBox = FPDF_FORMFIELD_CHECKBOX,
|
|
fftRadioButton = FPDF_FORMFIELD_RADIOBUTTON,
|
|
fftComboBox = FPDF_FORMFIELD_COMBOBOX,
|
|
fftListBox = FPDF_FORMFIELD_LISTBOX,
|
|
fftTextField = FPDF_FORMFIELD_TEXTFIELD,
|
|
fftSignature = FPDF_FORMFIELD_SIGNATURE,
|
|
|
|
fftXFA = FPDF_FORMFIELD_XFA,
|
|
fftXFACheckBox = FPDF_FORMFIELD_XFA_CHECKBOX,
|
|
fftXFAComboBox = FPDF_FORMFIELD_XFA_COMBOBOX,
|
|
fftXFAImageField = FPDF_FORMFIELD_XFA_IMAGEFIELD,
|
|
fftXFAListBox = FPDF_FORMFIELD_XFA_LISTBOX,
|
|
fftXFAPushButton = FPDF_FORMFIELD_XFA_PUSHBUTTON,
|
|
fftXFASignature = FPDF_FORMFIELD_XFA_SIGNATURE,
|
|
fftXfaTextField = FPDF_FORMFIELD_XFA_TEXTFIELD
|
|
);
|
|
|
|
TPdfFormFieldFlagsType = (
|
|
fffReadOnly,
|
|
fffRequired,
|
|
fffNoExport,
|
|
|
|
fffTextMultiLine,
|
|
fffTextPassword,
|
|
|
|
fffChoiceCombo,
|
|
fffChoiceEdit,
|
|
fffChoiceMultiSelect
|
|
);
|
|
TPdfFormFieldFlags = set of TPdfFormFieldFlagsType;
|
|
|
|
TPdfObjectType = (
|
|
otUnknown = FPDF_OBJECT_UNKNOWN,
|
|
otBoolean = FPDF_OBJECT_BOOLEAN,
|
|
otNumber = FPDF_OBJECT_NUMBER,
|
|
otString = FPDF_OBJECT_STRING,
|
|
otName = FPDF_OBJECT_NAME,
|
|
otArray = FPDF_OBJECT_ARRAY,
|
|
otDictinary = FPDF_OBJECT_DICTIONARY,
|
|
otStream = FPDF_OBJECT_STREAM,
|
|
otNullObj = FPDF_OBJECT_NULLOBJ,
|
|
otReference = FPDF_OBJECT_REFERENCE
|
|
);
|
|
|
|
TPdfAnnotationLinkType = (
|
|
altUnsupported = PDFACTION_UNSUPPORTED, // Unsupported action type.
|
|
altGoto = PDFACTION_GOTO, // Go to a destination within current document.
|
|
altRemoteGoto = PDFACTION_REMOTEGOTO, // Go to a destination within another document.
|
|
altURI = PDFACTION_URI, // Universal Resource Identifier, including web pages and
|
|
// other Internet based resources.
|
|
altLaunch = PDFACTION_LAUNCH, // Launch an application or open a file.
|
|
altEmbeddedGoto = PDFACTION_EMBEDDEDGOTO // Go to a destination in an embedded file.
|
|
);
|
|
|
|
TPdfLinkGotoDestinationViewKind = (
|
|
lgdvUnknown = PDFDEST_VIEW_UNKNOWN_MODE,
|
|
lgdvXYZ = PDFDEST_VIEW_XYZ,
|
|
lgdvFit = PDFDEST_VIEW_FIT,
|
|
lgdvFitH = PDFDEST_VIEW_FITH,
|
|
lgdvFitV = PDFDEST_VIEW_FITV,
|
|
lgdvFitR = PDFDEST_VIEW_FITR,
|
|
lgdvFitB = PDFDEST_VIEW_FITB,
|
|
lgdvFitBH = PDFDEST_VIEW_FITBH,
|
|
lgdvFitBV = PDFDEST_VIEW_FITBV
|
|
);
|
|
|
|
// Make the TObject.Create constructor private to hide it, so that the TPdfBitmap.Create
|
|
// overloads won't allow calling TObject.Create.
|
|
_TPdfBitmapHideCtor = class(TObject)
|
|
private
|
|
constructor Create;
|
|
end;
|
|
|
|
TPdfBitmap = class(_TPdfBitmapHideCtor)
|
|
private
|
|
FBitmap: FPDF_BITMAP;
|
|
FOwnsBitmap: Boolean;
|
|
FWidth: Integer;
|
|
FHeight: Integer;
|
|
FBytesPerScanLine: Integer;
|
|
public
|
|
constructor Create(ABitmap: FPDF_BITMAP; AOwnsBitmap: Boolean = False); overload;
|
|
constructor Create(AWidth, AHeight: Integer; AAlpha: Boolean); overload;
|
|
constructor Create(AWidth, AHeight: Integer; AFormat: TPdfBitmapFormat); overload;
|
|
constructor Create(AWidth, AHeight: Integer; AFormat: TPdfBitmapFormat; ABuffer: Pointer; ABytesPerScanline: Integer); overload;
|
|
destructor Destroy; override;
|
|
|
|
procedure FillRect(ALeft, ATop, AWidth, AHeight: Integer; AColor: FPDF_DWORD);
|
|
function GetBuffer: Pointer;
|
|
|
|
property Width: Integer read FWidth;
|
|
property Height: Integer read FHeight;
|
|
property BytesPerScanline: Integer read FBytesPerScanLine;
|
|
property Bitmap: FPDF_BITMAP read FBitmap;
|
|
end;
|
|
|
|
PPdfFormFillHandler = ^TPdfFormFillHandler;
|
|
TPdfFormFillHandler = record
|
|
FormFillInfo: FPDF_FORMFILLINFO;
|
|
Document: TPdfDocument;
|
|
end;
|
|
|
|
TPdfFormField = class(TObject)
|
|
private
|
|
FPage: TPdfPage;
|
|
FHandle: FPDF_ANNOTATION;
|
|
FAnnotation: TPdfAnnotation;
|
|
function GetFlags: TPdfFormFieldFlags;
|
|
function GetReadOnly: Boolean;
|
|
function GetName: string;
|
|
function GetAlternateName: string;
|
|
function GetFieldType: TPdfFormFieldType;
|
|
function GetValue: string;
|
|
function GetExportValue: string;
|
|
function GetOptionCount: Integer;
|
|
function GetOptionLabel(Index: Integer): string;
|
|
function GetChecked: Boolean;
|
|
function GetControlIndex: Integer;
|
|
function GetControlCount: Integer;
|
|
|
|
procedure SetValue(const Value: string);
|
|
procedure SetChecked(const Value: Boolean);
|
|
protected
|
|
constructor Create(AAnnotation: TPdfAnnotation);
|
|
|
|
function BeginEditFormField: FPDF_ANNOTATION;
|
|
procedure EndEditFormField(LastFocusedAnnot: FPDF_ANNOTATION);
|
|
public
|
|
destructor Destroy; override;
|
|
|
|
function IsXFAFormField: Boolean;
|
|
|
|
function IsOptionSelected(OptionIndex: Integer): Boolean;
|
|
function SelectComboBoxOption(OptionIndex: Integer): Boolean;
|
|
function SelectListBoxOption(OptionIndex: Integer; Selected: Boolean = True): Boolean;
|
|
|
|
property Flags: TPdfFormFieldFlags read GetFlags;
|
|
property ReadOnly: Boolean read GetReadOnly;
|
|
property Name: string read GetName;
|
|
property AlternateName: string read GetAlternateName;
|
|
property FieldType: TPdfFormFieldType read GetFieldType;
|
|
property Value: string read GetValue write SetValue;
|
|
property ExportValue: string read GetExportValue;
|
|
|
|
// ComboBox/ListBox
|
|
property OptionCount: Integer read GetOptionCount;
|
|
property OptionLabels[Index: Integer]: string read GetOptionLabel;
|
|
|
|
// CheckBox/RadioButton
|
|
property Checked: Boolean read GetChecked write SetChecked;
|
|
property ControlIndex: Integer read GetControlIndex;
|
|
property ControlCount: Integer read GetControlCount;
|
|
|
|
property Annotation: TPdfAnnotation read FAnnotation;
|
|
property Handle: FPDF_ANNOTATION read FHandle;
|
|
end;
|
|
|
|
TPdfFormFieldList = class(TObject)
|
|
private
|
|
FItems: TList;
|
|
function GetCount: Integer;
|
|
function GetItem(Index: Integer): TPdfFormField;
|
|
protected
|
|
procedure DestroyingItem(Item: TPdfFormField);
|
|
public
|
|
constructor Create(AAnnotations: TPdfAnnotationList);
|
|
destructor Destroy; override;
|
|
|
|
property Count: Integer read GetCount;
|
|
property Items[Index: Integer]: TPdfFormField read GetItem; default;
|
|
end;
|
|
|
|
TPdfLinkGotoDestination = class(TObject)
|
|
private
|
|
FPageIndex: Integer;
|
|
FXValid: Boolean;
|
|
FYValid: Boolean;
|
|
FZoomValid: Boolean;
|
|
FX: Single;
|
|
FY: Single;
|
|
FZoom: Single;
|
|
FViewKind: TPdfLinkGotoDestinationViewKind;
|
|
FViewParams: TPdfFloatArray;
|
|
public
|
|
constructor Create(APageIndex: Integer; AXValid, AYValid, AZoomValid: Boolean; AX, AY, AZoom: Single;
|
|
AViewKind: TPdfLinkGotoDestinationViewKind; const AViewParams: TPdfFloatArray);
|
|
|
|
property PageIndex: Integer read FPageIndex;
|
|
|
|
property XValid: Boolean read FXValid;
|
|
property YValid: Boolean read FYValid;
|
|
property ZoomValid: Boolean read FZoomValid;
|
|
|
|
property X: Single read FX;
|
|
property Y: Single read FY;
|
|
property Zoom: Single read FZoom;
|
|
|
|
property ViewKind: TPdfLinkGotoDestinationViewKind read FViewKind;
|
|
property ViewParams: TPdfFloatArray read FViewParams;
|
|
end;
|
|
|
|
TPdfAnnotation = class(TObject)
|
|
private
|
|
FPage: TPdfPage;
|
|
FHandle: FPDF_ANNOTATION;
|
|
FFormField: TPdfFormField;
|
|
FSubType: FPDF_ANNOTATION_SUBTYPE;
|
|
FLinkDest: FPDF_DEST;
|
|
FLinkType: TPdfAnnotationLinkType;
|
|
|
|
function GetPdfLinkAction: FPDF_ACTION;
|
|
function GetFormField: TPdfFormField;
|
|
function GetLinkUri: string;
|
|
function GetAnnotationRect: TPdfRect;
|
|
function GetLinkFileName: string;
|
|
protected
|
|
constructor Create(APage: TPdfPage; AHandle: FPDF_ANNOTATION);
|
|
public
|
|
destructor Destroy; override;
|
|
|
|
function IsFormField: Boolean;
|
|
function IsLink: Boolean;
|
|
|
|
function GetLinkGotoDestination(var LinkGotoDestination: TPdfLinkGotoDestination; ARemoteDocument: TPdfDocument = nil): Boolean;
|
|
|
|
// IsFormField:
|
|
property FormField: TPdfFormField read GetFormField;
|
|
// IsLink:
|
|
property LinkType: TPdfAnnotationLinkType read FLinkType;
|
|
property LinkUri: string read GetLinkUri;
|
|
property LinkFileName: string read GetLinkFileName;
|
|
|
|
property AnnotationRect: TPdfRect read GetAnnotationRect;
|
|
property Handle: FPDF_ANNOTATION read FHandle;
|
|
end;
|
|
|
|
TPdfAnnotationList = class(TObject)
|
|
private
|
|
FPage: TPdfPage;
|
|
FItems: TObjectList;
|
|
FFormFields: TPdfFormFieldList;
|
|
function GetCount: Integer;
|
|
function GetItem(Index: Integer): TPdfAnnotation;
|
|
function GetFormFields: TPdfFormFieldList;
|
|
function GetAnnotationsLoaded: Boolean;
|
|
protected
|
|
procedure DestroyingItem(Item: TPdfAnnotation);
|
|
procedure DestroyingFormField(FormField: TPdfFormField);
|
|
function FindLink(Link: FPDF_LINK): TPdfAnnotation;
|
|
public
|
|
constructor Create(APage: TPdfPage);
|
|
destructor Destroy; override;
|
|
procedure CloseAnnotations;
|
|
{ NewTextAnnotation creates a new text annotation on the page. After adding one or more
|
|
annotations you must call Page.ApplyChanges to show them and make the persist before
|
|
saving the file. R is in page coordinates. }
|
|
function NewTextAnnotation(const Text: string; const R: TPdfRect): Boolean; {experimental;}
|
|
|
|
property AnnotationsLoaded: Boolean read GetAnnotationsLoaded;
|
|
|
|
property Count: Integer read GetCount;
|
|
property Items[Index: Integer]: TPdfAnnotation read GetItem; default;
|
|
|
|
{ A list of all form field annotations }
|
|
property FormFields: TPdfFormFieldList read GetFormFields;
|
|
end;
|
|
|
|
TPdfLinkInfo = class(TObject)
|
|
private
|
|
FLinkAnnotation: TPdfAnnotation;
|
|
FWebLinkUrl: string;
|
|
function GetLinkFileName: string;
|
|
function GetLinkType: TPdfAnnotationLinkType;
|
|
function GetLinkUri: string;
|
|
public
|
|
constructor Create(ALinkAnnotation: TPdfAnnotation; const AWebLinkUrl: string);
|
|
function GetLinkGotoDestination(var LinkGotoDestination: TPdfLinkGotoDestination; ARemoteDocument: TPdfDocument = nil): Boolean;
|
|
|
|
function IsAnnontation: Boolean;
|
|
function IsWebLink: Boolean;
|
|
|
|
property LinkType: TPdfAnnotationLinkType read GetLinkType;
|
|
property LinkUri: string read GetLinkUri;
|
|
property LinkFileName: string read GetLinkFileName;
|
|
|
|
property LinkAnnotation: TPdfAnnotation read FLinkAnnotation;
|
|
end;
|
|
|
|
{ TPdfPageWebLinksInfo caches all the WebLinks for one page. This makes the IsWebLinkAt() methods
|
|
much faster than always calling into the PDFium library. The URLs are not cached. }
|
|
TPdfPageWebLinksInfo = class(TObject)
|
|
private
|
|
FPage: TPdfPage;
|
|
FWebLinksRects: array of TPdfRectArray;
|
|
procedure GetPageWebLinks;
|
|
function GetWebLinkIndex(X, Y: Double): Integer;
|
|
|
|
function GetCount: Integer;
|
|
function GetRect(Index: Integer): TPdfRectArray;
|
|
function GetURL(Index: Integer): string;
|
|
public
|
|
constructor Create(APage: TPdfPage);
|
|
|
|
function IsWebLinkAt(X, Y: Double): Boolean; overload;
|
|
function IsWebLinkAt(X, Y: Double; var Url: string): Boolean; overload;
|
|
|
|
property Count: Integer read GetCount;
|
|
property URLs[Index: Integer]: string read GetURL;
|
|
property Rects[Index: Integer]: TPdfRectArray read GetRect;
|
|
end;
|
|
|
|
TPdfPage = class(TObject)
|
|
private
|
|
FDocument: TPdfDocument;
|
|
FPage: FPDF_PAGE;
|
|
FWidth: Single;
|
|
FHeight: Single;
|
|
FTransparency: Boolean;
|
|
FRotation: TPdfPageRotation;
|
|
FAnnotations: TPdfAnnotationList;
|
|
FTextHandle: FPDF_TEXTPAGE;
|
|
FSearchHandle: FPDF_SCHHANDLE;
|
|
FPageLinkHandle: FPDF_PAGELINK;
|
|
constructor Create(ADocument: TPdfDocument; APage: FPDF_PAGE);
|
|
procedure UpdateMetrics;
|
|
procedure Open;
|
|
procedure SetRotation(const Value: TPdfPageRotation);
|
|
function BeginText: Boolean;
|
|
function BeginWebLinks: Boolean;
|
|
class function GetDrawFlags(const Options: TPdfPageRenderOptions): Integer; static;
|
|
procedure AfterOpen;
|
|
function IsValidForm: Boolean;
|
|
function ShiftStateToModifier(const Shift: TShiftState): Integer;
|
|
function GetHandle: FPDF_PAGE;
|
|
function GetTextHandle: FPDF_TEXTPAGE;
|
|
function GetFormFields: TPdfFormFieldList;
|
|
protected
|
|
function GetPdfActionFilePath(Action: FPDF_ACTION): string;
|
|
function GetPdfActionUriPath(Action: FPDF_ACTION): string;
|
|
public
|
|
destructor Destroy; override;
|
|
procedure Close;
|
|
function IsLoaded: Boolean;
|
|
|
|
{$IFDEF MSWINDOWS}
|
|
// Draw the PDF page and the form into the device context.
|
|
procedure Draw(DC: HDC; X, Y, Width, Height: Integer; Rotate: TPdfPageRotation = prNormal;
|
|
const Options: TPdfPageRenderOptions = []; PageBackground: TColorRef = $FFFFFF); overload;
|
|
{$ENDIF MSWINDOWS}
|
|
// Draw the PDF page and the form into the bitmap.
|
|
procedure Draw(APdfBitmap: TPdfBitmap; X, Y, Width, Height: Integer; Rotate: TPdfPageRotation = prNormal;
|
|
const Options: TPdfPageRenderOptions = []; PageBackground: TColorRef = $FFFFFF); overload;
|
|
|
|
// Draw the PDF page without the form field values into the bitmap.
|
|
procedure DrawToPdfBitmap(APdfBitmap: TPdfBitmap; X, Y, Width, Height: Integer; Rotate: TPdfPageRotation = prNormal;
|
|
const Options: TPdfPageRenderOptions = []);
|
|
// Draw the PDF form field values into the bitmap.
|
|
procedure DrawFormToPdfBitmap(APdfBitmap: TPdfBitmap; X, Y, Width, Height: Integer; Rotate: TPdfPageRotation = prNormal;
|
|
const Options: TPdfPageRenderOptions = []);
|
|
|
|
function DeviceToPage(X, Y, Width, Height: Integer; DeviceX, DeviceY: Integer; Rotate: TPdfPageRotation = prNormal): TPdfPoint; overload;
|
|
function PageToDevice(X, Y, Width, Height: Integer; PageX, PageY: Double; Rotate: TPdfPageRotation = prNormal): TPoint; overload;
|
|
function DeviceToPage(X, Y, Width, Height: Integer; const R: TRect; Rotate: TPdfPageRotation = prNormal): TPdfRect; overload;
|
|
function PageToDevice(X, Y, Width, Height: Integer; const R: TPdfRect; Rotate: TPdfPageRotation = prNormal): TRect; overload;
|
|
|
|
procedure ApplyChanges;
|
|
procedure Flatten(AFlatPrint: Boolean);
|
|
|
|
function FormEventFocus(const Shift: TShiftState; PageX, PageY: Double): Boolean;
|
|
function FormEventMouseWheel(const Shift: TShiftState; WheelDelta: Integer; PageX, PageY: Double): Boolean;
|
|
function FormEventMouseMove(const Shift: TShiftState; PageX, PageY: Double): Boolean;
|
|
function FormEventLButtonDown(const Shift: TShiftState; PageX, PageY: Double): Boolean;
|
|
function FormEventLButtonUp(const Shift: TShiftState; PageX, PageY: Double): Boolean;
|
|
function FormEventRButtonDown(const Shift: TShiftState; PageX, PageY: Double): Boolean;
|
|
function FormEventRButtonUp(const Shift: TShiftState; PageX, PageY: Double): Boolean;
|
|
function FormEventKeyDown(KeyCode: Word; const Shift: TShiftState): Boolean;
|
|
function FormEventKeyUp(KeyCode: Word; const Shift: TShiftState): Boolean;
|
|
function FormEventKeyPress(Key: Word; const Shift: TShiftState): Boolean;
|
|
function FormEventKillFocus: Boolean;
|
|
function FormGetFocusedText: string;
|
|
function FormGetSelectedText: string;
|
|
function FormReplaceSelection(const ANewText: string): Boolean;
|
|
function FormReplaceAndKeepSelection(const ANewText: string): Boolean;
|
|
function FormSelectAllText: Boolean;
|
|
function FormCanUndo: Boolean;
|
|
function FormCanRedo: Boolean;
|
|
function FormUndo: Boolean;
|
|
function FormRedo: Boolean;
|
|
|
|
function BeginFind(const SearchString: string; MatchCase, MatchWholeWord: Boolean; FromEnd: Boolean): Boolean;
|
|
function FindNext(var CharIndex, Count: Integer): Boolean;
|
|
function FindPrev(var CharIndex, Count: Integer): Boolean;
|
|
procedure EndFind;
|
|
|
|
function GetCharCount: Integer;
|
|
function ReadChar(CharIndex: Integer): WideChar;
|
|
function GetCharFontSize(CharIndex: Integer): Double;
|
|
function GetCharBox(CharIndex: Integer): TPdfRect;
|
|
function GetCharIndexAt(PageX, PageY, ToleranceX, ToleranceY: Double): Integer;
|
|
function ReadText(CharIndex, Count: Integer): string;
|
|
function GetTextAt(const R: TPdfRect): string; overload;
|
|
function GetTextAt(Left, Top, Right, Bottom: Double): string; overload;
|
|
|
|
function GetTextRectCount(CharIndex, Count: Integer): Integer;
|
|
function GetTextRect(RectIndex: Integer): TPdfRect;
|
|
|
|
function HasFormFieldAtPoint(X, Y: Double): TPdfFormFieldType;
|
|
|
|
{ IsUriLinkAtPoint returns true if a Link annotation is at the specified coordinates.
|
|
X, Y are in page coordinates. }
|
|
function IsUriLinkAtPoint(X, Y: Double): Boolean; overload;
|
|
{ IsUriLinkAtPoint returns true if a Link annotation is at the specified coordinates. If one is found
|
|
the Uri parameter is set to the link's URI.
|
|
X, Y are in page coordinates. }
|
|
function IsUriLinkAtPoint(X, Y: Double; var Uri: string): Boolean; overload;
|
|
{ GetLinkAtPoint returns the link annotation for the specified coordinates. If no link annotation
|
|
was found it return nil. It not only returns Uri but also Goto, RemoteGoto, Launch, EmbeddedGoto
|
|
link annotations. }
|
|
function GetLinkAtPoint(X, Y: Double): TPdfAnnotation;
|
|
|
|
{ WebLinks are URLs that are parsed from the PDFs text content. No link annotation exists
|
|
for them, so the IsUriLinkAtPoint and GetLinkAtPoint methods don't work for them. }
|
|
function GetWebLinkCount: Integer;
|
|
function GetWebLinkURL(LinkIndex: Integer): string;
|
|
function GetWebLinkRectCount(LinkIndex: Integer): Integer;
|
|
function GetWebLinkRect(LinkIndex, RectIndex: Integer): TPdfRect;
|
|
function IsWebLinkAtPoint(X, Y: Double): Boolean; overload;
|
|
function IsWebLinkAtPoint(X, Y: Double; var URL: string): Boolean; overload;
|
|
|
|
property Handle: FPDF_PAGE read GetHandle;
|
|
property TextHandle: FPDF_TEXTPAGE read GetTextHandle;
|
|
|
|
property Width: Single read FWidth;
|
|
property Height: Single read FHeight;
|
|
property Transparency: Boolean read FTransparency;
|
|
property Rotation: TPdfPageRotation read FRotation write SetRotation;
|
|
|
|
property Annotations: TPdfAnnotationList read FAnnotations;
|
|
property FormFields: TPdfFormFieldList read GetFormFields;
|
|
end;
|
|
|
|
TPdfFormInvalidateEvent = procedure(Document: TPdfDocument; Page: TPdfPage; const PageRect: TPdfRect) of object;
|
|
TPdfFormOutputSelectedRectEvent = procedure(Document: TPdfDocument; Page: TPdfPage; const PageRect: TPdfRect) of object;
|
|
TPdfFormGetCurrentPageEvent = procedure(Document: TPdfDocument; var CurrentPage: TPdfPage) of object;
|
|
TPdfFormFieldFocusEvent = procedure(Document: TPdfDocument; Value: PWideChar; ValueLen: Integer; FieldFocused: Boolean) of object;
|
|
TPdfExecuteNamedActionEvent = procedure(Document: TPdfDocument; NamedAction: TPdfNamedActionType) of object;
|
|
|
|
TPdfAttachment = record
|
|
private
|
|
FDocument: TPdfDocument;
|
|
FHandle: FPDF_ATTACHMENT;
|
|
procedure CheckValid;
|
|
|
|
function GetName: string;
|
|
function GetKeyValue(const Key: string): string;
|
|
procedure SetKeyValue(const Key, Value: string);
|
|
function GetContentSize: Integer;
|
|
public
|
|
// SetContent/LoadFromXxx clears the Values[] dictionary.
|
|
procedure SetContent(const ABytes: TBytes); overload;
|
|
procedure SetContent(const ABytes: TBytes; Index: NativeInt; Count: Integer); overload;
|
|
procedure SetContent(ABytes: PByte; Count: Integer); overload;
|
|
procedure SetContent(const Value: RawByteString); overload;
|
|
procedure SetContent(const Value: string; Encoding: TEncoding = nil); overload; // Default-encoding is UTF-8
|
|
procedure LoadFromStream(Stream: TStream);
|
|
procedure LoadFromFile(const FileName: string);
|
|
|
|
procedure GetContent(var ABytes: TBytes); overload;
|
|
procedure GetContent(Buffer: PByte); overload; // use ContentSize to allocate enough memory
|
|
procedure GetContent(var Value: RawByteString); overload;
|
|
procedure GetContent(var Value: string; Encoding: TEncoding = nil); overload;
|
|
function GetContentAsBytes: TBytes;
|
|
function GetContentAsRawByteString: RawByteString;
|
|
function GetContentAsString(Encoding: TEncoding = nil): string; // Default-encoding is UTF-8
|
|
|
|
procedure SaveToStream(Stream: TStream);
|
|
procedure SaveToFile(const FileName: string);
|
|
|
|
function HasContent: Boolean;
|
|
|
|
function HasKey(const Key: string): Boolean;
|
|
function GetValueType(const Key: string): TPdfObjectType;
|
|
|
|
property Name: string read GetName;
|
|
property Values[const Key: string]: string read GetKeyValue write SetKeyValue;
|
|
property ContentSize: Integer read GetContentSize;
|
|
|
|
property Handle: FPDF_ATTACHMENT read FHandle;
|
|
end;
|
|
|
|
TPdfAttachmentList = class(TObject)
|
|
private
|
|
FDocument: TPdfDocument;
|
|
function GetCount: Integer;
|
|
function GetItem(Index: Integer): TPdfAttachment;
|
|
public
|
|
constructor Create(ADocument: TPdfDocument);
|
|
|
|
function Add(const Name: string): TPdfAttachment;
|
|
procedure Delete(Index: Integer);
|
|
function IndexOf(const Name: string): Integer;
|
|
|
|
property Count: Integer read GetCount;
|
|
property Items[Index: Integer]: TPdfAttachment read GetItem; default;
|
|
end;
|
|
|
|
TPdfDocument = class(TObject)
|
|
private type
|
|
PCustomLoadDataRec = ^TCustomLoadDataRec;
|
|
TCustomLoadDataRec = record
|
|
Param: Pointer;
|
|
GetBlock: TPdfDocumentCustomReadProc;
|
|
FileAccess: TFPDFFileAccess;
|
|
end;
|
|
private
|
|
FDocument: FPDF_DOCUMENT;
|
|
FPages: TObjectList;
|
|
FAttachments: TPdfAttachmentList;
|
|
FFileName: string;
|
|
{$IFDEF MSWINDOWS}
|
|
FFileHandle: THandle;
|
|
FFileMapping: THandle;
|
|
{$ELSE}
|
|
FFileStream: TFileStream;
|
|
{$ENDIF MSWINDOWS}
|
|
FBuffer: PByte;
|
|
FBytes: TBytes;
|
|
FClosing: Boolean;
|
|
FUnsupportedFeatures: Boolean;
|
|
FCustomLoadData: PCustomLoadDataRec;
|
|
|
|
FForm: FPDF_FORMHANDLE;
|
|
FJSPlatform: IPDF_JsPlatform;
|
|
FFormFillHandler: TPdfFormFillHandler;
|
|
FFormFieldHighlightColor: TColorRef;
|
|
FFormFieldHighlightAlpha: Integer;
|
|
FPrintHidesFormFieldHighlight: Boolean;
|
|
FFormModified: Boolean;
|
|
|
|
FOnFormInvalidate: TPdfFormInvalidateEvent;
|
|
FOnFormOutputSelectedRect: TPdfFormOutputSelectedRectEvent;
|
|
FOnFormGetCurrentPage: TPdfFormGetCurrentPageEvent;
|
|
FOnFormFieldFocus: TPdfFormFieldFocusEvent;
|
|
FOnExecuteNamedAction: TPdfExecuteNamedActionEvent;
|
|
|
|
procedure InternLoadFromFile(const FileName: string; const Password: UTF8String);
|
|
procedure InternLoadFromMem(Buffer: PByte; Size: NativeInt; const Password: UTF8String);
|
|
procedure InternLoadFromCustom(ReadFunc: TPdfDocumentCustomReadProc; Size: LongWord;
|
|
Param: Pointer; const Password: UTF8String);
|
|
function InternImportPages(Source: TPdfDocument; PageIndices: PInteger; PageIndicesCount: Integer;
|
|
const Range: AnsiString; Index: Integer; ImportByRange: Boolean): Boolean;
|
|
function GetPage(Index: Integer): TPdfPage;
|
|
function GetPageCount: Integer;
|
|
procedure ExtractPage(APage: TPdfPage);
|
|
function ReloadPage(APage: TPdfPage): FPDF_PAGE;
|
|
function GetPrintScaling: Boolean;
|
|
function GetActive: Boolean;
|
|
procedure CheckActive;
|
|
function GetSecurityHandlerRevision: Integer;
|
|
function GetDocPermissions: Integer;
|
|
function GetFileVersion: Integer;
|
|
function GetPageSize(Index: Integer): TPdfPoint;
|
|
function GetPageMode: TPdfDocumentPageMode;
|
|
function GetNumCopies: Integer;
|
|
procedure DocumentLoaded;
|
|
procedure SetFormFieldHighlightAlpha(Value: Integer);
|
|
procedure SetFormFieldHighlightColor(const Value: TColorRef);
|
|
function FindPage(Page: FPDF_PAGE): TPdfPage;
|
|
procedure UpdateFormFieldHighlight;
|
|
public
|
|
constructor Create;
|
|
destructor Destroy; override;
|
|
|
|
procedure LoadFromCustom(ReadFunc: TPdfDocumentCustomReadProc; Size: LongWord; Param: Pointer; const Password: UTF8String = '');
|
|
procedure LoadFromActiveStream(Stream: TStream; const Password: UTF8String = ''); // Stream must not be released until the document is closed
|
|
procedure LoadFromActiveBuffer(Buffer: Pointer; Size: NativeInt; const Password: UTF8String = ''); // Buffer must not be released until the document is closed
|
|
procedure LoadFromBytes(const Bytes: TBytes; const Password: UTF8String = ''); overload;
|
|
procedure LoadFromBytes(const Bytes: TBytes; Index: NativeInt; Count: NativeInt; const Password: UTF8String = ''); overload;
|
|
procedure LoadFromStream(Stream: TStream; const Password: UTF8String = '');
|
|
procedure LoadFromFile(const FileName: string; const Password: UTF8String = ''; LoadOption: TPdfDocumentLoadOption = dloDefault);
|
|
procedure Close;
|
|
|
|
procedure SaveToFile(const AFileName: string; Option: TPdfDocumentSaveOption = dsoRemoveSecurity; FileVersion: Integer = -1);
|
|
procedure SaveToStream(Stream: TStream; Option: TPdfDocumentSaveOption = dsoRemoveSecurity; FileVersion: Integer = -1);
|
|
procedure SaveToBytes(var Bytes: TBytes; Option: TPdfDocumentSaveOption = dsoRemoveSecurity; FileVersion: Integer = -1);
|
|
|
|
function NewDocument: Boolean;
|
|
class function CreateNPagesOnOnePageDocument(Source: TPdfDocument; NewPageWidth, NewPageHeight: Double; NumPagesXAxis, NumPagesYAxis: Integer): TPdfDocument; overload;
|
|
class function CreateNPagesOnOnePageDocument(Source: TPdfDocument; NumPagesXAxis, NumPagesYAxis: Integer): TPdfDocument; overload;
|
|
function ImportAllPages(Source: TPdfDocument; Index: Integer = -1): Boolean;
|
|
function ImportPages(Source: TPdfDocument; const Range: string = ''; Index: Integer = -1): Boolean;
|
|
function ImportPageRange(Source: TPdfDocument; PageIndex: Integer; Count: Integer = -1; Index: Integer = -1): Boolean;
|
|
function ImportPagesByIndex(Source: TPdfDocument; const PageIndices: array of Integer; Index: Integer = -1): Boolean;
|
|
procedure DeletePage(Index: Integer);
|
|
function NewPage(Width, Height: Double; Index: Integer = -1): TPdfPage; overload;
|
|
function NewPage(Index: Integer = -1): TPdfPage; overload;
|
|
function ApplyViewerPreferences(Source: TPdfDocument): Boolean;
|
|
function IsPageLoaded(PageIndex: Integer): Boolean;
|
|
|
|
function GetFileIdentifier(IdType: TPdfFileIdType): string;
|
|
function GetMetaText(const TagName: string): string;
|
|
|
|
class function SetPrintMode(PrintMode: TPdfPrintMode): Boolean; static;
|
|
|
|
property FileName: string read FFileName;
|
|
property PageCount: Integer read GetPageCount;
|
|
property Pages[Index: Integer]: TPdfPage read GetPage;
|
|
property PageSizes[Index: Integer]: TPdfPoint read GetPageSize;
|
|
|
|
property Attachments: TPdfAttachmentList read FAttachments;
|
|
|
|
property Active: Boolean read GetActive;
|
|
property PrintScaling: Boolean read GetPrintScaling;
|
|
property NumCopies: Integer read GetNumCopies;
|
|
property SecurityHandlerRevision: Integer read GetSecurityHandlerRevision;
|
|
property DocPermissions: Integer read GetDocPermissions;
|
|
property FileVersion: Integer read GetFileVersion;
|
|
property PageMode: TPdfDocumentPageMode read GetPageMode;
|
|
|
|
// if UnsupportedFeatures is True, then the document has unsupported features. It is updated
|
|
// after accessing a page.
|
|
property UnsupportedFeatures: Boolean read FUnsupportedFeatures;
|
|
property Handle: FPDF_DOCUMENT read FDocument;
|
|
property FormHandle: FPDF_FORMHANDLE read FForm;
|
|
|
|
property FormFieldHighlightColor: TColorRef read FFormFieldHighlightColor write SetFormFieldHighlightColor default $FFE4DD;
|
|
property FormFieldHighlightAlpha: Integer read FFormFieldHighlightAlpha write SetFormFieldHighlightAlpha default 100;
|
|
property PrintHidesFormFieldHighlight: Boolean read FPrintHidesFormFieldHighlight write FPrintHidesFormFieldHighlight default True;
|
|
|
|
property FormModified: Boolean read FFormModified write FFormModified;
|
|
property OnFormInvalidate: TPdfFormInvalidateEvent read FOnFormInvalidate write FOnFormInvalidate;
|
|
property OnFormOutputSelectedRect: TPdfFormOutputSelectedRectEvent read FOnFormOutputSelectedRect write FOnFormOutputSelectedRect;
|
|
property OnFormGetCurrentPage: TPdfFormGetCurrentPageEvent read FOnFormGetCurrentPage write FOnFormGetCurrentPage;
|
|
property OnFormFieldFocus: TPdfFormFieldFocusEvent read FOnFormFieldFocus write FOnFormFieldFocus;
|
|
property OnExecuteNamedAction: TPdfExecuteNamedActionEvent read FOnExecuteNamedAction write FOnExecuteNamedAction;
|
|
end;
|
|
|
|
{$IFDEF MSWINDOWS}
|
|
TPdfDocumentPrinterStatusEvent = procedure(Sender: TObject; CurrentPageNum, PageCount: Integer) of object;
|
|
|
|
TPdfDocumentPrinter = class(TObject)
|
|
private
|
|
FBeginPrintCounter: Integer;
|
|
|
|
FPrinterDC: HDC;
|
|
FPrintPortraitOrientation: Boolean;
|
|
FPaperSize: TSize;
|
|
FPrintArea: TSize;
|
|
FMargins: TPoint;
|
|
|
|
FFitPageToPrintArea: Boolean;
|
|
FOnPrintStatus: TPdfDocumentPrinterStatusEvent;
|
|
|
|
function IsPortraitOrientation(AWidth, AHeight: Integer): Boolean;
|
|
procedure GetPrinterBounds;
|
|
protected
|
|
function PrinterStartDoc(const AJobTitle: string): Boolean; virtual; abstract;
|
|
procedure PrinterEndDoc; virtual; abstract;
|
|
procedure PrinterStartPage; virtual; abstract;
|
|
procedure PrinterEndPage; virtual; abstract;
|
|
function GetPrinterDC: HDC; virtual; abstract;
|
|
|
|
procedure InternPrintPage(APage: TPdfPage; X, Y, Width, Height: Double);
|
|
public
|
|
constructor Create;
|
|
|
|
{ BeginPrint must be called before printing multiple documents.
|
|
Returns false if the printer can't print. (e.g. The user aborted the PDF Printer's FileDialog) }
|
|
function BeginPrint(const AJobTitle: string = ''): Boolean;
|
|
{ EndPrint must be called after printing multiple documents were printed. }
|
|
procedure EndPrint;
|
|
|
|
{ Prints a range of PDF document pages (0..PageCount-1) }
|
|
function Print(ADocument: TPdfDocument; AFromPageIndex, AToPageIndex: Integer): Boolean; overload;
|
|
{ Prints all pages of the PDF document. }
|
|
function Print(ADocument: TPdfDocument): Boolean; overload;
|
|
|
|
|
|
{ If FitPageToPrintArea is true the page fill be scaled to fit into the printable area. }
|
|
property FitPageToPrintArea: Boolean read FFitPageToPrintArea write FFitPageToPrintArea default True;
|
|
|
|
{ OnPrintStatus is triggered after every printed page }
|
|
property OnPrintStatus: TPdfDocumentPrinterStatusEvent read FOnPrintStatus write FOnPrintStatus;
|
|
end;
|
|
{$ENDIF MSWINDOWS}
|
|
|
|
function SetThreadPdfUnsupportedFeatureHandler(const Handler: TPdfUnsupportedFeatureHandler): TPdfUnsupportedFeatureHandler;
|
|
|
|
var
|
|
PDFiumDllDir: string = '';
|
|
PDFiumDllFileName: string = ''; // use this instead of PDFiumDllDir if you want to change the DLLs file name
|
|
{$IF declared(FPDF_InitEmbeddedLibraries)}
|
|
PDFiumResDir: string = '';
|
|
{$IFEND}
|
|
|
|
implementation
|
|
|
|
resourcestring
|
|
RsUnsupportedFeature = 'Function %s not supported';
|
|
RsArgumentsOutOfRange = 'Function argument "%s" (%d) out of range';
|
|
RsDocumentNotActive = 'PDF document is not open';
|
|
{$IFNDEF CPUX64}
|
|
RsFileTooLarge = 'PDF file "%s" is too large';
|
|
{$ENDIF ~CPUX64}
|
|
|
|
RsPdfCannotDeleteAttachmnent = 'Cannot delete the PDF attachment %d';
|
|
RsPdfCannotAddAttachmnent = 'Cannot add the PDF attachment "%s"';
|
|
RsPdfCannotSetAttachmentContent = 'Cannot set the PDF attachment content';
|
|
RsPdfAttachmentContentNotSet = 'Content must be set before accessing string PDF attachmemt values';
|
|
|
|
RsPdfAnnotationNotAFormFieldError = 'The annotation is not a form field';
|
|
RsPdfAnnotationLinkRemoteGotoRequiresRemoteDocument = 'A remote goto annotation link requires a remote document';
|
|
|
|
RsPdfErrorSuccess = 'No error';
|
|
RsPdfErrorUnknown = 'Unknown error';
|
|
RsPdfErrorFile = 'File not found or can''t be opened';
|
|
RsPdfErrorFormat = 'File is not a PDF document or is corrupted';
|
|
RsPdfErrorPassword = 'Password required oder invalid password';
|
|
RsPdfErrorSecurity = 'Security schema is not support';
|
|
RsPdfErrorPage = 'Page does not exist or data error';
|
|
RsPdfErrorXFALoad = 'Load XFA error';
|
|
RsPdfErrorXFALayout = 'Layout XFA error';
|
|
|
|
threadvar
|
|
ThreadPdfUnsupportedFeatureHandler: TPdfUnsupportedFeatureHandler;
|
|
UnsupportedFeatureCurrentDocument: TPdfDocument;
|
|
|
|
type
|
|
{ We don't want to use a TBytes temporary array if we can convert directly into the destination
|
|
buffer. }
|
|
TEncodingAccess = class(TEncoding)
|
|
public
|
|
function GetMemCharCount(Bytes: PByte; ByteCount: Integer): Integer;
|
|
function GetMemChars(Bytes: PByte; ByteCount: Integer; Chars: PWideChar; CharCount: Integer): Integer;
|
|
end;
|
|
|
|
function TEncodingAccess.GetMemCharCount(Bytes: PByte; ByteCount: Integer): Integer;
|
|
begin
|
|
Result := GetCharCount(Bytes, ByteCount);
|
|
end;
|
|
|
|
function TEncodingAccess.GetMemChars(Bytes: PByte; ByteCount: Integer; Chars: PWideChar; CharCount: Integer): Integer;
|
|
begin
|
|
Result := GetChars(Bytes, ByteCount, Chars, CharCount);
|
|
end;
|
|
|
|
function SetThreadPdfUnsupportedFeatureHandler(const Handler: TPdfUnsupportedFeatureHandler): TPdfUnsupportedFeatureHandler;
|
|
begin
|
|
Result := ThreadPdfUnsupportedFeatureHandler;
|
|
ThreadPdfUnsupportedFeatureHandler := Handler;
|
|
end;
|
|
|
|
{$IF defined(MSWINDOWS) and not declared(GetFileSizeEx)}
|
|
function GetFileSizeEx(hFile: THandle; var lpFileSize: Int64): BOOL; stdcall;
|
|
external kernel32 name 'GetFileSizeEx';
|
|
{$IFEND}
|
|
|
|
procedure SwapInts(var X, Y: Integer);
|
|
var
|
|
Tmp: Integer;
|
|
begin
|
|
Tmp := X;
|
|
X := Y;
|
|
Y := Tmp;
|
|
end;
|
|
|
|
function GetUnsupportedFeatureName(nType: Integer): string;
|
|
begin
|
|
case nType of
|
|
FPDF_UNSP_DOC_XFAFORM:
|
|
Result := 'XFA';
|
|
|
|
FPDF_UNSP_DOC_PORTABLECOLLECTION:
|
|
Result := 'Portfolios_Packages';
|
|
|
|
FPDF_UNSP_DOC_ATTACHMENT,
|
|
FPDF_UNSP_ANNOT_ATTACHMENT:
|
|
Result := 'Attachment';
|
|
|
|
FPDF_UNSP_DOC_SECURITY:
|
|
Result := 'Rights_Management';
|
|
|
|
FPDF_UNSP_DOC_SHAREDREVIEW:
|
|
Result := 'Shared_Review';
|
|
|
|
FPDF_UNSP_DOC_SHAREDFORM_ACROBAT,
|
|
FPDF_UNSP_DOC_SHAREDFORM_FILESYSTEM,
|
|
FPDF_UNSP_DOC_SHAREDFORM_EMAIL:
|
|
Result := 'Shared_Form';
|
|
|
|
FPDF_UNSP_ANNOT_3DANNOT:
|
|
Result := '3D';
|
|
|
|
FPDF_UNSP_ANNOT_MOVIE:
|
|
Result := 'Movie';
|
|
|
|
FPDF_UNSP_ANNOT_SOUND:
|
|
Result := 'Sound';
|
|
|
|
FPDF_UNSP_ANNOT_SCREEN_MEDIA,
|
|
FPDF_UNSP_ANNOT_SCREEN_RICHMEDIA:
|
|
Result := 'Screen';
|
|
|
|
FPDF_UNSP_ANNOT_SIG:
|
|
Result := 'Digital_Signature';
|
|
|
|
else
|
|
Result := 'Unknown';
|
|
end;
|
|
end;
|
|
|
|
procedure UnsupportedHandler(pThis: PUNSUPPORT_INFO; nType: Integer); cdecl;
|
|
var
|
|
Document: TPdfDocument;
|
|
begin
|
|
Document := UnsupportedFeatureCurrentDocument;
|
|
if Document <> nil then
|
|
Document.FUnsupportedFeatures := True;
|
|
|
|
if Assigned(ThreadPdfUnsupportedFeatureHandler) then
|
|
ThreadPdfUnsupportedFeatureHandler(nType, GetUnsupportedFeatureName(nType));
|
|
//raise EPdfUnsupportedFeatureException.CreateResFmt(@RsUnsupportedFeature, [GetUnsupportedFeatureName]);
|
|
end;
|
|
|
|
var
|
|
PDFiumInitCritSect: TRTLCriticalSection;
|
|
UnsupportInfo: TUnsupportInfo = (
|
|
version: 1;
|
|
FSDK_UnSupport_Handler: UnsupportedHandler;
|
|
);
|
|
|
|
procedure InitLib;
|
|
{$J+}
|
|
const
|
|
Initialized: Integer = 0;
|
|
{$J-}
|
|
begin
|
|
if Initialized = 0 then
|
|
begin
|
|
EnterCriticalSection(PDFiumInitCritSect);
|
|
try
|
|
if Initialized = 0 then
|
|
begin
|
|
if PDFiumDllFileName <> '' then
|
|
InitPDFiumEx(PDFiumDllFileName {$IF declared(FPDF_InitEmbeddedLibraries)}, PDFiumResDir{$IFEND})
|
|
else
|
|
InitPDFium(PDFiumDllDir {$IF declared(FPDF_InitEmbeddedLibraries)}, PDFiumResDir{$IFEND});
|
|
FSDK_SetUnSpObjProcessHandler(@UnsupportInfo);
|
|
Initialized := 1;
|
|
end;
|
|
finally
|
|
LeaveCriticalSection(PDFiumInitCritSect);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure RaiseLastPdfError;
|
|
begin
|
|
case FPDF_GetLastError() of
|
|
FPDF_ERR_SUCCESS:
|
|
raise EPdfException.CreateRes(@RsPdfErrorSuccess);
|
|
FPDF_ERR_FILE:
|
|
raise EPdfException.CreateRes(@RsPdfErrorFile);
|
|
FPDF_ERR_FORMAT:
|
|
raise EPdfException.CreateRes(@RsPdfErrorFormat);
|
|
FPDF_ERR_PASSWORD:
|
|
raise EPdfException.CreateRes(@RsPdfErrorPassword);
|
|
FPDF_ERR_SECURITY:
|
|
raise EPdfException.CreateRes(@RsPdfErrorSecurity);
|
|
FPDF_ERR_PAGE:
|
|
raise EPdfException.CreateRes(@RsPdfErrorPage);
|
|
{$IF declared(FPDF_ERR_XFALOAD)}
|
|
FPDF_ERR_XFALOAD:
|
|
raise EPdfException.CreateRes(@RsPdfErrorXFALoad);
|
|
FPDF_ERR_XFALAYOUT:
|
|
raise EPdfException.CreateRes(@RsPdfErrorXFALayout);
|
|
{$IFEND}
|
|
else
|
|
raise EPdfException.CreateRes(@RsPdfErrorUnknown);
|
|
end;
|
|
end;
|
|
|
|
procedure FFI_Invalidate(pThis: PFPDF_FORMFILLINFO; page: FPDF_PAGE; left, top, right, bottom: Double); cdecl;
|
|
var
|
|
Handler: PPdfFormFillHandler;
|
|
Pg: TPdfPage;
|
|
R: TPdfRect;
|
|
begin
|
|
Handler := PPdfFormFillHandler(pThis);
|
|
if Assigned(Handler.Document.OnFormInvalidate) then
|
|
begin
|
|
Pg := Handler.Document.FindPage(page);
|
|
if Pg <> nil then
|
|
begin
|
|
R.Left := left;
|
|
R.Top := top;
|
|
R.Right := right;
|
|
R.Bottom := bottom;
|
|
Handler.Document.OnFormInvalidate(Handler.Document, Pg, R);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure FFI_Change(pThis: PFPDF_FORMFILLINFO); cdecl;
|
|
var
|
|
Handler: PPdfFormFillHandler;
|
|
begin
|
|
Handler := PPdfFormFillHandler(pThis);
|
|
Handler.Document.FormModified := True;
|
|
end;
|
|
|
|
procedure FFI_OutputSelectedRect(pThis: PFPDF_FORMFILLINFO; page: FPDF_PAGE; left, top, right, bottom: Double); cdecl;
|
|
var
|
|
Handler: PPdfFormFillHandler;
|
|
Pg: TPdfPage;
|
|
R: TPdfRect;
|
|
begin
|
|
Handler := PPdfFormFillHandler(pThis);
|
|
if Assigned(Handler.Document.OnFormOutputSelectedRect) then
|
|
begin
|
|
Pg := Handler.Document.FindPage(Page);
|
|
if Pg <> nil then
|
|
begin
|
|
R.Left := left;
|
|
R.Top := top;
|
|
R.Right := right;
|
|
R.Bottom := bottom;
|
|
Handler.Document.OnFormOutputSelectedRect(Handler.Document, Pg, R);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
{$IFDEF MSWINDOWS}
|
|
type
|
|
TFFITimer = record
|
|
Id: UINT;
|
|
Proc: TFPDFTimerCallback;
|
|
end;
|
|
|
|
var
|
|
FFITimers: array of TFFITimer;
|
|
FFITimersCritSect: TRTLCriticalSection;
|
|
|
|
procedure FormTimerProc(hwnd: HWND; uMsg: UINT; timerId: UINT; dwTime: DWORD); stdcall;
|
|
var
|
|
I: Integer;
|
|
Proc: TFPDFTimerCallback;
|
|
begin
|
|
Proc := nil;
|
|
EnterCriticalSection(FFITimersCritSect);
|
|
try
|
|
for I := 0 to Length(FFITimers) - 1 do
|
|
begin
|
|
if FFITimers[I].Id = timerId then
|
|
begin
|
|
Proc := FFITimers[I].Proc;
|
|
Break;
|
|
end;
|
|
end;
|
|
finally
|
|
LeaveCriticalSection(FFITimersCritSect);
|
|
end;
|
|
|
|
if Assigned(Proc) then
|
|
Proc(timerId);
|
|
end;
|
|
|
|
function FFI_SetTimer(pThis: PFPDF_FORMFILLINFO; uElapse: Integer; lpTimerFunc: TFPDFTimerCallback): Integer; cdecl;
|
|
var
|
|
I: Integer;
|
|
Id: UINT;
|
|
begin
|
|
Id := SetTimer(0, 0, uElapse, @FormTimerProc);
|
|
Result := Integer(Id);
|
|
if Id <> 0 then
|
|
begin
|
|
EnterCriticalSection(FFITimersCritSect);
|
|
try
|
|
for I := 0 to Length(FFITimers) - 1 do
|
|
begin
|
|
if FFITimers[I].Id = 0 then
|
|
begin
|
|
FFITimers[I].Id := Id;
|
|
FFITimers[I].Proc := lpTimerFunc;
|
|
Exit;
|
|
end;
|
|
end;
|
|
I := Length(FFITimers);
|
|
SetLength(FFITimers, I + 1);
|
|
FFITimers[I].Id := Id;
|
|
FFITimers[I].Proc := lpTimerFunc;
|
|
finally
|
|
LeaveCriticalSection(FFITimersCritSect);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure FFI_KillTimer(pThis: PFPDF_FORMFILLINFO; nTimerID: Integer); cdecl;
|
|
var
|
|
I: Integer;
|
|
begin
|
|
if nTimerID <> 0 then
|
|
begin
|
|
KillTimer(0, nTimerID);
|
|
|
|
EnterCriticalSection(FFITimersCritSect);
|
|
try
|
|
for I := 0 to Length(FFITimers) - 1 do
|
|
begin
|
|
if FFITimers[I].Id = UINT(nTimerID) then
|
|
begin
|
|
FFITimers[I].Id := 0;
|
|
FFITimers[I].Proc := nil;
|
|
end;
|
|
end;
|
|
|
|
I := Length(FFITimers) - 1;
|
|
while (I >= 0) and (FFITimers[I].Id = 0) do
|
|
Dec(I);
|
|
if Length(FFITimers) <> I + 1 then
|
|
SetLength(FFITimers, I + 1);
|
|
finally
|
|
LeaveCriticalSection(FFITimersCritSect);
|
|
end;
|
|
end;
|
|
end;
|
|
{$ELSE}
|
|
type
|
|
TFFITimer = class(TTimer)
|
|
public
|
|
FId: Integer;
|
|
FTimerFunc: TFPDFTimerCallback;
|
|
procedure DoTimerEvent(Sender: TObject);
|
|
end;
|
|
|
|
var
|
|
FFITimers: array of TFFITimer;
|
|
FFITimersCritSect: TRTLCriticalSection;
|
|
|
|
{ TFFITimer }
|
|
|
|
procedure TFFITimer.DoTimerEvent(Sender: TObject);
|
|
begin
|
|
FTimerFunc(FId);
|
|
end;
|
|
|
|
function FFI_SetTimer(pThis: PFPDF_FORMFILLINFO; uElapse: Integer; lpTimerFunc: TFPDFTimerCallback): Integer; cdecl;
|
|
var
|
|
I: Integer;
|
|
Id: Integer;
|
|
Timer: TFFITimer;
|
|
begin
|
|
// Find highest Id
|
|
EnterCriticalSection(FFITimersCritSect);
|
|
try
|
|
Id := 0;
|
|
for I := 0 to Length(FFITimers) - 1 do
|
|
if (FFITimers[I] <> nil) and (FFITimers[I].FId > Id) then
|
|
Id := FFITimers[I].FId;
|
|
Inc(Id);
|
|
|
|
Timer := TFFITimer.Create(nil);
|
|
Timer.FId := Id;
|
|
Timer.FTimerFunc:= lpTimerFunc;
|
|
Timer.OnTimer := Timer.DoTimerEvent;
|
|
Timer.Interval := uElapse;
|
|
|
|
Result := Id;
|
|
for I := 0 to Length(FFITimers) - 1 do
|
|
begin
|
|
if FFITimers[I] = nil then
|
|
begin
|
|
FFITimers[I] := Timer;
|
|
Exit;
|
|
end;
|
|
end;
|
|
I := Length(FFITimers);
|
|
SetLength(FFITimers, I + 1);
|
|
FFITimers[I] := Timer;
|
|
finally
|
|
LeaveCriticalSection(FFITimersCritSect);
|
|
end;
|
|
end;
|
|
|
|
procedure FFI_KillTimer(pThis: PFPDF_FORMFILLINFO; nTimerID: Integer); cdecl;
|
|
var
|
|
I: Integer;
|
|
begin
|
|
if nTimerID <> 0 then
|
|
begin
|
|
EnterCriticalSection(FFITimersCritSect);
|
|
try
|
|
for I := 0 to Length(FFITimers) - 1 do
|
|
if (FFITimers[I] <> nil) and (FFITimers[I].FId = nTimerID) then
|
|
FreeAndNil(FFITimers[I]);
|
|
|
|
I := Length(FFITimers) - 1;
|
|
while (I >= 0) and (FFITimers[I] = nil) do
|
|
Dec(I);
|
|
if Length(FFITimers) <> I + 1 then
|
|
SetLength(FFITimers, I + 1);
|
|
finally
|
|
LeaveCriticalSection(FFITimersCritSect);
|
|
end;
|
|
end;
|
|
end;
|
|
{$ENDIF MSWINDOWS}
|
|
|
|
function FFI_GetLocalTime(pThis: PFPDF_FORMFILLINFO): FPDF_SYSTEMTIME; cdecl;
|
|
{$IF not declared(PSystemTime)}
|
|
type
|
|
PSystemTime = ^TSystemTime;
|
|
{$IFEND}
|
|
begin
|
|
GetLocalTime(PSystemTime(@Result)^);
|
|
end;
|
|
|
|
function FFI_GetPage(pThis: PFPDF_FORMFILLINFO; document: FPDF_DOCUMENT; nPageIndex: Integer): FPDF_PAGE; cdecl;
|
|
var
|
|
Handler: PPdfFormFillHandler;
|
|
begin
|
|
Handler := PPdfFormFillHandler(pThis);
|
|
Result := nil;
|
|
if (Handler.Document <> nil) and (Handler.Document.FDocument = document) then
|
|
begin
|
|
if (nPageIndex >= 0) and (nPageIndex < Handler.Document.PageCount) then
|
|
Result := Handler.Document.Pages[nPageIndex].FPage;
|
|
end;
|
|
end;
|
|
|
|
function FFI_GetCurrentPage(pThis: PFPDF_FORMFILLINFO; document: FPDF_DOCUMENT): FPDF_PAGE; cdecl;
|
|
var
|
|
Handler: PPdfFormFillHandler;
|
|
Pg: TPdfPage;
|
|
begin
|
|
Handler := PPdfFormFillHandler(pThis);
|
|
Result := nil;
|
|
if (Handler.Document <> nil) and (Handler.Document.FDocument = document) and Assigned(Handler.Document.OnFormGetCurrentPage) then
|
|
begin
|
|
Pg := nil;
|
|
Handler.Document.OnFormGetCurrentPage(Handler.Document, Pg);
|
|
Result := nil;
|
|
if Pg <> nil then
|
|
Result := Pg.FPage;
|
|
end;
|
|
end;
|
|
|
|
function FFI_GetRotation(pThis: PFPDF_FORMFILLINFO; page: FPDF_PAGE): Integer; cdecl;
|
|
begin
|
|
Result := 0;
|
|
end;
|
|
|
|
procedure FFI_ExecuteNamedAction(pThis: PFPDF_FORMFILLINFO; namedAction: FPDF_BYTESTRING); cdecl;
|
|
var
|
|
Handler: PPdfFormFillHandler;
|
|
NamedActionType: TPdfNamedActionType;
|
|
S: UTF8String;
|
|
begin
|
|
Handler := PPdfFormFillHandler(pThis);
|
|
if Assigned(Handler.Document.OnExecuteNamedAction) then
|
|
begin
|
|
S := namedAction;
|
|
|
|
if S = 'Print' then
|
|
NamedActionType := naPrint
|
|
else if S = 'NextPage' then
|
|
NamedActionType := naNextPage
|
|
else if S = 'PrevPage' then
|
|
NamedActionType := naPrevPage
|
|
else if S = 'FirstPage' then
|
|
NamedActionType := naFirstPage
|
|
else if S = 'LastPage' then
|
|
NamedActionType := naLastPage
|
|
else
|
|
Exit;
|
|
|
|
Handler.Document.OnExecuteNamedAction(Handler.Document, NamedActionType);
|
|
end;
|
|
end;
|
|
|
|
procedure FFI_SetCursor(pThis: PFPDF_FORMFILLINFO; nCursorType: Integer); cdecl;
|
|
begin
|
|
// A better solution is to check what form field type is under the mouse cursor in the
|
|
// MoveMove event. Chrome/Edge doesn't rely on SetCursor either.
|
|
end;
|
|
|
|
procedure FFI_SetTextFieldFocus(pThis: PFPDF_FORMFILLINFO; value: FPDF_WIDESTRING; valueLen: FPDF_DWORD; is_focus: FPDF_BOOL); cdecl;
|
|
var
|
|
Handler: PPdfFormFillHandler;
|
|
begin
|
|
Handler := PPdfFormFillHandler(pThis);
|
|
if (Handler.Document <> nil) and Assigned(Handler.Document.OnFormFieldFocus) then
|
|
Handler.Document.OnFormFieldFocus(Handler.Document, value, valueLen, is_focus <> 0);
|
|
end;
|
|
|
|
procedure FFI_FocusChange(param: PFPDF_FORMFILLINFO; annot: FPDF_ANNOTATION; page_index: Integer); cdecl;
|
|
begin
|
|
end;
|
|
|
|
|
|
{ TPdfRect }
|
|
|
|
procedure TPdfRect.Offset(XOffset, YOffset: Double);
|
|
begin
|
|
Left := Left + XOffset;
|
|
Top := Top + YOffset;
|
|
Right := Right + XOffset;
|
|
Bottom := Bottom + YOffset;
|
|
end;
|
|
|
|
class function TPdfRect.Empty: TPdfRect;
|
|
begin
|
|
Result.Left := 0;
|
|
Result.Top := 0;
|
|
Result.Right := 0;
|
|
Result.Bottom := 0;
|
|
end;
|
|
|
|
function TPdfRect.GetHeight: Double;
|
|
begin
|
|
Result := Bottom - Top;
|
|
end;
|
|
|
|
function TPdfRect.GetWidth: Double;
|
|
begin
|
|
Result := Right - Left;
|
|
end;
|
|
|
|
procedure TPdfRect.SetHeight(const Value: Double);
|
|
begin
|
|
Bottom := Top + Value;
|
|
end;
|
|
|
|
procedure TPdfRect.SetWidth(const Value: Double);
|
|
begin
|
|
Right := Left + Value;
|
|
end;
|
|
|
|
class function TPdfRect.New(Left, Top, Right, Bottom: Double): TPdfRect;
|
|
begin
|
|
Result.Left := Left;
|
|
Result.Top := Top;
|
|
Result.Right := Right;
|
|
Result.Bottom := Bottom;
|
|
end;
|
|
|
|
function TPdfRect.PtIn(const Pt: TPdfPoint): Boolean;
|
|
begin
|
|
Result := (Pt.X >= Left) and (Pt.X < Right);
|
|
if Result then
|
|
begin
|
|
// Page coordinates are upside down.
|
|
if Top > Bottom then
|
|
Result := (Pt.Y >= Bottom) and (Pt.Y < Top)
|
|
else
|
|
Result := (Pt.Y >= Top) and (Pt.Y < Bottom)
|
|
end;
|
|
end;
|
|
|
|
|
|
{ TPdfDocument }
|
|
|
|
constructor TPdfDocument.Create;
|
|
begin
|
|
inherited Create;
|
|
FPages := TObjectList.Create;
|
|
FAttachments := TPdfAttachmentList.Create(Self);
|
|
{$IFDEF MSWINDOWS}
|
|
FFileHandle := INVALID_HANDLE_VALUE;
|
|
{$ENDIF MSWINDOWS}
|
|
FFormFieldHighlightColor := $FFE4DD;
|
|
FFormFieldHighlightAlpha := 100;
|
|
FPrintHidesFormFieldHighlight := True;
|
|
|
|
InitLib;
|
|
end;
|
|
|
|
destructor TPdfDocument.Destroy;
|
|
begin
|
|
Close;
|
|
FAttachments.Free;
|
|
FPages.Free;
|
|
inherited Destroy;
|
|
end;
|
|
|
|
procedure TPdfDocument.Close;
|
|
begin
|
|
FClosing := True;
|
|
try
|
|
FPages.Clear;
|
|
FUnsupportedFeatures := False;
|
|
|
|
if FDocument <> nil then
|
|
begin
|
|
if FForm <> nil then
|
|
begin
|
|
FORM_DoDocumentAAction(FForm, FPDFDOC_AACTION_WC);
|
|
FPDFDOC_ExitFormFillEnvironment(FForm);
|
|
FForm := nil;
|
|
end;
|
|
|
|
FPDF_CloseDocument(FDocument);
|
|
FDocument := nil;
|
|
end;
|
|
|
|
if FCustomLoadData <> nil then
|
|
begin
|
|
Dispose(FCustomLoadData);
|
|
FCustomLoadData := nil;
|
|
end;
|
|
|
|
{$IFDEF MSWINDOWS}
|
|
if FFileMapping <> 0 then
|
|
begin
|
|
if FBuffer <> nil then
|
|
begin
|
|
UnmapViewOfFile(FBuffer);
|
|
FBuffer := nil;
|
|
end;
|
|
CloseHandle(FFileMapping);
|
|
FFileMapping := 0;
|
|
end
|
|
else
|
|
{$ENDIF MSWINDOWS}
|
|
if FBuffer <> nil then
|
|
begin
|
|
FreeMem(FBuffer);
|
|
FBuffer := nil;
|
|
end;
|
|
FBytes := nil;
|
|
|
|
{$IFDEF MSWINDOWS}
|
|
if FFileHandle <> INVALID_HANDLE_VALUE then
|
|
begin
|
|
CloseHandle(FFileHandle);
|
|
FFileHandle := INVALID_HANDLE_VALUE;
|
|
end;
|
|
{$ELSE}
|
|
FreeAndNil(FFileStream);
|
|
{$ENDIF MSWINDOWS}
|
|
|
|
FFileName := '';
|
|
FFormModified := False;
|
|
finally
|
|
FClosing := False;
|
|
end;
|
|
end;
|
|
|
|
{$IFDEF MSWINDOWS}
|
|
function ReadFromActiveFileHandle(Param: Pointer; Position: LongWord; Buffer: PByte; Size: LongWord): Boolean;
|
|
var
|
|
NumRead: DWORD;
|
|
begin
|
|
if Buffer <> nil then
|
|
begin
|
|
SetFilePointer(THandle(Param), Position, nil, FILE_BEGIN);
|
|
Result := ReadFile(THandle(Param), Buffer^, Size, NumRead, nil) and (NumRead = Size);
|
|
end
|
|
else
|
|
Result := Size = 0;
|
|
end;
|
|
{$ENDIF MSWINDOWS}
|
|
|
|
procedure TPdfDocument.LoadFromFile(const FileName: string; const Password: UTF8String; LoadOption: TPdfDocumentLoadOption);
|
|
{$IFDEF MSWINDOWS}
|
|
var
|
|
Size: Int64;
|
|
Offset: NativeInt;
|
|
NumRead: DWORD;
|
|
LastError: DWORD;
|
|
{$ENDIF MSWINDOWS}
|
|
begin
|
|
Close;
|
|
if LoadOption = dloDefault then
|
|
begin
|
|
InternLoadFromFile(FileName, Password);
|
|
FFileName := FileName;
|
|
Exit;
|
|
end;
|
|
|
|
{$IFDEF MSWINDOWS}
|
|
FFileHandle := CreateFileW(PWideChar(FileName), GENERIC_READ, FILE_SHARE_READ, nil, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
|
|
if FFileHandle = INVALID_HANDLE_VALUE then
|
|
RaiseLastOSError;
|
|
try
|
|
if not GetFileSizeEx(FFileHandle, Size) then
|
|
RaiseLastOSError;
|
|
if Size > High(Integer) then // PDFium LoadCustomDocument() can only handle PDFs up to 2 GB (see FPDF_FILEACCESS)
|
|
begin
|
|
{$IFDEF CPUX64}
|
|
// FPDF_LoadCustomDocument wasn't updated to load larger files, so we fall back to MMF.
|
|
if LoadOption = dloOnDemand then
|
|
LoadOption := dloMMF;
|
|
{$ELSE}
|
|
raise EPdfException.CreateResFmt(@RsFileTooLarge, [ExtractFileName(FileName)]);
|
|
{$ENDIF CPUX64}
|
|
end;
|
|
|
|
case LoadOption of
|
|
dloMemory:
|
|
begin
|
|
if Size > 0 then
|
|
begin
|
|
try
|
|
GetMem(FBuffer, Size);
|
|
Offset := 0;
|
|
while Offset < Size do
|
|
begin
|
|
if ((Size - Offset) and not $FFFFFFFF) <> 0 then
|
|
NumRead := $40000000
|
|
else
|
|
NumRead := Size - Offset;
|
|
|
|
if not ReadFile(FFileHandle, FBuffer[Offset], NumRead, NumRead, nil) then
|
|
begin
|
|
LastError := GetLastError;
|
|
FreeMem(FBuffer);
|
|
FBuffer := nil;
|
|
RaiseLastOSError(LastError);
|
|
end;
|
|
Inc(Offset, NumRead);
|
|
end;
|
|
finally
|
|
CloseHandle(FFileHandle);
|
|
FFileHandle := INVALID_HANDLE_VALUE;
|
|
end;
|
|
|
|
InternLoadFromMem(FBuffer, Size, Password);
|
|
end;
|
|
end;
|
|
|
|
dloMMF:
|
|
begin
|
|
FFileMapping := CreateFileMapping(FFileHandle, nil, PAGE_READONLY, 0, 0, nil);
|
|
if FFileMapping = 0 then
|
|
RaiseLastOSError;
|
|
FBuffer := MapViewOfFile(FFileMapping, FILE_MAP_READ, 0, 0, Size);
|
|
if FBuffer = nil then
|
|
RaiseLastOSError;
|
|
|
|
InternLoadFromMem(FBuffer, Size, Password);
|
|
end;
|
|
|
|
dloOnDemand:
|
|
InternLoadFromCustom(ReadFromActiveFileHandle, Size, Pointer(FFileHandle), Password);
|
|
end;
|
|
except
|
|
Close;
|
|
raise;
|
|
end;
|
|
{$ELSE}
|
|
FFileStream := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite);
|
|
try
|
|
case LoadOption of
|
|
dloMemory, dloMMF:
|
|
begin
|
|
try
|
|
LoadFromStream(FFileStream, Password);
|
|
finally
|
|
FreeAndNil(FFileStream);
|
|
end;
|
|
end;
|
|
dloOnDemand:
|
|
LoadFromActiveStream(FFileStream, Password);
|
|
end;
|
|
except
|
|
FreeAndNil(FFileStream);
|
|
raise;
|
|
end;
|
|
{$ENDIF MSWINDOWS}
|
|
FFileName := FileName;
|
|
end;
|
|
|
|
procedure TPdfDocument.LoadFromStream(Stream: TStream; const Password: UTF8String);
|
|
var
|
|
Size: NativeInt;
|
|
begin
|
|
Close;
|
|
Size := Stream.Size;
|
|
if Size > 0 then
|
|
begin
|
|
GetMem(FBuffer, Size);
|
|
try
|
|
Stream.ReadBuffer(FBuffer^, Size);
|
|
InternLoadFromMem(FBuffer, Size, Password);
|
|
except
|
|
Close;
|
|
raise;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TPdfDocument.LoadFromActiveBuffer(Buffer: Pointer; Size: NativeInt; const Password: UTF8String);
|
|
begin
|
|
Close;
|
|
InternLoadFromMem(Buffer, Size, Password);
|
|
end;
|
|
|
|
procedure TPdfDocument.LoadFromBytes(const Bytes: TBytes; const Password: UTF8String);
|
|
begin
|
|
LoadFromBytes(Bytes, 0, Length(Bytes), Password);
|
|
end;
|
|
|
|
procedure TPdfDocument.LoadFromBytes(const Bytes: TBytes; Index, Count: NativeInt;
|
|
const Password: UTF8String);
|
|
var
|
|
Len: NativeInt;
|
|
begin
|
|
Close;
|
|
|
|
Len := Length(Bytes);
|
|
if Index >= Len then
|
|
raise EPdfArgumentOutOfRange.CreateResFmt(@RsArgumentsOutOfRange, ['Index', Index]);
|
|
if Index + Count > Len then
|
|
raise EPdfArgumentOutOfRange.CreateResFmt(@RsArgumentsOutOfRange, ['Count', Count]);
|
|
|
|
FBytes := Bytes; // keep alive after return
|
|
InternLoadFromMem(@Bytes[Index], Count, Password);
|
|
end;
|
|
|
|
function ReadFromActiveStream(Param: Pointer; Position: LongWord; Buffer: PByte; Size: LongWord): Boolean;
|
|
begin
|
|
if Buffer <> nil then
|
|
begin
|
|
TStream(Param).Seek(Position, TSeekOrigin.soBeginning);
|
|
Result := TStream(Param).Read(Buffer^, Size) = Integer(Size);
|
|
end
|
|
else
|
|
Result := Size = 0;
|
|
end;
|
|
|
|
procedure TPdfDocument.LoadFromActiveStream(Stream: TStream; const Password: UTF8String);
|
|
begin
|
|
if Stream = nil then
|
|
Close
|
|
else
|
|
LoadFromCustom(ReadFromActiveStream, Stream.Size, Stream, Password);
|
|
end;
|
|
|
|
procedure TPdfDocument.LoadFromCustom(ReadFunc: TPdfDocumentCustomReadProc; Size: LongWord;
|
|
Param: Pointer; const Password: UTF8String);
|
|
begin
|
|
Close;
|
|
InternLoadFromCustom(ReadFunc, Size, Param, Password);
|
|
end;
|
|
|
|
function GetLoadFromCustomBlock(Param: Pointer; Position: LongWord; Buffer: PByte; Size: LongWord): Integer; cdecl;
|
|
var
|
|
Data: TPdfDocument.PCustomLoadDataRec;
|
|
begin
|
|
Data := TPdfDocument(param).FCustomLoadData;
|
|
Result := Ord(Data.GetBlock(Data.Param, Position, Buffer, Size));
|
|
end;
|
|
|
|
procedure TPdfDocument.InternLoadFromCustom(ReadFunc: TPdfDocumentCustomReadProc; Size: LongWord;
|
|
Param: Pointer; const Password: UTF8String);
|
|
var
|
|
OldCurDoc: TPdfDocument;
|
|
begin
|
|
if Assigned(ReadFunc) then
|
|
begin
|
|
New(FCustomLoadData);
|
|
FCustomLoadData.Param := Param;
|
|
FCustomLoadData.GetBlock := ReadFunc;
|
|
FCustomLoadData.FileAccess.m_FileLen := Size;
|
|
FCustomLoadData.FileAccess.m_GetBlock := GetLoadFromCustomBlock;
|
|
FCustomLoadData.FileAccess.m_Param := Self;
|
|
|
|
OldCurDoc := UnsupportedFeatureCurrentDocument;
|
|
try
|
|
UnsupportedFeatureCurrentDocument := Self;
|
|
FDocument := FPDF_LoadCustomDocument(@FCustomLoadData.FileAccess, PAnsiChar(Pointer(Password)));
|
|
finally
|
|
UnsupportedFeatureCurrentDocument := OldCurDoc;
|
|
end;
|
|
DocumentLoaded;
|
|
end;
|
|
end;
|
|
|
|
procedure TPdfDocument.InternLoadFromMem(Buffer: PByte; Size: NativeInt; const Password: UTF8String);
|
|
var
|
|
OldCurDoc: TPdfDocument;
|
|
begin
|
|
if Size > 0 then
|
|
begin
|
|
OldCurDoc := UnsupportedFeatureCurrentDocument;
|
|
try
|
|
UnsupportedFeatureCurrentDocument := Self;
|
|
FDocument := FPDF_LoadMemDocument64(Buffer, Size, PAnsiChar(Pointer(Password)));
|
|
finally
|
|
UnsupportedFeatureCurrentDocument := OldCurDoc;
|
|
end;
|
|
DocumentLoaded;
|
|
end;
|
|
end;
|
|
|
|
procedure TPdfDocument.InternLoadFromFile(const FileName: string; const Password: UTF8String);
|
|
var
|
|
OldCurDoc: TPdfDocument;
|
|
Utf8FileName: UTF8String;
|
|
begin
|
|
Utf8FileName := UTF8Encode(FileName);
|
|
OldCurDoc := UnsupportedFeatureCurrentDocument;
|
|
try
|
|
UnsupportedFeatureCurrentDocument := Self;
|
|
// UTF8 now works with LoadDocument and it can handle large PDF files (2 GB+) what
|
|
// FPDF_LoadCustomDocument can't because of the data types in FPDF_FILEACCESS.
|
|
FDocument := FPDF_LoadDocument(PAnsiChar(Utf8FileName), PAnsiChar(Pointer(Password)));
|
|
finally
|
|
UnsupportedFeatureCurrentDocument := OldCurDoc;
|
|
end;
|
|
DocumentLoaded;
|
|
end;
|
|
|
|
procedure TPdfDocument.DocumentLoaded;
|
|
begin
|
|
FFormModified := False;
|
|
if FDocument = nil then
|
|
RaiseLastPdfError;
|
|
|
|
FPages.Count := FPDF_GetPageCount(FDocument);
|
|
|
|
FillChar(FFormFillHandler, SizeOf(TPdfFormFillHandler), 0);
|
|
FFormFillHandler.Document := Self;
|
|
FFormFillHandler.FormFillInfo.version := 1; // will be set to 2 if we use an XFA-enabled DLL
|
|
FFormFillHandler.FormFillInfo.FFI_Invalidate := FFI_Invalidate;
|
|
FFormFillHandler.FormFillInfo.FFI_OnChange := FFI_Change;
|
|
FFormFillHandler.FormFillInfo.FFI_OutputSelectedRect := FFI_OutputSelectedRect;
|
|
FFormFillHandler.FormFillInfo.FFI_SetTimer := FFI_SetTimer;
|
|
FFormFillHandler.FormFillInfo.FFI_KillTimer := FFI_KillTimer;
|
|
FFormFillHandler.FormFillInfo.FFI_GetLocalTime := FFI_GetLocalTime;
|
|
FFormFillHandler.FormFillInfo.FFI_GetPage := FFI_GetPage;
|
|
FFormFillHandler.FormFillInfo.FFI_GetCurrentPage := FFI_GetCurrentPage;
|
|
FFormFillHandler.FormFillInfo.FFI_GetRotation := FFI_GetRotation;
|
|
FFormFillHandler.FormFillInfo.FFI_ExecuteNamedAction := FFI_ExecuteNamedAction;
|
|
FFormFillHandler.FormFillInfo.FFI_SetCursor := FFI_SetCursor;
|
|
FFormFillHandler.FormFillInfo.FFI_SetTextFieldFocus := FFI_SetTextFieldFocus;
|
|
FFormFillHandler.FormFillInfo.FFI_OnFocusChange := FFI_FocusChange;
|
|
// FFormFillHandler.FormFillInfo.FFI_DoURIAction := FFI_DoURIAction;
|
|
// FFormFillHandler.FormFillInfo.FFI_DoGoToAction := FFI_DoGoToAction;
|
|
|
|
if PDF_USE_XFA then
|
|
begin
|
|
FJSPlatform.version := 3;
|
|
// FJSPlatform callbacks not implemented
|
|
|
|
FFormFillHandler.FormFillInfo.m_pJsPlatform := @FJSPlatform;
|
|
|
|
FFormFillHandler.FormFillInfo.version := 2;
|
|
FFormFillHandler.FormFillInfo.xfa_disabled := 1; // Disable XFA support for now
|
|
end;
|
|
|
|
FForm := FPDFDOC_InitFormFillEnvironment(FDocument, @FFormFillHandler.FormFillInfo);
|
|
if FForm <> nil then
|
|
begin
|
|
if PDF_USE_XFA and (FFormFillHandler.FormFillInfo.xfa_disabled = 0) then
|
|
FPDF_LoadXFA(FDocument);
|
|
UpdateFormFieldHighlight;
|
|
|
|
FORM_DoDocumentJSAction(FForm);
|
|
FORM_DoDocumentOpenAction(FForm);
|
|
end;
|
|
end;
|
|
|
|
procedure TPdfDocument.UpdateFormFieldHighlight;
|
|
begin
|
|
FPDF_SetFormFieldHighlightColor(FForm, 0, FFormFieldHighlightColor);
|
|
FPDF_SetFormFieldHighlightAlpha(FForm, FFormFieldHighlightAlpha);
|
|
end;
|
|
|
|
function TPdfDocument.IsPageLoaded(PageIndex: Integer): Boolean;
|
|
var
|
|
Page: TPdfPage;
|
|
begin
|
|
Page := TPdfPage(FPages[PageIndex]);
|
|
Result := (Page <> nil) and Page.IsLoaded;
|
|
end;
|
|
|
|
function TPdfDocument.GetPage(Index: Integer): TPdfPage;
|
|
var
|
|
LPage: FPDF_PAGE;
|
|
begin
|
|
Result := TPdfPage(FPages[Index]);
|
|
if Result = nil then
|
|
begin
|
|
LPage := FPDF_LoadPage(FDocument, Index);
|
|
if LPage = nil then
|
|
RaiseLastPdfError;
|
|
Result := TPdfPage.Create(Self, LPage);
|
|
FPages[Index] := Result;
|
|
end
|
|
end;
|
|
|
|
function TPdfDocument.GetPageCount: Integer;
|
|
begin
|
|
Result := FPages.Count;
|
|
end;
|
|
|
|
procedure TPdfDocument.ExtractPage(APage: TPdfPage);
|
|
begin
|
|
if not FClosing then
|
|
FPages.Extract(APage);
|
|
end;
|
|
|
|
function TPdfDocument.ReloadPage(APage: TPdfPage): FPDF_PAGE;
|
|
var
|
|
Index: Integer;
|
|
begin
|
|
CheckActive;
|
|
Index := FPages.IndexOf(APage);
|
|
Result := FPDF_LoadPage(FDocument, Index);
|
|
if Result = nil then
|
|
RaiseLastPdfError;
|
|
end;
|
|
|
|
function TPdfDocument.GetPrintScaling: Boolean;
|
|
begin
|
|
CheckActive;
|
|
Result := FPDF_VIEWERREF_GetPrintScaling(FDocument) <> 0;
|
|
end;
|
|
|
|
function TPdfDocument.GetActive: Boolean;
|
|
begin
|
|
Result := FDocument <> nil;
|
|
end;
|
|
|
|
procedure TPdfDocument.CheckActive;
|
|
begin
|
|
if not Active then
|
|
raise EPdfException.CreateRes(@RsDocumentNotActive);
|
|
end;
|
|
|
|
class function TPdfDocument.CreateNPagesOnOnePageDocument(Source: TPdfDocument;
|
|
NumPagesXAxis, NumPagesYAxis: Integer): TPdfDocument;
|
|
begin
|
|
if Source.PageCount > 0 then
|
|
Result := CreateNPagesOnOnePageDocument(Source, Source.PageSizes[0].X, Source.PageSizes[0].Y, NumPagesXAxis, NumPagesYAxis)
|
|
else
|
|
Result := CreateNPagesOnOnePageDocument(Source, PdfDefaultPageWidth, PdfDefaultPageHeight, NumPagesXAxis, NumPagesYAxis); // DIN A4 page
|
|
end;
|
|
|
|
class function TPdfDocument.CreateNPagesOnOnePageDocument(Source: TPdfDocument;
|
|
NewPageWidth, NewPageHeight: Double; NumPagesXAxis, NumPagesYAxis: Integer): TPdfDocument;
|
|
var
|
|
OldCurDoc: TPdfDocument;
|
|
begin
|
|
Result := TPdfDocument.Create;
|
|
try
|
|
if (Source = nil) or not Source.Active then
|
|
Result.NewDocument
|
|
else
|
|
begin
|
|
OldCurDoc := UnsupportedFeatureCurrentDocument;
|
|
try
|
|
UnsupportedFeatureCurrentDocument := Result;
|
|
Result.FDocument := FPDF_ImportNPagesToOne(Source.FDocument, NewPageWidth, NewPageHeight, NumPagesXAxis, NumPagesYAxis);
|
|
finally
|
|
UnsupportedFeatureCurrentDocument := OldCurDoc;
|
|
end;
|
|
if Result.FDocument <> nil then
|
|
Result.DocumentLoaded
|
|
else
|
|
Result.NewDocument;
|
|
end;
|
|
except
|
|
Result.Free;
|
|
raise;
|
|
end;
|
|
end;
|
|
|
|
function TPdfDocument.InternImportPages(Source: TPdfDocument; PageIndices: PInteger; PageIndicesCount: Integer;
|
|
const Range: AnsiString; Index: Integer; ImportByRange: Boolean): Boolean;
|
|
var
|
|
I, NewCount, OldCount, InsertCount: Integer;
|
|
begin
|
|
CheckActive;
|
|
Source.CheckActive;
|
|
|
|
OldCount := FPDF_GetPageCount(FDocument);
|
|
if Index < 0 then
|
|
Index := OldCount;
|
|
|
|
if ImportByRange then // Range = '' => Import all pages
|
|
Result := FPDF_ImportPages(FDocument, Source.FDocument, PAnsiChar(Pointer(Range)), Index) <> 0
|
|
else
|
|
Result := FPDF_ImportPagesByIndex(FDocument, Source.FDocument, PageIndices, PageIndicesCount, Index) <> 0;
|
|
|
|
NewCount := FPDF_GetPageCount(FDocument);
|
|
InsertCount := NewCount - OldCount;
|
|
if InsertCount > 0 then
|
|
begin
|
|
FPages.Count := NewCount;
|
|
if Index < OldCount then
|
|
begin
|
|
Move(FPages.List[Index], FPages.List[Index + InsertCount], (OldCount - Index) * SizeOf(TObject));
|
|
for I := Index to Index + InsertCount - 1 do
|
|
FPages.List[Index] := nil;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function TPdfDocument.ImportAllPages(Source: TPdfDocument; Index: Integer): Boolean;
|
|
begin
|
|
Result := InternImportPages(Source, nil, 0, '', Index, False);
|
|
end;
|
|
|
|
function TPdfDocument.ImportPages(Source: TPdfDocument; const Range: string; Index: Integer): Boolean;
|
|
begin
|
|
Result := InternImportPages(Source, nil, 0, AnsiString(Range), Index, True)
|
|
end;
|
|
|
|
function TPdfDocument.ImportPageRange(Source: TPdfDocument; PageIndex, Count, Index: Integer): Boolean;
|
|
begin
|
|
Result := False;
|
|
if (Source <> nil) and (PageIndex >= 0) then
|
|
begin
|
|
if Count = -1 then
|
|
Count := Source.PageCount - PageIndex
|
|
else if Count < 0 then
|
|
Exit;
|
|
|
|
if Count > 0 then
|
|
begin
|
|
if PageIndex + Count > Source.PageCount then
|
|
begin
|
|
Count := Source.PageCount - PageIndex;
|
|
if Count = 0 then
|
|
Exit;
|
|
end;
|
|
if (PageIndex = 0) and (Count = Source.PageCount) then
|
|
Result := ImportAllPages(Source, Index)
|
|
else
|
|
Result := ImportPages(Source, Format('%d-%d', [PageIndex, PageIndex + Count - 1]));
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function TPdfDocument.ImportPagesByIndex(Source: TPdfDocument; const PageIndices: array of Integer; Index: Integer = -1): Boolean;
|
|
begin
|
|
if Length(PageIndices) > 0 then
|
|
Result := InternImportPages(Source, @PageIndices[0], Length(PageIndices), '', Index, False)
|
|
else
|
|
Result := ImportAllPages(Source, Index);
|
|
end;
|
|
|
|
procedure TPdfDocument.SaveToFile(const AFileName: string; Option: TPdfDocumentSaveOption; FileVersion: Integer);
|
|
var
|
|
Stream: TFileStream;
|
|
begin
|
|
Stream := TFileStream.Create(AFileName, fmCreate or fmShareDenyWrite);
|
|
try
|
|
SaveToStream(Stream, Option, FileVersion);
|
|
finally
|
|
Stream.Free;
|
|
end;
|
|
end;
|
|
|
|
type
|
|
PFPDFFileWriteEx = ^TFPDFFileWriteEx;
|
|
TFPDFFileWriteEx = record
|
|
Inner: TFPDFFileWrite; // emulate object inheritance
|
|
Stream: TStream;
|
|
end;
|
|
|
|
function WriteBlockToStream(pThis: PFPDF_FILEWRITE; pData: Pointer; size: LongWord): Integer; cdecl;
|
|
begin
|
|
Result := Ord(LongWord(PFPDFFileWriteEx(pThis).Stream.Write(pData^, size)) = size);
|
|
end;
|
|
|
|
procedure TPdfDocument.SaveToStream(Stream: TStream; Option: TPdfDocumentSaveOption; FileVersion: Integer);
|
|
var
|
|
FileWriteInfo: TFPDFFileWriteEx;
|
|
begin
|
|
CheckActive;
|
|
|
|
FileWriteInfo.Inner.version := 1;
|
|
FileWriteInfo.Inner.WriteBlock := @WriteBlockToStream;
|
|
FileWriteInfo.Stream := Stream;
|
|
|
|
if FForm <> nil then
|
|
begin
|
|
FORM_ForceToKillFocus(FForm); // also save the form field data that is currently focused
|
|
FORM_DoDocumentAAction(FForm, FPDFDOC_AACTION_WS); // BeforeSave
|
|
end;
|
|
|
|
if FileVersion <> -1 then
|
|
FPDF_SaveWithVersion(FDocument, @FileWriteInfo, Ord(Option), FileVersion)
|
|
else
|
|
FPDF_SaveAsCopy(FDocument, @FileWriteInfo, Ord(Option));
|
|
|
|
if FForm <> nil then
|
|
FORM_DoDocumentAAction(FForm, FPDFDOC_AACTION_DS); // AfterSave
|
|
end;
|
|
|
|
procedure TPdfDocument.SaveToBytes(var Bytes: TBytes; Option: TPdfDocumentSaveOption; FileVersion: Integer);
|
|
var
|
|
Stream: TBytesStream;
|
|
Size: NativeInt;
|
|
begin
|
|
CheckActive;
|
|
|
|
Stream := TBytesStream.Create(nil);
|
|
try
|
|
SaveToStream(Stream, Option, FileVersion);
|
|
Size := Stream.Size;
|
|
Bytes := Stream.Bytes;
|
|
finally
|
|
Stream.Free;
|
|
end;
|
|
// Trim the byte array from the stream's capacity to the actual size
|
|
if Length(Bytes) <> Size then
|
|
SetLength(Bytes, Size);
|
|
end;
|
|
|
|
function TPdfDocument.NewDocument: Boolean;
|
|
begin
|
|
Close;
|
|
FDocument := FPDF_CreateNewDocument;
|
|
Result := FDocument <> nil;
|
|
FFormModified := False;
|
|
end;
|
|
|
|
procedure TPdfDocument.DeletePage(Index: Integer);
|
|
begin
|
|
CheckActive;
|
|
FPages.Delete(Index);
|
|
FPDFPage_Delete(FDocument, Index);
|
|
end;
|
|
|
|
function TPdfDocument.NewPage(Width, Height: Double; Index: Integer): TPdfPage;
|
|
var
|
|
LPage: FPDF_PAGE;
|
|
begin
|
|
CheckActive;
|
|
if Index < 0 then
|
|
Index := FPages.Count; // append new page
|
|
LPage := FPDFPage_New(FDocument, Index, Width, Height);
|
|
if LPage <> nil then
|
|
begin
|
|
Result := TPdfPage.Create(Self, LPage);
|
|
FPages.Insert(Index, Result);
|
|
end
|
|
else
|
|
Result := nil;
|
|
end;
|
|
|
|
function TPdfDocument.NewPage(Index: Integer = -1): TPdfPage;
|
|
begin
|
|
Result := NewPage(PdfDefaultPageWidth, PdfDefaultPageHeight, Index);
|
|
end;
|
|
|
|
function TPdfDocument.ApplyViewerPreferences(Source: TPdfDocument): Boolean;
|
|
begin
|
|
CheckActive;
|
|
Source.CheckActive;
|
|
Result := FPDF_CopyViewerPreferences(FDocument, Source.FDocument) <> 0;
|
|
end;
|
|
|
|
function TPdfDocument.GetFileIdentifier(IdType: TPdfFileIdType): string;
|
|
var
|
|
Len: Integer;
|
|
A: AnsiString;
|
|
begin
|
|
CheckActive;
|
|
Len := FPDF_GetFileIdentifier(FDocument, FPDF_FILEIDTYPE(IdType), nil, 0) div SizeOf(AnsiChar) - 1;
|
|
if Len > 0 then
|
|
begin
|
|
SetLength(A, Len);
|
|
FPDF_GetFileIdentifier(FDocument, FPDF_FILEIDTYPE(IdType), PAnsiChar(A), (Len + 1) * SizeOf(AnsiChar));
|
|
Result := string(A);
|
|
end
|
|
else
|
|
Result := '';
|
|
end;
|
|
|
|
function TPdfDocument.GetMetaText(const TagName: string): string;
|
|
var
|
|
Len: Integer;
|
|
A: AnsiString;
|
|
begin
|
|
CheckActive;
|
|
A := AnsiString(TagName);
|
|
Len := FPDF_GetMetaText(FDocument, PAnsiChar(A), nil, 0) div SizeOf(WideChar) - 1;
|
|
if Len > 0 then
|
|
begin
|
|
SetLength(Result, Len);
|
|
FPDF_GetMetaText(FDocument, PAnsiChar(A), PWideChar(Result), (Len + 1) * SizeOf(WideChar));
|
|
end
|
|
else
|
|
Result := '';
|
|
end;
|
|
|
|
function TPdfDocument.GetSecurityHandlerRevision: Integer;
|
|
begin
|
|
CheckActive;
|
|
Result := FPDF_GetSecurityHandlerRevision(FDocument);
|
|
end;
|
|
|
|
function TPdfDocument.GetDocPermissions: Integer;
|
|
begin
|
|
CheckActive;
|
|
Result := Integer(FPDF_GetDocPermissions(FDocument));
|
|
end;
|
|
|
|
function TPdfDocument.GetFileVersion: Integer;
|
|
begin
|
|
CheckActive;
|
|
if FPDF_GetFileVersion(FDocument, Result) = 0 then
|
|
Result := 0;
|
|
end;
|
|
|
|
function TPdfDocument.GetPageSize(Index: Integer): TPdfPoint;
|
|
var
|
|
SizeF: TFSSizeF;
|
|
begin
|
|
CheckActive;
|
|
Result.X := 0;
|
|
Result.Y := 0;
|
|
if FPDF_GetPageSizeByIndexF(FDocument, Index, @SizeF) <> 0 then
|
|
begin
|
|
Result.X := SizeF.width;
|
|
Result.Y := SizeF.height;
|
|
end;
|
|
end;
|
|
|
|
function TPdfDocument.GetPageMode: TPdfDocumentPageMode;
|
|
begin
|
|
CheckActive;
|
|
Result := TPdfDocumentPageMode(FPDFDoc_GetPageMode(FDocument));
|
|
end;
|
|
|
|
function TPdfDocument.GetNumCopies: Integer;
|
|
begin
|
|
CheckActive;
|
|
Result := FPDF_VIEWERREF_GetNumCopies(FDocument);
|
|
end;
|
|
|
|
class function TPdfDocument.SetPrintMode(PrintMode: TPdfPrintMode): Boolean;
|
|
begin
|
|
InitLib;
|
|
{$IFDEF MSWINDOWS}
|
|
Result := FPDF_SetPrintMode(Ord(PrintMode)) <> 0;
|
|
{$ELSE}
|
|
Result := False;
|
|
{$ENDIF MSWINDOWS}
|
|
end;
|
|
|
|
procedure TPdfDocument.SetFormFieldHighlightAlpha(Value: Integer);
|
|
begin
|
|
if Value < 0 then
|
|
Value := 0;
|
|
if Value > 255 then
|
|
Value := 255;
|
|
|
|
if Value <> FFormFieldHighlightAlpha then
|
|
begin
|
|
FFormFieldHighlightAlpha := Value;
|
|
if Active then
|
|
FPDF_SetFormFieldHighlightAlpha(FForm, FFormFieldHighlightAlpha);
|
|
end;
|
|
end;
|
|
|
|
procedure TPdfDocument.SetFormFieldHighlightColor(const Value: TColorRef);
|
|
begin
|
|
if Value <> FFormFieldHighlightColor then
|
|
begin
|
|
FFormFieldHighlightColor := Value;
|
|
if Active then
|
|
FPDF_SetFormFieldHighlightColor(FForm, 0, FFormFieldHighlightColor);
|
|
end;
|
|
end;
|
|
|
|
function TPdfDocument.FindPage(Page: FPDF_PAGE): TPdfPage;
|
|
var
|
|
I: Integer;
|
|
begin
|
|
// The page must be already loaded
|
|
for I := 0 to PageCount - 1 do
|
|
begin
|
|
Result := TPdfPage(FPages[I]);
|
|
if (Result <> nil) and (Result.FPage = Page) then
|
|
Exit;
|
|
end;
|
|
Result := nil;
|
|
end;
|
|
|
|
|
|
{ TPdfPage }
|
|
|
|
constructor TPdfPage.Create(ADocument: TPdfDocument; APage: FPDF_PAGE);
|
|
begin
|
|
inherited Create;
|
|
FDocument := ADocument;
|
|
FPage := APage;
|
|
FAnnotations := TPdfAnnotationList.Create(Self);
|
|
|
|
AfterOpen;
|
|
end;
|
|
|
|
destructor TPdfPage.Destroy;
|
|
begin
|
|
Close;
|
|
FDocument.ExtractPage(Self);
|
|
FreeAndNil(FAnnotations);
|
|
inherited Destroy;
|
|
end;
|
|
|
|
function TPdfPage.IsValidForm: Boolean;
|
|
begin
|
|
Result := (FDocument <> nil) and (FDocument.FForm <> nil) and (FPage <> nil);
|
|
end;
|
|
|
|
procedure TPdfPage.AfterOpen;
|
|
var
|
|
OldCurDoc: TPdfDocument;
|
|
begin
|
|
if IsValidForm then
|
|
begin
|
|
OldCurDoc := UnsupportedFeatureCurrentDocument;
|
|
try
|
|
UnsupportedFeatureCurrentDocument := FDocument;
|
|
FORM_OnAfterLoadPage(FPage, FDocument.FForm);
|
|
FORM_DoPageAAction(FPage, FDocument.FForm, FPDFPAGE_AACTION_OPEN);
|
|
finally
|
|
UnsupportedFeatureCurrentDocument := OldCurDoc;
|
|
end;
|
|
end;
|
|
|
|
UpdateMetrics;
|
|
end;
|
|
|
|
procedure TPdfPage.Close;
|
|
begin
|
|
FAnnotations.CloseAnnotations;
|
|
|
|
if IsValidForm then
|
|
begin
|
|
FORM_DoPageAAction(FPage, FDocument.FForm, FPDFPAGE_AACTION_CLOSE);
|
|
FORM_OnBeforeClosePage(FPage, FDocument.FForm);
|
|
end;
|
|
|
|
if FPageLinkHandle <> nil then
|
|
begin
|
|
FPDFLink_CloseWebLinks(FPageLinkHandle);
|
|
FPageLinkHandle := nil;
|
|
end;
|
|
if FSearchHandle <> nil then
|
|
begin
|
|
FPDFText_FindClose(FSearchHandle);
|
|
FSearchHandle := nil;
|
|
end;
|
|
if FTextHandle <> nil then
|
|
begin
|
|
FPDFText_ClosePage(FTextHandle);
|
|
FTextHandle := nil;
|
|
end;
|
|
if FPage <> nil then
|
|
begin
|
|
FPDF_ClosePage(FPage);
|
|
FPage := nil;
|
|
end;
|
|
end;
|
|
|
|
procedure TPdfPage.Open;
|
|
begin
|
|
if FPage = nil then
|
|
begin
|
|
FPage := FDocument.ReloadPage(Self);
|
|
AfterOpen;
|
|
end;
|
|
end;
|
|
|
|
function TPdfPage.GetPdfActionFilePath(Action: FPDF_ACTION): string;
|
|
var
|
|
ByteSize: Integer;
|
|
Buf: UTF8String;
|
|
begin
|
|
Result := '';
|
|
if Action <> nil then
|
|
begin
|
|
case FPDFAction_GetType(Action) of
|
|
PDFACTION_LAUNCH,
|
|
PDFACTION_REMOTEGOTO:
|
|
begin
|
|
ByteSize := FPDFAction_GetFilePath(Action, nil, 0);
|
|
if ByteSize > 0 then
|
|
begin
|
|
SetLength(Buf, ByteSize); // we could optimize this with "SetLength(Buf, ByteSize - 1)" and use already existing #0 terminator
|
|
ByteSize := FPDFAction_GetFilePath(Action, PAnsiChar(Buf), Length(Buf));
|
|
end;
|
|
if ByteSize > 0 then
|
|
begin
|
|
SetLength(Buf, ByteSize - 1); // ByteSize includes #0
|
|
Result := UTF8ToString(Buf);
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function TPdfPage.GetPdfActionUriPath(Action: FPDF_ACTION): string;
|
|
var
|
|
ByteSize: Integer;
|
|
Buf: UTF8String;
|
|
begin
|
|
Result := '';
|
|
if Action <> nil then
|
|
begin
|
|
ByteSize := FPDFAction_GetURIPath(FDocument.Handle, Action, nil, 0);
|
|
if ByteSize > 0 then
|
|
begin
|
|
SetLength(Buf, ByteSize); // we could optimize this with "SetLength(Buf, ByteSize - 1)" and use already existing #0 terminator
|
|
ByteSize := FPDFAction_GetURIPath(FDocument.Handle, Action, PAnsiChar(Buf), Length(Buf));
|
|
end;
|
|
if ByteSize > 0 then
|
|
begin
|
|
SetLength(Buf, ByteSize - 1); // ByteSize includes #0
|
|
Result := UTF8ToString(Buf);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
class function TPdfPage.GetDrawFlags(const Options: TPdfPageRenderOptions): Integer;
|
|
begin
|
|
Result := 0;
|
|
if proAnnotations in Options then
|
|
Result := Result or FPDF_ANNOT;
|
|
if proLCDOptimized in Options then
|
|
Result := Result or FPDF_LCD_TEXT;
|
|
if proNoNativeText in Options then
|
|
Result := Result or FPDF_NO_NATIVETEXT;
|
|
if proNoCatch in Options then
|
|
Result := Result or FPDF_NO_CATCH;
|
|
if proLimitedImageCacheSize in Options then
|
|
Result := Result or FPDF_RENDER_LIMITEDIMAGECACHE;
|
|
if proForceHalftone in Options then
|
|
Result := Result or FPDF_RENDER_FORCEHALFTONE;
|
|
if proPrinting in Options then
|
|
Result := Result or FPDF_PRINTING;
|
|
if proReverseByteOrder in Options then
|
|
Result := Result or FPDF_REVERSE_BYTE_ORDER;
|
|
end;
|
|
|
|
{$IFDEF MSWINDOWS}
|
|
procedure TPdfPage.Draw(DC: HDC; X, Y, Width, Height: Integer; Rotate: TPdfPageRotation;
|
|
const Options: TPdfPageRenderOptions; PageBackground: TColorRef);
|
|
var
|
|
BitmapInfo: TBitmapInfo;
|
|
Bmp, OldBmp: HBITMAP;
|
|
BmpBits: Pointer;
|
|
PdfBmp: TPdfBitmap;
|
|
BmpDC: HDC;
|
|
begin
|
|
Open;
|
|
|
|
if proPrinting in Options then
|
|
begin
|
|
if IsValidForm and (FPDFPage_GetAnnotCount(FPage) > 0) then
|
|
begin
|
|
// Form content isn't printed unless it was flattend and the page was reloaded.
|
|
ApplyChanges;
|
|
Flatten(True);
|
|
Close;
|
|
Open;
|
|
end;
|
|
FPDF_RenderPage(DC, FPage, X, Y, Width, Height, Ord(Rotate), GetDrawFlags(Options));
|
|
Exit;
|
|
end;
|
|
|
|
FillChar(BitmapInfo, SizeOf(BitmapInfo), 0);
|
|
BitmapInfo.bmiHeader.biSize := SizeOf(BitmapInfo);
|
|
BitmapInfo.bmiHeader.biWidth := Width;
|
|
BitmapInfo.bmiHeader.biHeight := -Height; // negative Height means top to bottom for Y values
|
|
BitmapInfo.bmiHeader.biPlanes := 1;
|
|
BitmapInfo.bmiHeader.biBitCount := 32;
|
|
BitmapInfo.bmiHeader.biCompression := BI_RGB;
|
|
BmpBits := nil;
|
|
Bmp := CreateDIBSection(DC, BitmapInfo, DIB_RGB_COLORS, BmpBits, 0, 0);
|
|
if Bmp <> 0 then
|
|
begin
|
|
try
|
|
// Use the Windows Bitmap's bits for the PdfBmp
|
|
PdfBmp := TPdfBitmap.Create(Width, Height, bfBGRA, BmpBits, Width * 4);
|
|
try
|
|
Draw(PdfBmp, 0, 0, Width, Height, Rotate, Options, PageBackground);
|
|
finally
|
|
PdfBmp.Free;
|
|
end;
|
|
|
|
BmpDC := CreateCompatibleDC(DC);
|
|
OldBmp := SelectObject(BmpDC, Bmp);
|
|
BitBlt(DC, X, Y, Width, Height, BmpDC, 0, 0, SRCCOPY);
|
|
SelectObject(BmpDC, OldBmp);
|
|
DeleteDC(BmpDC);
|
|
finally
|
|
DeleteObject(Bmp);
|
|
end;
|
|
end;
|
|
end;
|
|
{$ENDIF MSWINDOWS}
|
|
|
|
procedure TPdfPage.Draw(APdfBitmap: TPdfBitmap; X, Y, Width, Height: Integer; Rotate: TPdfPageRotation = prNormal;
|
|
const Options: TPdfPageRenderOptions = []; PageBackground: TColorRef = $FFFFFF);
|
|
begin
|
|
APdfBitmap.FillRect(0, 0, Width, Height, $FF000000 or PageBackground);
|
|
DrawToPdfBitmap(APdfBitmap, 0, 0, Width, Height, Rotate, Options);
|
|
DrawFormToPdfBitmap(APdfBitmap, 0, 0, Width, Height, Rotate, Options);
|
|
end;
|
|
|
|
procedure TPdfPage.DrawToPdfBitmap(APdfBitmap: TPdfBitmap; X, Y, Width, Height: Integer;
|
|
Rotate: TPdfPageRotation; const Options: TPdfPageRenderOptions);
|
|
begin
|
|
Open;
|
|
FPDF_RenderPageBitmap(APdfBitmap.FBitmap, FPage, X, Y, Width, Height, Ord(Rotate), GetDrawFlags(Options));
|
|
end;
|
|
|
|
procedure TPdfPage.DrawFormToPdfBitmap(APdfBitmap: TPdfBitmap; X, Y, Width, Height: Integer;
|
|
Rotate: TPdfPageRotation; const Options: TPdfPageRenderOptions);
|
|
begin
|
|
Open;
|
|
if IsValidForm then
|
|
begin
|
|
if proPrinting in Options then
|
|
begin
|
|
if FDocument.PrintHidesFormFieldHighlight then
|
|
FPDF_RemoveFormFieldHighlight(FDocument.FForm);
|
|
//FPDF_SetFormFieldHighlightAlpha(FDocument.FForm, 0); // hide the highlight
|
|
FormEventKillFocus;
|
|
end;
|
|
try
|
|
FPDF_FFLDraw(FDocument.FForm, APdfBitmap.FBitmap, FPage, X, Y, Width, Height, Ord(Rotate), GetDrawFlags(Options));
|
|
finally
|
|
if (proPrinting in Options) and FDocument.PrintHidesFormFieldHighlight then
|
|
FDocument.UpdateFormFieldHighlight;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TPdfPage.UpdateMetrics;
|
|
begin
|
|
FWidth := FPDF_GetPageWidthF(FPage);
|
|
FHeight := FPDF_GetPageHeightF(FPage);
|
|
FTransparency := FPDFPage_HasTransparency(FPage) <> 0;
|
|
FRotation := TPdfPageRotation(FPDFPage_GetRotation(FPage));
|
|
end;
|
|
|
|
function TPdfPage.DeviceToPage(X, Y, Width, Height: Integer; DeviceX, DeviceY: Integer; Rotate: TPdfPageRotation): TPdfPoint;
|
|
begin
|
|
Open;
|
|
FPDF_DeviceToPage(FPage, X, Y, Width, Height, Ord(Rotate), DeviceX, DeviceY, Result.X, Result.Y);
|
|
end;
|
|
|
|
function TPdfPage.PageToDevice(X, Y, Width, Height: Integer; PageX, PageY: Double;
|
|
Rotate: TPdfPageRotation): TPoint;
|
|
begin
|
|
Open;
|
|
FPDF_PageToDevice(FPage, X, Y, Width, Height, Ord(Rotate), PageX, PageY, Result.X, Result.Y);
|
|
end;
|
|
|
|
function TPdfPage.DeviceToPage(X, Y, Width, Height: Integer; const R: TRect; Rotate: TPdfPageRotation): TPdfRect;
|
|
begin
|
|
Result.TopLeft := DeviceToPage(X, Y, Width, Height, R.Left, R.Top, Rotate);
|
|
Result.BottomRight := DeviceToPage(X, Y, Width, Height, R.Right, R.Bottom, Rotate);
|
|
end;
|
|
|
|
function TPdfPage.PageToDevice(X, Y, Width, Height: Integer; const R: TPdfRect; Rotate: TPdfPageRotation): TRect;
|
|
var
|
|
T: Integer;
|
|
begin
|
|
Result.TopLeft := PageToDevice(X, Y, Width, Height, R.Left, R.Top, Rotate);
|
|
Result.BottomRight := PageToDevice(X, Y, Width, Height, R.Right, R.Bottom, Rotate);
|
|
// Page coordinales are upside down, but device coordinates aren't.
|
|
if Result.Top > Result.Bottom then
|
|
begin
|
|
T := Result.Top;
|
|
Result.Top := Result.Bottom;
|
|
Result.Bottom := T;
|
|
end;
|
|
end;
|
|
|
|
procedure TPdfPage.SetRotation(const Value: TPdfPageRotation);
|
|
begin
|
|
Open;
|
|
FPDFPage_SetRotation(FPage, Ord(Value));
|
|
FRotation := TPdfPageRotation(FPDFPage_GetRotation(FPage));
|
|
end;
|
|
|
|
procedure TPdfPage.ApplyChanges;
|
|
begin
|
|
if FPage <> nil then
|
|
begin
|
|
FPDFPage_GenerateContent(FPage);
|
|
|
|
// Newly added text annotations will not show the text popup unless the page is notified.
|
|
FAnnotations.CloseAnnotations;
|
|
if IsValidForm then
|
|
begin
|
|
FORM_DoPageAAction(FPage, FDocument.FForm, FPDFPAGE_AACTION_CLOSE);
|
|
FORM_OnBeforeClosePage(FPage, FDocument.FForm);
|
|
|
|
FORM_OnAfterLoadPage(FPage, FDocument.FForm);
|
|
FORM_DoPageAAction(FPage, FDocument.FForm, FPDFPAGE_AACTION_OPEN);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TPdfPage.Flatten(AFlatPrint: Boolean);
|
|
const
|
|
Flags: array[Boolean] of Integer = (FLAT_NORMALDISPLAY, FLAT_PRINT);
|
|
begin
|
|
if FPage <> nil then
|
|
FPDFPage_Flatten(FPage, Flags[AFlatPrint]);
|
|
end;
|
|
|
|
function TPdfPage.BeginText: Boolean;
|
|
begin
|
|
if FTextHandle = nil then
|
|
begin
|
|
Open;
|
|
FTextHandle := FPDFText_LoadPage(FPage);
|
|
end;
|
|
Result := FTextHandle <> nil;
|
|
end;
|
|
|
|
function TPdfPage.BeginWebLinks: Boolean;
|
|
begin
|
|
// WebLinks are not stored in the PDF but are created by parsing the page's text for URLs.
|
|
// They are accessed differently than annotation links, which are stored in the PDF.
|
|
if (FPageLinkHandle = nil) and BeginText then
|
|
FPageLinkHandle := FPDFLink_LoadWebLinks(FTextHandle);
|
|
Result := FPageLinkHandle <> nil;
|
|
end;
|
|
|
|
function TPdfPage.BeginFind(const SearchString: string; MatchCase, MatchWholeWord,
|
|
FromEnd: Boolean): Boolean;
|
|
var
|
|
Flags, StartIndex: Integer;
|
|
begin
|
|
EndFind;
|
|
if BeginText then
|
|
begin
|
|
Flags := 0;
|
|
if MatchCase then
|
|
Flags := Flags or FPDF_MATCHCASE;
|
|
if MatchWholeWord then
|
|
Flags := Flags or FPDF_MATCHWHOLEWORD;
|
|
|
|
if FromEnd then
|
|
StartIndex := -1
|
|
else
|
|
StartIndex := 0;
|
|
|
|
FSearchHandle := FPDFText_FindStart(FTextHandle, PWideChar(SearchString), Flags, StartIndex);
|
|
end;
|
|
Result := FSearchHandle <> nil;
|
|
end;
|
|
|
|
procedure TPdfPage.EndFind;
|
|
begin
|
|
if FSearchHandle <> nil then
|
|
begin
|
|
FPDFText_FindClose(FSearchHandle);
|
|
FSearchHandle := nil;
|
|
end;
|
|
end;
|
|
|
|
function TPdfPage.FindNext(var CharIndex, Count: Integer): Boolean;
|
|
begin
|
|
CharIndex := 0;
|
|
Count := 0;
|
|
if FSearchHandle <> nil then
|
|
begin
|
|
Result := FPDFText_FindNext(FSearchHandle) <> 0;
|
|
if Result then
|
|
begin
|
|
CharIndex := FPDFText_GetSchResultIndex(FSearchHandle);
|
|
Count := FPDFText_GetSchCount(FSearchHandle);
|
|
end;
|
|
end
|
|
else
|
|
Result := False;
|
|
end;
|
|
|
|
function TPdfPage.FindPrev(var CharIndex, Count: Integer): Boolean;
|
|
begin
|
|
CharIndex := 0;
|
|
Count := 0;
|
|
if FSearchHandle <> nil then
|
|
begin
|
|
Result := FPDFText_FindPrev(FSearchHandle) <> 0;
|
|
if Result then
|
|
begin
|
|
CharIndex := FPDFText_GetSchResultIndex(FSearchHandle);
|
|
Count := FPDFText_GetSchCount(FSearchHandle);
|
|
end;
|
|
end
|
|
else
|
|
Result := False;
|
|
end;
|
|
|
|
function TPdfPage.GetCharCount: Integer;
|
|
begin
|
|
if BeginText then
|
|
Result := FPDFText_CountChars(FTextHandle)
|
|
else
|
|
Result := 0;
|
|
end;
|
|
|
|
function TPdfPage.ReadChar(CharIndex: Integer): WideChar;
|
|
begin
|
|
if BeginText then
|
|
Result := FPDFText_GetUnicode(FTextHandle, CharIndex)
|
|
else
|
|
Result := #0;
|
|
end;
|
|
|
|
function TPdfPage.GetCharFontSize(CharIndex: Integer): Double;
|
|
begin
|
|
if BeginText then
|
|
Result := FPDFText_GetFontSize(FTextHandle, CharIndex)
|
|
else
|
|
Result := 0;
|
|
end;
|
|
|
|
function TPdfPage.GetCharBox(CharIndex: Integer): TPdfRect;
|
|
begin
|
|
if BeginText then
|
|
FPDFText_GetCharBox(FTextHandle, CharIndex, Result.Left, Result.Right, Result.Bottom, Result.Top)
|
|
else
|
|
Result := TPdfRect.Empty;
|
|
end;
|
|
|
|
function TPdfPage.GetCharIndexAt(PageX, PageY, ToleranceX, ToleranceY: Double): Integer;
|
|
begin
|
|
if BeginText then
|
|
Result := FPDFText_GetCharIndexAtPos(FTextHandle, PageX, PageY, ToleranceX, ToleranceY)
|
|
else
|
|
Result := 0;
|
|
end;
|
|
|
|
function TPdfPage.ReadText(CharIndex, Count: Integer): string;
|
|
var
|
|
Len: Integer;
|
|
begin
|
|
if (Count > 0) and BeginText then
|
|
begin
|
|
SetLength(Result, Count); // we let GetText overwrite our #0 terminator with its #0
|
|
Len := FPDFText_GetText(FTextHandle, CharIndex, Count, PWideChar(Result)) - 1; // returned length includes the #0
|
|
if Len <= 0 then
|
|
Result := ''
|
|
else if Len < Count then
|
|
SetLength(Result, Len);
|
|
end
|
|
else
|
|
Result := '';
|
|
end;
|
|
|
|
function TPdfPage.GetTextAt(Left, Top, Right, Bottom: Double): string;
|
|
var
|
|
Len: Integer;
|
|
begin
|
|
if BeginText then
|
|
begin
|
|
Len := FPDFText_GetBoundedText(FTextHandle, Left, Top, Right, Bottom, nil, 0); // excluding #0 terminator
|
|
SetLength(Result, Len);
|
|
if Len > 0 then
|
|
FPDFText_GetBoundedText(FTextHandle, Left, Top, Right, Bottom, PWideChar(Result), Len);
|
|
end
|
|
else
|
|
Result := '';
|
|
end;
|
|
|
|
function TPdfPage.GetTextAt(const R: TPdfRect): string;
|
|
begin
|
|
Result := GetTextAt(R.Left, R.Top, R.Right, R.Bottom);
|
|
end;
|
|
|
|
function TPdfPage.GetTextRectCount(CharIndex, Count: Integer): Integer;
|
|
begin
|
|
if BeginText then
|
|
Result := FPDFText_CountRects(FTextHandle, CharIndex, Count)
|
|
else
|
|
Result := 0;
|
|
end;
|
|
|
|
function TPdfPage.GetTextRect(RectIndex: Integer): TPdfRect;
|
|
begin
|
|
if BeginText then
|
|
FPDFText_GetRect(FTextHandle, RectIndex, Result.Left, Result.Top, Result.Right, Result.Bottom)
|
|
else
|
|
Result := TPdfRect.Empty;
|
|
end;
|
|
|
|
function TPdfPage.IsUriLinkAtPoint(X, Y: Double): Boolean;
|
|
var
|
|
Link: FPDF_LINK;
|
|
Action: FPDF_ACTION;
|
|
begin
|
|
Result := False;
|
|
Link := FPDFLink_GetLinkAtPoint(Handle, X, Y);
|
|
if Link <> nil then
|
|
begin
|
|
Action := FPDFLink_GetAction(Link);
|
|
if (Action <> nil) and (FPDFAction_GetType(Action) = PDFACTION_URI) then
|
|
Result := True;
|
|
end;
|
|
end;
|
|
|
|
function TPdfPage.IsUriLinkAtPoint(X, Y: Double; var Uri: string): Boolean;
|
|
var
|
|
Link: FPDF_LINK;
|
|
Action: FPDF_ACTION;
|
|
begin
|
|
Action := nil;
|
|
Result := False;
|
|
Link := FPDFLink_GetLinkAtPoint(Handle, X, Y);
|
|
if Link <> nil then
|
|
begin
|
|
Action := FPDFLink_GetAction(Link);
|
|
if (Action <> nil) and (FPDFAction_GetType(Action) = PDFACTION_URI) then
|
|
Result := True;
|
|
end;
|
|
|
|
if Result then
|
|
Uri := GetPdfActionUriPath(Action)
|
|
else
|
|
Uri := '';
|
|
end;
|
|
|
|
function TPdfPage.GetLinkAtPoint(X, Y: Double): TPdfAnnotation;
|
|
var
|
|
Link: FPDF_LINK;
|
|
begin
|
|
Link := FPDFLink_GetLinkAtPoint(Handle, X, Y);
|
|
if Link <> nil then
|
|
begin
|
|
Result := Annotations.FindLink(Link);
|
|
if (Result <> nil) and (Result.LinkType = altUnsupported) then
|
|
Result := nil;
|
|
end
|
|
else
|
|
Result := nil;
|
|
end;
|
|
|
|
function TPdfPage.GetWebLinkCount: Integer;
|
|
begin
|
|
if BeginWebLinks then
|
|
begin
|
|
Result := FPDFLink_CountWebLinks(FPageLinkHandle);
|
|
if Result < 0 then
|
|
Result := 0;
|
|
end
|
|
else
|
|
Result := 0;
|
|
end;
|
|
|
|
function TPdfPage.GetWebLinkURL(LinkIndex: Integer): string;
|
|
var
|
|
Len: Integer;
|
|
begin
|
|
Result := '';
|
|
if BeginWebLinks then
|
|
begin
|
|
Len := FPDFLink_GetURL(FPageLinkHandle, LinkIndex, nil, 0) - 1; // including #0 terminator
|
|
if Len > 0 then
|
|
begin
|
|
SetLength(Result, Len);
|
|
FPDFLink_GetURL(FPageLinkHandle, LinkIndex, PWideChar(Result), Len + 1); // including #0 terminator
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function TPdfPage.GetWebLinkRectCount(LinkIndex: Integer): Integer;
|
|
begin
|
|
if BeginWebLinks then
|
|
Result := FPDFLink_CountRects(FPageLinkHandle, LinkIndex)
|
|
else
|
|
Result := 0;
|
|
end;
|
|
|
|
function TPdfPage.GetWebLinkRect(LinkIndex, RectIndex: Integer): TPdfRect;
|
|
begin
|
|
if BeginWebLinks then
|
|
FPDFLink_GetRect(FPageLinkHandle, LinkIndex, RectIndex, Result.Left, Result.Top, Result.Right, Result.Bottom)
|
|
else
|
|
Result := TPdfRect.Empty;
|
|
end;
|
|
|
|
function TPdfPage.IsWebLinkAtPoint(X, Y: Double): Boolean;
|
|
var
|
|
LinkIndex, RectIndex: Integer;
|
|
Pt: TPdfPoint;
|
|
begin
|
|
Result := True;
|
|
Pt.X := X;
|
|
Pt.Y := Y;
|
|
for LinkIndex := 0 to GetWebLinkCount - 1 do
|
|
for RectIndex := 0 to GetWebLinkRectCount(LinkIndex) - 1 do
|
|
if GetWebLinkRect(LinkIndex, RectIndex).PtIn(Pt) then
|
|
Exit;
|
|
Result := False;
|
|
end;
|
|
|
|
function TPdfPage.IsWebLinkAtPoint(X, Y: Double; var URL: string): Boolean;
|
|
var
|
|
LinkIndex, RectIndex: Integer;
|
|
Pt: TPdfPoint;
|
|
begin
|
|
Result := True;
|
|
Pt.X := X;
|
|
Pt.Y := Y;
|
|
for LinkIndex := 0 to GetWebLinkCount - 1 do
|
|
begin
|
|
for RectIndex := 0 to GetWebLinkRectCount(LinkIndex) - 1 do
|
|
begin
|
|
if GetWebLinkRect(LinkIndex, RectIndex).PtIn(Pt) then
|
|
begin
|
|
URL := GetWebLinkURL(LinkIndex);
|
|
Exit;
|
|
end;
|
|
end;
|
|
end;
|
|
Result := False;
|
|
end;
|
|
|
|
function TPdfPage.ShiftStateToModifier(const Shift: TShiftState): Integer;
|
|
begin
|
|
Result := 0;
|
|
if ssShift in Shift then
|
|
Result := Result or FWL_EVENTFLAG_ShiftKey;
|
|
if ssCtrl in Shift then
|
|
Result := Result or FWL_EVENTFLAG_ControlKey;
|
|
if ssAlt in Shift then
|
|
Result := Result or FWL_EVENTFLAG_AltKey;
|
|
if ssLeft in Shift then
|
|
Result := Result or FWL_EVENTFLAG_LeftButtonDown;
|
|
if ssMiddle in Shift then
|
|
Result := Result or FWL_EVENTFLAG_MiddleButtonDown;
|
|
if ssRight in Shift then
|
|
Result := Result or FWL_EVENTFLAG_RightButtonDown;
|
|
end;
|
|
|
|
function TPdfPage.FormEventFocus(const Shift: TShiftState; PageX, PageY: Double): Boolean;
|
|
begin
|
|
if IsValidForm then
|
|
Result := FORM_OnFocus(FDocument.FForm, FPage, ShiftStateToModifier(Shift), PageX, PageY) <> 0
|
|
else
|
|
Result := False;
|
|
end;
|
|
|
|
function TPdfPage.FormEventMouseWheel(const Shift: TShiftState; WheelDelta: Integer; PageX, PageY: Double): Boolean;
|
|
var
|
|
Pt: TFSPointF;
|
|
WheelX, WheelY: Integer;
|
|
begin
|
|
if IsValidForm then
|
|
begin
|
|
Pt.X := PageX;
|
|
Pt.Y := PageY;
|
|
WheelX := 0;
|
|
WheelY := 0;
|
|
if ssShift in Shift then
|
|
WheelX := WheelDelta
|
|
else
|
|
WheelY := WheelDelta;
|
|
Result := FORM_OnMouseWheel(FDocument.FForm, FPage, ShiftStateToModifier(Shift), @Pt, WheelX, WheelY) <> 0;
|
|
end
|
|
else
|
|
Result := False;
|
|
end;
|
|
|
|
function TPdfPage.FormEventMouseMove(const Shift: TShiftState; PageX, PageY: Double): Boolean;
|
|
begin
|
|
if IsValidForm then
|
|
Result := FORM_OnMouseMove(FDocument.FForm, FPage, ShiftStateToModifier(Shift), PageX, PageY) <> 0
|
|
else
|
|
Result := False;
|
|
end;
|
|
|
|
function TPdfPage.FormEventLButtonDown(const Shift: TShiftState; PageX, PageY: Double): Boolean;
|
|
begin
|
|
if IsValidForm then
|
|
Result := FORM_OnLButtonDown(FDocument.FForm, FPage, ShiftStateToModifier(Shift), PageX, PageY) <> 0
|
|
else
|
|
Result := False;
|
|
end;
|
|
|
|
function TPdfPage.FormEventLButtonUp(const Shift: TShiftState; PageX, PageY: Double): Boolean;
|
|
begin
|
|
if IsValidForm then
|
|
Result := FORM_OnLButtonUp(FDocument.FForm, FPage, ShiftStateToModifier(Shift), PageX, PageY) <> 0
|
|
else
|
|
Result := False;
|
|
end;
|
|
|
|
function TPdfPage.FormEventRButtonDown(const Shift: TShiftState; PageX, PageY: Double): Boolean;
|
|
begin
|
|
if IsValidForm then
|
|
Result := FORM_OnRButtonDown(FDocument.FForm, FPage, ShiftStateToModifier(Shift), PageX, PageY) <> 0
|
|
else
|
|
Result := False;
|
|
end;
|
|
|
|
function TPdfPage.FormEventRButtonUp(const Shift: TShiftState; PageX, PageY: Double): Boolean;
|
|
begin
|
|
if IsValidForm then
|
|
Result := FORM_OnRButtonUp(FDocument.FForm, FPage, ShiftStateToModifier(Shift), PageX, PageY) <> 0
|
|
else
|
|
Result := False;
|
|
end;
|
|
|
|
function TPdfPage.FormEventKeyDown(KeyCode: Word; const Shift: TShiftState): Boolean;
|
|
begin
|
|
if IsValidForm then
|
|
Result := FORM_OnKeyDown(FDocument.FForm, FPage, KeyCode, ShiftStateToModifier(Shift)) <> 0
|
|
else
|
|
Result := False;
|
|
end;
|
|
|
|
function TPdfPage.FormEventKeyUp(KeyCode: Word; const Shift: TShiftState): Boolean;
|
|
begin
|
|
if IsValidForm then
|
|
Result := FORM_OnKeyUp(FDocument.FForm, FPage, KeyCode, ShiftStateToModifier(Shift)) <> 0
|
|
else
|
|
Result := False;
|
|
end;
|
|
|
|
function TPdfPage.FormEventKeyPress(Key: Word; const Shift: TShiftState): Boolean;
|
|
begin
|
|
if IsValidForm then
|
|
Result := FORM_OnChar(FDocument.FForm, FPage, Key, ShiftStateToModifier(Shift)) <> 0
|
|
else
|
|
Result := False;
|
|
end;
|
|
|
|
function TPdfPage.FormEventKillFocus: Boolean;
|
|
begin
|
|
if IsValidForm then
|
|
Result := FORM_ForceToKillFocus(FDocument.FForm) <> 0
|
|
else
|
|
Result := False;
|
|
end;
|
|
|
|
function TPdfPage.FormGetFocusedText: string;
|
|
var
|
|
ByteLen: LongWord;
|
|
begin
|
|
if IsValidForm then
|
|
begin
|
|
ByteLen := FORM_GetFocusedText(FDocument.FForm, FPage, nil, 0); // UTF 16 including #0 terminator in byte size
|
|
if ByteLen <= 2 then // WideChar(#0) => empty string
|
|
Result := ''
|
|
else
|
|
begin
|
|
SetLength(Result, ByteLen div SizeOf(WideChar) - 1);
|
|
FORM_GetFocusedText(FDocument.FForm, FPage, PWideChar(Result), ByteLen);
|
|
end;
|
|
end
|
|
else
|
|
Result := '';
|
|
end;
|
|
|
|
function TPdfPage.FormGetSelectedText: string;
|
|
var
|
|
ByteLen: LongWord;
|
|
begin
|
|
if IsValidForm then
|
|
begin
|
|
ByteLen := FORM_GetSelectedText(FDocument.FForm, FPage, nil, 0); // UTF 16 including #0 terminator in byte size
|
|
if ByteLen <= 2 then // WideChar(#0) => empty string
|
|
Result := ''
|
|
else
|
|
begin
|
|
SetLength(Result, ByteLen div SizeOf(WideChar) - 1);
|
|
FORM_GetSelectedText(FDocument.FForm, FPage, PWideChar(Result), ByteLen);
|
|
end;
|
|
end
|
|
else
|
|
Result := '';
|
|
end;
|
|
|
|
function TPdfPage.FormReplaceSelection(const ANewText: string): Boolean;
|
|
begin
|
|
if IsValidForm then
|
|
begin
|
|
FORM_ReplaceSelection(FDocument.FForm, FPage, PWideChar(ANewText));
|
|
Result := True;
|
|
end
|
|
else
|
|
Result := False;
|
|
end;
|
|
|
|
function TPdfPage.FormReplaceAndKeepSelection(const ANewText: string): Boolean;
|
|
begin
|
|
if IsValidForm then
|
|
begin
|
|
FORM_ReplaceAndKeepSelection(FDocument.FForm, FPage, PWideChar(ANewText));
|
|
Result := True;
|
|
end
|
|
else
|
|
Result := False;
|
|
end;
|
|
|
|
function TPdfPage.FormSelectAllText: Boolean;
|
|
begin
|
|
if IsValidForm then
|
|
Result := FORM_SelectAllText(FDocument.FForm, FPage) <> 0
|
|
else
|
|
Result := False;
|
|
end;
|
|
|
|
function TPdfPage.FormCanUndo: Boolean;
|
|
begin
|
|
if IsValidForm then
|
|
Result := FORM_CanUndo(FDocument.FForm, FPage) <> 0
|
|
else
|
|
Result := False;
|
|
end;
|
|
|
|
function TPdfPage.FormCanRedo: Boolean;
|
|
begin
|
|
if IsValidForm then
|
|
Result := FORM_CanRedo(FDocument.FForm, FPage) <> 0
|
|
else
|
|
Result := False;
|
|
end;
|
|
|
|
function TPdfPage.FormUndo: Boolean;
|
|
begin
|
|
if IsValidForm then
|
|
Result := FORM_Undo(FDocument.FForm, FPage) <> 0
|
|
else
|
|
Result := False;
|
|
end;
|
|
|
|
function TPdfPage.FormRedo: Boolean;
|
|
begin
|
|
if IsValidForm then
|
|
Result := FORM_Redo(FDocument.FForm, FPage) <> 0
|
|
else
|
|
Result := False;
|
|
end;
|
|
|
|
function TPdfPage.HasFormFieldAtPoint(X, Y: Double): TPdfFormFieldType;
|
|
begin
|
|
Result := TPdfFormFieldType(FPDFPage_HasFormFieldAtPoint(FDocument.FForm, FPage, X, Y));
|
|
if (Result < Low(TPdfFormFieldType)) or (Result > High(TPdfFormFieldType)) then
|
|
Result := fftUnknown;
|
|
end;
|
|
|
|
function TPdfPage.GetHandle: FPDF_PAGE;
|
|
begin
|
|
Open;
|
|
Result := FPage;
|
|
end;
|
|
|
|
function TPdfPage.IsLoaded: Boolean;
|
|
begin
|
|
Result := FPage <> nil;
|
|
end;
|
|
|
|
function TPdfPage.GetTextHandle: FPDF_TEXTPAGE;
|
|
begin
|
|
if BeginText then
|
|
Result := FTextHandle
|
|
else
|
|
Result := nil;
|
|
end;
|
|
|
|
function TPdfPage.GetFormFields: TPdfFormFieldList;
|
|
begin
|
|
Result := Annotations.FormFields;
|
|
end;
|
|
|
|
|
|
{ _TPdfBitmapHideCtor }
|
|
|
|
constructor _TPdfBitmapHideCtor.Create;
|
|
begin
|
|
inherited Create;
|
|
end;
|
|
|
|
|
|
{ TPdfBitmap }
|
|
|
|
constructor TPdfBitmap.Create(ABitmap: FPDF_BITMAP; AOwnsBitmap: Boolean);
|
|
begin
|
|
inherited Create;
|
|
FBitmap := ABitmap;
|
|
FOwnsBitmap := AOwnsBitmap;
|
|
if FBitmap <> nil then
|
|
begin
|
|
FWidth := FPDFBitmap_GetWidth(FBitmap);
|
|
FHeight := FPDFBitmap_GetHeight(FBitmap);
|
|
FBytesPerScanLine := FPDFBitmap_GetStride(FBitmap);
|
|
end;
|
|
end;
|
|
|
|
constructor TPdfBitmap.Create(AWidth, AHeight: Integer; AAlpha: Boolean);
|
|
begin
|
|
Create(FPDFBitmap_Create(AWidth, AHeight, Ord(AAlpha)), True);
|
|
end;
|
|
|
|
constructor TPdfBitmap.Create(AWidth, AHeight: Integer; AFormat: TPdfBitmapFormat);
|
|
begin
|
|
Create(FPDFBitmap_CreateEx(AWidth, AHeight, Ord(AFormat), nil, 0), True);
|
|
end;
|
|
|
|
constructor TPdfBitmap.Create(AWidth, AHeight: Integer; AFormat: TPdfBitmapFormat; ABuffer: Pointer;
|
|
ABytesPerScanLine: Integer);
|
|
begin
|
|
Create(FPDFBitmap_CreateEx(AWidth, AHeight, Ord(AFormat), ABuffer, ABytesPerScanline), True);
|
|
end;
|
|
|
|
destructor TPdfBitmap.Destroy;
|
|
begin
|
|
if FOwnsBitmap and (FBitmap <> nil) then
|
|
FPDFBitmap_Destroy(FBitmap);
|
|
inherited Destroy;
|
|
end;
|
|
|
|
function TPdfBitmap.GetBuffer: Pointer;
|
|
begin
|
|
if FBitmap <> nil then
|
|
Result := FPDFBitmap_GetBuffer(FBitmap)
|
|
else
|
|
Result := nil;
|
|
end;
|
|
|
|
procedure TPdfBitmap.FillRect(ALeft, ATop, AWidth, AHeight: Integer; AColor: FPDF_DWORD);
|
|
begin
|
|
if FBitmap <> nil then
|
|
FPDFBitmap_FillRect(FBitmap, ALeft, ATop, AWidth, AHeight, AColor);
|
|
end;
|
|
|
|
{ TPdfPoint }
|
|
|
|
procedure TPdfPoint.Offset(XOffset, YOffset: Double);
|
|
begin
|
|
X := X + XOffset;
|
|
Y := Y + YOffset;
|
|
end;
|
|
|
|
class function TPdfPoint.Empty: TPdfPoint;
|
|
begin
|
|
Result.X := 0;
|
|
Result.Y := 0;
|
|
end;
|
|
|
|
|
|
{ TPdfAttachmentList }
|
|
|
|
constructor TPdfAttachmentList.Create(ADocument: TPdfDocument);
|
|
begin
|
|
inherited Create;
|
|
FDocument := ADocument;
|
|
end;
|
|
|
|
function TPdfAttachmentList.GetCount: Integer;
|
|
begin
|
|
FDocument.CheckActive;
|
|
Result := FPDFDoc_GetAttachmentCount(FDocument.Handle);
|
|
end;
|
|
|
|
function TPdfAttachmentList.GetItem(Index: Integer): TPdfAttachment;
|
|
var
|
|
Attachment: FPDF_ATTACHMENT;
|
|
begin
|
|
FDocument.CheckActive;
|
|
Attachment := FPDFDoc_GetAttachment(FDocument.Handle, Index);
|
|
if Attachment = nil then
|
|
raise EPdfArgumentOutOfRange.CreateResFmt(@RsArgumentsOutOfRange, ['Index']);
|
|
Result.FDocument := FDocument;
|
|
Result.FHandle := Attachment;
|
|
end;
|
|
|
|
procedure TPdfAttachmentList.Delete(Index: Integer);
|
|
begin
|
|
FDocument.CheckActive;
|
|
if FPDFDoc_DeleteAttachment(FDocument.Handle, Index) = 0 then
|
|
raise EPdfException.CreateResFmt(@RsPdfCannotDeleteAttachmnent, [Index]);
|
|
end;
|
|
|
|
function TPdfAttachmentList.Add(const Name: string): TPdfAttachment;
|
|
begin
|
|
FDocument.CheckActive;
|
|
Result.FDocument := FDocument;
|
|
Result.FHandle := FPDFDoc_AddAttachment(FDocument.Handle, PWideChar(Name));
|
|
if Result.FHandle = nil then
|
|
raise EPdfException.CreateResFmt(@RsPdfCannotAddAttachmnent, [Name]);
|
|
end;
|
|
|
|
function TPdfAttachmentList.IndexOf(const Name: string): Integer;
|
|
begin
|
|
for Result := 0 to Count - 1 do
|
|
if Items[Result].Name = Name then
|
|
Exit;
|
|
Result := -1;
|
|
end;
|
|
|
|
|
|
{ TPdfAttachment }
|
|
|
|
function TPdfAttachment.GetName: string;
|
|
var
|
|
ByteLen: LongWord;
|
|
begin
|
|
CheckValid;
|
|
ByteLen := FPDFAttachment_GetName(Handle, nil, 0); // UTF 16 including #0 terminator in byte size
|
|
if ByteLen <= 2 then
|
|
Result := ''
|
|
else
|
|
begin
|
|
SetLength(Result, ByteLen div SizeOf(WideChar) - 1);
|
|
FPDFAttachment_GetName(FHandle, PWideChar(Result), ByteLen);
|
|
end;
|
|
end;
|
|
|
|
procedure TPdfAttachment.CheckValid;
|
|
begin
|
|
if FDocument <> nil then
|
|
FDocument.CheckActive;
|
|
end;
|
|
|
|
procedure TPdfAttachment.SetContent(ABytes: PByte; Count: Integer);
|
|
begin
|
|
CheckValid;
|
|
if FPDFAttachment_SetFile(FHandle, FDocument.Handle, ABytes, Count) = 0 then
|
|
raise EPdfException.CreateResFmt(@RsPdfCannotSetAttachmentContent, [Name]);
|
|
end;
|
|
|
|
procedure TPdfAttachment.SetContent(const Value: RawByteString);
|
|
begin
|
|
if Value = '' then
|
|
SetContent(nil, 0)
|
|
else
|
|
SetContent(PByte(PAnsiChar(Value)), Length(Value) * SizeOf(AnsiChar));
|
|
end;
|
|
|
|
procedure TPdfAttachment.SetContent(const Value: string; Encoding: TEncoding = nil);
|
|
begin
|
|
CheckValid;
|
|
if Value = '' then
|
|
SetContent(nil, 0)
|
|
else if (Encoding = nil) or (Encoding = TEncoding.UTF8) then
|
|
SetContent(UTF8Encode(Value))
|
|
else
|
|
SetContent(Encoding.GetBytes(Value));
|
|
end;
|
|
|
|
procedure TPdfAttachment.SetContent(const ABytes: TBytes; Index: NativeInt; Count: Integer);
|
|
var
|
|
Len: NativeInt;
|
|
begin
|
|
CheckValid;
|
|
|
|
Len := Length(ABytes);
|
|
if Index >= Len then
|
|
raise EPdfArgumentOutOfRange.CreateResFmt(@RsArgumentsOutOfRange, ['Index', Index]);
|
|
if Index + Count > Len then
|
|
raise EPdfArgumentOutOfRange.CreateResFmt(@RsArgumentsOutOfRange, ['Count', Count]);
|
|
|
|
if Count = 0 then
|
|
SetContent(nil, 0)
|
|
else
|
|
SetContent(@ABytes[Index], Count);
|
|
end;
|
|
|
|
procedure TPdfAttachment.SetContent(const ABytes: TBytes);
|
|
begin
|
|
SetContent(ABytes, 0, Length(ABytes));
|
|
end;
|
|
|
|
procedure TPdfAttachment.LoadFromStream(Stream: TStream);
|
|
var
|
|
StreamPos, StreamSize: Int64;
|
|
Buf: PByte;
|
|
Count: Integer;
|
|
begin
|
|
CheckValid;
|
|
|
|
StreamPos := Stream.Position;
|
|
StreamSize := Stream.Size;
|
|
Count := StreamSize - StreamPos;
|
|
if Count = 0 then
|
|
SetContent(nil, 0)
|
|
else
|
|
begin
|
|
if Stream is TCustomMemoryStream then // direct access to the memory
|
|
begin
|
|
SetContent(PByte(TCustomMemoryStream(Stream).Memory) + StreamPos, Count);
|
|
Stream.Position := StreamSize; // simulate the ReadBuffer call
|
|
end
|
|
else
|
|
begin
|
|
if Count = 0 then
|
|
SetContent(nil, 0)
|
|
else
|
|
begin
|
|
GetMem(Buf, Count);
|
|
try
|
|
Stream.ReadBuffer(Buf^, Count);
|
|
SetContent(Buf, Count);
|
|
finally
|
|
FreeMem(Buf);
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TPdfAttachment.LoadFromFile(const FileName: string);
|
|
var
|
|
Stream: TFileStream;
|
|
begin
|
|
CheckValid;
|
|
|
|
Stream := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite);
|
|
try
|
|
LoadFromStream(Stream);
|
|
finally
|
|
Stream.Free;
|
|
end;
|
|
end;
|
|
|
|
function TPdfAttachment.HasKey(const Key: string): Boolean;
|
|
begin
|
|
CheckValid;
|
|
Result := FPDFAttachment_HasKey(FHandle, PAnsiChar(UTF8Encode(Key))) <> 0;
|
|
end;
|
|
|
|
function TPdfAttachment.GetValueType(const Key: string): TPdfObjectType;
|
|
begin
|
|
CheckValid;
|
|
Result := TPdfObjectType(FPDFAttachment_GetValueType(FHandle, PAnsiChar(UTF8Encode(Key))));
|
|
end;
|
|
|
|
procedure TPdfAttachment.SetKeyValue(const Key, Value: string);
|
|
begin
|
|
CheckValid;
|
|
if FPDFAttachment_SetStringValue(FHandle, PAnsiChar(UTF8Encode(Key)), PWideChar(Value)) = 0 then
|
|
raise EPdfException.CreateRes(@RsPdfAttachmentContentNotSet);
|
|
end;
|
|
|
|
function TPdfAttachment.GetKeyValue(const Key: string): string;
|
|
var
|
|
ByteLen: LongWord;
|
|
Utf8Key: UTF8String;
|
|
begin
|
|
CheckValid;
|
|
Utf8Key := UTF8Encode(Key);
|
|
ByteLen := FPDFAttachment_GetStringValue(FHandle, PAnsiChar(Utf8Key), nil, 0);
|
|
if ByteLen = 0 then
|
|
raise EPdfException.CreateRes(@RsPdfAttachmentContentNotSet);
|
|
|
|
if ByteLen <= 2 then
|
|
Result := ''
|
|
else
|
|
begin
|
|
SetLength(Result, (ByteLen div SizeOf(WideChar) - 1));
|
|
FPDFAttachment_GetStringValue(FHandle, PAnsiChar(Utf8Key), PWideChar(Result), ByteLen);
|
|
end;
|
|
end;
|
|
|
|
function TPdfAttachment.GetContentSize: Integer;
|
|
var
|
|
OutBufLen: LongWord;
|
|
begin
|
|
CheckValid;
|
|
if FPDFAttachment_GetFile(FHandle, nil, 0, OutBufLen) = 0 then
|
|
Result := 0
|
|
else
|
|
Result := Integer(OutBufLen);
|
|
end;
|
|
|
|
function TPdfAttachment.HasContent: Boolean;
|
|
var
|
|
OutBufLen: LongWord;
|
|
begin
|
|
CheckValid;
|
|
Result := FPDFAttachment_GetFile(FHandle, nil, 0, OutBufLen) <> 0;
|
|
end;
|
|
|
|
procedure TPdfAttachment.SaveToFile(const FileName: string);
|
|
var
|
|
Stream: TStream;
|
|
begin
|
|
CheckValid;
|
|
|
|
Stream := TFileStream.Create(FileName, fmCreate or fmShareDenyWrite);
|
|
try
|
|
SaveToStream(Stream);
|
|
finally
|
|
Stream.Free;
|
|
end;
|
|
end;
|
|
|
|
procedure TPdfAttachment.SaveToStream(Stream: TStream);
|
|
var
|
|
Size: Integer;
|
|
OutBufLen: LongWord;
|
|
StreamPos: Int64;
|
|
Buf: PByte;
|
|
begin
|
|
Size := ContentSize;
|
|
|
|
if Size > 0 then
|
|
begin
|
|
if Stream is TCustomMemoryStream then // direct access to the memory
|
|
begin
|
|
StreamPos := Stream.Position;
|
|
if StreamPos + Size > Stream.Size then
|
|
Stream.Size := StreamPos + Size; // allocate enough memory
|
|
Stream.Position := StreamPos;
|
|
|
|
FPDFAttachment_GetFile(FHandle, PByte(TCustomMemoryStream(Stream).Memory) + StreamPos, Size, OutBufLen);
|
|
Stream.Position := StreamPos + Size; // simulate Stream.WriteBuffer
|
|
end
|
|
else
|
|
begin
|
|
GetMem(Buf, Size);
|
|
try
|
|
FPDFAttachment_GetFile(FHandle, Buf, Size, OutBufLen);
|
|
Stream.WriteBuffer(Buf^, Size);
|
|
finally
|
|
FreeMem(Buf);
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TPdfAttachment.GetContent(var Value: string; Encoding: TEncoding);
|
|
var
|
|
Size: Integer;
|
|
OutBufLen: LongWord;
|
|
Buf: PByte;
|
|
begin
|
|
Size := ContentSize;
|
|
if Size <= 0 then
|
|
Value := ''
|
|
else if Encoding = TEncoding.Unicode then // no conversion needed
|
|
begin
|
|
SetLength(Value, Size div SizeOf(WideChar));
|
|
FPDFAttachment_GetFile(FHandle, PWideChar(Value), Size, OutBufLen);
|
|
end
|
|
else
|
|
begin
|
|
if Encoding = nil then
|
|
Encoding := TEncoding.UTF8;
|
|
|
|
GetMem(Buf, Size);
|
|
try
|
|
FPDFAttachment_GetFile(FHandle, Buf, Size, OutBufLen);
|
|
SetLength(Value, TEncodingAccess(Encoding).GetMemCharCount(Buf, Size));
|
|
if Value <> '' then
|
|
TEncodingAccess(Encoding).GetMemChars(Buf, Size, PWideChar(Value), Length(Value));
|
|
finally
|
|
FreeMem(Buf);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TPdfAttachment.GetContent(var Value: RawByteString);
|
|
var
|
|
Size: Integer;
|
|
OutBufLen: LongWord;
|
|
begin
|
|
Size := ContentSize;
|
|
|
|
if Size <= 0 then
|
|
Value := ''
|
|
else
|
|
begin
|
|
SetLength(Value, Size);
|
|
FPDFAttachment_GetFile(FHandle, PAnsiChar(Value), Size, OutBufLen);
|
|
end;
|
|
end;
|
|
|
|
procedure TPdfAttachment.GetContent(Buffer: PByte);
|
|
var
|
|
OutBufLen: LongWord;
|
|
begin
|
|
FPDFAttachment_GetFile(FHandle, Buffer, ContentSize, OutBufLen);
|
|
end;
|
|
|
|
procedure TPdfAttachment.GetContent(var ABytes: TBytes);
|
|
var
|
|
Size: Integer;
|
|
OutBufLen: LongWord;
|
|
begin
|
|
Size := ContentSize;
|
|
|
|
if Size <= 0 then
|
|
ABytes := nil
|
|
else
|
|
begin
|
|
SetLength(ABytes, Size);
|
|
FPDFAttachment_GetFile(FHandle, @ABytes[0], Size, OutBufLen);
|
|
end;
|
|
end;
|
|
|
|
function TPdfAttachment.GetContentAsBytes: TBytes;
|
|
begin
|
|
GetContent(Result);
|
|
end;
|
|
|
|
function TPdfAttachment.GetContentAsRawByteString: RawByteString;
|
|
begin
|
|
GetContent(Result);
|
|
end;
|
|
|
|
function TPdfAttachment.GetContentAsString(Encoding: TEncoding): string;
|
|
begin
|
|
GetContent(Result, Encoding);
|
|
end;
|
|
|
|
|
|
{ TPdfAnnotationList }
|
|
|
|
constructor TPdfAnnotationList.Create(APage: TPdfPage);
|
|
begin
|
|
inherited Create;
|
|
FPage := APage;
|
|
FItems := TObjectList.Create;
|
|
end;
|
|
|
|
destructor TPdfAnnotationList.Destroy;
|
|
begin
|
|
FreeAndNil(FFormFields);
|
|
FreeAndNil(FItems); // closes all annotations
|
|
inherited Destroy;
|
|
end;
|
|
|
|
procedure TPdfAnnotationList.CloseAnnotations;
|
|
begin
|
|
FreeAndNil(FFormFields);
|
|
FreeAndNil(FItems); // closes all annotations
|
|
FItems := TObjectList.Create;
|
|
end;
|
|
|
|
function TPdfAnnotationList.GetCount: Integer;
|
|
begin
|
|
Result := FPDFPage_GetAnnotCount(FPage.Handle);
|
|
end;
|
|
|
|
function TPdfAnnotationList.GetItem(Index: Integer): TPdfAnnotation;
|
|
var
|
|
Annot: FPDF_ANNOTATION;
|
|
begin
|
|
FPage.FDocument.CheckActive;
|
|
|
|
if (Index < 0) or (Index >= FItems.Count) or (FItems[Index] = nil) then
|
|
begin
|
|
Annot := FPDFPage_GetAnnot(FPage.Handle, Index);
|
|
if Annot = nil then
|
|
raise EPdfArgumentOutOfRange.CreateResFmt(@RsArgumentsOutOfRange, ['Index']);
|
|
|
|
while FItems.Count <= Index do
|
|
FItems.Add(nil);
|
|
FItems[Index] := TPdfAnnotation.Create(FPage, Annot);
|
|
end;
|
|
Result := FItems[Index] as TPdfAnnotation;
|
|
end;
|
|
|
|
procedure TPdfAnnotationList.DestroyingItem(Item: TPdfAnnotation);
|
|
var
|
|
Index: Integer;
|
|
begin
|
|
if (Item <> nil) and (FItems <> nil) then
|
|
begin
|
|
Index := FItems.IndexOf(Item);
|
|
if Index <> -1 then
|
|
FItems.List[Index] := nil; // Bypass the Items[] setter to not destroy the Item twice
|
|
end;
|
|
end;
|
|
|
|
procedure TPdfAnnotationList.DestroyingFormField(FormField: TPdfFormField);
|
|
begin
|
|
if FFormFields <> nil then
|
|
FFormFields.DestroyingItem(FormField);
|
|
end;
|
|
|
|
function TPdfAnnotationList.GetFormFields: TPdfFormFieldList;
|
|
begin
|
|
if FFormFields = nil then
|
|
FFormFields := TPdfFormFieldList.Create(Self);
|
|
Result := FFormFields;
|
|
end;
|
|
|
|
function TPdfAnnotationList.GetAnnotationsLoaded: Boolean;
|
|
begin
|
|
Result := FItems.Count > 0;
|
|
end;
|
|
|
|
function TPdfAnnotationList.NewTextAnnotation(const Text: string; const R: TPdfRect): Boolean;
|
|
var
|
|
Annot: FPDF_ANNOTATION;
|
|
SingleR: FS_RECTF;
|
|
begin
|
|
FPage.FDocument.CheckActive;
|
|
SingleR.left := R.Left;
|
|
SingleR.right := R.Right;
|
|
// Page coordinates are upside down
|
|
if R.Top < R.Bottom then
|
|
begin
|
|
SingleR.top := R.Bottom;
|
|
SingleR.bottom := R.Top;
|
|
end
|
|
else
|
|
begin
|
|
SingleR.top := R.Top;
|
|
SingleR.bottom := R.Bottom;
|
|
end;
|
|
|
|
|
|
Annot := FPDFPage_CreateAnnot(FPage.Handle, FPDF_ANNOT_TEXT);
|
|
Result := Annot <> nil;
|
|
if Result then
|
|
begin
|
|
FPDFAnnot_SetRect(Annot, @SingleR);
|
|
FPDFAnnot_SetStringValue(Annot, 'Contents', PWideChar(Text));
|
|
end;
|
|
end;
|
|
|
|
function TPdfAnnotationList.FindLink(Link: FPDF_LINK): TPdfAnnotation;
|
|
var
|
|
I: Integer;
|
|
begin
|
|
for I := 0 to Count - 1 do
|
|
begin
|
|
Result := Items[I];
|
|
if (Result.IsLink) and (FPDFAnnot_GetLink(Result.Handle) = Link) then
|
|
Exit;
|
|
end;
|
|
Result := nil;
|
|
end;
|
|
|
|
|
|
{ TPdfFormFieldList }
|
|
|
|
constructor TPdfFormFieldList.Create(AAnnotations: TPdfAnnotationList);
|
|
var
|
|
I: Integer;
|
|
begin
|
|
inherited Create;
|
|
FItems := TList.Create;
|
|
|
|
for I := 0 to AAnnotations.Count - 1 do
|
|
if AAnnotations[I].IsFormField then
|
|
FItems.Add(AAnnotations[I].FormField);
|
|
end;
|
|
|
|
destructor TPdfFormFieldList.Destroy;
|
|
begin
|
|
FItems.Free;
|
|
inherited Destroy;
|
|
end;
|
|
|
|
function TPdfFormFieldList.GetCount: Integer;
|
|
begin
|
|
Result := FItems.Count;
|
|
end;
|
|
|
|
function TPdfFormFieldList.GetItem(Index: Integer): TPdfFormField;
|
|
begin
|
|
Result := TObject(FItems[Index]) as TPdfFormField;
|
|
end;
|
|
|
|
procedure TPdfFormFieldList.DestroyingItem(Item: TPdfFormField);
|
|
begin
|
|
if (Item <> nil) and (FItems <> nil) then
|
|
FItems.Extract(Item);
|
|
end;
|
|
|
|
|
|
{ TPdfAnnotation }
|
|
|
|
constructor TPdfAnnotation.Create(APage: TPdfPage; AHandle: FPDF_ANNOTATION);
|
|
var
|
|
Action: FPDF_ACTION;
|
|
begin
|
|
inherited Create;
|
|
FPage := APage;
|
|
FHandle := AHandle;
|
|
|
|
FSubType := FPDFAnnot_GetSubtype(FHandle);
|
|
FLinkType := altUnsupported;
|
|
case FSubType of
|
|
FPDF_ANNOT_WIDGET,
|
|
FPDF_ANNOT_XFAWIDGET:
|
|
FFormField := TPdfFormField.Create(Self);
|
|
|
|
FPDF_ANNOT_LINK:
|
|
begin
|
|
Action := GetPdfLinkAction;
|
|
if Action <> nil then
|
|
FLinkType := TPdfAnnotationLinkType(FPDFAction_GetType(Action))
|
|
else
|
|
begin
|
|
// If we have a Dest-Link then we treat it like a Goto Action-Link (see GetLinkGotoDestination)
|
|
FLinkDest := FPDFLink_GetDest(FPage.FDocument.Handle, FPDFAnnot_GetLink(Handle));
|
|
if FLinkDest <> nil then
|
|
FLinkType := altGoto;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
destructor TPdfAnnotation.Destroy;
|
|
begin
|
|
FreeAndNil(FFormField);
|
|
if FHandle <> nil then
|
|
begin
|
|
FPDFPage_CloseAnnot(FHandle);
|
|
FHandle := nil;
|
|
end;
|
|
if FPage.FAnnotations <> nil then
|
|
FPage.FAnnotations.DestroyingItem(Self);
|
|
inherited Destroy;
|
|
end;
|
|
|
|
function TPdfAnnotation.GetPdfLinkAction: FPDF_ACTION;
|
|
var
|
|
Link: FPDF_LINK;
|
|
begin
|
|
Result := nil;
|
|
if FSubType = FPDF_ANNOT_LINK then
|
|
begin
|
|
Link := FPDFAnnot_GetLink(Handle);
|
|
if Link <> nil then
|
|
Result := FPDFLink_GetAction(Link);
|
|
end;
|
|
end;
|
|
|
|
function TPdfAnnotation.IsLink: Boolean;
|
|
begin
|
|
Result := FSubType = FPDF_ANNOT_LINK;
|
|
end;
|
|
|
|
function TPdfAnnotation.IsFormField: Boolean;
|
|
begin
|
|
Result := FFormField <> nil;
|
|
end;
|
|
|
|
function TPdfAnnotation.GetFormField: TPdfFormField;
|
|
begin
|
|
if FFormField = nil then
|
|
raise EPdfException.CreateRes(@RsPdfAnnotationNotAFormFieldError);
|
|
Result := FFormField;
|
|
end;
|
|
|
|
function TPdfAnnotation.GetAnnotationRect: TPdfRect;
|
|
var
|
|
R: FS_RECTF;
|
|
begin
|
|
if FPDFAnnot_GetRect(Handle, @R) <> 0 then
|
|
Result := TPdfRect.New(R.left, R.top, R.right, R.bottom)
|
|
else
|
|
Result := TPdfRect.Empty;
|
|
end;
|
|
|
|
function TPdfAnnotation.GetLinkUri: string;
|
|
begin
|
|
if LinkType = altURI then
|
|
Result := FPage.GetPdfActionUriPath(GetPdfLinkAction)
|
|
else
|
|
Result := '';
|
|
end;
|
|
|
|
function TPdfAnnotation.GetLinkFileName: string;
|
|
begin
|
|
if LinkType in [altRemoteGoto, altLaunch, altEmbeddedGoto] then // PDFium documentation is missing the PDFACTION_EMBEDDEDGOTO part.
|
|
Result := FPage.GetPdfActionFilePath(GetPdfLinkAction)
|
|
else
|
|
Result := '';
|
|
end;
|
|
|
|
function TPdfAnnotation.GetLinkGotoDestination(var LinkGotoDestination: TPdfLinkGotoDestination; ARemoteDocument: TPdfDocument): Boolean;
|
|
var
|
|
Action: FPDF_ACTION;
|
|
Dest: FPDF_DEST;
|
|
Doc: TPdfDocument;
|
|
PageIndex: Integer;
|
|
HasXVal, HasYVal, HasZoomVal: FPDF_BOOL;
|
|
X, Y, Zoom: FS_FLOAT;
|
|
ViewKind: TPdfLinkGotoDestinationViewKind;
|
|
NumViewParams: LongWord;
|
|
ViewParams: TPdfFloatArray;
|
|
begin
|
|
Result := False;
|
|
|
|
Action := GetPdfLinkAction;
|
|
if ((Action <> nil) or (FLinkDest <> nil)) and (LinkType in [altGoto, altRemoteGoto, altEmbeddedGoto]) then
|
|
begin
|
|
Doc := FPage.FDocument;
|
|
if LinkType = altRemoteGoto then
|
|
begin
|
|
// For RemoteGoto the FPDFAction_GetDest function must be called with the remote document
|
|
if ARemoteDocument <> nil then
|
|
raise EPdfException.CreateRes(@RsPdfAnnotationLinkRemoteGotoRequiresRemoteDocument);
|
|
ARemoteDocument.CheckActive;
|
|
|
|
Doc := ARemoteDocument;
|
|
end;
|
|
|
|
// If we have a Dest-Link instead of a Goto Action-Link we treat it as if it was a Goto Action-Link
|
|
if FLinkDest <> nil then
|
|
Dest := FLinkDest
|
|
else
|
|
Dest := FPDFAction_GetDest(Doc.Handle, Action);
|
|
|
|
// Extract the information
|
|
if Dest <> nil then
|
|
begin
|
|
PageIndex := FPDFDest_GetDestPageIndex(Doc.Handle, Dest);
|
|
if PageIndex <> -1 then
|
|
begin
|
|
if FPDFDest_GetLocationInPage(Dest, HasXVal, HasYVal, HasZoomVal, X, Y, Zoom) <> 0 then
|
|
begin
|
|
SetLength(ViewParams, 4); // max. 4 params
|
|
NumViewParams := 4;
|
|
ViewKind := TPdfLinkGotoDestinationViewKind(FPDFDest_GetView(Dest, @NumViewParams, @ViewParams[0]));
|
|
if NumViewParams > 4 then // range check
|
|
NumViewParams := 4;
|
|
SetLength(ViewParams, NumViewParams);
|
|
|
|
LinkGotoDestination := TPdfLinkGotoDestination.Create(
|
|
PageIndex,
|
|
HasXVal <> 0, HasYVal <> 0, HasZoomVal <> 0,
|
|
X, Y, Zoom,
|
|
ViewKind, ViewParams
|
|
);
|
|
Result := True;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
|
|
{ TPdfFormField }
|
|
|
|
constructor TPdfFormField.Create(AAnnotation: TPdfAnnotation);
|
|
begin
|
|
inherited Create;
|
|
FAnnotation := AAnnotation;
|
|
FPage := FAnnotation.FPage;
|
|
FHandle := FAnnotation.Handle;
|
|
end;
|
|
|
|
destructor TPdfFormField.Destroy;
|
|
begin
|
|
FAnnotation.FFormField := nil;
|
|
FAnnotation.FPage.Annotations.DestroyingFormField(Self);
|
|
inherited Destroy;
|
|
end;
|
|
|
|
function TPdfFormField.IsXFAFormField: Boolean;
|
|
begin
|
|
Result := IS_XFA_FORMFIELD(FPDFAnnot_GetFormFieldType(FPage.FDocument.FormHandle, Handle));
|
|
end;
|
|
|
|
function TPdfFormField.GetReadOnly: Boolean;
|
|
begin
|
|
Result := fffReadOnly in Flags;
|
|
end;
|
|
|
|
function TPdfFormField.GetFlags: TPdfFormFieldFlags;
|
|
var
|
|
FormFlags: Integer;
|
|
begin
|
|
FormFlags := FPDFAnnot_GetFormFieldFlags(FPage.FDocument.FormHandle, Handle);
|
|
|
|
Result := [];
|
|
if FormFlags <> FPDF_FORMFLAG_NONE then
|
|
begin
|
|
if FormFlags and FPDF_FORMFLAG_READONLY <> 0 then
|
|
Include(Result, fffReadOnly);
|
|
if FormFlags and FPDF_FORMFLAG_REQUIRED <> 0 then
|
|
Include(Result, fffRequired);
|
|
if FormFlags and FPDF_FORMFLAG_NOEXPORT <> 0 then
|
|
Include(Result, fffNoExport);
|
|
|
|
if FormFlags and FPDF_FORMFLAG_TEXT_MULTILINE <> 0 then
|
|
Include(Result, fffTextMultiLine);
|
|
if FormFlags and FPDF_FORMFLAG_TEXT_PASSWORD <> 0 then
|
|
Include(Result, fffTextPassword);
|
|
|
|
if FormFlags and FPDF_FORMFLAG_CHOICE_COMBO <> 0 then
|
|
Include(Result, fffChoiceCombo);
|
|
if FormFlags and FPDF_FORMFLAG_CHOICE_EDIT <> 0 then
|
|
Include(Result, fffChoiceEdit);
|
|
if FormFlags and FPDF_FORMFLAG_CHOICE_MULTI_SELECT <> 0 then
|
|
Include(Result, fffChoiceMultiSelect);
|
|
end;
|
|
end;
|
|
|
|
function TPdfFormField.GetName: string;
|
|
var
|
|
Len: Integer;
|
|
begin
|
|
Len := FPDFAnnot_GetFormFieldName(FPage.FDocument.FormHandle, Handle, nil, 0) div SizeOf(WideChar) - 1;
|
|
if Len > 0 then
|
|
begin
|
|
SetLength(Result, Len);
|
|
FPDFAnnot_GetFormFieldName(FPage.FDocument.FormHandle, Handle, PWideChar(Result), (Len + 1) * SizeOf(WideChar));
|
|
end
|
|
else
|
|
Result := '';
|
|
end;
|
|
|
|
function TPdfFormField.GetAlternateName: string;
|
|
var
|
|
Len: Integer;
|
|
begin
|
|
Len := FPDFAnnot_GetFormFieldAlternateName(FPage.FDocument.FormHandle, Handle, nil, 0) div SizeOf(WideChar) - 1;
|
|
if Len > 0 then
|
|
begin
|
|
SetLength(Result, Len);
|
|
FPDFAnnot_GetFormFieldAlternateName(FPage.FDocument.FormHandle, Handle, PWideChar(Result), (Len + 1) * SizeOf(WideChar));
|
|
end
|
|
else
|
|
Result := '';
|
|
end;
|
|
|
|
function TPdfFormField.GetFieldType: TPdfFormFieldType;
|
|
begin
|
|
Result := TPdfFormFieldType(FPDFAnnot_GetFormFieldType(FPage.FDocument.FormHandle, Handle));
|
|
if (Result < Low(TPdfFormFieldType)) or (Result > High(TPdfFormFieldType)) then
|
|
Result := fftUnknown;
|
|
end;
|
|
|
|
function TPdfFormField.GetValue: string;
|
|
var
|
|
Len: Integer;
|
|
begin
|
|
Len := FPDFAnnot_GetFormFieldValue(FPage.FDocument.FormHandle, Handle, nil, 0) div SizeOf(WideChar) - 1;
|
|
if Len > 0 then
|
|
begin
|
|
SetLength(Result, Len);
|
|
FPDFAnnot_GetFormFieldValue(FPage.FDocument.FormHandle, Handle, PWideChar(Result), (Len + 1) * SizeOf(WideChar));
|
|
end
|
|
else
|
|
Result := '';
|
|
end;
|
|
|
|
function TPdfFormField.GetExportValue: string;
|
|
var
|
|
Len: Integer;
|
|
begin
|
|
Len := FPDFAnnot_GetFormFieldExportValue(FPage.FDocument.FormHandle, Handle, nil, 0) div SizeOf(WideChar) - 1;
|
|
if Len > 0 then
|
|
begin
|
|
SetLength(Result, Len);
|
|
FPDFAnnot_GetFormFieldExportValue(FPage.FDocument.FormHandle, Handle, PWideChar(Result), (Len + 1) * SizeOf(WideChar));
|
|
end
|
|
else
|
|
Result := '';
|
|
end;
|
|
|
|
function TPdfFormField.GetOptionCount: Integer;
|
|
begin
|
|
Result := FPDFAnnot_GetOptionCount(FPage.FDocument.FormHandle, Handle);
|
|
if Result < 0 then // annotation types that don't support options will return -1
|
|
Result := 0;
|
|
end;
|
|
|
|
function TPdfFormField.GetOptionLabel(Index: Integer): string;
|
|
var
|
|
Len: Integer;
|
|
begin
|
|
Len := FPDFAnnot_GetOptionLabel(FPage.FDocument.FormHandle, Handle, Index, nil, 0) div SizeOf(WideChar) - 1;
|
|
if Len > 0 then
|
|
begin
|
|
SetLength(Result, Len);
|
|
FPDFAnnot_GetOptionLabel(FPage.FDocument.FormHandle, Handle, Index, PWideChar(Result), (Len + 1) * SizeOf(WideChar));
|
|
end
|
|
else
|
|
Result := '';
|
|
end;
|
|
|
|
function TPdfFormField.IsOptionSelected(OptionIndex: Integer): Boolean;
|
|
begin
|
|
Result := FPDFAnnot_IsOptionSelected(FPage.FDocument.FormHandle, Handle, OptionIndex) <> 0;
|
|
end;
|
|
|
|
function TPdfFormField.GetChecked: Boolean;
|
|
begin
|
|
Result := FPDFAnnot_IsChecked(FPage.FDocument.FormHandle, Handle) <> 0;
|
|
end;
|
|
|
|
function TPdfFormField.GetControlCount: Integer;
|
|
begin
|
|
Result := FPDFAnnot_GetFormControlCount(FPage.FDocument.FormHandle, Handle);
|
|
end;
|
|
|
|
function TPdfFormField.GetControlIndex: Integer;
|
|
begin
|
|
Result := FPDFAnnot_GetFormControlIndex(FPage.FDocument.FormHandle, Handle);
|
|
end;
|
|
|
|
function TPdfFormField.BeginEditFormField: FPDF_ANNOTATION;
|
|
var
|
|
AnnotPageIndex: Integer;
|
|
begin
|
|
FPage.FDocument.CheckActive;
|
|
|
|
// Obtain the currently focused form field/annotation so that we can restore the focus after
|
|
// editing our form field.
|
|
if FORM_GetFocusedAnnot(FPage.FDocument.FormHandle, AnnotPageIndex, Result) = 0 then
|
|
Result := nil;
|
|
end;
|
|
|
|
procedure TPdfFormField.EndEditFormField(LastFocusedAnnot: FPDF_ANNOTATION);
|
|
begin
|
|
// Restore the focus to the form field/annotation that had the focus before changing our form field.
|
|
// If no previous form field was focused, kill the focus.
|
|
if LastFocusedAnnot <> nil then
|
|
begin
|
|
if FORM_SetFocusedAnnot(FPage.FDocument.FormHandle, Handle) = 0 then
|
|
FORM_ForceToKillFocus(FPage.FDocument.FormHandle);
|
|
FPDFPage_CloseAnnot(LastFocusedAnnot);
|
|
end
|
|
else
|
|
FORM_ForceToKillFocus(FPage.FDocument.FormHandle);
|
|
end;
|
|
|
|
procedure TPdfFormField.SetValue(const Value: string);
|
|
var
|
|
LastFocusedAnnot: FPDF_ANNOTATION;
|
|
begin
|
|
FPage.FDocument.CheckActive;
|
|
|
|
if not ReadOnly then
|
|
begin
|
|
LastFocusedAnnot := BeginEditFormField();
|
|
try
|
|
if FORM_SetFocusedAnnot(FPage.FDocument.FormHandle, Handle) <> 0 then
|
|
begin
|
|
FORM_SelectAllText(FPage.FDocument.FormHandle, FPage.Handle);
|
|
FORM_ReplaceSelection(FPage.FDocument.FormHandle, FPage.Handle, PWideChar(Value));
|
|
end;
|
|
finally
|
|
EndEditFormField(LastFocusedAnnot);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function TPdfFormField.SelectComboBoxOption(OptionIndex: Integer): Boolean;
|
|
begin
|
|
Result := SelectListBoxOption(OptionIndex, True);
|
|
end;
|
|
|
|
function TPdfFormField.SelectListBoxOption(OptionIndex: Integer; Selected: Boolean): Boolean;
|
|
var
|
|
LastFocusedAnnot: FPDF_ANNOTATION;
|
|
begin
|
|
FPage.FDocument.CheckActive;
|
|
|
|
Result := False;
|
|
if not ReadOnly then
|
|
begin
|
|
LastFocusedAnnot := BeginEditFormField();
|
|
try
|
|
if FORM_SetFocusedAnnot(FPage.FDocument.FormHandle, Handle) <> 0 then
|
|
Result := FORM_SetIndexSelected(FPage.FDocument.FormHandle, FPage.Handle, OptionIndex, Ord(Selected <> False)) <> 0;
|
|
finally
|
|
EndEditFormField(LastFocusedAnnot);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TPdfFormField.SetChecked(const Value: Boolean);
|
|
var
|
|
LastFocusedAnnot: FPDF_ANNOTATION;
|
|
begin
|
|
FPage.FDocument.CheckActive;
|
|
|
|
if not ReadOnly and (FieldType in [fftCheckBox, fftRadioButton, fftXFACheckBox]) then
|
|
begin
|
|
if Value <> Checked then
|
|
begin
|
|
LastFocusedAnnot := BeginEditFormField();
|
|
try
|
|
if FORM_SetFocusedAnnot(FPage.FDocument.FormHandle, Handle) <> 0 then
|
|
begin
|
|
// Toggle the RadioButton/Checkbox by emulating "pressing the space bar".
|
|
FORM_OnKeyDown(FPage.FDocument.FormHandle, FPage.Handle, Ord(' '), 0);
|
|
FORM_OnChar(FPage.FDocument.FormHandle, FPage.Handle, Ord(' '), 0);
|
|
FORM_OnKeyUp(FPage.FDocument.FormHandle, FPage.Handle, Ord(' '), 0);
|
|
end;
|
|
finally
|
|
EndEditFormField(LastFocusedAnnot);
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
|
|
{ TPdfLinkGotoDestination }
|
|
|
|
constructor TPdfLinkGotoDestination.Create(APageIndex: Integer; AXValid, AYValid, AZoomValid: Boolean;
|
|
AX, AY, AZoom: Single; AViewKind: TPdfLinkGotoDestinationViewKind; const AViewParams: TPdfFloatArray);
|
|
begin
|
|
inherited Create;
|
|
FPageIndex := APageIndex;
|
|
|
|
FXValid := AXValid;
|
|
FYValid := AYValid;
|
|
FZoomValid := AZoomValid;
|
|
|
|
FX := AX;
|
|
FY := AY;
|
|
FZoom := AZoom;
|
|
|
|
FViewKind := AViewKind;
|
|
FViewParams := AViewParams;
|
|
end;
|
|
|
|
|
|
{ TPdfLinkInfo }
|
|
|
|
constructor TPdfLinkInfo.Create(ALinkAnnotation: TPdfAnnotation; const AWebLinkUrl: string);
|
|
begin
|
|
inherited Create;
|
|
FLinkAnnotation := ALinkAnnotation;
|
|
FWebLinkUrl := AWebLinkUrl;
|
|
end;
|
|
|
|
function TPdfLinkInfo.IsAnnontation: Boolean;
|
|
begin
|
|
Result := FLinkAnnotation <> nil;
|
|
end;
|
|
|
|
function TPdfLinkInfo.IsWebLink: Boolean;
|
|
begin
|
|
Result := FLinkAnnotation = nil;
|
|
end;
|
|
|
|
function TPdfLinkInfo.GetLinkFileName: string;
|
|
begin
|
|
if FLinkAnnotation <> nil then
|
|
Result := FLinkAnnotation.LinkFileName;
|
|
end;
|
|
|
|
function TPdfLinkInfo.GetLinkType: TPdfAnnotationLinkType;
|
|
begin
|
|
if FLinkAnnotation <> nil then
|
|
Result := FLinkAnnotation.LinkType
|
|
else if FWebLinkUrl <> '' then
|
|
Result := altURI
|
|
else
|
|
Result := altUnsupported;
|
|
end;
|
|
|
|
function TPdfLinkInfo.GetLinkUri: string;
|
|
begin
|
|
if FLinkAnnotation <> nil then
|
|
Result := FLinkAnnotation.LinkUri
|
|
else
|
|
Result := FWebLinkUrl;
|
|
end;
|
|
|
|
function TPdfLinkInfo.GetLinkGotoDestination(var LinkGotoDestination: TPdfLinkGotoDestination;
|
|
ARemoteDocument: TPdfDocument): Boolean;
|
|
begin
|
|
if FLinkAnnotation <> nil then
|
|
Result := FLinkAnnotation.GetLinkGotoDestination(LinkGotoDestination, ARemoteDocument)
|
|
else
|
|
Result := False;
|
|
end;
|
|
|
|
|
|
{ TPdfPageWebLinksInfo }
|
|
|
|
constructor TPdfPageWebLinksInfo.Create(APage: TPdfPage);
|
|
begin
|
|
inherited Create;
|
|
FPage := APage;
|
|
GetPageWebLinks;
|
|
end;
|
|
|
|
procedure TPdfPageWebLinksInfo.GetPageWebLinks;
|
|
var
|
|
LinkIndex, LinkCount: Integer;
|
|
RectIndex, RectCount: Integer;
|
|
begin
|
|
if FPage <> nil then
|
|
begin
|
|
LinkCount := FPage.GetWebLinkCount;
|
|
SetLength(FWebLinksRects, LinkCount);
|
|
for LinkIndex := 0 to LinkCount - 1 do
|
|
begin
|
|
RectCount := FPage.GetWebLinkRectCount(LinkIndex);
|
|
SetLength(FWebLinksRects[LinkIndex], RectCount);
|
|
for RectIndex := 0 to RectCount - 1 do
|
|
FWebLinksRects[LinkIndex][RectIndex] := FPage.GetWebLinkRect(LinkIndex, RectIndex);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function TPdfPageWebLinksInfo.GetWebLinkIndex(X, Y: Double): Integer;
|
|
var
|
|
RectIndex: Integer;
|
|
Pt: TPdfPoint;
|
|
begin
|
|
if FPage <> nil then
|
|
begin
|
|
Pt.X := X;
|
|
Pt.Y := Y;
|
|
for Result := 0 to Length(FWebLinksRects) - 1 do
|
|
for RectIndex := 0 to Length(FWebLinksRects[Result]) - 1 do
|
|
if FWebLinksRects[Result][RectIndex].PtIn(Pt) then
|
|
Exit;
|
|
end;
|
|
Result := -1;
|
|
end;
|
|
|
|
function TPdfPageWebLinksInfo.GetCount: Integer;
|
|
begin
|
|
Result := Length(FWebLinksRects);
|
|
end;
|
|
|
|
function TPdfPageWebLinksInfo.GetRect(Index: Integer): TPdfRectArray;
|
|
begin
|
|
Result := FWebLinksRects[Index];
|
|
end;
|
|
|
|
function TPdfPageWebLinksInfo.GetURL(Index: Integer): string;
|
|
begin
|
|
Result := FPage.GetWebLinkURL(Index);
|
|
end;
|
|
|
|
function TPdfPageWebLinksInfo.IsWebLinkAt(X, Y: Double): Boolean;
|
|
begin
|
|
Result := GetWebLinkIndex(X, Y) <> -1;
|
|
end;
|
|
|
|
function TPdfPageWebLinksInfo.IsWebLinkAt(X, Y: Double; var Url: string): Boolean;
|
|
var
|
|
Index: Integer;
|
|
begin
|
|
Index := GetWebLinkIndex(X, Y);
|
|
Result := Index <> -1;
|
|
if Result then
|
|
Url := FPage.GetWebLinkURL(Index)
|
|
else
|
|
Url := '';
|
|
end;
|
|
|
|
{$IFDEF MSWINDOWS}
|
|
{ TPdfDocumentPrinter }
|
|
|
|
constructor TPdfDocumentPrinter.Create;
|
|
begin
|
|
inherited Create;
|
|
FFitPageToPrintArea := True;
|
|
end;
|
|
|
|
function TPdfDocumentPrinter.IsPortraitOrientation(AWidth, AHeight: Integer): Boolean;
|
|
begin
|
|
Result := AHeight > AWidth;
|
|
end;
|
|
|
|
procedure TPdfDocumentPrinter.GetPrinterBounds;
|
|
begin
|
|
FPaperSize.cx := GetDeviceCaps(FPrinterDC, PHYSICALWIDTH);
|
|
FPaperSize.cy := GetDeviceCaps(FPrinterDC, PHYSICALHEIGHT);
|
|
|
|
FPrintArea.cx := GetDeviceCaps(FPrinterDC, HORZRES);
|
|
FPrintArea.cy := GetDeviceCaps(FPrinterDC, VERTRES);
|
|
|
|
FMargins.X := GetDeviceCaps(FPrinterDC, PHYSICALOFFSETX);
|
|
FMargins.Y := GetDeviceCaps(FPrinterDC, PHYSICALOFFSETY);
|
|
end;
|
|
|
|
function TPdfDocumentPrinter.BeginPrint(const AJobTitle: string): Boolean;
|
|
begin
|
|
Inc(FBeginPrintCounter);
|
|
if FBeginPrintCounter = 1 then
|
|
begin
|
|
Result := PrinterStartDoc(AJobTitle);
|
|
if Result then
|
|
begin
|
|
FPrinterDC := GetPrinterDC;
|
|
|
|
GetPrinterBounds;
|
|
FPrintPortraitOrientation := IsPortraitOrientation(FPaperSize.cx, FPaperSize.cy);
|
|
end
|
|
else
|
|
begin
|
|
FPrinterDC := 0;
|
|
Dec(FBeginPrintCounter);
|
|
end;
|
|
end
|
|
else
|
|
Result := True;
|
|
end;
|
|
|
|
procedure TPdfDocumentPrinter.EndPrint;
|
|
begin
|
|
Dec(FBeginPrintCounter);
|
|
if FBeginPrintCounter = 0 then
|
|
begin
|
|
if FPrinterDC <> 0 then
|
|
begin
|
|
FPrinterDC := 0;
|
|
PrinterEndDoc;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function TPdfDocumentPrinter.Print(ADocument: TPdfDocument): Boolean;
|
|
begin
|
|
if ADocument <> nil then
|
|
Result := Print(ADocument, 0, ADocument.PageCount - 1)
|
|
else
|
|
Result := False;
|
|
end;
|
|
|
|
function TPdfDocumentPrinter.Print(ADocument: TPdfDocument; AFromPageIndex, AToPageIndex: Integer): Boolean;
|
|
var
|
|
PageIndex: Integer;
|
|
WasPageLoaded: Boolean;
|
|
PdfPage: TPdfPage;
|
|
PagePortraitOrientation: Boolean;
|
|
X, Y, W, H: Integer;
|
|
PrintedPageNum, PrintPageCount: Integer;
|
|
begin
|
|
Result := False;
|
|
if ADocument = nil then
|
|
Exit;
|
|
|
|
if AFromPageIndex < 0 then
|
|
raise EPdfArgumentOutOfRange.CreateResFmt(@RsArgumentsOutOfRange, ['FromPage', AFromPageIndex]);
|
|
if (AToPageIndex < AFromPageIndex) or (AToPageIndex >= ADocument.PageCount) then
|
|
raise EPdfArgumentOutOfRange.CreateResFmt(@RsArgumentsOutOfRange, ['ToPage', AToPageIndex]);
|
|
|
|
PrintedPageNum := 0;
|
|
PrintPageCount := AToPageIndex - AFromPageIndex + 1;
|
|
|
|
if BeginPrint then
|
|
begin
|
|
try
|
|
if ADocument.FForm <> nil then
|
|
FORM_DoDocumentAAction(ADocument.FForm, FPDFDOC_AACTION_WP); // BeforePrint
|
|
|
|
for PageIndex := AFromPageIndex to AToPageIndex do
|
|
begin
|
|
PdfPage := nil;
|
|
WasPageLoaded := ADocument.IsPageLoaded(PageIndex);
|
|
try
|
|
PdfPage := ADocument.Pages[PageIndex];
|
|
PagePortraitOrientation := IsPortraitOrientation(Trunc(PdfPage.Width), Trunc(PdfPage.Height));
|
|
|
|
if FitPageToPrintArea then
|
|
begin
|
|
X := 0;
|
|
Y := 0;
|
|
W := FPrintArea.cx;
|
|
H := FPrintArea.cy;
|
|
end
|
|
else
|
|
begin
|
|
X := -FMargins.X;
|
|
Y := -FMargins.Y;
|
|
W := FPaperSize.cx;
|
|
H := FPaperSize.cy;
|
|
end;
|
|
|
|
if PagePortraitOrientation <> FPrintPortraitOrientation then
|
|
begin
|
|
SwapInts(X, Y);
|
|
SwapInts(W, H);
|
|
end;
|
|
|
|
// Print page
|
|
PrinterStartPage;
|
|
try
|
|
if (W > 0) and (H > 0) then
|
|
InternPrintPage(PdfPage, X, Y, W, H);
|
|
finally
|
|
PrinterEndPage;
|
|
end;
|
|
Inc(PrintedPageNum);
|
|
if Assigned(OnPrintStatus) then
|
|
OnPrintStatus(Self, PrintedPageNum, PrintPageCount);
|
|
finally
|
|
if not WasPageLoaded and (PdfPage <> nil) then
|
|
PdfPage.Close; // release memory
|
|
end;
|
|
if ADocument.FForm <> nil then
|
|
FORM_DoDocumentAAction(ADocument.FForm, FPDFDOC_AACTION_DP); // AfterPrint
|
|
end;
|
|
finally
|
|
EndPrint;
|
|
end;
|
|
Result := True;
|
|
end;
|
|
end;
|
|
|
|
procedure TPdfDocumentPrinter.InternPrintPage(APage: TPdfPage; X, Y, Width, Height: Double);
|
|
|
|
function RoundToInt(Value: Double): Integer;
|
|
var
|
|
F: Double;
|
|
begin
|
|
Result := Trunc(Value);
|
|
F := Frac(Value);
|
|
if F < 0 then
|
|
begin
|
|
if F <= -0.5 then
|
|
Result := Result - 1;
|
|
end
|
|
else if F >= 0.5 then
|
|
Result := Result + 1;
|
|
end;
|
|
|
|
var
|
|
PageWidth, PageHeight: Double;
|
|
PageScale, PrintScale: Double;
|
|
ScaledWidth, ScaledHeight: Double;
|
|
begin
|
|
PageWidth := APage.Width;
|
|
PageHeight := APage.Height;
|
|
|
|
PageScale := PageHeight / PageWidth;
|
|
PrintScale := Height / Width;
|
|
|
|
ScaledWidth := Width;
|
|
ScaledHeight := Height;
|
|
if PageScale > PrintScale then
|
|
ScaledWidth := Width * (PrintScale / PageScale)
|
|
else
|
|
ScaledHeight := Height * (PageScale / PrintScale);
|
|
|
|
X := X + (Width - ScaledWidth) / 2;
|
|
Y := Y + (Height - ScaledHeight) / 2;
|
|
|
|
APage.Draw(
|
|
FPrinterDC,
|
|
RoundToInt(X), RoundToInt(Y), RoundToInt(ScaledWidth), RoundToInt(ScaledHeight),
|
|
prNormal, [proPrinting, proAnnotations]
|
|
);
|
|
end;
|
|
{$ENDIF MSWINDOWS}
|
|
|
|
initialization
|
|
{$IFDEF FPC}
|
|
InitCriticalSection(PDFiumInitCritSect);
|
|
InitCriticalSection(FFITimersCritSect);
|
|
{$ELSE}
|
|
InitializeCriticalSectionAndSpinCount(PDFiumInitCritSect, 4000);
|
|
InitializeCriticalSectionAndSpinCount(FFITimersCritSect, 4000);
|
|
{$ENDIF FPC}
|
|
|
|
finalization
|
|
{$IFDEF FPC}
|
|
DoneCriticalSection(FFITimersCritSect);
|
|
DoneCriticalSection(PDFiumInitCritSect);
|
|
{$ELSE}
|
|
DeleteCriticalSection(FFITimersCritSect);
|
|
DeleteCriticalSection(PDFiumInitCritSect);
|
|
{$ENDIF FPC}
|
|
|
|
end.
|