Files Delphi

Title: Create PDF files from your apps with the shareware "PDFReport" component: a primer.
Question: How can I create PDF Files directly from within my Delphi/Kylix application?
Answer:
If you want to build PDF files, reports, catalogues from your Delphi application, you've got only 2 ways:
1. using a PDF Printer Driver and using the Printer.Canvas
2. using a specialized component to create the file programmatically, like PDFReport.
The first solution is very useful in single user environments, and perfectly integrates with existing reporting engines: you only have to choose programmatically the filename of the printed report and set the PDF driver as the current printer for the reporting component.
There are many drawbacks, too:
- you have to pay for a PDF Printer Driver for every copy of the software you use or sell (Acrobat costs about 300 USD ESRP): the cost can be so high you couldn't sell but a few copies of the software
- the program can't be thread safe: if it is a server program, like a CGI, ISAPI or APACHE module you should avoid absolutely this solution.
- You must have the Driver installed on the system: so, you have to use professional installation programs, and you cannot implement solutions on Internet Service Providers.
The second solution: using a PDF Report engine is less intuitive, as you cannot leverage the reports already built with QReport or similar softwares, but it has several advantages:
- Pay once, deploy freely: no runtime fees, unlimited distribution rights
- Compiles in your exe! This solution eliminates all installation problems: you install the Exe, everything works! No need for Acrobat SDK, no 20 Megabytes setup programs, no DLL. The components adds only about 20 Kb to the exe's size.
- Inexpensive: costs only 149 USD, 50% less then a Printer Driver from Adobe
- Thread Safe: you can generate multiple PDF files in the same time. Perfect for use inside ISAPI apps.
- Can send back the PDF file generated directly in the user's browser: you can put an HTML form on your website which shows directly in the browser customized reports, invoices, tax declarations....
- Flexibility: if you have to use your logo on every page of a report, PDFReports saves it inside the PDF file only once and uses it even 10.000 times; Acrobat would save it 10.000 times making the pdf file very, very big.
- More features: insert bookmarks and indexes, floating wndows with notes...
==========================
Conclusion: if you need absolute precision or immediate integration with your actual reporting engine, and if you plan to make a desktop application to be used only by few customers, Adobe Acrobat is the right solution for you.
If you plan to deploy to more than 2/3 users your application, if you need a thread safe solution, if you don't want to go mad with installation and configuration issues, PDFReport is the way to go.
==========================
Let's analize now an example showing how to create an advanced master detail report with PDFReport.
The report similar to a reseller's catalogue/pricelist will be made with these features:
- Every product will have it's own page
- The Acrobat file will have an index with the names of the products directly linked to the correct page
- On every Page there will be the logo of the reseller; the logo will be saved once and used many times
- There will be a table with prices at the bottom of the page
- You will be able to customize the code in a few seconds
Here's the commented code:
procedure TForm1.Button1Click(Sender: TObject);
var
x: TPDFReport;
i: integer;
y: Integer;
InizioY: Integer;
Interlinea: Integer;
OldMarca: String;
ImmagineLogo: String;
begin
x := TPDFReport.Create(Nil); // creates the object
InizioY := 90; // the master band height is set in millimiters here with this variable
Interlinea := 14; // detail band height in millimeters
x.NewPDF('Example of Software Price List'); // creates the pdf file
// Insert the logo image as a resource of the pdf file, which
// dimensions are 109x273 pixel; it is stored in the same path of the
// *.exe, but it could have been elsewhere. ImmagineLogo is a string variable
// in which you store the name internally assigned to the Image
ImmagineLogo := x.Insert_JPG_Res(ExtractFilePath(Application.ExeName)+'YourLogo.jpg', 109, 273);
// Starts working with the datas, I use ADO, but it could have been dbExpress or BDE
// AdoQuery1.SQL is "Select * from Software order by marca"
// the name of the brand is inside the field "marca" (which is the italian term for brand)
// This way you have all the products ordered by brand in the PDFFile
if ADOQuery1.Active then ADOQuery1.Close;
ADOQuery1.Open;
// In the variable OldMarca there is the previous record's brand name
OldMarca := ADOQuery1.fieldbyname('Marca').AsString;
y := InizioY; // inizio corpo pagina
// prima testata
y := InizioY; // inizio corpo pagina
FOrm1.Caption := IntToStr(i);
Application.ProcessMessages;
OldMarca := ADOQuery1.fieldbyname('Marca').AsString;
// Draws the logo using the resource imported before
// 273 is the width in PDF Units (1 unit = 2.833333 mm)
// 109 is the height, 40 is the distance from the left border of the page,
// 700 is the distance from the bottom of the page
x.Draw_Jpg_Res(ImmagineLogo, 273, 109, 40, 700);
{
Creates the header, composed of a rectangle with the fields' names written inside
}
x.SetFillColor (0,255,255); // sets fill color in RGB values
// Draws the filled rectangle
x.DrawRect(1, 1, 1, 25, 205, y-20, y-6);
x.SetFillColor (0,0,0); // sets fill color again, now for the fields
// writes the field names
x.WriteText('Marca', 30, y-Interlinea, 10, 0, '/F4', 0);
x.WriteText('Codice', 80, y-Interlinea, 10, 0, '/F4', 0);
x.WriteText('Descrizione', 30, y+4-Interlinea, 8, 0, '/F4', 0);
x.WriteText('Disp.', 160, y-Interlinea, 10, 0, '/F4', 0);
x.WriteText('Prezzo listino', 180, y-Interlinea, 10, 0, '/F4', 0);
// inserts in the index a reference to this page using the name of the brand
x.MakeOutline(ADOQuery1.fieldbyname('Marca').AsString);
for i := 1 to ADOQuery1.RecordCount do
begin
if OldMarca ADOQuery1.fieldbyname('Marca').AsString then // sql order by marca = quindi quando marca cambia nuova sezione
begin
// creates a new page as there is a new brand
// see previous comments for the header
x.CreatePage;
y := InizioY; // restarts from the top of the page less the header's height
FOrm1.Caption := IntToStr(i);
Application.ProcessMessages; // to update the form's caption and see the speed of the PDF generation
OldMarca := ADOQuery1.fieldbyname('Marca').AsString;
x.Draw_Jpg_Res(ImmagineLogo, 273, 109, 40, 700);
x.SetFillColor (0,255,255);
x.DrawRect(1, 1, 1, 25, 205, y-20, y-6);
x.SetFillColor (0,0,0);
x.WriteText('Marca', 30, y-Interlinea, 10, 0, '/F4', 0);
x.WriteText('Codice', 80, y-Interlinea, 10, 0, '/F4', 0);
x.WriteText('Descrizione', 30, y+4-Interlinea, 8, 0, '/F4', 0);
x.WriteText('Disp.', 160, y-Interlinea, 10, 0, '/F4', 0);
x.WriteText('Prezzo listino', 180, y-Interlinea, 10, 0, '/F4', 0);
// inserts in the index a reference to this page using the name of the brand
x.MakeOutline(ADOQuery1.fieldbyname('Marca').AsString);
end;
if y 260 then
begin
// if we are going to write over 260 mm frommthe top of the page
// restart from the beginning with another header
x.CreatePage;
y := InizioY; // inizio corpo pagina
FOrm1.Caption := IntToStr(i);
Application.ProcessMessages;
OldMarca := ADOQuery1.fieldbyname('Marca').AsString;
x.Draw_Jpg_Res(ImmagineLogo, 273, 109, 40, 700);
x.SetFillColor (0,255,255);
x.DrawRect(1, 1, 1, 25, 205, y-20, y-6);
x.SetFillColor (0,0,0);
x.WriteText('Marca', 30, y-Interlinea, 10, 0, '/F4', 0);
x.WriteText('Codice', 80, y-Interlinea, 10, 0, '/F4', 0);
x.WriteText('Descrizione', 30, y+4-Interlinea, 8, 0, '/F4', 0);
x.WriteText('Disp.', 160, y-Interlinea, 10, 0, '/F4', 0);
x.WriteText('Prezzo listino', 180, y-Interlinea, 10, 0, '/F4', 0);
end;
// writes the detail band
x.WriteText(ADOQuery1.fieldbyname('Marca').AsString, 30, y, 10, 0, '/F1', 0);
x.WriteText(ADOQuery1.fieldbyname('codice').AsString, 80, y, 10, 0, '/F1', 0);
x.WriteText(ADOQuery1.fieldbyname('descrizione').AsString, 30, y+4, 8, 0, '/F1', 0);
x.WriteText(ADOQuery1.fieldbyname('disponibilit').AsString, 160, y, 10, 0, '/F1', 0);
x.WriteText(ADOQuery1.fieldbyname('prezzo listino').AsString, 180, y, 10, 0, '/F1', 0);
ADOQuery1.next; // next record
inc(y, Interlinea); // incrementemthe y position by the height of the detail band
end;
x.SavePDF(ExtractFilePath(Application.ExeName)+'prova.pdf'); // save the report as....
x.Free; // free the resources
if ADOQuery1.Active then ADOQuery1.Close;
end;
To customize the code for your needs, just set the new heights for header and detail bands and set the correct field names, change the logo dimensions, position and filename and then you're done!
You could also with other 2 lines of code draw the brand's logo or the product's picture on every page.
The whole report took only less then 200 lines of commented code and the generation of the PDF took less then 10 seconds on my old PC (over 400 pages with graphics!).
PS: The same code was recompiled under Kylix in less then one minute: the only change was the dataset specification - no other differences!
To know more about PDFReport, to find the prices or to order please visit http://www.dreamscape.it