Presha
Presha

Reputation: 104

jspdf with html2canvas, div is cut

I am trying to create/download pdf with jspdf using html2canvas. I have certain div made circle around and colored with some value inside. When I download pdf in certain cases this is cut. and is in 2 different pages partially as in image. I want the div to move to other page instead of cutting like this. enter image description here

Code to download it is as following:

downloadPDF(): void {
const buttons = document.querySelectorAll('.button-content');
buttons.forEach((button) => {
  (button as HTMLElement).style.display = 'none';
});

const content = document.getElementById('pdf-content');
if (content) {
  html2canvas(content, { scale: 2, useCORS: true }).then((canvas) => {
    const imgWidth = 210;
    const pageHeight = 295;
    const imgHeight = (canvas.height * imgWidth) / canvas.width;
    let heightLeft = imgHeight;

    const doc = new jsPDF('p', 'mm', 'a4');
    let position = 0;

    doc.addImage(
      canvas.toDataURL('image/png'),
      'PNG',
      0,
      position,
      imgWidth,
      imgHeight,
    );
    heightLeft -= pageHeight;

    while (heightLeft >= 0) {
      position = heightLeft - imgHeight;
      doc.addPage();
      doc.addImage(
        canvas.toDataURL('image/png'),
        'PNG',
        0,
        position,
        imgWidth,
        imgHeight,
      );
      heightLeft -= pageHeight;
    }

    doc.save('Maturity Assessment Result Report.pdf');

    buttons.forEach((button) => {
      (button as HTMLElement).style.display = 'inline-block';
    });
  });
}

}

Upvotes: 0

Views: 144

Answers (2)

Luis Lobo
Luis Lobo

Reputation: 859

From what I understand, basically, you are taking a very large div and converting it into a single image. jsPDF is just inserting this giant image cut in half between the pages.

To avoid this problem, you need to iterate over the elements and create an image for each of them. This way, you can add them one by one and then calculate if there is available space on the screen or not.

Additionally, I made several small adjustments. I removed TypeScript and the scale to simplify the example.

That's all. Iterate over the images and add them until there is no more available space on the page. If there isn't, create a new one.

enter image description here

I had to put the complete code because the stackoverflow iframe would give CORS problem...

Enter the console and run the generatePDF() command:

<!doctype html>
<html lang="pt-br">

<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, user-scalable=yes, shrink-to-fit=no" />

    <title></title>

    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/jspdf.umd.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script>
    <script>
        const { jsPDF } = jspdf

        function generatePDF() {
            const contents = [...document.querySelectorAll('.pdf-content .grade')],
                canvasImgs = []
            let finishedCanvas = 0

            if (contents.length) {
                contents.forEach((grade, index) => {
                    html2canvas(grade, { scale: 1, useCORS: true }).then(canvas => {
                        canvasImgs[index] = canvas
                        finishedCanvas++

                        // are all of them finished?
                        if (contents.length === finishedCanvas) {
                            callbackFn(canvasImgs)
                        }
                    })
                })
            }

            function callbackFn(canvasImgs) {
                const doc = new jsPDF('p', 'mm', 'a4')

                const pageWidth = 210,
                    pageHeight = 297

                let heightLeft = pageHeight,
                    position = 0

                canvasImgs.forEach(currentCanvas => {
                    const imgWidth = pixelsToMm(currentCanvas.width),
                        imgHeight = pixelsToMm(currentCanvas.height),
                        marginBottom = 5 // 5mm

                    if (heightLeft <= imgHeight + marginBottom) {
                        doc.addPage()
                        heightLeft = pageHeight
                        position = 0
                    }

                    doc.addImage(
                        currentCanvas.toDataURL('image/png'),
                        'PNG',
                        0,
                        position,
                        imgWidth,
                        imgHeight
                    )

                    position += imgHeight + marginBottom
                    heightLeft -= imgHeight + marginBottom
                })

                doc.save('Maturity Assessment Result Report.pdf')
            }
        }

        function pixelsToMm(pixels, dpi = 96) {
            const inches = pixels / dpi,
                mm = inches * 25.4

            return mm
        }
    </script>

    <style>
        .grade {
            display: grid;
            place-items: center;

            height: 150px;
            width: 150px;

            background-color: #ccc;
            border-radius: 9999px;
        }

        .text {
            position: relative;
            font-size: 32px;
            font-weight: 700;
        }
    </style>
</head>

<body>
    <div>
        <div class="pdf-content">
            <div class="grade">
                <div class="text">
                    1
                </div>
            </div>
            <div class="grade">
                <div class="text">
                    2
                </div>
            </div>
            <div class="grade">
                <div class="text">
                    3
                </div>
            </div>
            <div class="grade">
                <div class="text">
                    4
                </div>
            </div>
            <div class="grade">
                <div class="text">
                    5
                </div>
            </div>
            <div class="grade">
                <div class="text">
                    6
                </div>
            </div>
            <div class="grade">
                <div class="text">
                    7
                </div>
            </div>
            <div class="grade">
                <div class="text">
                    8
                </div>
            </div>
            <div class="grade">
                <div class="text">
                    9
                </div>
            </div>
            <div class="grade">
                <div class="text">
                    10
                </div>
            </div>
            <div class="grade">
                <div class="text">
                    11
                </div>
            </div>
            <div class="grade">
                <div class="text">
                    12
                </div>
            </div>
            <div class="grade">
                <div class="text">
                    13
                </div>
            </div>
            <div class="grade">
                <div class="text">
                    14
                </div>
            </div>
            <div class="grade">
                <div class="text">
                    15
                </div>
            </div>
            <div class="grade">
                <div class="text">
                    16
                </div>
            </div>
        </div>
    </div>
</body>

</html>

Upvotes: 2

Hai Quan Nguyen
Hai Quan Nguyen

Reputation: 70

If you want to export pdf with many pages from html code, I recommend you to use html2pdf.js library, this library will solve the problem of page cutting as well as the content of the pages will not be lost above and below.

Upvotes: 0

Related Questions