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

6

19

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?

Estovers answered 20/11, 2014 at 17:21 Comment(0)
M
48

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');`
Marcelmarcela answered 5/8, 2016 at 12:9 Comment(6)
How to create the dynamic width and height of the page ?Sucrose
var height = Math.max(body.scrollHeight, body.offsetHeight, html.clientHeight, html.scrollHeight, html.offsetHeight)Ary
document.body.style.height = ${height}pxAry
@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.Ary
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 meScientism
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 lotAnemograph
L
5

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.

Lavonda answered 23/9, 2015 at 9:56 Comment(3)
There is an issue on jsPDF's Github that discusses the stretching problems and blurry problems. They do provide some workarounds.Vend
It stretches the HTML though?Betsey
With the latest jsPDF, it works fine for me with pagesplit option = true.Shandy
G
1

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();
        });
      });
    }
})();
Gospel answered 25/6, 2019 at 15:13 Comment(0)
C
1

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.

Curr answered 23/10, 2019 at 16:20 Comment(1)
Thank you @Curr for this line: let pdf = new jsPDF('p', 'mm', [PDF_Width, PDF_Height]); Saved my day.Merrilee
K
0

Found out the solution to this,putting a rectangle as border for each pdf page and also starting the second page and other pages from a litte down by making difference in pageHeight,hope this will resolve for few...

html2canvas(myCanvas).then(function (canvas) {

      var imgWidth = 210;
      var pageHeight = 290;
      var imgHeight = canvas.height * imgWidth / canvas.width;
      var heightLeft = imgHeight;


      var doc = new jsPDF('p', 'mm');
      var position = 0;
      var pageData = canvas.toDataURL('image/jpeg', 1.0);
      var imgData = encodeURIComponent(pageData);
      doc.addImage(imgData, 'PNG', 0, position, imgWidth, imgHeight);
      doc.setLineWidth(5);
      doc.setDrawColor(255, 255, 255);
      doc.rect(0, 0, 210, 295);
      heightLeft -= pageHeight;

      while (heightLeft >= 0) {
        position = heightLeft - imgHeight;
        doc.addPage();
        doc.addImage(imgData, 'PNG', 0, position, imgWidth, imgHeight);
        doc.setLineWidth(5);
        doc.setDrawColor(255, 255, 255);
        doc.rect(0, 0, 210, 295);
        heightLeft -= pageHeight;
      }
      doc.save('file.pdf');

    });
  };
Kiely answered 8/9, 2021 at 12:46 Comment(0)
C
0

Do not try this combo, it's absolutely impossible to make it work for multiple pages. I used react-to-pdf and it worked out of the box.

Campo answered 3/7 at 13:48 Comment(1)
Your answer could be improved with additional supporting information. Please edit to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers in the help center.Uniformity

© 2022 - 2024 — McMap. All rights reserved.