ChatGPT解决这个技术问题 Extra ChatGPT

Generate pdf from HTML in div using Javascript

I have the following html code:

<!DOCTYPE html>
<html>
    <body>
        <p>don't print this to pdf</p>
        <div id="pdf">
            <p><font size="3" color="red">print this to pdf</font></p>
        </div>
    </body>
</html>

All I want to do is to print to pdf whatever is found in the div with an id of "pdf". This must be done using JavaScript. The "pdf" document should then be automatically downloaded with a filename of "foobar.pdf"

I've been using jspdf to do this, but the only function it has is "text" which accepts only string values. I want to submit HTML to jspdf, not text.

As mentioned above I do not want to use the "text" function. I want to give it HTML. Your link only deals with plain text and not html
jsPDF does have a fromHTML function; see the "HTML Renderer" example at: mrrio.github.io/jsPDF

M
Mosh Feu

jsPDF is able to use plugins. In order to enable it to print HTML, you have to include certain plugins and therefore have to do the following:

Go to https://github.com/MrRio/jsPDF and download the latest Version. Include the following Scripts in your project: jspdf.js jspdf.plugin.from_html.js jspdf.plugin.split_text_to_size.js jspdf.plugin.standard_fonts_metrics.js

If you want to ignore certain elements, you have to mark them with an ID, which you can then ignore in a special element handler of jsPDF. Therefore your HTML should look like this:

<!DOCTYPE html>
<html>
  <body>
    <p id="ignorePDF">don't print this to pdf</p>
    <div>
      <p><font size="3" color="red">print this to pdf</font></p>
    </div>
  </body>
</html>

Then you use the following JavaScript code to open the created PDF in a PopUp:

var doc = new jsPDF();          
var elementHandler = {
  '#ignorePDF': function (element, renderer) {
    return true;
  }
};
var source = window.document.getElementsByTagName("body")[0];
doc.fromHTML(
    source,
    15,
    15,
    {
      'width': 180,'elementHandlers': elementHandler
    });

doc.output("dataurlnewwindow");

For me this created a nice and tidy PDF that only included the line 'print this to pdf'.

Please note that the special element handlers only deal with IDs in the current version, which is also stated in a GitHub Issue. It states:

Because the matching is done against every element in the node tree, my desire was to make it as fast as possible. In that case, it meant "Only element IDs are matched" The element IDs are still done in jQuery style "#id", but it does not mean that all jQuery selectors are supported.

Therefore replacing '#ignorePDF' with class selectors like '.ignorePDF' did not work for me. Instead you will have to add the same handler for each and every element, which you want to ignore like:

var elementHandler = {
  '#ignoreElement': function (element, renderer) {
    return true;
  },
  '#anotherIdToBeIgnored': function (element, renderer) {
    return true;
  }
};

From the examples it is also stated that it is possible to select tags like 'a' or 'li'. That might be a little bit to unrestrictive for the most usecases though:

We support special element handlers. Register them with jQuery-style ID selector for either ID or node name. ("#iAmID", "div", "span" etc.) There is no support for any other type of selectors (class, of compound) at this time.

One very important thing to add is that you lose all your style information (CSS). Luckily jsPDF is able to nicely format h1, h2, h3 etc., which was enough for my purposes. Additionally it will only print text within text nodes, which means that it will not print the values of textareas and the like. Example:

<body>
  <ul>
    <!-- This is printed as the element contains a textnode -->        
    <li>Print me!</li>
  </ul>
  <div>
    <!-- This is not printed because jsPDF doesn't deal with the value attribute -->
    <input type="textarea" value="Please print me, too!">
  </div>
</body>

I'm going to guess the element handler can be a class? That would be much more semantically in line with HTML5 standards. An ID not only carries too much speificity weight in CSS, it also has to be unique.
@snrlx I get blank pdf and this error: renderer.pdf.sHashCode is not a function
"If you want to ignore certain elements, you have to mark them with an ID" - a wonderful library, ruined by that inverted reqiurement. The OP wanted to print a single <div>, which could be one of hundreds - so he has to mark all the unwanted DOM elements??
This plugin might be good, but it's poorly documented. Most of the examples I see in stackoverflow don't even work.
If I'm not wrong doc.fromHTML is no longer supported
A
AGuyCalledGerald

This is the simple solution. This works for me. You can use the javascript print concept and simple save this as pdf.

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title></title>
    <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>
    <script type="text/javascript">
        $("#btnPrint").live("click", function () {
            var divContents = $("#dvContainer").html();
            var printWindow = window.open('', '', 'height=400,width=800');
            printWindow.document.write('<html><head><title>DIV Contents</title>');
            printWindow.document.write('</head><body >');
            printWindow.document.write(divContents);
            printWindow.document.write('</body></html>');
            printWindow.document.close();
            printWindow.print();
        });
    </script>
</head>
<body>
    <form id="form1">
    <div id="dvContainer">
        This content needs to be printed.
    </div>
    <input type="button" value="Print Div Contents" id="btnPrint" />
    </form>
</body>
</html>

"and simple save this as pdf" - I missed that part. How do you do it?
This worked for me, for solving the problem of CSS styling, I created another css file called printPDF.css and added using link tag just like this in above example: printWindow.document.write('DIV Contents'); printWindow.document.write(''); printWindow.document.write('');
Some comments: 1) You don't need an especific stylesheet for printing. In your current stylesheet, do something like: @media print { .pageBreak { page-break-before: always; } .labelPdf { font-weight: bold; font-size: 20px; } .noPrintPdf { display: none; } } and use these classes as your needs. 2) .live("click", ...) doesn't work to me, so I use .on("click", ...) instead.
@DavidsonLima this code creates new window which will not see old window css. That's why it's "ignoring" css, it's not ignoring, just in new window it's not loaded. Just load it in head and it will be rendering.
Just in case any one is trying to make this happen with Angular, please put your CSS file in the assets folder.
L
Lukas Liesis

No depenencies, pure JS

To add CSS or images - do not use relative URLs, use full URLs http://...domain.../path.css or so. It creates separate HTML document and it has no context of main thing.

you can also embed images as base64

This served me for years now:

export default function printDiv({divId, title}) {
  let mywindow = window.open('', 'PRINT', 'height=650,width=900,top=100,left=150');

  mywindow.document.write(`<html><head><title>${title}</title>`);
  mywindow.document.write('</head><body >');
  mywindow.document.write(document.getElementById(divId).innerHTML);
  mywindow.document.write('</body></html>');

  mywindow.document.close(); // necessary for IE >= 10
  mywindow.focus(); // necessary for IE >= 10*/

  mywindow.print();
  mywindow.close();

  return true;
}

Of course this will open print dialog and user will have to know she/he can select print to pdf option, to get pdf. There may be printer pre-selected and if user confirms may get this document actually printed. To avoid such situation and to provide PDF without any extras, you need to make PDF file. Probably on the server side. You could have tiny html page with invoice only and convert it to PDF file with headless chrome. It's super easy with puppeteer. No need to install/config chrome, just install npm package puppeteer (managed by chrome team) and run it. Keep in mind this will actually launch real chrome just w/o GUI, so you need to have some RAM & CPU for this. Most servers will be fine with low enough traffic. Here is code sample but this must run on the BACKEND. Nodejs. Also it's slow call, it's resources intensive call. You should run it NOT on api call but e.g. after invoice was created - create pdf for it and store, if pdf was not generated yet, just show message to try again in couple minutes.

const puppeteer = require('puppeteer');
(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  await page.goto('https://your-domain.com/path-to-invoice', {
    waitUntil: 'networkidle2',
  });
  await page.pdf({ path: 'invoice-file-path.pdf', format: 'a4' });

  await browser.close();
})();

Learn more here: https://pptr.dev/


The problem with this is the pdf will not have any css effects in it.
@ShadabFaiz It will but it may not be the same as main window. You can still add custom css tho this html too.
Does not render images, though.
I love this! Some tweaks here and there and it looks good. And one small thing don't remove the extra spacing at <body > it needs this :P
@ДаянаДимитрова it will print HTML from URL, if your values are in html, those will be printed. If you use PHP, you will need to render on server side. This code will literally open browser, navigate to url and print. You can read puppeteer API docs, it is possible to fill up the form or execute any JS on page after load, so maybe that's what you are looking for. But probably for your context it will be easier to just build dedicated endpoint with PHP. For 1st example it will take HTML out of page, so you could render hidden div with values from inputs inside HTML and use that div for print
W
Wael Mahmoud

if you need to downloadable pdf of a specific page just add button like this

<h4 onclick="window.print();"> Print </h4>

use window.print() to print your all page not just a div


Just a simple addition to it, if you wanna create a downloadable pdf of a iframe, then use the developer console: document.querySelector("#myIframe").contentWindow.print()
It doesn't print multipage HTML. only single page pdf
This will not work on chrome on Android
c
cnotethegr8

You can use autoPrint() and set output to 'dataurlnewwindow' like this:

function printPDF() {
    var printDoc = new jsPDF();
    printDoc.fromHTML($('#pdf').get(0), 10, 10, {'width': 180});
    printDoc.autoPrint();
    printDoc.output("dataurlnewwindow"); // this opens a new popup,  after this the PDF opens the print window view but there are browser inconsistencies with how this is handled
}

I'm curious, has this ever worked for anyone other than OP ? From reading the code, I seem to understand that it would only work with elements that have an ID. It's probably a bit more complicated than that, anyhow I have no idea how to make this work.
From what I observed, very ironically, fromHTML only works if the element sent as a parameter doesn't contain any HTML: only plain text supported. Kinda kills the purpose of the whole thing imo.
Worked perfectly for me. The element you want to pass in is not neccessarily required to have an ID. That's just the way that the replicant found the node he wanted to pass in. Additionally this solution also works without 'printDoc.autoPrint()'. If you want to leave this specific line in the code, then you are required to include the autoPrint-Plugin.
B
BaptWaels

As mentioned, you should use jsPDF and html2canvas. I've also found a function inside issues of jsPDF which splits automatically your pdf into multiple pages (sources)

function makePDF() {

    var quotes = document.getElementById('container-fluid');

    html2canvas(quotes, {
        onrendered: function(canvas) {

        //! MAKE YOUR PDF
        var pdf = new jsPDF('p', 'pt', 'letter');

        for (var i = 0; i <= quotes.clientHeight/980; i++) {
            //! This is all just html2canvas stuff
            var srcImg  = canvas;
            var sX      = 0;
            var sY      = 980*i; // start 980 pixels down for every new page
            var sWidth  = 900;
            var sHeight = 980;
            var dX      = 0;
            var dY      = 0;
            var dWidth  = 900;
            var dHeight = 980;

            window.onePageCanvas = document.createElement("canvas");
            onePageCanvas.setAttribute('width', 900);
            onePageCanvas.setAttribute('height', 980);
            var ctx = onePageCanvas.getContext('2d');
            // details on this usage of this function: 
            // https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Using_images#Slicing
            ctx.drawImage(srcImg,sX,sY,sWidth,sHeight,dX,dY,dWidth,dHeight);

            // document.body.appendChild(canvas);
            var canvasDataURL = onePageCanvas.toDataURL("image/png", 1.0);

            var width         = onePageCanvas.width;
            var height        = onePageCanvas.clientHeight;

            //! If we're on anything other than the first page,
            // add another page
            if (i > 0) {
                pdf.addPage(612, 791); //8.5" x 11" in pts (in*72)
            }
            //! now we declare that we're working on that page
            pdf.setPage(i+1);
            //! now we add content to that page!
            pdf.addImage(canvasDataURL, 'PNG', 20, 40, (width*.62), (height*.62));

        }
        //! after the for loop is finished running, we save the pdf.
        pdf.save('test.pdf');
    }
  });
}

it doesn't convert images
thanks for the answer, could you give any hint about how to put it to A4 page format?
This does not really make a good vector pdf, it makes a lot of bitmaps with canvas, and stacks them as images. The result has many disadvantages - large, low quality, cannot copy&paste from the PDF, etc...
jsfiddle.net/jfr34mgL I wrote this example to cover images on multiple pages
R
Reegan Miranda

i use jspdf and html2canvas for css rendering and i export content of specific div as this is my code

$(document).ready(function () {
    let btn=$('#c-oreder-preview');
    btn.text('download');
    btn.on('click',()=> {

        $('#c-invoice').modal('show');
        setTimeout(function () {
            html2canvas(document.querySelector("#c-print")).then(canvas => {
                //$("#previewBeforeDownload").html(canvas);
                var imgData = canvas.toDataURL("image/jpeg",1);
                var pdf = new jsPDF("p", "mm", "a4");
                var pageWidth = pdf.internal.pageSize.getWidth();
                var pageHeight = pdf.internal.pageSize.getHeight();
                var imageWidth = canvas.width;
                var imageHeight = canvas.height;

                var ratio = imageWidth/imageHeight >= pageWidth/pageHeight ? pageWidth/imageWidth : pageHeight/imageHeight;
                //pdf = new jsPDF(this.state.orientation, undefined, format);
                pdf.addImage(imgData, 'JPEG', 0, 0, imageWidth * ratio, imageHeight * ratio);
                pdf.save("invoice.pdf");
                //$("#previewBeforeDownload").hide();
                $('#c-invoice').modal('hide');
            });
        },500);

        });
});

this working but it converting the content to image
Also, how to set page break so that the content/image will print on new page if it doesn't fit on current page?
for contents converted to image, name each div in a sequence,
and so, and when converting them to PDF, refer to them via array.... something like this: html2canvas($("#div_pdf"+i)[0]).then(function (canvas) { variable i can be the result of a class in common: var clases = document.querySelectorAll(".export_pdf"); var len = clases.length;
R
Rohit Parte

One way is to use window.print() function. Which does not require any library

Pros

1.No external library require.

2.We can print only selected parts of body also.

3.No css conflicts and js issues.

4.Core html/js functionality

---Simply add below code

CSS to

@media print {
        body * {
            visibility: hidden; // part to hide at the time of print
            -webkit-print-color-adjust: exact !important; // not necessary use         
               if colors not visible
        }

        #printBtn {
            visibility: hidden !important; // To hide 
        }

        #page-wrapper * {
            visibility: visible; // Print only required part
            text-align: left;
            -webkit-print-color-adjust: exact !important;
        }
    }

JS code - Call bewlow function on btn click

$scope.printWindow = function () {
  window.print()
}

Note: Use !important in every css object

Example -

.legend  {
  background: #9DD2E2 !important;
}

There are problems with Print function of the browsers. Users usually have default options selected for the Print view (margins, page size and more). Therefore, it is very difficult to produce required PDF with required styling without training user, which is way more difficult and approx impossible...
i
ilmiacs

Use pdfMake.js and this Gist.

(I found the Gist here along with a link to the package html-to-pdfmake, which I end up not using for now.)

After npm install pdfmake and saving the Gist in htmlToPdf.js I use it like this:

const pdfMakeX = require('pdfmake/build/pdfmake.js');
const pdfFontsX = require('pdfmake-unicode/dist/pdfmake-unicode.js');
pdfMakeX.vfs = pdfFontsX.pdfMake.vfs;
import * as pdfMake from 'pdfmake/build/pdfmake';
import htmlToPdf from './htmlToPdf.js';

var docDef = htmlToPdf(`<b>Sample</b>`);
pdfMake.createPdf({content:docDef}).download('sample.pdf');

Remarks:

My use case is to create the relevant html from a markdown document (with markdown-it) and subsequently generating the pdf, and uploading its binary content (which I can get with pdfMake's getBuffer() function), all from the browser. The generated pdf turns out to be nicer for this kind of html than with other solutions I have tried.

I am dissatisfied with the results I got from jsPDF.fromHTML() suggested in the accepted answer, as that solution gets easily confused by special characters in my HTML that apparently are interpreted as a sort of markup and totally mess up the resulting PDF.

Using canvas based solutions (like the deprecated jsPDF.from_html() function, not to be confused with the one from the accepted answer) is not an option for me since I want the text in the generated PDF to be pasteable, whereas canvas based solutions generate bitmap based PDFs.

Direct markdown to pdf converters like md-to-pdf are server side only and would not work for me.

Using the printing functionality of the browser would not work for me as I do not want to display the generated PDF but upload its binary content.


If I read the code correctly, this does not support CSS border styles (e.g. on tables), correct?
Off topic, I use pdfmake to create pdf with the content not from html. My question is : how to provide our own specific file name instead of random file name resulted when using its method : pdfMake.createPdf(docDefinition).open() ?
Now on-topic, the gist you mentioned does not exist. What is the problem with html-to-pdfmake which you said you ended up not using it ? I saw in the github it was still maintained recently.
W
Wasskinny

I was able to get jsPDF to print dynamically created tables from a div.

$(document).ready(function() {

        $("#pdfDiv").click(function() {

    var pdf = new jsPDF('p','pt','letter');
    var specialElementHandlers = {
    '#rentalListCan': function (element, renderer) {
        return true;
        }
    };

    pdf.addHTML($('#rentalListCan').first(), function() {
        pdf.save("caravan.pdf");
    });
    });
});

Works great with Chrome and Firefox... formatting is all blown up in IE.

I also included these:

<script src="js/jspdf.js"></script>
    <script src="js/jspdf.plugin.from_html.js"></script>
    <script src="js/jspdf.plugin.addhtml.js"></script>
    <script src="//mrrio.github.io/jsPDF/dist/jspdf.debug.js"></script>
    <script src="http://html2canvas.hertzen.com/build/html2canvas.js"></script>
    <script type="text/javascript" src="./libs/FileSaver.js/FileSaver.js"></script>
    <script type="text/javascript" src="./libs/Blob.js/Blob.js"></script>
    <script type="text/javascript" src="./libs/deflate.js"></script>
    <script type="text/javascript" src="./libs/adler32cs.js/adler32cs.js"></script>

    <script type="text/javascript" src="js/jspdf.plugin.addimage.js"></script>
    <script type="text/javascript" src="js/jspdf.plugin.sillysvgrenderer.js"></script>
    <script type="text/javascript" src="js/jspdf.plugin.split_text_to_size.js"></script>
    <script type="text/javascript" src="js/jspdf.plugin.standard_fonts_metrics.js"></script>

can u pls post example how u pass html
but your code is <script src="html2canvas.hertzen.com/build/html2canvas.js"></…> which means it needs internet ?
@Erum you can download the File.
V
Vladimir Georgiev

If you want to export a table, you can take a look at this export sample provided by the Shield UI Grid widget.

It is done by extending the configuration like this:

...
exportOptions: {
    proxy: "/filesaver/save",
    pdf: {
        fileName: "shieldui-export",
        author: "John Smith",
        dataSource: {
            data: gridData
        },
        readDataSource: true,
        header: {
            cells: [
                { field: "id", title: "ID", width: 50 },
                { field: "name", title: "Person Name", width: 100 },
                { field: "company", title: "Company Name", width: 100 },
                { field: "email", title: "Email Address" }
            ]
        }
    }
}
...

The "Export to PDF" functionality resulted in an empty PDF document.
Probably wrong configuration on your end... If you want, post a question with the shieldui tag
R
Ribaz

This example works great.

<button onclick="genPDF()">Generate PDF</button>

<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/1.5.3/jspdf.min.js"></script>
<script>
    function genPDF() {
        var doc = new jsPDF();
        doc.text(20, 20, 'Hello world!');
        doc.text(20, 30, 'This is client-side Javascript, pumping out a PDF.');
        doc.addPage();
        doc.text(20, 20, 'Do you like that?');
    
        doc.save('Test.pdf');
    }
</script>

Though this answer introduces a js to pdf library, The question is to create pdf from the contents of an HTML element and not manually creating the pdf's structure with code.
R
Ray Foss

Dissatisfied with the rendering of html2canvas and lack of modern CSS3/JS and print specific CSS support of pdfMake's outdated version of WebKit...

Here's a theoretical solution, it's headless and can render pages faithfully, supports page breaks, margins, different page sizes and can be automated. You can even render WebGl to PDF.

Chrome has a devtools protocol... which has a printtoPDF function Excerpt: https://gitlab.com/-/snippets/new Official 20k-SLOC spec: https://github.com/ChromeDevTools/devtools-protocol/blob/master/json/browser_protocol.json

Excerpt: https://gitlab.com/-/snippets/new

Official 20k-SLOC spec: https://github.com/ChromeDevTools/devtools-protocol/blob/master/json/browser_protocol.json

You can use node and https://github.com/GoogleChrome/chrome-launcher to run chrome headless... wait for it to render.

Profit

printToPDF command you'd run on chrome_devtools protocol:

printToPDF({
  printBackground: false,
  pageWidth: 8.5,
  pageHeight: 11,
  transferMode: "ReturnAsStream" // or ReturnAsBase64
})


J
Johnny

To capture div as PDF you can use https://grabz.it solution. It's got a JavaScript API which is easy and flexible and will allow you to capture the contents of a single HTML element such as a div or a span

In order to implement it you will need to first get an app key and secret and download the (free) SDK.

And now an example.

Let's say you have the HTML:

<div id="features">
    <h4>Acme Camera</h4>
    <label>Price</label>$399<br />
    <label>Rating</label>4.5 out of 5
</div>
<p>Cras ut velit sed purus porttitor aliquam. Nulla tristique magna ac libero tempor, ac vestibulum felisvulput ate. Nam ut velit eget
risus porttitor tristique at ac diam. Sed nisi risus, rutrum a metus suscipit, euismod tristique nulla. Etiam venenatis rutrum risus at
blandit. In hac habitasse platea dictumst. Suspendisse potenti. Phasellus eget vehicula felis.</p>

To capture what is under the features id you will need to:

//add the sdk
<script type="text/javascript" src="grabzit.min.js"></script>
<script type="text/javascript">
//login with your key and secret. 
GrabzIt("KEY", "SECRET").ConvertURL("http://www.example.com/my-page.html",
{"target": "#features", "format": "pdf"}).Create();
</script>

Please note the target: #feature. #feature is you CSS selector, like in the previous example. Now, when the page is loaded an image screenshot will now be created in the same location as the script tag, which will contain all of the contents of the features div and nothing else.

The are other configuration and customization you can do to the div-screenshot mechanism, please check them out here


N
Noa

2022 Answer:

To generate PDF from HTML Element and prompt to save file:

import { jsPDF } from "jsPDF"

function generatePDF() {
  const doc = new jsPDF({ unit: 'pt' }) // create jsPDF object
  const pdfElement = document.getElementById('pdf') // HTML element to be converted to PDF

  doc.html(pdfElement, {
    callback: (pdf) => {
      pdf.save('MyPdfFile.pdf')
    },
    margin: 32, // optional: page margin
    // optional: other HTMLOptions
  })
}
<button onclick="generatePDF()">Save PDF</button>

...

To preview PDF without printing:

doc.html(pdfElement, {
  callback: (pdf) => {
    const myPdfData = pdf.output('datauri')
  }
})
<embed type="application/pdf" src={myPdfData} />

...
For more HTMLOptions:
https://github.com/parallax/jsPDF/blob/master/types/index.d.ts


G
General Grievance

any one try this

    (function () {  
        var  
         form = $('.form'),  
         cache_width = form.width(),  
         a4 = [595.28, 841.89]; // for a4 size paper width and height  

        $('#create_pdf').on('click', function () {  
            $('body').scrollTop(0);  
            createPDF();  
        });  
        //create pdf  
        function createPDF() {  
            getCanvas().then(function (canvas) {  
                var  
                 img = canvas.toDataURL("image/png"),  
                 doc = new jsPDF({  
                     unit: 'px',  
                     format: 'a4'  
                 });  
                doc.addImage(img, 'JPEG', 20, 20);  
                doc.save('Bhavdip-html-to-pdf.pdf');  
                form.width(cache_width);  
            });  
        }  

        // create canvas object  
        function getCanvas() {  
            form.width((a4[0] * 1.33333) - 80).css('max-width', 'none');  
            return html2canvas(form, {  
                imageTimeout: 2000,  
                removeContainer: true  
            });  
        }  

    }());