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

Custom Fonts not loaded when pdf generated #3183

Closed
KrifaYounes opened this issue Sep 4, 2018 · 24 comments · Fixed by #12175
Closed

Custom Fonts not loaded when pdf generated #3183

KrifaYounes opened this issue Sep 4, 2018 · 24 comments · Fixed by #12175
Assignees
Labels
bug chromium Issues with Puppeteer-Chromium confirmed P3

Comments

@KrifaYounes
Copy link

KrifaYounes commented Sep 4, 2018

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();
};
@aslushnikov aslushnikov added the chromium Issues with Puppeteer-Chromium label Dec 6, 2018
@krokofant
Copy link

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

@AlaaHamoudah
Copy link

I ams still facing this issue, @aslushnikov do you have any suggestions?

@lovenick
Copy link

Not sure if you've seen #422 , but it might be relevant to your issue.

@shibli786
Copy link

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;
}

@haan123
Copy link

haan123 commented Mar 22, 2020

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();
})();

@sorkhemiri
Copy link

i had the same issue. i finally ended up using fonts served in directory on public URL as CDN and it works fine for me right now.

@elrumordelaluz
Copy link

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.

@billyvg
Copy link

billyvg commented Aug 19, 2020

@elrumordelaluz Have you tried the above with page.setContent instead of page.goto?

@616b2f
Copy link

616b2f commented Mar 15, 2021

@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: #422 (comment)

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

@thomasdvd
Copy link

I have tried some of the solutions above but none of them hold up when generating 30+ pdfs

so i ended up doing this

try {
	await page.goto(url, { waitUntil: 'load', timeout: 5 * 1000 });
} catch {
	await page.goto(url, { waitUntil: 'networkidle2', timeout: 15 * 1000 });
}

giving puppetteer some room to breath between opening pages also seems to help , as I had very inconsistent results

@wakproductions
Copy link

None of the above solutions to fix the loading issues worked for me. I've done the base 64 encoding and the time delay hacks. The page and its custom fonts render fine through the web browser, but show up blank in the PDF. What worked for me what installing the font in the OS as a system font and then referencing it by name in the style sheet. For my Linux instance, and in Docker, copying the TTF file into /usr/share/fonts/truetype resolved the font anomalies in PDF generation.

@ahmafi
Copy link

ahmafi commented May 3, 2022

I have the same issue currently, and It's probably not a network-related issue, because the fonts are loaded, I can see the correct fonts when I take a screenshot exactly before generating the PDF, but the fonts are not loaded in PDF for some reason.

For now, changing the puppeteer product to firefox fixed this issue.

@stale
Copy link

stale bot commented Jul 2, 2022

We're marking this issue as unconfirmed because it has not had recent activity and we weren't able to confirm it yet. It will be closed if no further activity occurs within the next 30 days.

@stale stale bot added the unconfirmed label Jul 2, 2022
@alexgrinseit
Copy link

alexgrinseit commented Jul 30, 2022

We also noticed that web fonts sometimes don't render in PDFs created with Puppeteer 14.3.0 and Node 12.22.8

@stale stale bot removed the unconfirmed label Jul 30, 2022
@piyushk96
Copy link

piyushk96 commented Aug 4, 2022

you can try this alixaxel/chrome-aws-lambda#246 (comment). This worked for me

@stevecj
Copy link

stevecj commented Aug 17, 2022

I have tried some of the solutions above but none of them hold up when generating 30+ pdfs

so i ended up doing this

try {
	await page.goto(url, { waitUntil: 'load', timeout: 5 * 1000 });
} catch {
	await page.goto(url, { waitUntil: 'networkidle2', timeout: 15 * 1000 });
}

giving puppetteer some room to breath between opening pages also seems to help , as I had very inconsistent results

I don't know yet whether it will work in all cases, but this is the first thing that worked for me.

Can you say more about what this does? It looks to me like it does not actually add a time delay. It just waits for load instead of for networkidle2. Is that right? Since others have said this doesn't help them, would it help to add an actual sleep as well, and how should that be done?

@stevecj
Copy link

stevecj commented Aug 17, 2022

I have tried some of the solutions above but none of them hold up when generating 30+ pdfs
so i ended up doing this

try {
	await page.goto(url, { waitUntil: 'load', timeout: 5 * 1000 });
} catch {
	await page.goto(url, { waitUntil: 'networkidle2', timeout: 15 * 1000 });
}

giving puppetteer some room to breath between opening pages also seems to help , as I had very inconsistent results

I don't know yet whether it will work in all cases, but this is the first thing that worked for me.

Can you say more about what this does? It looks to me like it does not actually add a time delay. It just waits for load instead of for networkidle2. Is that right? Since others have said this doesn't help them, would it help to add an actual sleep as well, and how should that be done?

It turns out that only worked for me on the first try. :( I tried adding a sleep afterward (await page.evaluate('new Promise(resolve => setTimeout(resolve, 1000));'); and that also didn't help.

None of the advice here is working for me. I also tried generating a screenshot first, etc.

@evmer
Copy link

evmer commented Sep 7, 2022

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.

@aneuwald
Copy link

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.

Worked like a charm! Thansk @evmer !

@kkarkos
Copy link

kkarkos commented May 19, 2023

Try await page.waitForFunction('document.fonts.ready');

@skerit
Copy link

skerit commented Nov 5, 2023

The only thing that worked for me was base64 inlining the fonts.

@mariusrak
Copy link

Try await page.waitForFunction('document.fonts.ready');

I tried this and my webfonts do not appear. If I use waitUntil: "networkidle0" they work correctly. But it adds 500ms, which I don't like. So is there a way to make use of the document.fonts.ready?
Thanks.

@mariusrak
Copy link

I used waitUntil: "load" and then await page.waitForFunction('document.fonts.ready'); and it works correctly. But I shaved off only like 50ms :D

@gabrielmicko
Copy link

await page.evaluateHandle('document.fonts.ready'); did the trick for me, thank you.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug chromium Issues with Puppeteer-Chromium confirmed P3
Projects
None yet
Development

Successfully merging a pull request may close this issue.