Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

jsPDF Server Side html() function generates empty pdf #2805

Closed
sakos95 opened this issue Jul 9, 2020 · 17 comments
Closed

jsPDF Server Side html() function generates empty pdf #2805

sakos95 opened this issue Jul 9, 2020 · 17 comments

Comments

@sakos95
Copy link

sakos95 commented Jul 9, 2020

I have a scenario, where I need to create a pdf file from html source code on server side. I tried the solution from issue #2248 , and the doc.text() function worked, but the doc.html() function creates an empty pdf and I got the following error message:

html2canvas not loaded.
jspdf.node.min.js:150

(node:7560) UnhandledPromiseRejectionWarning: ReferenceError: document is not defined
warning.js:27
    at r (\node_modules\jspdf\dist\jspdf.node.min.js:150:335)
    at Promise.<anonymous> (\dist\node_modules\jspdf\dist\jspdf.node.min.js:150:1413)
    at processTicksAndRejections (internal/process/task_queues.js:94:5)
(node:7560) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 1)

I found this solution, that solved the "html2canvas not loaded" error, but I still get an empty pdf and an UnhandledPromiseRejectionWarning:

(node:7136) UnhandledPromiseRejectionWarning: ReferenceError: document is not defined
warning.js:27
    at r (\node_modules\jspdf\dist\jspdf.node.min.js:150:335)
    at Promise.<anonymous> (\node_modules\jspdf\dist\jspdf.node.min.js:150:1413)
    at processTicksAndRejections (internal/process/task_queues.js:94:5)
(node:7136) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 1)
warning.js:27
(node:7136) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

This is the code that I tried to run:

global.window = {document: {createElementNS: () => {return {}} }};
global.navigator = {};
global.btoa = () => {};

const fs = require('fs')
const html2canvas = require('html2canvas');
window.html2canvas = html2canvas;
const jsPDF = require('jspdf/dist/jspdf.node.min')

// Default export is a4 paper, portrait, using milimeters for units
var doc = new jsPDF()
doc.html('<p>Hello world!</p>');

fs.writeFileSync('./output.pdf', doc.output())

delete global.window;
delete global.navigator;
delete global.btoa;

Is there any way to solve this issue?

@HackbrettXXX
Copy link
Collaborator

HackbrettXXX commented Jul 9, 2020

With the new release planned, running jsPDF in node will work much more seamless. You can try the pull request #2804.

However, html2canvas requires some DOM APIs in order to work. You could try jsdom. It would be very great, if you could test the pull request together with jsdom and see if that works. If so, we can add a node/html2canvas example to the examples.

@sakos95
Copy link
Author

sakos95 commented Jul 9, 2020

Thanks, I'll try it.

@sakos95
Copy link
Author

sakos95 commented Jul 22, 2020

I tried the pull request and jsdom, but they didn't solve the issue.

Using jsdom gave the following error (even though I manually imported html2canvas):

(node:20068) UnhandledPromiseRejectionWarning: ReferenceError: html2canvas is not defined
warning.js:32
    at Promise.<anonymous> (\node_modules\jspdf\dist\jspdf.node.min.js:150:4891)
    at processTicksAndRejections (internal/process/task_queues.js:97:5)
(node:20068) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 1)
warning.js:32
(node:20068) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

While the jspdf from the pull request gave an error (despite the global.btoa = () => {}; line): Error: Cannot find module 'btoa'

@HackbrettXXX
Copy link
Collaborator

Did you install the dependencies? The dependencies are now

  "dependencies": {
    "atob": "^2.1.2",
    "btoa": "^1.2.1"
  },
  "optionalDependencies": {
    "canvg": "^3.0.6",
    "core-js": "^3.6.0",
    "dompurify": "^2.0.12",
    "html2canvas": "^1.0.0-rc.5"
  },

You need to include dompurify and html2canvas in your own package.json in order to be installed. In theory, you can reduce your snippet to this:

const { jsPDF } = require("jspdf")
const doc = new jsPDF()
doc.html('<p>Hello world!</p>');

doc.save('./output.pdf)

@manuelamaria
Copy link

manuelamaria commented Aug 31, 2020

I'm also trying to add html to pdf on the server side with jspdf v2.1.0 and node v14.9.0. Is this supposed to work out of the box, or do I still need to add jsdom?

[I get UnhandledPromiseRejectionWarning: ReferenceError: document is not defined.]

I tried wrapping my html with jsdom, and then passing it to the html function, but I get UnhandledPromiseRejectionWarning: Error: Unknown source type.:

const html = '...';
var domhtml = new JSDOM(html, { contentType: "text/html" });
  
var doc = new jsPDF();
doc.html(domhtml, {
    callback: function(d) {
      d.save();
    }
});

Thanks for any input!

@HackbrettXXX
Copy link
Collaborator

You need to set the document globally:

global.window = domhtml.window
global.document = window.document

And then use something like document.body for the doc.html call.

@manuelamaria
Copy link

Thanks for the answer!

Unfortunately I get more errors from jsdom: Error: Not implemented: window.computedStyle(elt, pseudoElt), Error: Not implemented: window.scrollTo

This is my code:

const jsdom = require("jsdom");
const { JSDOM } = jsdom;
const { jsPDF } = require("jspdf");

var myhtml = `<div><p>test me</p></div>`;
const htmldom = new JSDOM(myhtml);

global.window = htmldom.window;
global.document = window.document;
global.Node = window.Node;

var doc = new jsPDF('p', 'mm', 'a4');
doc.html(document.body, {
    callback: function(d) {
        d.save();
    }
});
doc.save("a4.pdf");

@HackbrettXXX
Copy link
Collaborator

Well, seems like jsdom doesn't implement all required APIs. What you might try is running jsPDF in a headless browser on the backend instead.

@mountiny
Copy link

@manuelamaria @HackbrettXXX Hi! Have you managed to solve this? I cant make the html function to work. I am using the latest version of jspdf. Thank you!

@manuelamaria
Copy link

@mountiny we ended up creating a node service where we use puppeteer to render html and then save as pdf. Something like this: https://blog.risingstack.com/pdf-from-html-node-js-puppeteer/#option3

@mountiny
Copy link

@manuelamaria Thank you very much, that seems quite complicated for us now since we are in time/budget pressure.

@github-actions
Copy link

This issue is stale because it has been open 90 days with no activity. It will be closed soon. Please comment/reopen if this issue is still relevant.

@muskan3006
Copy link

I am still facing this issue, do we have a solution now?

@Azer0s
Copy link

Azer0s commented Feb 5, 2021

Yep, me too. Would be interested in an answer.

@HackbrettXXX
Copy link
Collaborator

Well, seems like jsdom doesn't implement all required APIs. What you might try is running jsPDF in a headless browser on the backend instead.

There is simply no easy way to convert html to PDF without a working DOM API. So a headless browser on the backend is probably the best you can do.

@Stefanuk12
Copy link

So a headless browser on the backend is probably the best you can do.

I've tried this solution with puppeteer but cannot get it to work. It errors with the error "document is not defined"

// Dependencies
import * as fs from "fs"
import * as puppeteer from "puppeteer"
import { jsPDF } from "jspdf"

// Create a base PDF
const PDF = new jsPDF()

// Load a browser
const browser = await puppeteer.launch()
const page = await browser.newPage()

// Load the SVG and expose the function we need
const InputSVGPath = "svgs/MARI42DF/page-0001.svg"
const SVGImage = fs.readFileSync(InputSVGPath, "utf-8")
await page.exposeFunction("addSvgAsImage", PDF.addSvgAsImage)

// So we have access to the DOM
await page.evaluate((PDF, SVGImage) => {
    // So the addSvgAsImage can use it
    globalThis.document = document

    // Attempt to add the SVG
    return addSvgAsImage.call(PDF, SVGImage, 0, 0, 100, 100)
}, PDF, SVGImage)

// Save
PDF.save("test.pdf")

// Close the browser
await browser.close()

// So TypeScript does not error
const addSvgAsImage = PDF.addSvgAsImage

@sahilkul95
Copy link

sahilkul95 commented Jun 9, 2023

@Stefanuk12 , please try this as this worked for me:-

    const browser = await puppeteer.launch({
      headless: 'new',
    });

    // create a new page
    const page = await browser.newPage();

    // set your html as the pages content
    const html = fs.readFileSync(`${__dirname}/temp.html`, 'utf8');
    await page.setContent(html, {
      waitUntil: 'domcontentloaded',
    });

    // create a pdf buffer
    const pdfStream = await page.createPDFStream({
      format: 'A4',
      // path: `${__dirname}/sample-lead.pdf`,   // for pdf file
    });

    // close the browser
    await browser.close();```

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

8 participants