30

I have a component (Angular 6) which is an aggregation of several components. This produces a long HTML (I am using Bootstrap 4). Now I want to convert this HTML to PDF. I have searched far and wide and found many solutions that work on jsPDF. The requirement is to produce a crisp layout as it appears in the HTML (so that users can select, copy, etc). But the solutions I found either try to add lines of text manually, which is impossible in my scenario; or they convert (rasterize?) the HTML to image format. Also, I want to preserve the formatting and fonts and styling of my HTML.

So far I have tried: this and this.

Vivekanand P V
  • 861
  • 3
  • 13
  • 27

7 Answers7

35

Best possible solution I could come up with till now.

You would have to install the below packages from npm

html2canvas

jspdf

import * as jsPDF from 'jspdf';
import html2canvas from 'html2canvas';

htmltoPDF()
{
    // parentdiv is the html element which has to be converted to PDF
    html2canvas(document.querySelector("#parentdiv")).then(canvas => {

      var pdf = new jsPDF('p', 'pt', [canvas.width, canvas.height]);

      var imgData  = canvas.toDataURL("image/jpeg", 1.0);
      pdf.addImage(imgData,0,0,canvas.width, canvas.height);
      pdf.save('converteddoc.pdf');

  });

}

UPDATE:

Came up with another solution. I wasn't able to break it down into A4 size pages, but I was able to make a single pdf file.

Packages:

dom-to-image

jspdf

import domtoimage from 'dom-to-image';
import * as jsPDF from 'jspdf';



            downloadPDF()
            {

              var node = document.getElementById('parentdiv');

              var img;
              var filename;
              var newImage;


              domtoimage.toPng(node, { bgcolor: '#fff' })

                .then(function(dataUrl) {

                  img = new Image();
                  img.src = dataUrl;
                  newImage = img.src;

                  img.onload = function(){

                  var pdfWidth = img.width;
                  var pdfHeight = img.height;

                    // FileSaver.saveAs(dataUrl, 'my-pdfimage.png'); // Save as Image

                    var doc;

                    if(pdfWidth > pdfHeight)
                    {
                      doc = new jsPDF('l', 'px', [pdfWidth , pdfHeight]);
                    }
                    else
                    {
                      doc = new jsPDF('p', 'px', [pdfWidth , pdfHeight]);
                    }


                    var width = doc.internal.pageSize.getWidth();
                    var height = doc.internal.pageSize.getHeight();


                    doc.addImage(newImage, 'PNG',  10, 10, width, height);
                    filename = 'mypdf_' + '.pdf';
                    doc.save(filename);

                  };


                })
                .catch(function(error) {

                 // Error Handling

                });



            }
Anirudh
  • 2,767
  • 5
  • 69
  • 119
  • 1
    This saves the PDF directly to the browser window. How would you assign this to an Angular6 Observable, as the html2canvas method returns its own promise method? – Sean Halls Feb 05 '19 at 22:48
10

The following code was used and worked for my project

Step 1 : Run following commands to install npm packages

> npm install jspdf
> npm install html2canvas

Step 2: Import installed packages in app.components.ts. I haven't imported those packages in constructor()

> import * as jspdf from 'jspdf';
> import html2canvas from 'html2canvas';

Step 3: Give an id for the HTML div that has to be exported as PDF. Add a button that activates the function too.

<div id="MyDIv" style="margin-left: 45px;" class="main-container">

</div>
<div class="icon_image " title="Share As PDF" (click)="exportAsPDF('MyDIv');"><img src="assets/img/pdf.png"></div>

Step 4 : Write the code for generating PDF as follows

exportAsPDF(div_id)
  {
    let data = document.getElementById(div_id);  
    html2canvas(data).then(canvas => {
      const contentDataURL = canvas.toDataURL('image/png')  
      let pdf = new jspdf('l', 'cm', 'a4'); //Generates PDF in landscape mode
      // let pdf = new jspdf('p', 'cm', 'a4'); Generates PDF in portrait mode
      pdf.addImage(contentDataURL, 'PNG', 0, 0, 29.7, 21.0);  
      pdf.save('Filename.pdf');   
    }); 
  }
Parameshwar
  • 856
  • 8
  • 16
Basil
  • 1,664
  • 13
  • 25
  • This code expects 0 arguments from the html file. Or is it that you omitted the parameter in your .ts/.js file? – Brio Tech Sep 18 '20 at 09:13
  • Also, what is the format with regards to the code `pdf.addImage(contentDataURL, 'PNG', 0, 0, 29.7, 21.0)`? – Brio Tech Sep 18 '20 at 09:23
3

With some effort, the best solution my team and I came up with for our own Angular HTML to PDF problems involved sending the raw HTML to our Java API and then utilizing wkhtmltopdf (https://github.com/wkhtmltopdf/wkhtmltopdf) with the Java wrapper (https://github.com/jhonnymertz/java-wkhtmltopdf-wrapper). Overcoming a few gotchas (like making sure the server has the correctly installed fonts; everything is properly encoded; figuring out page breaks and making them work), we were able to reproduce our page exactly and use our Angular HTML as the universal template (which changes based on different scenarios). It is not an easy road, but it is one that produces great results in an enterprise-level production environment once you get it figured out.

It should be noted that we also tried jspdf (and other libraries) and came to similar conclusions as yourself: they just didn't do what we needed them to do. Hopefully this helps.

MapLion
  • 1,041
  • 1
  • 12
  • 38
2

I was having issues with html2canvas and therefore had to use html-to-image. I don't know why but html2canvas was rendering a blank image no matter what I tried. html-to-image although gave me proper rendering with ease.

Use as:

var node = <HTMLElement>document.getElementById('export');

htmlToImage.toPng(node)
           .then(function (dataUrl) {
                var img = new Image();
                img.src = dataUrl;

                var doc = new jsPDF('p', 'mm', 'a4');
                const bufferX = 5;
                const bufferY = 5;
                const imgProps = (<any>doc).getImageProperties(img);
                const pdfWidth = doc.internal.pageSize.getWidth() - 2 * bufferX;
                const pdfHeight = (imgProps.height * pdfWidth) / imgProps.width;
                doc.addImage(img, 'PNG', bufferX, bufferY, pdfWidth, pdfHeight, undefined, 'FAST');
                doc.save('Bill_'+new Date().toDateString()); 
            })
            .catch(function (error) {
                console.error('oops, something went wrong!', error);
            });
Josef
  • 2,869
  • 2
  • 22
  • 23
Ashique Razak
  • 487
  • 3
  • 8
1

Firstly install the following: 1) npm install jspdf 2) npm install html2canvas

Later import and use the following code

 public captureScreen() {
      const data = document.getElementById('invoice');
      html2canvas(data).then(canvas => {
      const imgWidth = 208;
      const pageHeight = 295;
      const imgHeight = canvas.height * imgWidth / canvas.width;
      const heightLeft = imgHeight;
      const contentDataURL = canvas.toDataURL('image/png');
      const pdf = new jspdf('p', 'mm', 'a4'); 
      const position = 0;
      pdf.addImage(contentDataURL, 'PNG', 0, position, imgWidth, imgHeight);
      pdf.save('invoice.pdf'); 
      });
      }
Vaishnavi
  • 419
  • 5
  • 9
0

Try to use a backend service for this such as Thymeleaf or itextpdf with Spring Boot.

dasunse
  • 2,839
  • 1
  • 14
  • 32
0

Step 1: Install required packages

npm install jspdf jspdf-autotable

Step 2: Import the installed packages to use

 import jsPDF from 'jspdf';
 import 'jspdf-autotable';

Step 3: If your data has images, convert images to Base64

tabledata = [{
    img: 'https://i.picsum.photos/id/258/536/354.jpg',
    name: 'Joseph',
    domain: 'Angular 9'
  },
  {
    img: 'https://i.picsum.photos/id/258/536/354.jpg',
    name: 'Vani',
    domain: 'Angular 8'
  }, {
    img: 'https://i.picsum.photos/id/258/536/354.jpg',
    name: 'Raj',
    domain: 'React.js'
  },
];

The above is data with images converted to base64. The convert function looks like:

convertImagetoBase64(url, callback) {
    const xhr = new XMLHttpRequest();
    xhr.onload = () => {
      const reader = new FileReader();
      reader.onloadend = () => {
        callback(reader.result);
      };
      reader.readAsDataURL(xhr.response);
    };
    xhr.open('GET', url);
    xhr.responseType = 'blob';
    xhr.send();
}

Let's convert the image URL to base64 before we bind to the table in HTML.

this.tabledata.forEach(elem => {
  this.convertImagetoBase64(elem.img, (b64Img) => {
    elem.img = b64Img;
});

Now bind the data to the table. If you don't want to display the table in the front end, just style to display none as in the below code:

<table id="imgTable" style=”display:none”>
<thead>
    <tr><th>Name</th>
    <th>Domain</th>
    <th>Image</th>
    </tr>
</thead>
<tbody>
    <tr *ngFor="let data of tabledata">
        <td>{{data.name}}</td>
        <td>{{data.domain}}</td>
        <td><img src="{{data.img}}" width="100" height="100" /></td>
    </tr>

</tbody>
</table>
<button (click)="generatePdf()">Generate Pdf</button>

The Generate Pdf function looks like:

app.component.ts

generatePdf(){
 doc.autoTable({
  html: '#imgTable',
  bodyStyles: { minCellHeight: 20 },
  theme: 'grid',
  styles: { valign: 'middle', overflow: 'linebreak', halign: 'center', minCellHeight: 21 },
  pageBreak: 'avoid',
  columnStyles: {
    2: { cellWidth: 22, minCellHeight: 22 },
  },
  headStyles: { fillColor: '#f2f2f2', textColor: '#000', fontStyle: 'bold', lineWidth: 0.5, lineColor: '#ccc' },
  didDrawCell: (data) => {
    if (data.column.index === 2 && data.cell.section === 'body') {
      const td = data.cell.raw;
      const img = td.getElementsByTagName('img')[0];
      // let dim = data.cell.height - data.cell.padding('vertical');
      // let textPos = data.cell.textPos;
      doc.addImage(img.src, data.cell.x + 1, data.cell.y + 1, 19, 19);

    }
  }
});
 doc.save('yourpdfname.pdf');
}

Example pdf generation looks like:

For more Pdf format without table visit helperscript.com

tripleee
  • 175,061
  • 34
  • 275
  • 318
Jasim Jas
  • 11
  • 1