puppeteer: Custom Fonts not loaded when pdf generated

I try to generate a PDF with custom font but it doesn’t work when I inject fonts like this :

import { injectGlobal } from 'styled-components'
import Book from './assets/fonts/718/sevenoneeight-medium.woff2';
import Book2 from './assets/fonts/718/sevenoneeight-medium.woff';

injectGlobal `
@font-face {
	font-family: "718book";
	src: url(${Book}) format("woff2"),
		url(${Book2}) format("woff");
}

body {
  font-family: '718book';
  -webkit-print-color-adjust: exact;
}
`;

Someone have an idea how to import fonts correclty ? I need a solution that works with any url -> http://localhost:3000, http://xxxxx.com

It works when I generated a fonts folder in my build and I inject fonts like this :


injectGlobal `
@font-face {
	font-family: "718book";
	src: url('http://localhost:3000/fonts/718/sevenoneeight-medium.woff2') format("woff2"),
		url('http://localhost:3000/fonts/718/sevenoneeight-medium.woff') format("woff");
}
`;

I convert my html to pdf like this :

let convertHTMLToPDF = async (html, callback, options = null) => {
    if (typeof html !== 'string') {
        throw new Error('xxxx.');
    }
    const browser = await puppeteer.launch();
    const page = await browser.newPage();
    await page.setRequestInterception(true);

    page.once('request', request => {
        request.respond({ body: html });
        page.on('request', request => request.continue());
    });

    const response = await page.goto('https://google.com');
    await page.setContent((await response.buffer()).toString('utf8'));
    await page.evaluateHandle('document.fonts.ready');
    await page.pdf(options).then(callback, function(error) {
        console.log(error);
    });
    await browser.close();
};

About this issue

  • Original URL
  • State: closed
  • Created 6 years ago
  • Reactions: 39
  • Comments: 24

Commits related to this issue

Most upvoted comments

@elrumordelaluz @billyvg What worked for us is to wait till the font’s are actually loaded before you create your pdf file, using this command:

await page.evaluateHandle('document.fonts.ready');

This seems the best solution so far. As you can use custom fonts that are registered system wide and do not need to load them as base64 strings.

All credits go to this post: https://github.com/puppeteer/puppeteer/issues/422#issuecomment-708142856

EDIT: Important to note that we are using custom fonts that are imported system wide and NOT inline as base64 encoded.

I’ll expose my tests in case is useful for anyone to continue debug or to find a better solution:

I am generating a pdf from an html string. Something like:

await page.goto(`data:text/html,${htmlString)}`, { waitUntil: 'networkidle0' })
const pdf = await page.pdf()

Since the html string uses an external font I tried different options that I’ll describe below. However only the last one works without differences on two environments. I am testing in local using OSX and in an external server using Linux. Here are the different options I tried:

  • Adding @import url('https://fonts.googleapis.com/css2?family=…'); into a <style> tag of the html string passed
  • Passing that url as addStyleTag url
  • Serving the web fonts and stylesheet in the same server where puppeteer is working and passing a addStyleTag path
  • Also tried playing with await page.evaluateHandle('document.fonts.ready') and another waitUntil values.

In all those scenarios, the resulting pdf were generated with the wrong font and using different fallback, since are different operating systems.

The solution that works cross platform is to pass this css declaration inside the html string:

@font-face {
      font-family: 'Inconsolata';
      src: url(data:application/font-woff2;charset=utf-8;base64,…),
         url(data:application/font-woff;charset=utf-8;base64,…);
      font-weight: normal;
      font-style: normal;
}

I hope there are better solutions since the base64 string for only one style of a font family is pretty big, and increases in cases where more styles or families are needed in the same page.

In some cases this is a bug related to the Access-Control-Allow-Origin limitations.

If you trust the url source, adding --disable-web-security fixes the issue.

I also facing the same issue. I have attached the PDf file generated from Puppeteer. However, in my case I am using all my custom fonts base64 encoded. The page is rendered properly in chrome, chromium and also I tested headless: false, in all these case pages is rendered fine. But in headless: true all font is missing. hn.pdf

@font-face { font-family:ffc; src:url('data:application/font-woff;base64,d09…)format(“woff”); }

.ffc{font-family:ffc;line-height:1.589369;font-style:normal; font-weight:normal; visibility:visible; }

If you intend to use this only for pdf then maybe you could base64 encode the font and load it via url.

Hi @shibli786,

I have found a work around, it seem like the font need to be rendered as lead one time, so i have create a dummy div right after body tag to render that font, e.g,

<body>
<div class="__dump ff36">_</div>
...
</body>

See my real code here:

const rfont = /@font-face.*font-family:([^;]+)/g;

function prepareContent(html) {
  const $ = cheerio.load(html);
  const $body = $('body');
  const $style = $('style');
  const fonts = [];

  $style.each((_, elem) => {
    const $this = $(elem);

    $this.html().replace(rfont, (_, font) => fonts.push(font));
  });

  fonts.forEach((font) => {
    $body.prepend(`<div class="__dump ${font}">_</div>`);
  });

  return $.html();
}

(async () => {
  html = prepareContent(html);

  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  // await page.goto(`file:${path.join(__dirname, 'books/Computer Programming with C++/1 - Introduction.html')}`, {
  await page.setContent(html);
  await page.evaluateHandle('document.fonts.ready');
  await page.evaluate(() => {
    var dumps = document.querySelectorAll('.__dump');
    dumps.forEach((dump) => dump.parentNode.removeChild(dump));
})
  await page.pdf({path: 'hn.pdf', format: 'A4'});

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