Skip to content
This repository has been archived by the owner on May 30, 2023. It is now read-only.

PhantomJS 2: PDF rendering too large, page.zoomFactor doesn't work #12685

Closed
thomasbachem opened this issue Oct 28, 2014 · 201 comments
Closed

PhantomJS 2: PDF rendering too large, page.zoomFactor doesn't work #12685

thomasbachem opened this issue Oct 28, 2014 · 201 comments
Assignees

Comments

@thomasbachem
Copy link

I compiled PhantomJS 2 HEAD on OS X 10.9.5 (MacBook Pro Retina) via brew install phantomjs --HEAD.

When rendering a PDF via rasterize.js, the page contents are rendered much larger than with PhantomJS 1.9, and using the zoom argument doesn't change anything at all.

Experimenting with paperSize, the page contents that do usually fit exactly into 210mm (A4) do now need 303mm, so there's a 144% increase in size.

@elgarfo
Copy link

elgarfo commented Nov 3, 2014

+1 experiencing exactly the same problem.
compiled from source (eddb0db) on debian 7.6

any idea how to work around this?

edit: used css to work around the problem for now
downsides: rendering gets a little ugly and its still a pixel too wide i'd say

html {
    zoom: 0.68; /*workaround for phantomJS2 rendering pages too large*/
}

@mgartner
Copy link

mgartner commented Jan 3, 2015

I'm seeing this issue too. It is especially frustrating that I'm trying to use 2.0 to get webfont support, but normal HTML content doesn't fit into PDFs like it did so nicely in 1.9.x.

I'd really appreciate it if anyone could point me in the right direction to what might be causing this in the PhantomJS code base. I'd glady work on submitting a PR to fix this.

@thomasbachem
Copy link
Author

@ariya I fear this bug will be overlooked quite easily regarding the amount of open tickets. Perhaps you can mark this ticket properly as a regression?

@9point6
Copy link

9point6 commented Feb 19, 2015

+1 still broken in 2.0 brach

@bompus
Copy link
Contributor

bompus commented Feb 20, 2015

Still broken for me also. The elgarfo patch works, but it's hacky :)

@ariya
Copy link
Owner

ariya commented Feb 22, 2015

@thomasbachem Good point, labelling it now. Thanks!

@thomasbachem
Copy link
Author

@ariya Thanks, I just linked two duplicate tickets.

@polarathene mentions some observations in #12936 that may be of help, though I couldn't verify those myself:

I've just done some thorough testing of 2.0.0 PDF conversions and noticed the paperSize 'A4' definition that was 992px wide(120dpi) in 1.9.8 is now 595px wide(72dpi) in 2.0.0. Previously in 1.9.8 your unit values other than % had were scaled down, so that the PDF version required you to zoom to 125% to view units at their original size, also caused the whitespace issue on the right present in your picture due to that scaling. In 2.0.0 units don't appear to be scaled down, they are 100% matching to the browser. paperSize however is now scaling up from it's given px size by 1/3rd, this is matching it to 96dpi/96px per 1 inch which is a CSS inch. Thus 72DPI 595px A4 paperSize is resized to 794px 96DPI A4 when rendered. I've also learnt from OSX(and likely Linux) users that 1.9.8 provided 100% match for their PDF renders but with 2.0.0 they're having to downscale their units from 96dpi to 72dpi, eg 794px wide element needs to be changed to 595px for correct 100% zoom A4 PDF, for comparision on 1.9.8 with windows users that meant changing from 96dpi to 120dpi, 794px wide element needed to be 992px wide. Also worth noting is that between both versions % units would still remain precise, 90% element would always be 90% of the PDF at 100% zoom, for whatever reason they were not scaled like other units.

@mkrn
Copy link

mkrn commented Feb 25, 2015

+1 Version 2.0 fixes Function.prototype.bind but breaks paperSize)

@Feendish
Copy link

Feendish commented Mar 6, 2015

Also experiencing this issue. I'd love to switch to v2 to fix the page-break-avoid:inside issue but this is a problem now.

elgarfo's tweak seems to work but as noted it causes rendering issues.

Has anyone a better work around? Are there adapted pixel settings for A4&Letter? I'm on Unix.

@polarathene
Copy link

@Feendish I wrote a lot on the mentioned issue above, but if you read the workaround I gave for mac it should work for unix.

@jimclarkuk
Copy link

@Feendish Following on from the analysis by @polarathene we opted for a transform which seemed to result in fewer rendering issues than using zoom.

.page {
    transform-origin: 0 0; 
    -webkit-transform-origin: 0 0; 
    transform: scale(0.75); 
   -webkit-transform: scale(0.75);
}

@Feendish
Copy link

Feendish commented Mar 9, 2015

@polarathene thanks. You gave a comprehensive break down. I just couldn't follow it exactly.

I tried using the dimensions_width formula you suggested but it was still off.

In the end given your note that the DPI is 72 in Mac/Unix I use a DPI to pixel calculator http://www.hdri.at/dpirechner/dpirechner_en.htm and just hard coded the pageSize to 595x842 for A4 Portrait. 595px = 8.26772 inches x 72dpi where 8.26772=210mm.

@jimclarkuk I tried your solution too with no luck. Page breaking was messed up with overlapping elements.

@thomasbachem
Copy link
Author

@Feendish What exactly do you mean? Setting

page.paperSize = { width: '595px', height: '842px', margin: '0px' };

Doesn't change or fix anything for me under Mac OS X 10.9. The page is still too small compared to 1.9.8.

@Feendish
Copy link

Feendish commented Mar 9, 2015

@thomasbachem I'm building the HTML from scratch to test it. Haven't tried running it on established HTML source yet.

I used Bootstrap 3 to make a sample long Invoice.

JS code-> http://pastie.org/10011943
HTML source -> http://s000.tinyupload.com/index.php?file_id=97390552363329839541
Bootstrap override.css -> http://pastie.org/10011951

It now generates a clean A4 sized portait PDF of 44 pages.

I'm on Linux (Centos 6.4).

@polarathene
Copy link

@thomasbachem It's been a while but from memory that's the dimensions for A4 at 72dpi(Mac/Linux): http://www.a4papersize.org/a4-paper-size-in-pixels.php

Setting A4 as your papersize would have the same effect. I don't have a mac and haven't tested on linux, what are the px dimensions of an A4 pdf document for you at 100%? On windows they're A4 at 96dpi, I'm guessing you get 72dpi(595x842)? It's been a while but I think you need to upscale your viewport from 72dpi px to 96dpi. On 1.9.8 I used a similar technique that @jimclarkuk provided, though mine scaled up(120dpi to 96dpi). The windows workaround on 1.9.8 wasn't perfect however, if you can get away with it, you should be able to adjust the paperSize to fit your viewports(probably not the same px width/height) and then alter the zoom on the pdf viewer to see the document as intended.

Another alternative could be to run a windows VM or use a web service like Azure to run Phantom on a windows instance.

@Feendish
Copy link

Feendish commented Mar 9, 2015

Just to clarify @polarathene on Linux setting "A4" as paperSize doesn't work. The content is sliced off on the right hand side.

I have to explicitly set the pixel width&height to get it to work on Linux with v2.0.0

@polarathene
Copy link

@Feendish what viewport size are you using with your papersize?

@Feendish
Copy link

Feendish commented Mar 9, 2015

I'm not setting a viewport. I always assumed it was one or the other based on Phantom examples.

Should I be setting one? In 1.9 branch it worked without viewport.

@polarathene
Copy link

I'd be interested to know if your results are different after setting the viewport. If you get a full A4 pdf filled with the website, also try setting your viewport to half just to confirm that you're getting half once it's rendered to pdf format.

My understanding is that there is a default viewport size, I can't recall what that is though. Plenty of responsive sites will adjust based on the viewport you provide, as well as fixed width sites. Both I imagine can be affected by having a poorly chosen viewport size?

@thomasbachem
Copy link
Author

@polarathene @Feendish I tried to play around with setting different viewport sizes, and it changed nothing with PhantomJS 2.

@polarathene
Copy link

@thomasbachem it would depend on the site you're rendering. If you use it on a responsive site that has a mobile layout at a small viewport, changing your viewport to a small size should trigger it just like resizing your chrome window would. I think rendering will still scroll the viewport if needed to fill in the papersize? I'll be using phantom again soon, perhaps can set up an example project for the issue with workaround :)

Honestly though, the issue is with phantom, with 2.0.0 osx/linux got the problems windows had with 1.9.8, while on 2.0.0 windows works like osx/linux used to. Whomever worked on that part of phantom should be able to provide a fix, even if it's a different build which breaks windows, I'd imagine that'd be the quick fix.

@polarathene
Copy link

If phantomjs itself is the cause, I can only see tinkering with the values here: https://github.com/ariya/phantomjs/blob/2.0/src/webpage.cpp#L1061 that seem like they'd be relevant, but it might actually be handled by QT which was updated with 2.0.0. I see plenty of references for 96dpi, perhaps dpi handling has changed between the QT versions used in 1.9.8 and 2.0.0....which'd mean phantomjs won't ever fix this issue until QT does? I have no QT experience, if someone from the phantomjs team could chime in, is it a QT bug or has phantomjs done something differently with pdf rendering via QT since?

@Feendish
Copy link

Feendish commented Mar 9, 2015

Setting a viewport of

page.viewportSize = {
width: 595,
height: 400
};

has no effect on the rendered page.

@polarathene
Copy link

@Feendish try 100x100,if you're still getting no difference then tweaking viewport won't help much. Again I did say it completely depends on the website design itself. For the website I was working with, js scripts were generating highchart graphs based on viewport width, they rendered incorrectly without setting the viewport properly.

@jrf0110
Copy link

jrf0110 commented Mar 10, 2015

+1 this sucks really bad :(

@Fabbok1x
Copy link

Fabbok1x commented May 14, 2017

Wow, phantom js dev seems dead, or just noone caring for the issues coming up. How can a zoomFactor issue persist for that long? It is one crucial function. Switching to electron pdf now. Works like a charm.

@miwim
Copy link

miwim commented May 16, 2017

Hi there,
I work in a project where MacOs and Windows are used with PhantomJs 2.1.1, and we discovered that the measures in cm are working in Windows but not in Mac and the measures in pixels are working in Mac but not in Windows. Therefore we set a different solution depending on the OS. Unfortunatelly I have not tested it in Linux, but I hope this could help.

@SWGFL
Copy link

SWGFL commented May 23, 2017

From my fiddlings, the solution I have come up with is to set the paper size to what it should be (A4 or whatever), then simply set the width of the document on the body tag in pixels, that seems to zoom it to fit the page to the paper size you have set.

I have rewritten rasterize.js to do this for you with a DPI setting. On the commandline use:

phantomjs rasterize.js https://google.com/ test.pdf --dpi 200[ --optionname value,]

Use the option names defined in args. Also you can use - for either input or output to read from STDIN or STDOUT:

"use strict";
var page = require('webpage').create(),
	system = require('system'),
	render = function (phantom, page, output, config) {
		
		// set DPI
		if (config.dpi !== null) {
			page.evaluate(function (dpi) {
				var div = document.createElement("div"),
					body = document.getElementsByTagName("body")[0],
					dpi;
				div.style.width = "1in";
				body.appendChild(div);
				if (dpi > div.offsetWidth) {
					body.style.width = ((body.offsetWidth / div.offsetWidth) * dpi) + "px";
				}
			}, config.dpi);
		}
		
		// timeout for javascript
		window.setTimeout(function () {
			page.render(output, {format: config.format, quality: config.quality});
			phantom.exit();
		}, config.delay);
	},
	config = {
		width: 800, // image width
		height: 600, // image height
		format: "pdf", // pdf png, gif, jpg
		paper: "A4",
		orientation: "portrait",
		margin: "2.54cm",
		delay: 300, // javascript delay
		href: "/", // the href alias of the page, allows it to retrieve assets relatively
		dpi: 200,
		quality: 80 // jpeg quality
	},
	output, key;

if (system.args.length > 2) {
	
	// process arguments
	system.args.slice(3).forEach(function (arg) {
		if (key) {
			config[key] = arg;
			key = null;
		} else if (arg.indexOf("--") === 0) {
			key = arg.substr(2);
		}
	});
	
	// set paper size
	if (config.format === "pdf") {
		page.paperSize = {
			format: config.paper,
			orientation: config.orientation,
			margin: config.margin
		};
	} else {
	
		// set viewport size
		page.viewportSize = {
			width: config.width,
			height: config.height
		};
		page.clipRect = { top: 0, left: 0, width: config.width, height: config.height };
	}
	
	output = system.args[2] === "-" ? "/dev/stdout" : system.args[2];
	if (phantom.version.major > 1) {
		system.stdout.setEncoding("ISO-8859-1");
	}
	
	// output page from stdin
	if (system.args[1] == "-") {
		if (phantom.version.major > 1) {
			system.stdin.setEncoding("UTF-8");
		}
		page.setContent(system.stdin.read(), config.href);
		render(phantom, page, output, config);
	
	// output page from url
	} else {
		page.open(system.args[1], function (status) {
			if (status !== 'success') {
				console.log('Unable to load the address!');
				phantom.exit(1);
			} else {
				render(phantom, page, output, config);
			}
		});
	}
}

I also had an issue where 2.1.1 rasterized everything on Linux, so I am now using 1.9.8 on there, which this script compensates for, so should work with old and new versions.

Note that this fix relies on your content being responsive (not in a fixed width container), it basically sets the width of the body tag, and the output zooms it to fit on the page.

@geoffcallender
Copy link

geoffcallender commented Jun 5, 2017

This really has to be re-opened. The workarounds above are hacks, should not be necessary, and so far I've found they can't handle my more complex reports, whereas 1.9 was stable (until #14558 which has been closed but not fixed). I'm marooned.

@apokryfos
Copy link

@geoffcallender The issue is closed because there's a fix in master that will be part of the next stable release of PhantomJS. However there hasn't been any activity in this project in quite a while now and I suspect it's dead so chances are there won't be a stable release.

@replete
Copy link

replete commented Jun 5, 2017

@geoffcallender You could try electron-pdf. I'm holding out for headless chrome.

For anyone else stuck, this will help your poor souls:

/*------------------------------------------------------------------------------
    PDF
--------------------------------------------------------------------------------
    This file contains styles for the main layout used to generate PDFs.
    Basic styles should be kept separate from renderer-specific fixes.

    PhantomJS-specific styles are at the bottom of this file
------------------------------------------------------------------------------*/

html.pdf-renderer {
  /*----------------------------------------------------------------------------
    Global styles
  ----------------------------------------------------------------------------*/
  body {
    background:#fff;
  }

  .main-container {
    width: 800px;
  }

  /*----------------------------------------------------------------------------
    Global fixes for PDF rendering
  ----------------------------------------------------------------------------*/

  // Remove box shadow:
  *,
  *::after,
  *::before {
    box-shadow: none !important
  }


  // print helpers: ------------------------------------------------------------
  // Add to elements to force page-break behaviour.
  .page-break-before {
    page-break-before: always !important;
    float: none;
  }

  // This is usually the solution you need. Add this to anything, and the page should not break inside it. If this didn't work, you have a child element with a 'float:' set. Remove it.
  .page-break-avoid {
    page-break-inside: avoid !important;
    float: none;
  }

  .page-break-after {
    page-break-after: always !important;
    float: none;
  }


  // PhantomJS-specific fixes: -------------------------------------------------
  &.is-phantomjs-pdf {
    /** 
     * This CSS class is added by phantomJS. Set 'injectJs' option to a new
     * javascript file that contains this one line:
          document.documentElement.classList.add('is-phantomjs-pdf');
     */

    // Fix extra blank pages
    height:0;

    body {

      // Fixes scale issue (renders at 96dpi instead of 72dpi)
      // https://github.com/ariya/phantomjs/issues/12685
      zoom: 0.65;

      // Fixes text color:
      color: inherit !important;
    }

    // Fixes ugly unstyled checkbox inputs:
    input[type=checkbox]:not(.checkbox) {
      -webkit-appearance:none;
      border: 1px solid #ccc;
      border-radius:2px;
      height:1.16em;
      width:1.16em;
      margin:0 0 0 0;
      display:inline-block;
      vertical-align:text-bottom;
      position:relative;

      &:checked::after {
        content:'';
        display:inline-block;
        width:1em;
        height:1em;
        -webkit-transform: translate(0, -1px);
      }
    }

    // Examples of some PhantomJS-specific fixes we found ourselves needing:

    // Chartist SVG fixes:
    .ct-chart svg {
      .ct-grid.ct-vertical {
        stroke-dasharray: none; // should be '0', but we must use 'none' for PhantomJS
        stroke: #eeeeee !important;
      }
    }


    .widget {
      // Border fixes:
     .widget-heading {
        border-bottom: 1px solid  #ddd;
      }

      // Button fixes:
      .btn {
        padding-bottom: 3px;
      }
    }
  }
}

@duskan
Copy link

duskan commented Jul 17, 2017

In my case,
First, I used print.css

zoom:0.75

but page layout is broken

so, I use this page_size (a4 / 0.75)

width : 210 * 4/3 // avoid pantomjs bug
height : 297 * 4/3 // avoid pantomjs bug

it realy work good!
but i, as possible, need to change a4 size without manual
(because of customer T.T)

@jazo10
Copy link

jazo10 commented Aug 31, 2017

Just adding to this, and hopefully saving a few headaches for people. the devices DPI is taken into effect in this.

We run phantomJS on an AWS machine that we remote into, one of our dev's (who has a 300+dpi laptop) connected and restarted the server, which updated the DPI of the server itself, causing all PDF outputs to become really small. we had to connect with a standard machine (non retina) to reset it back to normal.

@alvara
Copy link

alvara commented Sep 11, 2017

Thank you all for posting your solutions. I combined two of the above answers and was eventually able to get it to display properly using the following in my css:

html {
    height: 0;
    transform-origin: 0 0;
    -webkit-transform-origin: 0 0;
    transform: scale(0.53);
    -webkit-transform: scale(0.53);
}

For me, the key was setting the height to 0, without was causing my single page pdf to take up two pages.

@PierBover
Copy link

Hey @jazo10 so how can you set the AWS machine DPI setting?

@jazo10
Copy link

jazo10 commented Jan 29, 2018 via email

@SWGFL
Copy link

SWGFL commented Feb 2, 2018

Headless Chrome through Puppeteer is much better than this as it uses the latest Chromium rather than an ancient version of QT. Produces much better results.

@kromit
Copy link

kromit commented Feb 2, 2018

@SWGFL did you get header and footer configurable?

@apokryfos
Copy link

@kromit since Chrome/Chromium headless supports CSS3 now we can probably configure the header and footer via CSS instead of via custom PDF libraries. Check https://stackoverflow.com/a/46368450/487813 it is useful. It's not native but then again CSS3 page styles are there just for this reason (I think).

@SWGFL
Copy link

SWGFL commented Feb 2, 2018

Version 1.0.0. of Puppeteer has just been released, and this includes now support for headers and footers. See https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#pagepdfoptions

@PierBover
Copy link

@SWGFL thanks for the suggestion!

It seems rendering an HTML string is done using page.setContent(htmlString). I'll be doing some tests and report back.

@SWGFL
Copy link

SWGFL commented Feb 5, 2018

FYI: You might have more milage in using:

await page.goto("data:text/html,"+html, {waitUntil: "networkidle0"});

rather than:

const loaded = page.waitForNavigation({waitUntil: "load"});
await page.setContent(html);
await loaded;

@PierBover
Copy link

Thanks @SWGFL . We're on national holidays here, but I'll report back once I've made some tests.

@fergardi
Copy link

I'm running with this issue too. PhantomJS 2.1.1 here. In Windows is working flawlessly. In Linux, it appears zoomed and with text centering and other minor CSS issues (strange btw, because the WebKit version should be the same in both binaries).

Any advances?

@theaspect
Copy link

@fergardi phantomjs was discontinued, try headless chrome

@panamantis
Copy link

Wow. Per suggestions above for >2.0.0, I had luck enlarging my A4 for pdf generation by scaling the html css style from within the rasterizer.js just before the page.render.

page.evaluate(function () {
var the_page = document.getElementsByTagName("page")[0];
the_page.style.transformOrigin='0 0';
the_page.style.WebkitTransformOrigin='0 0';
the_page.style.transform='scale(1.28)';
the_page.style.webkitTransform ='scale(1.28)';
);

@gongfan99
Copy link

gongfan99 commented Jul 29, 2021

The problem with using "transform: scale(0.75);" is that it broke the page break.

The only method I found is that to set the options below. BTW, I use html-pdf which is wrapper around phantomjs, and I want letter size (8.5 inch by 10 inch) for the pdf.

    let width = 8.5, height = 11.0; // Letter size in inches
    if (process.platform !== "win32") { // phantomJs has issue creating pdf in linux; has to zoom 1/0.75 first, then use ghostscript later to resize to letter size
      width /= 0.75;
      height /= 0.75;
    }
    
    const options = { 
      "height": `${height}in`,        // allowed units: mm, cm, in, px
      "width": `${width}in`,            // allowed units: mm, cm, in, px
    };

The above settings should give good pdfs if you open the file with Acrobat Reader. The only caveat is that the pdf is not letter size but 4/3 larger than letter size.

If you want perfect result, you can then use ghostscript to resize the pdf to letter size. The command is:

const { exec } = require("child_process");

function execPromise(command){
  return new Promise(function(resolve, reject){
    exec(command, (error, stdout, stderr) => {
      if (error) reject(error.message);
      if (stderr) reject(stderr);
      resolve(stdout);
    });
  });
}

const inputFiles = '/tmp/input.pdf';
const outputFile =  '/tmp/output.pdf';

let command = `gs -dNOPAUSE -dFIXEDMEDIA -dPDFFitPage -sDEVICE=pdfwrite -sPAPERSIZE=letter -sOUTPUTFILE=${outputFile} -dBATCH ${inputFiles}`;
await execPromise(command); // sorry, I used await outside of async; you know what I mean :-)

@paulegradie
Copy link

paulegradie commented Dec 19, 2021

I realize this issue is now closed, however I thought I would share my simple solution for this problem. I can't guarantee it will work for you, but alas - it worked for me. I'm using phantomjs 2.1.1.

In summary - set the format (or I guess paper property, depending on which config type you use) property on the paper options config to A3.

I think A4 might be the default, so setting it A3 makes the page bigger, but the content still tries to fill it. So by changing the paper size, you don't change the aspect ratio, just the surface size. When I write out PDFs after doing this, the size is zoomed out correctly to match what I see when I set the format property to A4 on windows.

For example (taken at random from someone's code above in this thread):

config = {
    width: 800, 
    height: 600, 
    format: "pdf",
    paper: "A4",   <--------------------- Change this to "A3"
    orientation: "portrait",
    margin: "2.54cm",
    delay: 300, 
    href: "/",
    dpi: 200,
    quality: 80
}

This is what I do:

{
    orientation: 'portrait',
    format: 'A3',   <------------------ This made the magic happen
    border: '.3in',
    footer: {
      height: '14mm'
    }
}

@michalzan
Copy link

I realize this issue is now closed, however I thought I would share my simple solution for this problem. I can't guarantee it will work for you, but alas - it worked for me. I'm using phantomjs 2.1.1.

In summary - set the format (or I guess paper property, depending on which config type you use) property on the paper options config to A3.

Thanks this kind of works. The only downside is when you try to print the pdf created like this from chrome. You need to change the scale property to fit to paper, because default would print it bigger.

# for free to subscribe to this conversation on GitHub. Already have an account? #.
Projects
None yet
Development

No branches or pull requests