puppeteer / puppeteer

JavaScript API for Chrome and Firefox
https://pptr.dev
Apache License 2.0
88.33k stars 9.07k forks source link

[Bug]: inconsistent PDF rendering for transform on HTML from 19.2.0 onwards #12669

Open dtnaughton opened 2 months ago

dtnaughton commented 2 months ago

Minimal, reproducible example

# This forms part of a POST endpoint

const options = {
  printBackground: true,
  format: 'A4',
  margin: {
    top: '9mm',
    bottom: '9mm',
    left: '9mm',
    right: '9mm'
  }
};

const renderedContent = 
    `<!DOCTYPE html><html><head><title>Calculation Sheet</title><style>body{font-family:Arial,sans-serif}.report-content{width:100%;margin:0 auto}.report-content-info{display:flex;justify-content:space-between;margin-bottom:20px}.report-content-info-logo svg{width:100px}.report-content-info-title{flex-grow:1;text-align:center}.report-content-info-text-key{font-weight:700}.report-content-info-text-value-large{font-size:1.5em}.aligner{display:flex;align-items:center}table{width:100%;border-collapse:collapse}thead{display:table-header-group}tbody{display:table-row-group}tfoot{display:table-footer-group}td,th{padding:8px;border:1px solid #000}th{background-color:#f2f2f2}</style></head><body><div class="report-content"><table><thead><tr><td><div class="report-content-info"><div class="report-content-info-logo"><svg xmlns="http://www.w3.org/2000/svg" version="1.1" height="110" width="205" viewBox="0 0 122.14667 65.466667"><g transform="matrix(1.3333333,0,0,-1.3333333,0,65.466667)"><g transform="scale(0.1)"><rect width="122.14667" height="65.466667" style="fill:#000"></rect></g></g></svg></div><div class="report-content-info-title"><p class="report-content-info-title-text">Header</p></div><div class="report-content-info-text-key report-content-info-1"><p class="report-content-info-text-key">Subheading.</p></div><div class="report-content-info-text-key report-content-info-2"><p class="report-content-info-text-key">Subheading.</p></div><div class="report-content-info-3"><p class="report-content-info-text-value-large"></p></div><div class="report-content-info-4"><p class="report-content-info-text-value-large"></p></div><div class="report-content-info-text-key report-content-info-5"><p class="report-content-info-text-key">Subheading</p><div class="aligner"><p class="report-content-info-text-value"></p></div></div><div class="report-content-info-text-key report-content-info-6"><p class="report-content-info-text-key">Subheading</p><div class="aligner"><p class="report-content-info-text-value-large"></p></div></div><div class="report-content-info-text-key report-content-info-7"><p class="report-content-info-text-key">Subheading</p><div class="aligner"><p class="report-content-info-text-value"></p></div></div><div class="report-content-info-8"><p class="report-content-info-text-key">Subheading</p><div class="aligner"><p class="report-content-info-text-value"></p></div></div><div class="report-content-info-9"><p class="report-content-info-text-key">Subheading</p><div class="aligner"><p class="report-content-info-text-value">Date</p></div></div><div class="report-content-info-text-key report-content-info-10"><p class="report-content-info-text-key">Subheading</p><div class="aligner"><p class="report-content-info-text-value"></p></div></div></div></td></tr></thead><tbody class="report-content-calculation"><tr><td>Content Row 1</td></tr><tr><td>Content Row 2</td></tr><tr><td>Content Row 3</td></tr><tr><td>Content Row 4</td></tr><tr><td>Content Row 5</td></tr><tr><td>Content Row 6</td></tr><tr><td>Content Row 7</td></tr><tr><td>Content Row 8</td></tr><tr><td>Content Row 9</td></tr><tr><td>Content Row 10</td></tr><tr><td>Content Row 11</td></tr><tr><td>Content Row 12</td></tr><tr><td>Content Row 13</td></tr><tr><td>Content Row 14</td></tr><tr><td>Content Row 15</td></tr><tr><td>Content Row 16</td></tr><tr><td>Content Row 17</td></tr><tr><td>Content Row 18</td></tr><tr><td>Content Row 19</td></tr><tr><td>Content Row 20</td></tr><tr><td>Content Row 21</td></tr><tr><td>Content Row 22</td></tr><tr><td>Content Row 23</td></tr><tr><td>Content Row 24</td></tr><tr><td>Content Row 25</td></tr></tbody><tfoot><tr><td><div class="report-content-footer aligner"><p class="report-content-footer-key">Footer</p><div class="aligner"><p class="report-content-footer-value"></p></div></div></td></tr></tfoot></table></div></body></html>`

let browser = await getBrowser()

const page = await browser.newPage();
await page.setContent(renderedContent);
await page.addStyleTag({path: 'public/stylesheets/index/style.css'});
await page.addStyleTag({path: 'public/stylesheets/index/katex.local.css'});

const buffer = await page.pdf(options);
await page.close();

return res.status(200).contentType("application/pdf").send(buffer);

Error string

no error

Bug behavior

Background

I have noticed that from version 19.2.0 onwards, PDF rendering of the this HTML (https://codebeautify.org/htmlviewer/y24e10232) is no longer consistent with earlier versions. We are using the HTML to generate a PDF report that contains a thead which has an SVG that is scaled. In this example, I've made that SVG a simple rectangle as the issue reproduces with this.

I have tested with the latest version of Puppeteer (22.12.1), but did some scoping that this error started occurring in 19.2.0 onwards.

Expectation

I am expecting the output PDF to have a scaled black box from the SVG due to the transform being applied in thead.

The HTML can be viewed in the link shared in the background section, but the notable part occurs here with the scaling of the SVG:

<svg xmlns="http://www.w3.org/2000/svg" version="1.1" height="110" width="205" viewBox="0 0 122.14667 65.466667">
  <g transform="matrix(1.3333333,0,0,-1.3333333,0,65.466667)">
    <g transform="scale(0.1)">
      <rect width="122.14667" height="65.466667" style="fill:#000000;"></rect>
    </g>
  </g>
</svg>

All headers (for each page) in the output PDF should contain a scaled box, as per behaviour in versions < 19.2.0.

< 19.2.0:

correct

Reality

Instead of scaling being applied to the SVGs in the header of each PDF page, it only appears to apply to the SVG in the header of the FINAL page of the PDF. From version 19.2.0 and later:

>= 19.2.0:

err2

Puppeteer configuration file (if used)

// See https://pptr.dev/guides/configuration#examples

const {join} = require('path');

/**
 * @type {import("puppeteer").Configuration}
 */
module.exports = {
  // Changes the cache location for Puppeteer.
  cacheDirectory: join(__dirname, '.cache', 'puppeteer'),
};

Puppeteer version

22.12.1

Node version

20.14.0

Package manager

npm

Package manager version

10.7.0

Operating system

Windows

github-actions[bot] commented 2 months ago

This issue was not reproducible. Please check that your example runs locally and the following:

Once the above checks are satisfied, please edit your issue with the changes and we will try to reproduce the bug again.


Analyzer run

OrKoN commented 2 months ago

I had to update the script but I do not observe the same results:

import puppeteer from "puppeteer";
import fs from 'fs';

const browser = await puppeteer.launch();

const options = {
  printBackground: true,
  format: 'A4',
  path: 'transform.pdf',
  margin: {
    top: '9mm',
    bottom: '9mm',
    left: '9mm',
    right: '9mm'
  }
};

const renderedContent = 
    `<!DOCTYPE html><html><head><title>Calculation Sheet</title><style>body{font-family:Arial,sans-serif}.report-content{width:100%;margin:0 auto}.report-content-info{display:flex;justify-content:space-between;margin-bottom:20px}.report-content-info-logo svg{width:100px}.report-content-info-title{flex-grow:1;text-align:center}.report-content-info-text-key{font-weight:700}.report-content-info-text-value-large{font-size:1.5em}.aligner{display:flex;align-items:center}table{width:100%;border-collapse:collapse}thead{display:table-header-group}tbody{display:table-row-group}tfoot{display:table-footer-group}td,th{padding:8px;border:1px solid #000}th{background-color:#f2f2f2}</style></head><body><div class="report-content"><table><thead><tr><td><div class="report-content-info"><div class="report-content-info-logo"><svg xmlns="http://www.w3.org/2000/svg" version="1.1" height="110" width="205" viewBox="0 0 122.14667 65.466667"><g transform="matrix(1.3333333,0,0,-1.3333333,0,65.466667)"><g transform="scale(0.1)"><rect width="122.14667" height="65.466667" style="fill:#000"></rect></g></g></svg></div><div class="report-content-info-title"><p class="report-content-info-title-text">Header</p></div><div class="report-content-info-text-key report-content-info-1"><p class="report-content-info-text-key">Subheading.</p></div><div class="report-content-info-text-key report-content-info-2"><p class="report-content-info-text-key">Subheading.</p></div><div class="report-content-info-3"><p class="report-content-info-text-value-large"></p></div><div class="report-content-info-4"><p class="report-content-info-text-value-large"></p></div><div class="report-content-info-text-key report-content-info-5"><p class="report-content-info-text-key">Subheading</p><div class="aligner"><p class="report-content-info-text-value"></p></div></div><div class="report-content-info-text-key report-content-info-6"><p class="report-content-info-text-key">Subheading</p><div class="aligner"><p class="report-content-info-text-value-large"></p></div></div><div class="report-content-info-text-key report-content-info-7"><p class="report-content-info-text-key">Subheading</p><div class="aligner"><p class="report-content-info-text-value"></p></div></div><div class="report-content-info-8"><p class="report-content-info-text-key">Subheading</p><div class="aligner"><p class="report-content-info-text-value"></p></div></div><div class="report-content-info-9"><p class="report-content-info-text-key">Subheading</p><div class="aligner"><p class="report-content-info-text-value">Date</p></div></div><div class="report-content-info-text-key report-content-info-10"><p class="report-content-info-text-key">Subheading</p><div class="aligner"><p class="report-content-info-text-value"></p></div></div></div></td></tr></thead><tbody class="report-content-calculation"><tr><td>Content Row 1</td></tr><tr><td>Content Row 2</td></tr><tr><td>Content Row 3</td></tr><tr><td>Content Row 4</td></tr><tr><td>Content Row 5</td></tr><tr><td>Content Row 6</td></tr><tr><td>Content Row 7</td></tr><tr><td>Content Row 8</td></tr><tr><td>Content Row 9</td></tr><tr><td>Content Row 10</td></tr><tr><td>Content Row 11</td></tr><tr><td>Content Row 12</td></tr><tr><td>Content Row 13</td></tr><tr><td>Content Row 14</td></tr><tr><td>Content Row 15</td></tr><tr><td>Content Row 16</td></tr><tr><td>Content Row 17</td></tr><tr><td>Content Row 18</td></tr><tr><td>Content Row 19</td></tr><tr><td>Content Row 20</td></tr><tr><td>Content Row 21</td></tr><tr><td>Content Row 21</td></tr><tr><td>Content Row 21</td></tr><tr><td>Content Row 21</td></tr><tr><td>Content Row 21</td></tr><tr><td>Content Row 21</td></tr><tr><td>Content Row 21</td></tr><tr><td>Content Row 21</td></tr><tr><td>Content Row 21</td></tr><tr><td>Content Row 21</td></tr><tr><td>Content Row 21</td></tr><tr><td>Content Row 21</td></tr><tr><td>Content Row 21</td></tr><tr><td>Content Row 21</td></tr><tr><td>Content Row 21</td></tr><tr><td>Content Row 21</td></tr><tr><td>Content Row 21</td></tr><tr><td>Content Row 21</td></tr><tr><td>Content Row 21</td></tr><tr><td>Content Row 21</td></tr><tr><td>Content Row 21</td></tr><tr><td>Content Row 21</td></tr><tr><td>Content Row 21</td></tr><tr><td>Content Row 21</td></tr><tr><td>Content Row 21</td></tr><tr><td>Content Row 21</td></tr><tr><td>Content Row 21</td></tr><tr><td>Content Row 21</td></tr><tr><td>Content Row 21</td></tr><tr><td>Content Row 21</td></tr><tr><td>Content Row 21</td></tr><tr><td>Content Row 21</td></tr><tr><td>Content Row 21</td></tr><tr><td>Content Row 21</td></tr><tr><td>Content Row 22</td></tr><tr><td>Content Row 23</td></tr><tr><td>Content Row 24</td></tr><tr><td>Content Row 25</td></tr></tbody><tfoot><tr><td><div class="report-content-footer aligner"><p class="report-content-footer-key">Footer</p><div class="aligner"><p class="report-content-footer-value"></p></div></div></td></tr></tfoot></table></div></body></html>`;

fs.writeFileSync('transform.html', renderedContent);

const page = await browser.newPage();
await page.goto('file://' + process.cwd() + '/transform.html');
await page.pdf(options);

await browser.close();

I see that the box is only present on the second page and not on the first page. Is it what you are seeing?

OrKoN commented 2 months ago

I get the same result using 19.2.0

OrKoN commented 2 months ago

In 18.1.0, I do see the black triangle on all pages.

OrKoN commented 2 months ago

the demo including only the transformed SVG does not reproduce the issue:

import puppeteer from "puppeteer";
import fs from 'fs';

const browser = await puppeteer.launch();

const options = {
  printBackground: true,
  format: 'A4',
  path: 'transform.pdf',
  margin: {
    top: '9mm',
    bottom: '9mm',
    left: '9mm',
    right: '9mm'
  }
};

const renderedContent = 
    `<!DOCTYPE html>
<html>

<head>
  <title>Calculation Sheet</title>
  <style>
    body {
      font-family: Arial, sans-serif
    }

    .report-content {
      width: 100%;
      margin: 0 auto
    }

    .report-content-info {
      display: flex;
      justify-content: space-between;
      margin-bottom: 20px
    }

    .report-content-info-logo svg {
      width: 100px
    }

    .report-content-info-title {
      flex-grow: 1;
      text-align: center
    }

    .report-content-info-text-key {
      font-weight: 700
    }

    .report-content-info-text-value-large {
      font-size: 1.5em
    }

    .aligner {
      display: flex;
      align-items: center
    }

    table {
      width: 100%;
      border-collapse: collapse
    }

    thead {
      display: table-header-group
    }

    tbody {
      display: table-row-group
    }

    tfoot {
      display: table-footer-group
    }

    td,
    th {
      padding: 8px;
      border: 1px solid #000
    }

    th {
      background-color: #f2f2f2
    }
  </style>
</head>

<body>
  <div class="report-content">
    <table>
      <thead>
        <tr>
          <td>
            <div class="report-content-info">
              <div class="report-content-info-logo"><svg xmlns="http://www.w3.org/2000/svg" version="1.1" height="110"
                  width="205" viewBox="0 0 122.14667 65.466667">
                  <g transform="matrix(1.3333333,0,0,-1.3333333,0,65.466667)">
                    <g transform="scale(0.1)">
                      <rect width="122.14667" height="65.466667" style="fill:#000"></rect>
                    </g>
                  </g>
                </svg>
              </div>
          </td>
        </tr>
      </thead>
      <tbody class="report-content-calculation">
        ${Array(100).fill(0).map(i => `<tr>
          <td>Content Row</td>
        </tr>`).join('\n')}
      </tbody>
      <tfoot>
        <tr>
          <td>
            <div class="report-content-footer aligner">
              <p class="report-content-footer-key">Footer</p>
              <div class="aligner">
                <p class="report-content-footer-value"></p>
              </div>
            </div>
          </td>
        </tr>
      </tfoot>
    </table>
  </div>
</body>

</html>`;

fs.writeFileSync('transform.html', renderedContent);

const page = await browser.newPage();
await page.goto('file://' + process.cwd() + '/transform.html');
await page.pdf(options);

await browser.close();
dtnaughton commented 2 months ago

I had to update the script but I do not observe the same results:

import puppeteer from "puppeteer";
import fs from 'fs';

const browser = await puppeteer.launch();

const options = {
  printBackground: true,
  format: 'A4',
  path: 'transform.pdf',
  margin: {
    top: '9mm',
    bottom: '9mm',
    left: '9mm',
    right: '9mm'
  }
};

const renderedContent = 
    `<!DOCTYPE html><html><head><title>Calculation Sheet</title><style>body{font-family:Arial,sans-serif}.report-content{width:100%;margin:0 auto}.report-content-info{display:flex;justify-content:space-between;margin-bottom:20px}.report-content-info-logo svg{width:100px}.report-content-info-title{flex-grow:1;text-align:center}.report-content-info-text-key{font-weight:700}.report-content-info-text-value-large{font-size:1.5em}.aligner{display:flex;align-items:center}table{width:100%;border-collapse:collapse}thead{display:table-header-group}tbody{display:table-row-group}tfoot{display:table-footer-group}td,th{padding:8px;border:1px solid #000}th{background-color:#f2f2f2}</style></head><body><div class="report-content"><table><thead><tr><td><div class="report-content-info"><div class="report-content-info-logo"><svg xmlns="http://www.w3.org/2000/svg" version="1.1" height="110" width="205" viewBox="0 0 122.14667 65.466667"><g transform="matrix(1.3333333,0,0,-1.3333333,0,65.466667)"><g transform="scale(0.1)"><rect width="122.14667" height="65.466667" style="fill:#000"></rect></g></g></svg></div><div class="report-content-info-title"><p class="report-content-info-title-text">Header</p></div><div class="report-content-info-text-key report-content-info-1"><p class="report-content-info-text-key">Subheading.</p></div><div class="report-content-info-text-key report-content-info-2"><p class="report-content-info-text-key">Subheading.</p></div><div class="report-content-info-3"><p class="report-content-info-text-value-large"></p></div><div class="report-content-info-4"><p class="report-content-info-text-value-large"></p></div><div class="report-content-info-text-key report-content-info-5"><p class="report-content-info-text-key">Subheading</p><div class="aligner"><p class="report-content-info-text-value"></p></div></div><div class="report-content-info-text-key report-content-info-6"><p class="report-content-info-text-key">Subheading</p><div class="aligner"><p class="report-content-info-text-value-large"></p></div></div><div class="report-content-info-text-key report-content-info-7"><p class="report-content-info-text-key">Subheading</p><div class="aligner"><p class="report-content-info-text-value"></p></div></div><div class="report-content-info-8"><p class="report-content-info-text-key">Subheading</p><div class="aligner"><p class="report-content-info-text-value"></p></div></div><div class="report-content-info-9"><p class="report-content-info-text-key">Subheading</p><div class="aligner"><p class="report-content-info-text-value">Date</p></div></div><div class="report-content-info-text-key report-content-info-10"><p class="report-content-info-text-key">Subheading</p><div class="aligner"><p class="report-content-info-text-value"></p></div></div></div></td></tr></thead><tbody class="report-content-calculation"><tr><td>Content Row 1</td></tr><tr><td>Content Row 2</td></tr><tr><td>Content Row 3</td></tr><tr><td>Content Row 4</td></tr><tr><td>Content Row 5</td></tr><tr><td>Content Row 6</td></tr><tr><td>Content Row 7</td></tr><tr><td>Content Row 8</td></tr><tr><td>Content Row 9</td></tr><tr><td>Content Row 10</td></tr><tr><td>Content Row 11</td></tr><tr><td>Content Row 12</td></tr><tr><td>Content Row 13</td></tr><tr><td>Content Row 14</td></tr><tr><td>Content Row 15</td></tr><tr><td>Content Row 16</td></tr><tr><td>Content Row 17</td></tr><tr><td>Content Row 18</td></tr><tr><td>Content Row 19</td></tr><tr><td>Content Row 20</td></tr><tr><td>Content Row 21</td></tr><tr><td>Content Row 21</td></tr><tr><td>Content Row 21</td></tr><tr><td>Content Row 21</td></tr><tr><td>Content Row 21</td></tr><tr><td>Content Row 21</td></tr><tr><td>Content Row 21</td></tr><tr><td>Content Row 21</td></tr><tr><td>Content Row 21</td></tr><tr><td>Content Row 21</td></tr><tr><td>Content Row 21</td></tr><tr><td>Content Row 21</td></tr><tr><td>Content Row 21</td></tr><tr><td>Content Row 21</td></tr><tr><td>Content Row 21</td></tr><tr><td>Content Row 21</td></tr><tr><td>Content Row 21</td></tr><tr><td>Content Row 21</td></tr><tr><td>Content Row 21</td></tr><tr><td>Content Row 21</td></tr><tr><td>Content Row 21</td></tr><tr><td>Content Row 21</td></tr><tr><td>Content Row 21</td></tr><tr><td>Content Row 21</td></tr><tr><td>Content Row 21</td></tr><tr><td>Content Row 21</td></tr><tr><td>Content Row 21</td></tr><tr><td>Content Row 21</td></tr><tr><td>Content Row 21</td></tr><tr><td>Content Row 21</td></tr><tr><td>Content Row 21</td></tr><tr><td>Content Row 21</td></tr><tr><td>Content Row 21</td></tr><tr><td>Content Row 21</td></tr><tr><td>Content Row 22</td></tr><tr><td>Content Row 23</td></tr><tr><td>Content Row 24</td></tr><tr><td>Content Row 25</td></tr></tbody><tfoot><tr><td><div class="report-content-footer aligner"><p class="report-content-footer-key">Footer</p><div class="aligner"><p class="report-content-footer-value"></p></div></div></td></tr></tfoot></table></div></body></html>`;

fs.writeFileSync('transform.html', renderedContent);

const page = await browser.newPage();
await page.goto('file://' + process.cwd() + '/transform.html');
await page.pdf(options);

await browser.close();

I see that the box is only present on the second page and not on the first page. Is it what you are seeing?

Using this script, I am seeing the same thing as you.

18.1.0 - the box appears on all pages in PDF. in 22.12.1 - the box only appears on the last page in PDF.

OrKoN commented 2 months ago

I see, the issue needs to be filed at crbug.com/new I will try to do that next week.

OrKoN commented 2 months ago

Filed https://crbug.com/350858753