Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cannot render pdf with images stored locally #1643

Closed
acgrama opened this issue Dec 21, 2017 · 13 comments
Closed

Cannot render pdf with images stored locally #1643

acgrama opened this issue Dec 21, 2017 · 13 comments

Comments

@acgrama
Copy link

@acgrama acgrama commented Dec 21, 2017

I cannot get images stored locally to be rendered in generated pdf with Puppeteer, but external images for which I specify a url work.

In particular, in the sample code below, rendering the page in test_html1 works, while rendering the test_html2 does not work.

(async () => {
const browser = await puppeteer.launch({ args: ['--no-sandbox'] });
const page = await browser.newPage();
const test_html1 = `<html><h3>Hello world!</h3><img src="https://upload.wikimedia.org/wikipedia/commons/thumb/2/2f/Google_2015_logo.svg/1024px-Google_2015_logo.svg.png"></html>`;
 // const test_html2 = `<html><h3>Hello world!</h3><img src="file:///home/cristina/Documents/logo.jpg"></html>`;
await page.goto(`data:text/html,${test_html}`, { waitUntil: 'networkidle0' });
await page.pdf({ path: `${this.outputPath}/test-puppeteer.pdf`,
  format: 'A4', landscape: !data.isPortrait,
  margin: { top: '0.5cm', right: '1cm', bottom: '0.8cm', left: '1cm' }, printBackground: true });
await browser.close();
})();`

Result with test_html1:
test1

Result with test_html2:
test2

@yujiosaka
Copy link

@yujiosaka yujiosaka commented Dec 21, 2017

@acgrama

You can see why by launching with headless: false option.
I can see this error:
2017-12-21 21 58 18

It's a security block to protect from malicious websites to access local images, and cannot be disabled by --no-sandbox option.

@acgrama
Copy link
Author

@acgrama acgrama commented Dec 21, 2017

Aha, thank you, this is very valuable information!

Too bad I cannot start with headless: false option, since I'm running in docker as root (also the reason why I'm using the -no-sandbox option).

Is there any way to embed locally stored images in a rendered pdf? We are generating dynamic pdf reports, so if images cannot be embedded from local storage Puppeteer seems kind of useless for us...

@yujiosaka
Copy link

@yujiosaka yujiosaka commented Dec 21, 2017

@acgrama
In that case, why don't you just serve those static files? Something like this should work:

const puppeteer = require('puppeteer');
const express = require('express');
const app = express();
app.use(express.static('/home/cristina/Documents'))
app.listen(3000);

(async () => {
  const browser = await puppeteer.launch({ args: ['--no-sandbox'] });
  const page = await browser.newPage();
  const test_html = `<html><h3>Hello world!</h3><img src="http://localhost:3000/logo.jpg"></html>`;
  await page.goto(`data:text/html,${test_html}`, { waitUntil: 'networkidle0' });
  await page.pdf({ path: `${this.outputPath}/test-puppeteer.pdf`,
    format: 'A4', landscape: !data.isPortrait,
    margin: { top: '0.5cm', right: '1cm', bottom: '0.8cm', left: '1cm' }, printBackground: true });
  await browser.close();
})();

Also I checked Chromium options, but I don't find useful one for that.

@dhruv-m-patel
Copy link

@dhruv-m-patel dhruv-m-patel commented Dec 21, 2017

I faced this issue a while ago, hosting images on an s3 bucket and using that image worked fine.

+1 for local resource support request though when running puppeteer inside docker 👍

@ebidel
Copy link
Contributor

@ebidel ebidel commented Dec 21, 2017

Dupe of: #1472

As part of the docker image/script, you could also start a web server to serve the image. Basically run @yujiosaka's example :)

You could also write the file dynamically, navigate to it, and generate the pdf. This worked for me, assuming logo.jpg is in the same dir as your script:

const puppeteer = require('puppeteer');
const fs = require('fs');
const {promisify} = require('util');

const OUT_FILE = 'myfile.html';

(async() => {
  await promisify(fs.writeFile)(OUT_FILE, `
    <html>
      <h3>hello image!</h3>
      <img src="logo.jpg">
    </html>
  `);

  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  await page.goto(`file://${process.cwd()}/${OUT_FILE}`);
  await page.pdf({path: 'page.pdf'});

  promisify(fs.unlink)(OUT_FILE); // cleanup

  await browser.close();
})();

You'll probably need the --allow-file-access-from-files and --enable-local-file-accesses flags if you're loading other types of resources, but image load fine over file://.

@acgrama
Copy link
Author

@acgrama acgrama commented Dec 22, 2017

@yujiosaka @ebidel Thank you both for the suggestions! Yeah, I was lazily hoping we could reuse as much of our current (PhantomJS-based) code as possible ;-)
But it should be feasible to change our current approach without too much effort.

@aslushnikov aslushnikov closed this Jan 4, 2018
@tuxalin
Copy link

@tuxalin tuxalin commented Apr 16, 2018

I've stumbled with this issue too, another solution would be to use data streams for the img src, eg:

function base64_encode(file) {
var bitmap = fs.readFileSync(file);
return new Buffer(bitmap).toString('base64');
}
img.src = 'data:image/png;base64,' + base64_encode(imagePath);

randytarampi added a commit to randytarampi/me that referenced this issue Aug 16, 2018
randytarampi added a commit to randytarampi/me.resume that referenced this issue Aug 17, 2018
randytarampi added a commit to randytarampi/me.resume that referenced this issue Aug 17, 2018
randytarampi added a commit to randytarampi/me.resume that referenced this issue Aug 17, 2018
randytarampi added a commit to randytarampi/me.resume that referenced this issue Aug 18, 2018
randytarampi added a commit to randytarampi/me.resume that referenced this issue Aug 18, 2018
@bogacg
Copy link

@bogacg bogacg commented Aug 18, 2018

Puppeteer is officially supported under Google Cloud Functions, therefore I believe it would be correct thing to have an option to use local files without need of an extra server or temporarily saving html somewhere.

randytarampi added a commit to randytarampi/me.resume that referenced this issue Aug 19, 2018
randytarampi added a commit to randytarampi/me that referenced this issue Aug 19, 2018
@JaapWeijland
Copy link

@JaapWeijland JaapWeijland commented Sep 10, 2018

I am using Puppeteer for PDF generation on GCF too. There should be a way to embed an image without spinning up a server.

@TheProgrammer21
Copy link

@TheProgrammer21 TheProgrammer21 commented Sep 13, 2021

@acgrama
In that case, why don't you just serve those static files? Something like this should work:

const puppeteer = require('puppeteer');
const express = require('express');
const app = express();
app.use(express.static('/home/cristina/Documents'))
app.listen(3000);

(async () => {
  const browser = await puppeteer.launch({ args: ['--no-sandbox'] });
  const page = await browser.newPage();
  const test_html = `<html><h3>Hello world!</h3><img src="http://localhost:3000/logo.jpg"></html>`;
  await page.goto(`data:text/html,${test_html}`, { waitUntil: 'networkidle0' });
  await page.pdf({ path: `${this.outputPath}/test-puppeteer.pdf`,
    format: 'A4', landscape: !data.isPortrait,
    margin: { top: '0.5cm', right: '1cm', bottom: '0.8cm', left: '1cm' }, printBackground: true });
  await browser.close();
})();

Also I checked Chromium options, but I don't find useful one for that.

If I use your code it only quite works. When I look at the web page in headless: false mode the image is rendered, when I generate the pdf file (or a screenshot) though, the image file is not rendered:
image

Any ideas/solutions?

@TheProgrammer21
Copy link

@TheProgrammer21 TheProgrammer21 commented Sep 13, 2021

@acgrama
In that case, why don't you just serve those static files? Something like this should work:

const puppeteer = require('puppeteer');
const express = require('express');
const app = express();
app.use(express.static('/home/cristina/Documents'))
app.listen(3000);

(async () => {
  const browser = await puppeteer.launch({ args: ['--no-sandbox'] });
  const page = await browser.newPage();
  const test_html = `<html><h3>Hello world!</h3><img src="http://localhost:3000/logo.jpg"></html>`;
  await page.goto(`data:text/html,${test_html}`, { waitUntil: 'networkidle0' });
  await page.pdf({ path: `${this.outputPath}/test-puppeteer.pdf`,
    format: 'A4', landscape: !data.isPortrait,
    margin: { top: '0.5cm', right: '1cm', bottom: '0.8cm', left: '1cm' }, printBackground: true });
  await browser.close();
})();

Also I checked Chromium options, but I don't find useful one for that.

If I use your code it only quite works. When I look at the web page in headless: false mode the image is rendered, when I generate the pdf file (or a screenshot) though, the image file is not rendered:
image

Any ideas/solutions?

The reason is ERR_UNSAFE_PORT! The port 6666 is considered unsafe. A full list of unsafe ports can be seen here: https://superuser.com/questions/188058/which-ports-are-considered-unsafe-by-chrome
So just change the port to something that is not on this list and everything works fine.

@robmorgan
Copy link

@robmorgan robmorgan commented Oct 6, 2021

I adapted the example above to the following. This works for me with local images such as http://localhost:3000/image.png and automatically stops express after generating the PDF.

'use strict';

const fs = require('fs');
const puppeteer = require('puppeteer');
const express = require('express');

const app = express();
const port = 3000;

app.use(express.static('template'));

var server = app.listen(port, function(err){
  if (err) console.log('Error in Webserver setup')
  console.log(`Webserver listening at http://localhost:${port}`);
});

(async() => {    
  const browser = await puppeteer.launch({ args: ['--no-sandbox'] });
  const page = await browser.newPage();    

  var contentHtml = fs.readFileSync('./template/test.html', 'utf8');
  await page.setContent(contentHtml);

  await page.pdf({
    path: 'test.pdf',
    format: 'A4',
    margin: {
          top: "20px",
          left: "20px",
          right: "20px",
          bottom: "20px"
    }    
  });
  await browser.close();
  await server.close(function(){
    console.log('Stopping webserver.')
  });
})(server);

@JaiParakh
Copy link

@JaiParakh JaiParakh commented Oct 22, 2021

I faced this issue a while ago, hosting images on an s3 bucket and using that image worked fine.

+1 for local resource support request though when running puppeteer inside docker 👍

Could you please share a sample

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet