How do I get a multi-page pdf from a website using jsPDF and HTML2Canvas?

38,577

Solution 1

pdf.addHtml does not work if there are svg images on the web page. I copy the solution here, based on the picture being in a canvas already.

Here are the numbers (paper width and height) that I found to work. It still creates a little overlap part between the pages, but good enough for me. if you can find an official number from jsPDF, use them.

var imgData = canvas.toDataURL('image/png');
var imgWidth = 210; 
var pageHeight = 295;  
var imgHeight = canvas.height * imgWidth / canvas.width;
var heightLeft = imgHeight;
var doc = new jsPDF('p', 'mm');
var position = 0;

doc.addImage(imgData, 'PNG', 0, position, imgWidth, imgHeight);
heightLeft -= pageHeight;

while (heightLeft >= 0) {
  position = heightLeft - imgHeight;
  doc.addPage();
  doc.addImage(imgData, 'PNG', 0, position, imgWidth, imgHeight);
  heightLeft -= pageHeight;
}
doc.save( 'file.pdf');`

Solution 2

you can use page split option of addhtml like this:

var options = {
    background: '#fff',
    pagesplit: true
};

var doc = new jsPDF(orientation, 'mm', pagelayout);
doc.addHTML(source, 0, 0, options, function () {
        doc.save(filename + ".pdf");
        HideLoader();`enter code here`
});

Note: This will break the html on multiple pages but these pages will get stretched. Stretching is a issue in addhtml till now and i am still not able to find on internet how to solve this issue.

Solution 3

I was able to get it done using async functionality.

(async function loop() {
    var pages = [...]
    for (var i = 0; i < pages.length; i++) {
      await new Promise(function(resolve) {
        html2canvas(pages[i], {scale: 1}).then(function(canvas) {

          var img=canvas.toDataURL("image/png");
          doc.addImage(img,'JPEG', 10, 10);
          if ((i+1) == pages.length) {
            doc.save('menu.pdf');
          } else {
            doc.addPage();
          }
          resolve();
        });
      });
    }
})();

Solution 4

This is my aproach, also with jspdf and html2canvas which did a very good job for me:

I am using a wrapper node which holds the content to be converted to pdf:

<div id="main-content">
    <!-- the content goes here -->
</div>
<!-- and a button somewhere in the dom -->
 <a href="javascript:genPDF()">
     <i class="far fa-file-pdf"></i> &nbsp; Download PDF
 </a>

This is the JS integration ( I only checked this versions ) and call:

<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/1.3.3/jspdf.min.js"></script>
<script src="https://html2canvas.hertzen.com/dist/html2canvas.js"></script>
<script type="text/javascript">

    const documentSlug = "some Slug";
    const documentTitle ="Some Title";

    function genPDF(){
        let html2pdf = new pdfView();
        html2pdf.generate();
    }
</script>

And here you can see the pdf generation. I encapsulated the generation part in an extra js object (you need to include this file also):

//assuming jquery is in use

let pdfView = function(){
    //assuming documentSlug is a constant set earlier 
    this.documentSlug = documentSlug;
    //assuming documentTitle is a constant set earlier
    this.documentTitle = documentTitle;

    //in this html node the part which shall be converted to pdf
    this.nodeId = "main-content";
};

pdfView.prototype.generate = function(){
    let self = this;
    this.prepareDocumentToPrint();

    //variables
    let HTML_Width = $("#"+self.nodeId).width();
    let HTML_Height = $("#"+self.nodeId).height();
    let top_left_margin = 15;
    let PDF_Width = HTML_Width+(top_left_margin*2);
    let PDF_Height = (PDF_Width*1.5)+(top_left_margin*2);

    this.pdfWidth = PDF_Width;
    this.pdfHeight = PDF_Height;

    let canvas_image_width = HTML_Width;
    let canvas_image_height = HTML_Height;

    let totalPDFPages = Math.ceil(HTML_Height/PDF_Height)-1;

    html2canvas($("#"+self.nodeId)[0],{allowTaint:true, scale: 2}).then(function(canvas) {
        canvas.getContext('2d');

        //console.log(canvas.height+"  "+canvas.width);

        let imgData = canvas.toDataURL("image/jpeg", 1.0);
        let pdf = new jsPDF('p', 'mm',  [PDF_Width, PDF_Height]);
        pdf.addImage(imgData, 'JPG', top_left_margin, top_left_margin,canvas_image_width,canvas_image_height);

        pdf = self.addPdfHeader(pdf);
        pdf = self.addPdfFooter(pdf, 1);

        for (let i = 1; i <= totalPDFPages; i++) {
            pdf.addPage(PDF_Width, PDF_Height);

            pdf.addImage(imgData, 'JPG', top_left_margin, -(PDF_Height*i)+(top_left_margin*4) + top_left_margin,canvas_image_width,canvas_image_height);

            pdf = self.addPdfHeader(pdf);
            pdf = self.addPdfFooter(pdf, (i+1));
        }

        pdf.save(self.documentSlug+".pdf");

        self.resetDocumentAfterPrint();

    });
};

//this one sets a fixed viewport, to be able to 
//get the same pdf also on a mobile device. I also
//have a css file, which holds the rules for the 
//document if the body has the .printview class added
pdfView.prototype.prepareDocumentToPrint = function(){
    let viewport = document.querySelector("meta[name=viewport]");
    viewport.setAttribute('content', 'width=1280, initial-scale=0.1');
    $('body').addClass("printview");
    window.scrollTo(0,0);
};

pdfView.prototype.addPdfHeader = function(pdf){

    pdf.setFillColor(255,255,255);
    pdf.rect(0, 0, this.pdfWidth, 40, "F");

    pdf.setFontSize(40);
    pdf.text("Document: "+this.documentTitle+" - Example Lmtd. Inc. (Whatsoever)", 50, 22);

    return pdf;
};

pdfView.prototype.addPdfFooter = function(pdf, pageNumber){

    pdf.setFillColor(255,255,255);
    pdf.rect(0, pdf.internal.pageSize.height - 40, this.pdfWidth, this.pdfHeight, "F");

    pdf.setFontSize(40);
    pdf.text("Seite "+pageNumber, 50, pdf.internal.pageSize.height - 20);
    return pdf;
};

//and this one resets the doc back to normal
pdfView.prototype.resetDocumentAfterPrint = function(){
    $('body').removeClass("printview");
    let viewport = document.querySelector("meta[name=viewport]");
    viewport.setAttribute('content', 'device-width, initial-scale=1, shrink-to-fit=no');
};

my css example for the .printview case:

body.printview #header,
body.printview footer
{
    display: none !important;
}

body.printview{
    margin-top: 0px !important;
}

body.printview #main-content{
    margin-top: 0px !important;
}

body.printview h1{
    margin-top: 40px !important;
}

enjoy

Contributions to @Code Spy for the Link you provided, which was the basis for this aporach.

Share:
38,577

Related videos on Youtube

Tom Nolan
Author by

Tom Nolan

Updated on July 09, 2022

Comments

  • Tom Nolan
    Tom Nolan almost 2 years

    I have a script that uses HTML2Canvas to take a screenshot of a div within the page, and then converts it to a pdf using jsPDF.

    The problem is the pdf that is generated is only one page, and the screenshot requires more than one page in some instances. For example the screenshot is larger than 8.5x11. The width is fine, but I need it to create more than one page to fit the entire screenshot.

    Here is my script:

    var pdf = new jsPDF('portrait', 'pt', 'letter');
    $('.export').click(function() {
          pdf.addHTML($('.profile-expand')[0], function () {
               pdf.save('bfc-schedule.pdf');
          });
     });
    

    Any ideas how I could modify that to allow for multiple pages?

  • L84
    L84 over 8 years
    There is an issue on jsPDF's Github that discusses the stretching problems and blurry problems. They do provide some workarounds.
  • Dynamic
    Dynamic about 7 years
    It stretches the HTML though?
  • Thien Long
    Thien Long about 7 years
    With the latest jsPDF, it works fine for me with pagesplit option = true.
  • avoliva
    avoliva about 5 years
    did you even try this out?
  • avoliva
    avoliva almost 5 years
    It definitely doesn't work because promises are asynchronous and this is in a for loop. I tried it out and doesn't work.
  • Jessé Filho
    Jessé Filho almost 5 years
    it depends when and where you want generate your file... But it still works on my projects.
  • helle
    helle over 4 years
    This does not work, I get only the first page. But I guess the problem lies around the html2canvas function...
  • Manmeet Khurana
    Manmeet Khurana over 3 years
    How to create the dynamic width and height of the page ?
  • Paul Sender
    Paul Sender over 3 years
    var height = Math.max(body.scrollHeight, body.offsetHeight, html.clientHeight, html.scrollHeight, html.offsetHeight)
  • Paul Sender
    Paul Sender over 3 years
    document.body.style.height = ${height}px
  • Paul Sender
    Paul Sender over 3 years
    @ManmeetKhurana this is how I get the full height for any page. 1) calculate the height 2) set the height. html2canvas will then set it's 'canvas' height to that value. Can be done with width as well, simply change Height to Width.
  • Sanidhya Singh
    Sanidhya Singh over 3 years
    The code was breaking for me when the number of pages in the PDF exceeded 2. I used -pageHeight * counter for position (in the while loop), that fixed it for me
  • WEshruth
    WEshruth almost 3 years
    Thank you @helle for this line: let pdf = new jsPDF('p', 'mm', [PDF_Width, PDF_Height]); Saved my day.
  • Iswar
    Iswar over 2 years
    I was trying since not a long ago but unable to meet my requirement. This helps me to find the actual height of a page. Thanks a lot