使用 canvas 生成 Puppeteer 和 PDF

Puppeteer and PDF generation with canvas

我有一个带有 puppeteer 的 js 文件,可以生成 pdf 格式的大型报告。除了 canvas 之外,一切都很好。在反应文件中,这是 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',
        }}
      />
    </>
  );

如果我打开 url 或打印它 (CTRL + P),图像将代替 canvas 显示,但在 puppeteer 中,我的 pdf 有(损坏的)canvas尺寸错误(即使打印锁定为真)。

这是我所期望的,我可以在浏览器和打印中看到:

这就是我从 puppeteer 那里得到的:

有人可以帮我解决这个问题吗?提前致谢。

我找到了在 puppeteer 中添加 page.setViewport() 的解决方案。这是我的最终代码:

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);
};

这是我的最终结果: