puppeteer: PDF creation pixel sizes causes layout to change

Hi,

When creating PDFs from a responsive web page, lets say the page has a css rule to show a narrower version below 1024 pixels screen width and we supply width: "1024px" to pdf function. The rule should not apply on this case however it does and the layout changes in generated pdf.

Only when the width parameter is 4/3 of the actual width of viewport, then the pdf has exact same layout of given viewport width. This seems to be something related to an inches / points confusion somewhere (96 pixels per inch, 72 pixels per point. 96/72 = 4/3)

So, when I provide 1365px as pdf width I get the exact representation of the page at 1024px viewport width. Anything below 1365px, page acts like the viewport is below 1024px and applies the appropriate responsive css rules.

I’m not sure if this is an upstream bug or something else though. Margins are set to 0 btw.

Any ideas?

About this issue

  • Original URL
  • State: open
  • Created 7 years ago
  • Reactions: 19
  • Comments: 20 (2 by maintainers)

Commits related to this issue

Most upvoted comments

I had the same issue, I fixed it by doing this:

   const convertPixelToInches = (value, dpi) => {
      let inches = value/ dpi;
      return `${inches}in`; // Calculate inches value and round it up.
    }
    const pageWidth = convertPixelToInches(req.body.evidenceWidth, 72);
    const pageHeight = convertPixelToInches(realPageHeight < req.body.evidenceHeight ? req.body.evidenceHeight : realPageHeight, 96);

And passing the values as width and height. @aslushnikov @rossj The only thing I’d like to understand now is why the dpi for the width and height need to be different for the width an height, where, if I return them from headless Chromium are 96 for height and width.

What is req.body.evidenceWidth , req.body.evidenceHeight and realPageHeight , supposed to be?

@jbprat really curious about this too

wisdom_of_the_ancients

I had the same issue, I fixed it by doing this:

   const convertPixelToInches = (value, dpi) => {
      let inches = value/ dpi;
      return `${inches}in`; // Calculate inches value and round it up.
    }
    const pageWidth = convertPixelToInches(req.body.evidenceWidth, 72);
    const pageHeight = convertPixelToInches(realPageHeight < req.body.evidenceHeight ? req.body.evidenceHeight : realPageHeight, 96);

And passing the values as width and height.

@aslushnikov @rossj The only thing I’d like to understand now is why the dpi for the width and height need to be different for the width an height, where, if I return them from headless Chromium are 96 for height and width.

What is req.body.evidenceWidth , req.body.evidenceHeight and realPageHeight , supposed to be?

I had the same issue, I fixed it by doing this:

   const convertPixelToInches = (value, dpi) => {
      let inches = value/ dpi;
      return `${inches}in`; // Calculate inches value and round it up.
    }
    const pageWidth = convertPixelToInches(req.body.evidenceWidth, 72);
    const pageHeight = convertPixelToInches(realPageHeight < req.body.evidenceHeight ? req.body.evidenceHeight : realPageHeight, 96);

And passing the values as width and height.

@aslushnikov @rossj The only thing I’d like to understand now is why the dpi for the width and height need to be different for the width an height, where, if I return them from headless Chromium are 96 for height and width.

After a bit more investigation, I determined that the nominal print-to-PDF scale is 144 pixels per inch (2 pixels per point), but that Chrome will adjust this value within some range if the whole page is close to fitting in the designated PDF size.

By adding extra grids to my test page above, to make the page’s body larger, I was able to get out of this extra scaling logic. Then, requesting a 1366px / 14.24in wide PDF renders exactly 2048 pixels of the page. Since this is the width at which the conditional 1024px style kicks in, I believe that @media widths are scaled by 2 when printing.

@aslushnikov thanks for the suggestion, yeah I tried Chrome and it produces the expected PDF.

Here’s a test case: https://output.jsbin.com/qogozes/1

This page has a div that is red above 1024px width viewports and yellow below it. When I use chrome it always creates a red PDF. With puppeteer though;

With any of these calls, I get a yellow PDF:

await page.emulateMedia("screen");
await page.setViewport({ width: 1024, height: 768 });

await page.pdf({ 
  printBackground: true,
  format: 'A4',
  margin: { top: 0, right: 0, bottom: 0, left: 0 } 
});

await page.pdf({ 
  printBackground: true,
  width: "1024px",
  height: "768px",
  margin: { top: 0, right: 0, bottom: 0, left: 0 } 
});

await page.pdf({ width: "1280px", /* ... etc */ });
await page.pdf({ width: "1366px", /* ... etc */ });

Only when I go above 1366px width on pdf call:

await page.pdf({ width: "1367px", /* ... etc */ });

It produces a red PDF. That is exactly 4/3 of 1024px (the responsive step) and this is consistent, always 4/3.

Chrome on the other hand produces a red pdf each and every time.