Delphi 5 : Total number of page in pdf - delphi

I am maintaining an old application that is built with Delphi 5.
I need to determine the total number of pages in a given pdf file.
I guess I could translate any solution for plain C also.
The current solution I have is forking a pdftk process, and parse its output. But this is quite slow, so I was wondering if I could find a good open-source pdf parser library for delphi (5...)... And it seems it doesn't exist.
So I tried to implement things like looking into the file's raw content for occurences of "/Type /Page" or "/Count" or "Linearized ... /N". But none of these -nor a combination of them- work in every case.
So I wondered if I could find an open-source DLL that I could make use of from Delphi 5. But I couldn't find any neither. I stumbled upon iTextSharp but it is for .NET and I don't understand howto use it in a plain delphi5 program...
So my final thought is this : is there any change I could find the source code for -say- pdftk and compile it as a DLL? Could anyone point me to the right direction?
Is there any solution I am missing?
I thank you in advance for your help!

Did you try to use the PDFLib dll?
It is a very efficient library, and they have binding for Delphi, either via COM or via their dll. I guess you could use Delphi 5 with this library.

Did you try to run the free pdfinfo tool?
Download it from http://www.foolabs.com/xpdf
It returns such information:
Title: Optimizing software in C++
Keywords: software C++ optimization compiler
Author: Agner Fog
Creator: Microsoft® Word 2013
Producer: Microsoft® Word 2013
CreationDate: 12/15/14 14:25:13
ModDate: 12/15/14 14:25:13
Tagged: yes
Form: none
Pages: 37
Encrypted: no
Page size: 595.32 x 841.92 pts (A4) (rotated 0 degrees)
File size: 531693 bytes
Optimized: no
PDF version: 1.5
So here you have your number of pages.
And it is very fast.

If you install adobe acrobat (not reader) on user's PC then you can use OLE automation.
Here example how to open file and read pages count:
uses
ComObj, ActiveX;
function TmyForm.IsOLEObjectInstalled(Name: String): Boolean;
var
ClassID: TCLSID;
Rez: HRESULT;
begin
Rez := CLSIDFromProgID(PWideChar(WideString(Name)), ClassID);
Result := (Rez = S_OK);
end;
procedure TmyForm.ButtonCheckPagesClick(Sender: TObject);
var
doc: OleVariant;
pagesCount: Integer;
begin
if IsOLEObjectInstalled('AcroExch.PDDoc') then
begin
doc := CreateOleObject('AcroExch.PDDoc');
if doc.Open('C:\test\test.pdf') then
begin
pagesCount := doc.GetNumPages;
ShowMessageFmt('pages count = %d', [pagesCount]);
end
else
begin
ShowMessage('Can''t open file');
end;
end
else
begin
ShowMessage('You must install adobe acrobat for that feature');
end;
end;

Related

Can I modify a constant in the RTL class System.Classes.TStream and rebuild it at runtime in Delphi XE6?

I am trying to work around a known ugly performance limitation in System.Classes.pas, which has a 1980s era constant buffer limit ($F000) that looks like this:
function TStream.CopyFrom(const Source: TStream; Count: Int64): Int64;
const
MaxBufSize = $F000;
....
This is causing major performance penalties in our Delphi application. In delphi XE2 through XE5, we were able to modify this and use one of the following approaches:
I could modify the Delphi sources, and then, by invoking dcc32.exe from a batch file, rebuild the System.Classes.dcu file in the Delphi library folder. I realize this is ugly and I didn't like doing this, but I don't like this ugly performance issue in the RTL either, and our users can not live with the performance headaches it causes.
I could try to put a modified system.classes.pas file somewhere in my project search path.
Neither of the above approaches is working for me in Delphi XE6, now, thanks probably to some internal compiler changes. The error I get in a minimal command line application that includes System.Contnrs in its uses clause, is this:
[dcc32 Fatal Error] System.Classes.pas(19600): F2051 Unit System.Contnrs was compiled with a different version of System.Classes.TComponent
The sample program to reproduce this problem (assuming you have modified System.Classes.pas and changed the MaxBufSize constant), is shown here:
program consoletestproject;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.Contnrs,
System.SysUtils;
var
List:System.Contnrs.TObjectList;
begin
WriteLn('Hello world');
end.
Again, this problem reproduces easily in Delphi XE6, but is not a problem in XE5, or earlier.
What is the recommended practice when you absolutely MUST work around a fundamental RTL or VCL limitation using a modified copy of System.Classes.pas or System.SysUtils.pas or some other very low level unit? (Yes, I know you should NOT do this if you don't have to, don't bother with a lecture.)
Are there a magic set of command line parameters you can use via "dcc32.exe" on the command line, to produce a modified DCU that will link properly with the application example above?
As a secondary question, are there .dcu files for which no source exists that will break when one tries to do this, in which case the answer to all of the above is, "you can't fix this, and if there's a bug in the RTL, you're out of luck"?
One possible workaround is to include "$(BDS)\source\rtl\common" in your project search path (or library path), forcing each broken (needing recompile) DCU to rebuild EACH time, but this seems ugly and wrong.
You can overcome this limitation using a detour, try this sample which uses the Delphi Detours Library
First define the signature of the method to hook
var
Trampoline_TStreamCopyFrom : function (Self : TStream;const Source: TStream; Count: Int64): Int64 = nil;
then implement the detour
function Detour_TStreamCopyFrom(Self : TStream;const Source: TStream; Count: Int64): Int64;
const
MaxBufSize = 1024*1024; //use 1 mb now :)
var
BufSize, N: Integer;
Buffer: TBytes;
begin
if Count <= 0 then
begin
Source.Position := 0;
Count := Source.Size;
end;
Result := Count;
if Count > MaxBufSize then BufSize := MaxBufSize else BufSize := Count;
SetLength(Buffer, BufSize);
try
while Count <> 0 do
begin
if Count > BufSize then N := BufSize else N := Count;
Source.ReadBuffer(Buffer, N);
Self.WriteBuffer(Buffer, N);
Dec(Count, N);
end;
finally
SetLength(Buffer, 0);
end;
end;
Finally replace the original function by the trampoline (you can use this code in the initialization part of some unit)
Trampoline_TStreamCopyFrom := InterceptCreate(#TStream.CopyFrom, #Detour_TStreamCopyFrom);
And to release the hook you can use
if Assigned(Trampoline_TStreamCopyFrom) then
InterceptRemove(#Trampoline_TStreamCopyFrom);
Update 1: The suggestion below does not work for the Classes unit in XE6. The basic technique is sound and does solve similar problems. But for XE6, at least the Classes unit, it is not immediately obvious how to re-compile it.
This appears to be a fault introduced in XE6 because this technique is meant to work and is officially endorsed by Embarcadero: http://blog.marcocantu.com/blog/2014_august_buffer_overflow_bitmap.html
Update 2:
In XE7, this problem no longer exists. It would appear that whatever was broken in XE6 has been fixed.
You need the compiler options to match those used when the unit was compiled by Embarcadero. That's the reason why your implementation section only change fails when it seems like it ought to succeed.
Start a default project and use CTRL + O + O to generate these options. I get
{$A8,B-,C+,D+,E-,F-,G+,H+,I+,J-,K-,L+,M-,N-,O+,P+,Q-,R-,S-,T-,U-,V+,W-,X+,Y+,Z1}
when I do this in XE6.
Put that at the top of your copy of the unit and you should be good to go. You can probably get away with a cut-down subset of these, depending on your host project options. In my code I find that:
{$R-,T-,H+,X+}
suffices.

CopyFile docx makes hidden conversion to doc

i got a Delphi 7 program which has to copy a docx file. I'm using the Windows API CopyFile function. The problem is that this function seems to make a hidden conversion to the older doc format.
First thing: the file size increases after the copy.
Second: When opening the file in Office 2007 i got an error message stating that: Check your permissions to the document or disk, check free disk space.
And than the strange thing: if i change in TotalCommander the extension of the copied file from docx to doc it opens normally. So it seems to make a hidden conversion dont know why.
Tested on two different computers. Both Win XP Prof SP3, Office 2007 Prof Plus SP2
Any ideas?
Function body is below:
function TDlgNowySzablon.PobierzPlikNaDoc() : string;
var
openDlg : TOpenDialog;
begin
Result:='';
openDlg:=TOpenDialog.Create(self);
openDlg.Filter:='Dokumenty Microsoft Word (*.doc;*docx)|*.doc;*.docx';
if openDlg.Execute then begin
Result := IObsSzab.GetTempFullFileName( ExtractFileExt(openDlg.FileName) );
if not CopyFile(PChar(openDlg.FileName),PChar(Result),true) then begin
Result:='';
end;
end;
openDlg.Free;
end;
Try changing your code as follows:
Result := IObsSzab.GetTempFullFileName('.tmp');
Result := ChangeFileExt(Result, ExtractFileExt(openDlg.FileName));
I think your GetTempFullFileName function is truncating .docx to .doc. It's all guesswork though!
The CopyFile function does not modify the contents of the file.

How to save string into text files in Delphi?

What is the easiest way to create and save string into .txt files?
Use TStringList.
uses
Classes, Dialogs; // Classes for TStrings, Dialogs for ShowMessage
var
Lines: TStrings;
Line: string;
FileName: string;
begin
FileName := 'test.txt';
Lines := TStringList.Create;
try
Lines.Add('First line');
Lines.Add('Second line');
Lines.SaveToFile(FileName);
Lines.LoadFromFile(FileName);
for Line in Lines do
ShowMessage(Line);
finally
Lines.Free;
end;
end;
Also SaveToFile and LoadFromFile can take an additional Encoding in Delphi 2009 and newer to set the text encoding (Ansi, UTF-8, UTF-16, UTF-16 big endian).
Actually, I prefer this:
var
Txt: TextFile;
SomeFloatNumber: Double;
SomeStringVariable: string;
Buffer: Array[1..4096] of byte;
begin
SomeStringVariable := 'Text';
AssignFile(Txt, 'Some.txt');
Rewrite(Txt);
SetTextBuf(Txt, Buffer, SizeOf(Buffer));
try
WriteLn(Txt, 'Hello, World.');
WriteLn(Txt, SomeStringVariable);
SomeFloatNumber := 3.1415;
WriteLn(Txt, SomeFloatNumber:0:2); // Will save 3.14
finally CloseFile(Txt);
end;
end;
I consider this the easiest way, since you don't need the classes or any other unit for this code. And it works for all Delphi versions including -if I'm not mistaken- all .NET versions of Delphi...
I've added a call to SetTextBuf() to this example, which is a good trick to speed up textfiles in Delphi considerably. Normally, textfiles have a buffer of only 128 bytes. I tend to increase this buffer to a multiple of 4096 bytes. In several cases, I'va also implemented my own TextFile types, allowing me to use these "console" functions to write text to memo fields or even to another, external application! At this location is some example code (ZIP) I wrote in 2000 and just modified to make sure it compiles with Delphi 2007. Not sure about newer Delphi versions, though. Then again, this code is 10 years old already.These console functions have been a standard of the Pascal language since it's beginning so I don't expect them to disappear anytime soon. The TtextRec type might be modified in the future, though, so I can't predict if this code will work in the future... Some explanations:
WA_TextCustomEdit.AssignCustomEdit allows text to be written to CustomEdit-based objects like TMemo.
WA_TextDevice.TWATextDevice is a class that can be dropped on a form, which contains events where you can do something with the data written.
WA_TextLog.AssignLog is used by me to add timestamps to every line of text.
WA_TextNull.AssignNull is basically a dummy text device. It just discards anything you write to it.
WA_TextStream.AssignStream writes text to any TStream object, including memory streams, file streams, TCP/IP streams and whatever else you have.
Code in link is hereby licensed as CC-BY
Oh, the server with the ZIP file isn't very powerful, so it tends to be down a few times every day. Sorry about that.
The IOUtils unit which was introduced in Delphi 2010 provides some very convenient functions for writing/reading text files:
//add the text 'Some text' to the file 'C:\users\documents\test.txt':
TFile.AppendAllText('C:\users\documents\text.txt', 'Some text', TEncoding.ASCII);
Or if you are using an older version of Delphi (which does not have the for line in lines method of iterating a string list):
var i : integer;
begin
...
try
Lines.Add('First line');
Lines.Add('Second line');
Lines.SaveToFile(FileName);
Lines.LoadFromFile(FileName);
for i := 0 to Lines.Count -1 do
ShowMessage(Lines[i]);
finally
Lines.Free;
end;
If you're using a Delphi version >= 2009, give a look to the TStreamWriter class.
It will also take care of text file encodings and newline characters.
procedure String2File;
var s:ansiString;
begin
s:='My String';
with TFileStream.create('c:\myfile.txt',fmCreate) do
try
writeBuffer(s[1],length(s));
finally
free;
end;
end;
Care needed when using unicode strings....

Getting size of a file in Delphi 2010 or later?

Delphi 2010 has a nice set of new file access functions in IOUtils.pas (I especially like the UTC versions of the date-related functions). What I miss so far is something like
TFile.GetSize (const Path : String)
What is the Delphi 2010-way to get the size of a file? Do I have to go back and use FindFirst to access TSearchRec.FindData?
Thanks.
I'm not sure if there's a "Delphi 2010" way, but there is a Windows way that doesn't involve FindFirst and all that jazz.
I threw together this Delphi conversion of that routine (and in the process modified it to handle > 4GB size files, should you need that).
uses
WinApi.Windows;
function FileSize(const aFilename: String): Int64;
var
info: TWin32FileAttributeData;
begin
result := -1;
if NOT GetFileAttributesEx(PChar(aFileName), GetFileExInfoStandard, #info) then
EXIT;
result := Int64(info.nFileSizeLow) or Int64(info.nFileSizeHigh shl 32);
end;
You could actually just use GetFileSize() but this requires a file HANDLE, not just a file name, and similar to the GetCompressedFileSize() suggestion, this requires two variables to call. Both GetFileSize() and GetCompressedFileSize() overload their return value, so testing for success and ensuring a valid result is just that little bit more awkward.
GetFileSizeEx() avoids the nitty gritty of handling > 4GB file sizes and detecting valid results, but also requires a file HANDLE, rather than a name, and (as of Delphi 2009 at least, I haven't checked 2010) isn't declared for you in the VCL anywhere, you would have to provide your own import declaration.
Using an Indy unit:
uses IdGlobalProtocols;
function FileSizeByName(const AFilename: TIdFileName): Int64;
You can also use DSiFileSize from DSiWin32. Works in "all" Delphis. Internally it calls CreateFile and GetFileSize.
function DSiFileSize(const fileName: string): int64;
var
fHandle: DWORD;
begin
fHandle := CreateFile(PChar(fileName), 0, 0, nil, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
if fHandle = INVALID_HANDLE_VALUE then
Result := -1
else try
Int64Rec(Result).Lo := GetFileSize(fHandle, #Int64Rec(Result).Hi);
finally CloseHandle(fHandle); end;
end; { DSiFileSize }
I'd like to mention few Pure Delphi ways. Though i think Deltics made a most speed-effective answer for Windows platform, yet sometimes you want just rely on RTL and also make portable code that would work in Delphi for MacOS or in FreePascal/Virtual Pascal/whatever.
There is FileSize function left from Turbo Pascal days.
http://turbopascal.org/system-functions-filepos-and-filesize
http://docwiki.embarcadero.com/CodeExamples/XE2/en/SystemFileSize_(Delphi)
http://docwiki.embarcadero.com/Libraries/XE2/en/System.FileSize
The sample above lacks "read-only" mode setting. You would require that to open r/o file such as one on CD-ROM media or in folder with ACLs set to r/o. Before calling ReSet there should be zero assigned to FileMode global var.
http://docwiki.embarcadero.com/Libraries/XE2/en/System.FileMode
It would not work on files above 2GB size (maybe with negative to cardinal cast - up to 4GB) but is "out of the box" one.
There is one more approach, that you may be familiar if you ever did ASM programming for MS-DOS. You Seek file pointer to 1st byte, then to last byte, and check the difference.
I can't say exactly which Delphi version introduced those, but i think it was already in some ancient version like D5 or D7, though that is just common sense and i cannot check it.
That would take you an extra THandle variable and try-finally block to always close the handle after size was obtained.
Sample of getting length and such
http://docwiki.embarcadero.com/Libraries/XE2/en/System.SysUtils.FileOpen
http://docwiki.embarcadero.com/Libraries/XE2/en/System.SysUtils.FileSeek
Aside from 1st approach this is int64-capable.
It is also compatible with FreePascal, though with some limitations
http://www.freepascal.org/docs-html/rtl/sysutils/fileopen.html
You can also create and use TFileStream-typed object - which was the primary, officially blessed avenue for file operations since Delphi 1.0
http://www.freepascal.org/docs-html/rtl/classes/tfilestream.create.html
http://www.freepascal.org/docs-html/rtl/classes/tstream.size.html
http://docwiki.embarcadero.com/Libraries/XE2/en/System.Classes.TFileStream.Create
http://docwiki.embarcadero.com/Libraries/XE2/en/System.Classes.TStream.Size
As a side note, this avenue is of course integrated with aforementioned IOUtils unit.
http://docwiki.embarcadero.com/Libraries/XE3/en/System.IOUtils.TFile.OpenRead
This is a short solution using FileSize that does the job:
function GetFileSize(p_sFilePath : string) : Int64;
var
oFile : file of Byte;
begin
Result := -1;
AssignFile(oFile, p_sFilePath);
try
Reset(oFile);
Result := FileSize(oFile);
finally
CloseFile(oFile);
end;
end;
From what I know, FileSize is available only from XE2.
uses
System.Classes, System.IOUtils;
function GetFileSize(const FileName : string) : Int64;
var
Reader: TFileStream;
begin
Reader := TFile.OpenRead(FileName);
try
result := Reader.Size;
finally
Reader.Free;
end;
end;

Creating Compressed (Zipped) Folder using Delphi

Can I create Windows XP's Compressed (Zipped) Folder using Delphi?
If you are using Delphi X2, just use TZipFile from System.Zip:
To Zip a folder, use:
TZipFile.ZipDirectoryContents('ZipFile.zip', 'C:\Zip\this\right\now');
To Zip files, use:
Zip := TZipFile.Create;
try
Zip.Open('ZipFile.zip', zmWrite);
Zip.Add('FileToBeZipped.txt');
Zip.Add('ThisWillBeCompressedAgainForSureAndBecomeSmaller.zip');
finally
Zip.Free;
end
According to a thread in eggheadcafe, you can use CreateFile Function with FILE_FLAG_BACKUP_SEMANTICS to create a Compressed Folder.
For shell extensions route, take a look at Using Windows XP "Compressed Folder" shell extension to work with .zip files by Namespace Edanmo, which is written in VB.
I just found the similar question asked on C++. Take a look at Creating a ZIP file on Windows (XP/2003) in C/C++. I have a feeling the easiest route is buying ZipForge. See Zip a file in Delphi code sample.
Some time ago, I've tried all of the Delphi compression libraries that I could find, and eventually I ended up using KaZip by Kiril Antonov.
My requirements were:
Free;
Open source;
Native Delphi code;
No external dependencies (dll, exe). My most important requirement;
Small memory footprint;
Easy to use;
I use it mainly to turn .kml files into .kmz, and it does that amazingly fast.
Here's an example of how I use it:
uses
KaZip;
...
// replaces a .kml file with a .kmz file
procedure KmlToKmz(const aFileName: string);
var
FS: TFileStream;
KaZip:TKaZip;
KmzFileName:TFileName;
begin
KmzFileName := ChangeFileExt(aFileName, '.kmz');
KaZip := TKaZip.Create(nil);
try
// create an empty zipfile with .kmz extension:
FS := TFileStream.Create(KmzFileName, fmOpenReadWrite or FmCreate);
try
KaZip.CreateZip(FS);
finally
FS.Free;
end;
KaZip.Open(KmzFileName); // Open the new .kmz zipfile
KaZip.Entries.AddFile(aFileName); // add the .kml
KaZip.Close;
DeleteFile(aFileName); // delete the .kml
finally
KaZip.Free;
end;
end;
Take a look at this OpenSource SynZip unit. It's even faster for decompression than the default unit shipped with Delphi, and it will generate a smaller exe (crc tables are created at startup).
No external dll is needed. Works from Delphi 6 up to XE. No problem with Unicode version of Delphi. All in a single unit.
I just made some changes to handle Unicode file names inside Zip content, not only Win-Ansi charset but any Unicode chars. Feedback is welcome.
You could use TurboPower Abbrevia which is now open source.
A "zipped" folder in Windows is nothing more than a .ZIP file compressed using any standard zip library. Compressed folders are a different animal and require an NTFS disk format.
For the "Zip" file, I strongly suggest the Turbo Power Abbrevia, which is open source and works well. You might want to check this alternate site if your using Delphi 2009 as it might be a more recent copy.
If your wanting to use the compressed folders option, you will need to modify the directory flags on the directory handle. This will only impact NEW files added to that directory and will not automatically compress existing files. If you have an existing directory you are trying to compress, then rename each existing file, and load and save it back to the original name deleting the original file when complete with each one. Yozey had a good link to the MSDN documentation. Just remember that this only works with NTFS formatted disks, so you will need to add a check for that in your code.
You can use some command line version of any compressor like 7zip and do the task using ShellExecute, or you can use a free or comercial component like anyone of these.
I had used ZipMaster and it behaves very well for my purpose. I don't know what are your size, space and performance requirements.
Take a look at these:
File Compression
FSCTL_SET_COMPRESSION
The TZipFile.ZipDirectoryContents method did not work for me, so I created my own implementation using TZipFile.add(). I am posting it here if anyone needs it.
procedure CreateZipOfDirectory(directory: string);
var
zip: TZipFile;
Arr: tarray<string>;
str: string;
function GetAllFilesInDir(const Dir: string): tarray<string>;
var
Search: TSearchRec;
procedure addAll(arr: tarray<string>; parent: string);
var
tmp: string;
begin
for tmp in arr do
begin
setlength(result, length(result) + 1);
result[length(result) - 1] := IncludeTrailingBackslash(parent) + tmp;
end;
end;
begin
setlength(result, 0);
if FindFirst(IncludeTrailingBackslash(Dir) + '*.*', faAnyFile or faDirectory, Search) = 0 then
try
repeat
if (Search.Attr and faDirectory) = 0 then
begin
setlength(result, length(result) + 1);
result[length(result) - 1] := Search.Name;
end
else if (Search.Name <> '..') and (Search.Name <> '.') then
addAll(GetAllFilesInDir(IncludeTrailingBackslash(Dir) + Search.Name), Search.Name);
until FindNext(Search) <> 0;
finally
FindClose(Search);
end;
end;
begin
Zip := TZipFile.Create;
try
Zip.Open('Foo.zip', zmWrite);
arr := GetAllFilesInDir(directory); // The Delphi TZipFile.ZipDirectoryContents does not work properly, so let's create our own.
for str in arr do
zip.Add(directory + str, str); // Add the second parameter to make sure that the file structure is preserved.
finally
zip.Free;
end;
end;

Resources