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.