Jump to content

creating pdf documents


Recommended Posts

  • Moderators
One of the good things about Smart Mobile Studio is that it easily integrates with 3rd party javascript libraries.

 

This post is about producing pdf documents using jsPDF.js and accounting.js

 

The coding below loosely refers to creating pdf invoices.

Invoices usually have some kind of logo, a variable number of invoice lines, some graphics like rectangles and a footer with say banking details.

 

Creating such a pdf and including text and images is almost trivial :

 



procedure GeneratePDFInvoice;
begin
    asm
      var imgData = 'data:image/jpeg;base64,/9j/4AAQSkZJzGI5VkBA7klcfiayaKTSluAUUUU...................';  
//logo in data url format
      var doc = new jsPDF();
 
      doc.addImage(imgData, 'JPEG', 10, 0, 185, 50);   //logo - left, top, width, height
//
      doc.setFont("times");
      doc.setFontType("normal");
      doc.setFontSize(12);
      doc.text(25, 60, 'To :');
      doc.text(25, 65, @pdfcustomer);   //If the pdfcustomer string contains ctrl/lf #13#10 codes, 
                                        //like copied from a W3Memo.text, then it prints 
                                        //multiple lines
 
//    print a rectangle with rounded corners
      doc.roundedRect(25, 83, 160, 20, 2, 2);  //left, top, width, height, round
 
//    print a money formatted string using accounting.js and right-align it
//    calculate the length of the formatted number first
//    then to achieve right-alignment print from right border - length
      var ZERO = doc.getStringUnitWidth(accounting.formatMoney(@DispLabour, "$ ", 2), {fontName:'Times', fontStyle:'Roman'}) * 12;
      doc.text(194-ZERO+((ZERO-30)/1.5), 112, accounting.formatMoney(@DispLabour, "$ ", 2));
 
//    new page
      doc.addPage();
 
//    etc.
 
    end;
end;


 

 

Alternatively when using a normal url based reference (say 'res/logoheader.jpg') for images the process is a bit more intricate.

 

In the example below the image is preprocessed (getImageFromUrl) and a callback function (createPDF) looks after composing the actual pdf. 

 



procedure GeneratePDFInvoice;
begin
    asm
 
      var getImageFromUrl = function(url, callback) {
      var img = new Image, data, ret={data: null, pending: true};
 
    img.onError = function() {
   throw new Error('Cannot load image: "'+url+'"');
   }
   img.onload = function() {
   var canvas = document.createElement('canvas');
   document.body.appendChild(canvas);
   canvas.width = img.width;
   canvas.height = img.height;
 
   var ctx = canvas.getContext('2d');
   ctx.drawImage(img, 0, 0);
// Grab the image as a jpeg encoded in base64, but only the data
   data = canvas.toDataURL('image/jpeg').slice('data:image/jpeg;base64,'.length);
// Convert the data to binary form
   data = atob(data)
   document.body.removeChild(canvas);
 
    ret['data'] = data;
   ret['pending'] = false;
   if (typeof callback === 'function') {
   callback(data);
   }
   }
   img.src = url;
 
   return ret;
      }
//
 
      var createPDF = function(imgData) {
        var doc = new jsPDF();
        doc.addImage(imgData, 'JPEG', 10, 0, 185, 50);
 
        ... invoice creation code here, same as above ...
 
//      doc.save('MyInvoice.pdf');             // either save to file or
        data = doc.output('dataurlstring',{}); // Output in a popup window
        window.open(data,'_blank');
 
      }
//
 
      getImageFromUrl('res/logoheader.jpg', createPDF);
 
    end;
end;
 


 

 

 

The last part of the createPDF function takes care of saving or displaying the pdf document

 

Works on all browsers on all platforms, with the following caveats :

 

- Displaying the pdf document in a new window means you may need to set permission in your browser to accept pop-up windows from this particular website.

- On android devices the pdf is downloaded into the download directory rather than displayed directly.

- does not display in Edge. Hopefully may change after it has been open-sourced

 

Link to post
Share on other sites

How do you add the js library to your project so the "asm" block knows about the jsPDF() routine?

 

 

 

 

 

One of the good things about Smart Mobile Studio is that it easily integrates with 3rd party javascript libraries.
 
This post is about producing pdf documents using jsPDF.js and accounting.js
 
The coding below loosely refers to creating pdf invoices.
Invoices usually have some kind of logo, a variable number of invoice lines, some graphics like rectangles and a footer with say banking details.
 
Creating such a pdf and including text and images is almost trivial :
 
procedure GeneratePDFInvoice;
begin
    asm
      var imgData = 'data:image/jpeg;base64,/9j/4AAQSkZJzGI5VkBA7klcfiayaKTSluAUUUU...................';   
//logo in data url format
      var doc = new jsPDF();
 
      doc.addImage(imgData, 'JPEG', 10, 0, 185, 50);   //logo - left, top, width, height
//
      doc.setFont("times");
      doc.setFontType("normal");
      doc.setFontSize(12);
      doc.text(25, 60, 'To :');
      doc.text(25, 65, @pdfcustomer);   //If the pdfcustomer string contains ctrl/lf #13#10 codes, 
                                        //like copied from a W3Memo.text, then it prints 
                                        //multiple lines
 
//    print a rectangle with rounded corners
      doc.roundedRect(25, 83, 160, 20, 2, 2);  //left, top, width, height, round
 
//    print a money formatted string using accounting.js and right-align it
//    calculate the length of the formatted number first
//    then to achieve right-alignment print from right border - length
      var ZERO = doc.getStringUnitWidth(accounting.formatMoney(@DispLabour, "$ ", 2), {fontName:'Times', fontStyle:'Roman'}) * 12;
      doc.text(194-ZERO+((ZERO-30)/1.5), 112, accounting.formatMoney(@DispLabour, "$ ", 2));
 
//    new page
      doc.addPage();
 
//    etc.
 
    end;
end;
 
 
Alternatively when using a normal url based reference (say 'res/logoheader.jpg') for images the process is a bit more intricate.
 
In the example below the image is preprocessed (getImageFromUrl) and a callback function (createPDF) looks after composing the actual pdf. 
 
procedure GeneratePDFInvoice;
begin
    asm
 
      var getImageFromUrl = function(url, callback) {
       var img = new Image, data, ret={data: null, pending: true};
 
     img.onError = function() {
   throw new Error('Cannot load image: "'+url+'"');
   }
   img.onload = function() {
   var canvas = document.createElement('canvas');
   document.body.appendChild(canvas);
   canvas.width = img.width;
   canvas.height = img.height;
 
   var ctx = canvas.getContext('2d');
   ctx.drawImage(img, 0, 0);
// Grab the image as a jpeg encoded in base64, but only the data
   data = canvas.toDataURL('image/jpeg').slice('data:image/jpeg;base64,'.length);
// Convert the data to binary form
   data = atob(data)
   document.body.removeChild(canvas);
 
     ret['data'] = data;
   ret['pending'] = false;
   if (typeof callback === 'function') {
   callback(data);
   }
   }
   img.src = url;
 
   return ret;
      }
//
 
      var createPDF = function(imgData) {
        var doc = new jsPDF();
        doc.addImage(imgData, 'JPEG', 10, 0, 185, 50);
 
        ... invoice creation code here, same as above ...
 
//      doc.save('MyInvoice.pdf');             // either save to file or
        data = doc.output('dataurlstring',{}); // Output in a popup window
        window.open(data,'_blank');
 
      }
//
 
      getImageFromUrl('res/logoheader.jpg', createPDF);
 
    end;
end;
 
 
How to you add the JS 
 
 
 
The last part of the createPDF function takes care of saving or displaying the pdf document
 
Works on all browsers on all platforms, with the following caveats :
 
- Displaying the pdf document in a new window means you may need to set permission in your browser to accept pop-up windows from this particular website.
- On android devices the pdf is downloaded into the download directory rather than displayed directly.
- does not display in Edge. Hopefully may change after it has been open-sourced

 

Link to post
Share on other sites
  • Moderators

Easiest way:

 

In the IDE right-click anywhere in the project-manager window (left hand side, the window which shows your forms and units)

 

Choose 'Add Template ...' from the menu

 

Navigate to the 'Templates' sub-directory from where your copy of SmartMS.exe resides

 

Choose 'Default.html', this will add a 'Custom Template' entry in the project-manger window

 

Dbl-click this entry to open and you'll see the index.html template for your project

 

Add entries like

   <script type="text/javascript" src="lib/jspdf.js"></script>
   <script type="text/javascript" src="lib/accounting.min.js"></script>
in the <head> section

 

You can host these src files locally, in that case make sure these files are physically added to the lib directory

 

Alternatively many javascript libraries are hosted externally

jsPDF for instance can be accessed as 'https://cdnjs.cloudflare.com/ajax/libs/jspdf/1.1.135/jspdf.min.js'

and accounting.js as 'https://cdnjs.cloudflare.com/ajax/libs/accounting.js/0.4.1/accounting.min.js'

 

just put this in the src parameters

 

see https://cdnjs.com/libraries which lists a large number of externally hosted js libraries

 

(not all of these are very useful)

Link to post
Share on other sites
  • Moderators
Accessing js libraries directly through asm blocks still sort of feels like cheating.

It is probably more in line with the development environment to make a wrapper around the js functions and call these functions like any other class function.

 

The below unit is a wrapper around the jsPDF.js library and exports functions for text, fonts, fonstsize, rectangles etc.

 

Basically this wrapper is a simple class object. The reference to the js document variable is held as a THandle and the initialisation is structured same as the initialisation section of the SQLite wrapper by Jon Aasenden. 

 


unit PDFDoc;
 
interface
 
uses 
  SmartCL.System, system.types;
 
type
 
  TPDFHandle = THandle;
 
  EPDF = class(EW3Exception);
  TPDF = class(TObject)
  private
    FHandle:      TPDFHandle;
  protected
    Procedure     CreatePDF;
    procedure     ReleasePDF;
  public
    Property      Handle:TPDFHandle read FHandle;
 
    Procedure     Text(Left, Top : integer; text: string);
    Procedure     setFont(font: string);
    Procedure     setFontType(fonttype: string);
    Procedure     setFontSize(fontsize: integer);
    Procedure     setTextColor(r,g,b: integer);
    Procedure     rect(left, top, width, height : integer);
    Procedure     output(windowtype: string);
 
    Constructor   Create;virtual;
    Destructor    Destroy;Override;
  end;
 
Procedure PDFInitialize;overload;
Procedure PDFInitialize(DriverURL:String);overload;
function  PDFReady:Boolean;
 
implementation
 
uses SmartCL.fileutils;
 
 
{$R 'jspdf.js'}
 
var
  __PDFREADY:  Boolean := False;
 
 
Procedure TPDF.CreatePDF;
begin
  if (FHandle) then
    ReleasePDF;
 
  asm
    (@self.FHandle) = new jsPDF();
  end;
end;
 
procedure TPDF.ReleasePDF;
begin
  if FHandle then
  begin
    FHandle:=TVariant.CreateObject;
    FHandle:=NULL;
  end;
end;
 
Constructor TPDF.Create;
begin
  inherited Create;
 
  try
    CreatePDF;
  except
    on e: exception do
    Raise Exception.Create(e.message);
  end;
end;
 
Destructor TPDF.Destroy;
begin
  if (FHandle) then
    ReleasePDF;
  inherited;
end;
 
Procedure TPDF.Text(Left, Top : integer; text: string);
begin
  if (FHandle) then begin
    FHandle.text(left, top, text);
  end;
end;
 
Procedure TPDF.setFont(font: string);
begin
  if (FHandle) then begin
    FHandle.setFont(font);
  end;
end;
 
Procedure TPDF.setFontType(fonttype: string);
begin
  if (FHandle) then begin
    FHandle.setFontType(fonttype);
  end;
end;
 
Procedure TPDF.setFontSize(fontsize: integer);
begin
  if (FHandle) then begin
    FHandle.setFontSize(fontsize);
  end;
end;
 
Procedure TPDF.setTextColor(r,g,b: integer);
begin
  if (FHandle) then begin
    FHandle.setTextColor(r,g,B);
  end;
end;
 
Procedure TPDF.rect(left, top, width, height : integer);
begin
  if (FHandle) then begin
    FHandle.rect(left, top, width, height);
  end;
end;
 
Procedure TPDF.output(windowtype: string);
begin
  if (FHandle) then begin
    FHandle.output(windowtype);
  end;
end;
 
Procedure PDFInitialize(DriverURL:String);overload;
begin
  if not __PDFREADY then
  begin
    try
      writeln("Loading PDF driver");
      TW3Storage.LoadScript(DriverURL,
        procedure ()
        begin
          writeln("PDF Driver loaded and ready");
          w3_setTimeout( procedure ()
            begin
              __PDFREADY := true;
            end, 20);
        end);
    except
      on e: exception do
      Raise EPDF.CreateFmt
      ("Failed to load PDF driver, system threw exception %s with [%s]",
      [e.classname,e.message]);
    end;
  end;
end;
 
Procedure PDFInitialize;
begin
  PDFInitialize("/lib/jspdf.js");
end;
 
function  PDFReady:Boolean;
begin
  result:=__PDFREADY;
end;
 
initialization
begin
  __PDFReady:=False;
  PDFInitialize;
end;
 
end.
 

 

The reference to the js library is through a resource directive, so no need to edit the html template.

The exported functions are setFont, setFontType, setFontSize, setTextColor(r,g,b: integer), text, rect and output.

 

The form below produces a pdf document through the above wrapper.

Note : pdf document is not visible in the IDE, only when executed from a server

 


unit Form1;
 
interface
 
uses 
  SmartCL.System, SmartCL.Graphics, SmartCL.Components, SmartCL.Forms, 
  SmartCL.Fonts, SmartCL.Borders, SmartCL.Application, SmartCL.Controls.Button,
  PDFDoc;
 
type
  TForm1 = class(TW3Form)
    procedure W3Button1Click(Sender: TObject);
  private
    {$I 'Form1:intf'}
  protected
    procedure InitializeForm; override;
    procedure InitializeObject; override;
    procedure Resize; override;
    PDF1: TPDF;
  end;
 
implementation
 
{ TForm1 }
 
procedure TForm1.W3Button1Click(Sender: TObject);
begin
  PDF1.setFont("times");
  PDF1.setFontType("normal");
  PDF1.setFontSize(12);
  PDF1.setTextColor(90,90,90);
  PDF1.Text(5,10,'top of the world');
  PDF1.rect(25, 25, 30, 11);
  PDF1.output('dataurlnewwindow');
end;
 
procedure TForm1.InitializeForm;
begin
  inherited;
  // this is a good place to initialize components
  PDF1 := TPDF.Create;
end;
 
procedure TForm1.InitializeObject;
begin
  inherited;
  {$I 'Form1:impl'}
end;
 
procedure TForm1.Resize;
begin
  inherited;
end;
 
initialization
  Forms.RegisterForm({$I %FILE%}, TForm1);
end.
 

Link to post
Share on other sites
  • 1 month later...
  • Moderators

So after the discussions and explanations in this forum and /news the resulting wrapper around the third party 'jsPDF' library has become quite a bit simpler :

 

 

unit PDFDoc;
 
interface
 
uses 
  SmartCL.System, system.types;
 
type
 
  TPDFHandle = THandle;
 
  EPDF = class(EW3Exception);
  JPDF = class external 'jsPDF'
 
    Procedure     Text(Left, Top : integer; text: string); external 'text';
    Procedure     setFont(font: string); external 'setFont';
    Procedure     setFontType(fonttype: string); external 'setFontType';
    Procedure     setFontSize(fontsize: integer); external 'setFontSize';
    Procedure     setTextColor(r,g,b: integer); external 'setTextColor';
    Procedure     rect(left, top, width, height : integer); external 'rect';
    Procedure     output(windowtype: string); external 'output';
 
    Constructor   Create;
    Destructor    Destroy;
  end;
 
Procedure PDFInitialize;overload;
Procedure PDFInitialize(DriverURL:String);overload;
function  PDFReady:Boolean;
 
implementation
 
uses SmartCL.fileutils;
 
 
{$R 'jspdf.js'}
 
var
  __PDFREADY:  Boolean := False;
 
Procedure PDFInitialize(DriverURL:String);overload;
begin
  if not __PDFREADY then
  begin
    try
      writeln("Loading PDF driver");
      TW3Storage.LoadScript(DriverURL,
        procedure ()
        begin
          writeln("PDF Driver loaded and ready");
          w3_setTimeout( procedure ()
            begin
              __PDFREADY := true;
            end, 20);
        end);
    except
      on e: exception do
      Raise EPDF.CreateFmt
      ("Failed to load PDF driver, system threw exception %s with [%s]",
      [e.classname,e.message]);
    end;
  end;
end;
 
Procedure PDFInitialize;
begin
  PDFInitialize("/lib/jspdf.js");
end;
 
function  PDFReady:Boolean;
begin
  result:=__PDFREADY;
end;
 
initialization
begin
  __PDFReady:=False;
  PDFInitialize;
end;
 
end.
 
Link to post
Share on other sites
  • Moderators

Calling code is the same as the previous example.

Just a form with a single button.

 

Note : does not display the generated pdf document within the ide

but does from the local file system (../index.html) or from a server

 

Note2 : you may have to alter the pop-up settings on your browser

 

 

unit Form1;
 
interface
 
uses 
  SmartCL.System, SmartCL.Graphics, SmartCL.Components, SmartCL.Forms, 
  SmartCL.Fonts, SmartCL.Borders, SmartCL.Application, SmartCL.Controls.Button,
  PDFDoc;
 
type
  TForm1 = class(TW3Form)
    procedure W3Button1Click(Sender: TObject);
  private
    {$I 'Form1:intf'}
  protected
    procedure InitializeForm; override;
    procedure InitializeObject; override;
    procedure Resize; override;
    PDF1: JPDF;
  end;
 
implementation
 
{ TForm1 }
 
procedure TForm1.W3Button1Click(Sender: TObject);
begin
  PDF1.setFont("times");
  PDF1.setFontType("normal");
  PDF1.setFontSize(12);
  PDF1.setTextColor(90,90,90);
  PDF1.Text(5,10,'top of the world');
  PDF1.rect(25, 25, 30, 11);
  PDF1.output('dataurlnewwindow');
end;
 
procedure TForm1.InitializeForm;
begin
  inherited;
  // this is a good place to initialize components
  PDF1 := JPDF.Create;
end;
 
procedure TForm1.InitializeObject;
begin
  inherited;
  {$I 'Form1:impl'}
end;
 
procedure TForm1.Resize;
begin
  inherited;
end;
 
initialization
  Forms.RegisterForm({$I %FILE%}, TForm1);
end.
Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...