eKoopmans / html2pdf.js

Client-side HTML-to-PDF rendering using pure JS.
MIT License
3.96k stars 1.36k forks source link

blank pdf generated for large documents #311

Open kirtipriya opened 4 years ago

kirtipriya commented 4 years ago

When a document is big spanning 150-200 pages , blank pdf is generated. v0.9.0 is this a limitation of canvas element ? Can this be fixed ?

edbras commented 4 years ago

I encounter the same issue in Chrome, and not only for big documents, also for tinny docs

kirtipriya commented 4 years ago

@eKoopmans is there an error callback that will say that the limit has exceeded the canvas size. We can show an error message to user in that case then

ErikJungnickel commented 4 years ago

Same here. I have a pdf consisting of 93 blank pages. In another pdf with 81 pages some elements that should be in the pdf are only present on the first pages then disappear.

image

config:

      margin: [8, 8, 8, 8],
      filename: fileName,
      image: { type: 'jpeg', quality: 0.98 },
      html2canvas: { scale: 1 },
      jsPDF: { unit: 'mm', format: 'a4', orientation: 'landscape' },
      pagebreak: { after: '.page-break' }    

using version 0.9.2

edbras commented 4 years ago

What code and config are you guys using?

simonpinn commented 4 years ago

Same issue here - not consistently though, sometimes same input will print to PDF just fine, sometimes it is a semi-blank or completely blank output. Using v0.9.1 and config is:


html2pdf()
        .set({
          margin: [10, 10, 20, 10],
          filename: fileName,
          image: {type: 'jpeg', quality: 1},
          html2canvas: {dpi: 192, letterRendering: true},
          jsPDF: {unit: 'mm', format: 'letter', orientation: 'landscape'},
          pageBreak: {mode: ['avoid-all', 'css'], avoid: ['.pi-row']},
        })
        .from(container[0].innerHTML)
        .toPdf()
        .save();```
ErikJungnickel commented 4 years ago

So this is a known but hard to fix issue. See #19

kirtipriya commented 3 years ago

@eKoopmans Yes , i understand it is hard to fix , but can there be a way to atleast generate an error . I ask this because , in Firefox , if the document is huge , the screen gets frozen, and there is nothing that the user can do . He has to close the browser and restart it Chrome is working because it is atleast able to generate a pdf , eventhough it is blank. Screen is not frozen. Wheras Firefox is unable to handle a canas size that exceeds its limit. The screen gets frozen and i can see errors in the console of Firefox . i have reprted this issue here with the errors received --> https://github.com/eKoopmans/html2pdf.js/issues/351

kirtipriya commented 3 years ago

@eKoopmans Any update on this ?

shan-du commented 3 years ago

@kirtipriya the only way to fix is as suggested here you'll have to divide up your html content into different "pages" so that a new canvas is created for each "page", preventing the content height from exceeding canvas upper limit.

i did it this way in my code and it works totally fine albeit a bit slower:

// ---------- HTML output for pdf export ----------
<div aria-label="pdf-page-1">...content</div>
<div aria-label="pdf-page-2">...content</div>
...

// ---------- Javascript ----------
// pages => individual html blocks to print
// select all elements marked with 'pdf-page-*' to be printed into PDF 
const pages = Array.from(document.querySelectorAll('div[aria-label^="pdf-page-"]'))
const pdfOptions = { ... pdf options here }
await downloadPDF(pages, pdfOptions)

const downloadPDF = (elements, pdfOptions) => {
  let worker = html2pdf()
    .set(options)
    .from(elements[0])

  if (elements.length > 1) {
    worker = worker.toPdf() // worker is now a jsPDF instance

    // add each element/page individually to the PDF render process
    elements.slice(1).forEach((element, index) => {
      worker = worker
        .get('pdf')
        .then(pdf => {
          pdf.addPage()
        })
        .from(element)
        .toContainer()
        .toCanvas()
        .toPdf()
    })
  }

  worker = worker.save()
}
kirtipriya commented 3 years ago

@kirtipriya the only way to fix is as suggested here you'll have to divide up your html content into different "pages" so that a new canvas is created for each "page", preventing the content height from exceeding canvas upper limit.

i did it this way in my code and it works totally fine albeit a bit slower:

// ---------- HTML output for pdf export ----------
<div aria-label="pdf-page-1">...content</div>
<div aria-label="pdf-page-2">...content</div>
...

// ---------- Javascript ----------
// pages => individual html blocks to print
// select all elements marked with 'pdf-page-*' to be printed into PDF 
const pages = Array.from(document.querySelectorAll('div[aria-label^="pdf-page-"]'))
const pdfOptions = { ... pdf options here }
await downloadPDF(pages, pdfOptions)

const downloadPDF = (elements, pdfOptions) => {
  let worker = html2pdf()
    .set(options)
    .from(elements[0])

  if (elements.length > 1) {
    worker = worker.toPdf() // worker is now a jsPDF instance

    // add each element/page individually to the PDF render process
    elements.slice(1).forEach((element, index) => {
      worker = worker
        .get('pdf')
        .then(pdf => {
          pdf.addPage()
        })
        .from(element)
        .toContainer()
        .toCanvas()
        .toPdf()
    })
  }

  worker = worker.save()
}

@shan-du : Thank you for the suggestion. The content is dynamic in my case. It might be difficult to for me to identify which div tags i have to treat as pages - div[aria-label^="pdf-page-"], or do you have a solution for that too ?

shan-du commented 3 years ago

@kirtipriya the only way to fix is as suggested here you'll have to divide up your html content into different "pages" so that a new canvas is created for each "page", preventing the content height from exceeding canvas upper limit. i did it this way in my code and it works totally fine albeit a bit slower:

// ---------- HTML output for pdf export ----------
<div aria-label="pdf-page-1">...content</div>
<div aria-label="pdf-page-2">...content</div>
...

// ---------- Javascript ----------
// pages => individual html blocks to print
// select all elements marked with 'pdf-page-*' to be printed into PDF 
const pages = Array.from(document.querySelectorAll('div[aria-label^="pdf-page-"]'))
const pdfOptions = { ... pdf options here }
await downloadPDF(pages, pdfOptions)

const downloadPDF = (elements, pdfOptions) => {
  let worker = html2pdf()
    .set(options)
    .from(elements[0])

  if (elements.length > 1) {
    worker = worker.toPdf() // worker is now a jsPDF instance

    // add each element/page individually to the PDF render process
    elements.slice(1).forEach((element, index) => {
      worker = worker
        .get('pdf')
        .then(pdf => {
          pdf.addPage()
        })
        .from(element)
        .toContainer()
        .toCanvas()
        .toPdf()
    })
  }

  worker = worker.save()
}

@shan-du : Thank you for the suggestion. The content is dynamic in my case. It might be difficult to for me to identify which div tags i have to treat as pages - div[aria-label^="pdf-page-"], or do you have a solution for that too ?

not sure what your requirements are, but in that case, i would probably still try to divide the print content, not by page, but every X pages. for example: for every 10 <div> content blocks, or every 3000px, start a new page

alviando98 commented 3 years ago

@kirtipriya the only way to fix is as suggested here you'll have to divide up your html content into different "pages" so that a new canvas is created for each "page", preventing the content height from exceeding canvas upper limit. i did it this way in my code and it works totally fine albeit a bit slower:

// ---------- HTML output for pdf export ----------
<div aria-label="pdf-page-1">...content</div>
<div aria-label="pdf-page-2">...content</div>
...

// ---------- Javascript ----------
// pages => individual html blocks to print
// select all elements marked with 'pdf-page-*' to be printed into PDF 
const pages = Array.from(document.querySelectorAll('div[aria-label^="pdf-page-"]'))
const pdfOptions = { ... pdf options here }
await downloadPDF(pages, pdfOptions)

const downloadPDF = (elements, pdfOptions) => {
  let worker = html2pdf()
    .set(options)
    .from(elements[0])

  if (elements.length > 1) {
    worker = worker.toPdf() // worker is now a jsPDF instance

    // add each element/page individually to the PDF render process
    elements.slice(1).forEach((element, index) => {
      worker = worker
        .get('pdf')
        .then(pdf => {
          pdf.addPage()
        })
        .from(element)
        .toContainer()
        .toCanvas()
        .toPdf()
    })
  }

  worker = worker.save()
}

@shan-du : Thank you for the suggestion. The content is dynamic in my case. It might be difficult to for me to identify which div tags i have to treat as pages - div[aria-label^="pdf-page-"], or do you have a solution for that too ?

not sure what your requirements are, but in that case, i would probably still try to divide the print content, not by page, but every X pages. for example: for every 10 <div> content blocks, or every 3000px, start a new page

can it work with css?

ThePixelPixie commented 2 years ago

I'm also getting a blank document, but am trying to generate a very large PDF of 48"x69". Is this just not going to be possible?

yomajo commented 1 year ago

How can canvas be exhausted if space between "legacy" with <br class="html2pdf__page-break"> is max 500px height (300pages)

Prvn-IG commented 1 year ago

@kirtipriya the only way to fix is as suggested here you'll have to divide up your html content into different "pages" so that a new canvas is created for each "page", preventing the content height from exceeding canvas upper limit.

i did it this way in my code and it works totally fine albeit a bit slower:

// ---------- HTML output for pdf export ----------
<div aria-label="pdf-page-1">...content</div>
<div aria-label="pdf-page-2">...content</div>
...

// ---------- Javascript ----------
// pages => individual html blocks to print
// select all elements marked with 'pdf-page-*' to be printed into PDF 
const pages = Array.from(document.querySelectorAll('div[aria-label^="pdf-page-"]'))
const pdfOptions = { ... pdf options here }
await downloadPDF(pages, pdfOptions)

const downloadPDF = (elements, pdfOptions) => {
  let worker = html2pdf()
    .set(options)
    .from(elements[0])

  if (elements.length > 1) {
    worker = worker.toPdf() // worker is now a jsPDF instance

    // add each element/page individually to the PDF render process
    elements.slice(1).forEach((element, index) => {
      worker = worker
        .get('pdf')
        .then(pdf => {
          pdf.addPage()
        })
        .from(element)
        .toContainer()
        .toCanvas()
        .toPdf()
    })
  }

  worker = worker.save()
}

code is working but hyperlink is not able to click in exported pdf

suguoyao commented 3 months ago

@shan-du Your code is useful to me, but if you need to wait for all pages to be generated before doing something, you can change the code to the following:

const downloadPDF = (elements, pdfOptions) => {
      return new Promise((resolve, reject) => {
        try {
          let worker = html2pdf().set(pdfOptions).from(elements[0])
          let pageCount = 1

          if (elements.length > 1) {
            worker = worker.toPdf() // worker is now a jsPDF instance

            // add each element/page individually to the PDF render process
            elements.slice(1).forEach((element, index) => {
              worker = worker
                  .get('pdf')
                  .then(pdf => {
                    pageCount++
                    pdf.addPage()

                    if (pageCount === elements.length) {
                      resolve()
                    }
                  })
                  .from(element)
                  .toContainer()
                  .toCanvas()
                  .toPdf()
            })
          }

          worker = worker.save()

          if (elements.length === 1) {
            resolve()
          }
        } catch (e) {
          reject()
        }
      })
    }

// ...

loading = true
await downloadPDF(pages, pdfOptions)
loading = false
maksym-arturIn commented 1 month ago

Options for html2pdf

const options = {
    image: { type: 'jpeg', quality: 0.95 },
    filename: `report.pdf`,
    html2canvas: {
      dpi: 92,
      letterRendering: true,
      useCORS: true,
      margin: 1,
      scale: 2,
      allowTaint: true,
      imageTimeout: 5000,
      preferCanvas: true, // !!! Don't forget about this option if you are using  leaflet and render maps 
    },
    jsPDF: { unit: 'mm', format: 'a4', orientation: 'landscape' }
  }

generate pdf function

import * as html2pdf from 'html2pdf.js'
import { PDFDocument } from 'pdf-lib';

function _savePdf(blob, filename) {
    const link = document.createElement('a');
    link.href = URL.createObjectURL(blob);
    link.download = filename;
    document.body.appendChild(link);
    link.click();
    document.body.removeChild(link);
  }

  async function _mergePDFs(pdfs) {
    const mergedPdf = await PDFDocument.create();
    for (const pdfBlob of pdfs) {
      const pdfDoc = await PDFDocument.load(await pdfBlob.arrayBuffer());
      const copiedPages = await mergedPdf.copyPages(pdfDoc, pdfDoc.getPageIndices());
      copiedPages.forEach((page) => mergedPdf.addPage(page));
    }
    const mergedPdfBytes = await mergedPdf.save();
    return new Blob([mergedPdfBytes], { type: 'application/pdf' });
  }

async function generatePDF () {
    setIsLoading(true)

    if(containerRef.current) {
      await new Promise((resolve) => {setTimeout(resolve, 6000)}); // Wait till components rendered, in my case it's a big one 

      const pages = Array.from(containerRef.current.getElementsByClassName('html2pdf__page-template'))

      if(pages.length > 10) {
        const PDFs = [];

        for (const page of pages) {
          const pdfBlob = await html2pdf().set(options).from(page).outputPdf('blob');
          PDFs.push(pdfBlob);
        }

        const mergedPdf = await _mergePDFs(PDFs);
        _savePdf(mergedPdf, `report.pdf`);
      } else {
        await html2pdf().set(options).from(containerRef.current).save()
      }
    }

    setIsLoading(false)
  }