Search code examples
canvaschart.jspdf-generationpuppeteer

Puppeteer and PDF generation with canvas


I have a js file with puppeteer to generate a large report in pdf. Everything works pretty fine except for the canvas. In react file this is the basic logic for chartjs:

useEffect((): void => {
    if (refChart && refChart.current) {
      const newChartInstance = new Chart(refChart.current, chartConfig);

      newChartInstance.options.animation = {
        onComplete: (): void => {
          if (refImage && refImage.current) {
            refImage.current.src = newChartInstance.toBase64Image();
          }
        },
      };
    }
  }, [refChart]);

  return (
    <>
      <canvas ref={refChart} style={{ display: (printing) ? 'none' : 'block' }} width="100%" />
      <img
        alt="printing chart"
        ref={refImage}
        className={classes.chartImage}
        style={{
          display: (printing) ? 'block' : 'none',
        }}
      />
    </>
  );

If I open url or if I print it (CTRL + P) the images are shown in place of canvas but in puppeteer my pdf has the (broken) canvas with wrong size (even with printing locked as true).

This is what I expect and I can see in browser and printing: enter image description here

And this is what I got from puppeteer: enter image description here

Can anybody please help me with this? Thanks in advance.


Solution

  • I've found the solution adding page.setViewport() in puppeteer. This is my final code:

    const puppeteer = require('puppeteer');
    
    module.exports = async (callback, url) => {
      const browser = await puppeteer.launch({
        headless: true,
        executablePath: (process.platform === 'win32') ? null : '/usr/bin/chromium-browser',
        args: ['--no-sandbox', '--headless', '--disable-gpu', '--disable-dev-shm-usage'],
      })
        .catch((err) => {
          // eslint-disable-next-line no-console
          console.log('lauch: ', err);
        });
    
      const page = await browser.newPage()
        .catch((err) => {
          // eslint-disable-next-line no-console
          console.log('page: ', err);
        });
    
      await page.setViewport({
        width: 1280,
        height: 1024,
        deviceScaleFactor: 1,
      });
    
      await page.goto(`${url}/pdf`, {
        waitUntil: ['load', 'domcontentloaded', 'networkidle0', 'networkidle2'],
        timeout: 0,
      })
        .catch((err) => {
          // eslint-disable-next-line no-console
          console.log('goto: ', err);
        });
    
      await page.evaluate(async () => {
        let scrollPosition = 0;
        let documentHeight = document.body.scrollHeight;
    
        while (documentHeight > scrollPosition) {
          window.scrollBy(0, documentHeight);
          // eslint-disable-next-line no-await-in-loop
          await new Promise((resolve) => {
            setTimeout(resolve, 100);
          });
          scrollPosition = documentHeight;
          documentHeight = document.body.scrollHeight;
        }
      });
    
      // await page.evaluate(async () => {
      //   const matches = document.querySelectorAll('img');
    
      //   matches.forEach((canv) => {
      //     // eslint-disable-next-line no-param-reassign
      //     canv.style.maxWidth = '80%';
      //   });
      // });
    
      const buffer = await page.pdf({
        // path: 'hn.pdf',
        format: 'a4',
        margin: {
          top: '1cm',
          bottom: '1.5cm',
        },
      })
        .catch((err) => {
          // eslint-disable-next-line no-console
          console.log('page.pdf: ', err);
        });
    
      await browser.close()
        .catch((err) => {
          // eslint-disable-next-line no-console
          console.log('close: ', err);
        });
    
      const base64 = buffer.toString('base64');
      return callback(null, base64);
    };
    

    And this is my final result: enter image description here