bpampuch / pdfmake

Client/server side PDF printing in pure JavaScript
http://pdfmake.org
Other
11.64k stars 2.04k forks source link

Fonts Causing Issues in Vercel Deployment #2460

Closed SawkaDev closed 2 years ago

SawkaDev commented 2 years ago

Hi All,

I am generating a PDF on the server side then sending back to client. In localhost it works fine, once i deploy to Vercel (nextjs) I get an error.

Server Code:

      const fonts = {
        Roboto: {
          // Path relative to process.cwd()
          normal: 'src/pages/api/fonts/Roboto-Regular.ttf',
          italics: 'src/pages/api/fonts/Roboto-Italic.ttf',
          bold: 'src/pages/api/fonts/Roboto-Bold.ttf'
        }
      };

      const printer = new PdfPrinter(fonts);
      const docDefinition: TDocumentDefinitions = await generateDocDefinition(patient);
      const pdfDoc = printer.createPdfKitDocument(docDefinition);
      pdfDoc.pipe(BlobStream());
      pdfDoc.end();
      res.send(pdfDoc);

On Client:

const generatePDF = async (id: string) => {
  await apiClient
    .get(`/patient/generatePDF/${id}`, {
      responseType: 'blob'
    })
    .then((response) => {
      const file = new Blob([response.data], {
        type: 'application/pdf'
      });
      //Build a URL from the file
      const fileURL = URL.createObjectURL(file);
      //Open the URL on new Window
      window.open(fileURL);
    })
    .catch((error) => {
      console.log('Error: ' + error);
    });
};

In deploy environment it is hitting the catch block.

SawkaDev commented 2 years ago

After more investigation it seems related to how PDFMake takes in a absolute file path. When using NextJS and you are doing server side generation this issue happens.

Ebaneck commented 2 years ago

Hello @Sliffcak

I have a similar issue on Heroku with nodejs/express.

Somehow the default standard fonts seem to work. See here

      const fonts = {
        Times: {
          normal: 'Times-Roman',
          bold: 'Times-Bold',
          italics: 'Times-Italic',
          bolditalics: 'Times-BoldItalic',
        },
      };

While this does not work

   Roboto: {
      normal: path.resolve(dirname, './fonts/Roboto/Roboto-Regular.ttf'),
      bold: path.resolve(dirname, './fonts/Roboto/Roboto-Medium.ttf'),
      italics: path.resolve(dirname, './fonts/Roboto/Roboto-Italic.ttf'),
      bolditalics: path.resolve(dirname, './fonts/Roboto/Roboto-MediumItalic.ttf'),
  };
SawkaDev commented 2 years ago

Oh my....lord. Thank you so much. I will just deal with the default fonts for now. This was in my face the whole time, Trust the docs!

But seriously, I think there needs to be better documentation on either pdfmake or nextjs side to help with custom fonts.

liborm85 commented 2 years ago

And throwed error is?

SawkaDev commented 2 years ago

Sadly this error is now a bigger problem. I was going to just use the standard fonts but I see they do not support special characters like less than or equal to symbol.

@liborm85 I can not get anywhere to show me an error code. I just keep getting Error: AxiosError: Request failed with status code 501 on the front end.

I am trying to do server side pdf generation only.

SawkaDev commented 1 year ago

@liborm85 It works on localhost but when I deploy to vercel the PdfPrinter is not able to work. I do not see any useful errors or prints. Is there a way to turn on verbose printing for pdfmake?

SawkaDev commented 1 year ago

@liborm85 can I get this back open for now (seems eve @Ebaneck is having the same issue)? the official error I am geting in vercel is the following.

{"errno":-2,"syscall":"open","code":"ENOENT","path":"/assets/fonts/Roboto-Bold.ttf"}

Seems vercel can not locate the static file. I tired all sorts of combinations with the file path and it still gave this error without the beginnign slash

{"errno":-2,"syscall":"open","code":"ENOENT","path":"assets/fonts/Roboto-Bold.ttf"}

When i check the source in vercel I see the following, that path should exist right?

image

Here is the file directory structure in development / localhost: image

The error appears after this function gets called

const pdfDoc = printer.createPdfKitDocument(docDefinition);

acept-dev commented 1 year ago

I think that might be related to wherever you can import static files under public/ in the code.

I'm not familiar with Next.js but am facing similar issue with fonts and images using SvelteKit which calls Vite under the hood. And Vite's docs says assets in public/ cannot be imported in the code. Don't know if the same applies to you.

The problem is if I put the assets outside the public/, they won't appear in the build/ dir after building, which is uploaded to hosting. If I put them inside public/, I think the code cannot import them as mentioned above. For images I also tried writing the public URL like https://my-domain.com/img/hello.jpg (which I checked is accessible), but appears public URLs are not supported in server-side per pdfmake's docs. For fonts there're even fewer documented options server-side.

acept-dev commented 1 year ago

I made it work on Netlify by passing my font files to the included_files option in the netlify.toml config file. On Netlify, SvelteKit endpoints are hosted as serverless functions. Turns out Netlify has provided the above option to specify additional files (which are not imported explicitly) to be accessible in serverless functions.

Helpful example for Netlify: https://kisaragi-hiu.com/kemdict-sveltekit-sqlite.html

I haven't try the same on Vercel, but I'm under the impression they also convert your code into serverless functions. And they do have a includeFiles option in vercel.json which looks like the above counterpart on Netlify (though I'm not sure what should the glob path be, to represent your SvelteKit endpoint). I think you may experiment with it, and in the worst case perhaps try Netlify if you can't make it work on Vercel.

pattycodes commented 6 months ago

Anyone found a fix yet? Have tried the URL method & VFS method for adding new fonts, I get the same 405 Axios error anytime I add any logic for custom fonts, though everything works fine if i make everything default, tried the .ttf files themselves, the base64 encoded version and them hosted at URLS. Using Vercel & next.js.

Tempted to find another library and or just stick with default fonts. Very frustrating

diegoulloao commented 3 months ago

I made it work on Netlify by passing my font files to the included_files option in the netlify.toml config file. On Netlify, SvelteKit endpoints are hosted as serverless functions. Turns out Netlify has provided the above option to specify additional files (which are not imported explicitly) to be accessible in serverless functions.

Helpful example for Netlify: https://kisaragi-hiu.com/kemdict-sveltekit-sqlite.html

I haven't try the same on Vercel, but I'm under the impression they also convert your code into serverless functions. And they do have a includeFiles option in vercel.json which looks like the above counterpart on Netlify (though I'm not sure what should the glob path be, to represent your SvelteKit endpoint). I think you may experiment with it, and in the worst case perhaps try Netlify if you can't make it work on Vercel.

Having the same issue in Astro (endpoint), I tried a lot of combinations for paths too, using the path module, using import.meta.dirname in combination with path.resolve, etc, locating fonts in public and somewhere inside my project, none worked.

As mentioned above I guess files can't be read from the public directory, I've checked the output source in vercel and the fonts files are there, even though my paths are ok it doesn't work on vercel, only localhost.

I tried adding the includeFiles option inside the vercel.json config but the deploy is failing. I get the following message:

Screenshot

No solution seems to work. Also tried the vfs fonts approach.

diegoulloao commented 3 months ago

This didn't worked for me in Astro, it may work in Next.js or Sveltekit:

vercel.json

{
  "functions": {
    "api/route.js": {
      "includeFiles": "./src/dir/to/folder/**/*"
    }
  }
}

https://vercel.com/guides/how-can-i-use-files-in-serverless-functions#using-dynamic-require

diegoulloao commented 3 months ago

Tried this in Astro, didn't work...

astro.config.mjs

export default defineConfig({
  output: "hybrid",
  integrations: [
    svelte(),
  ],
  adapter: vercel({
    includeFiles: [
      "./src/factory/pdf/resources/fonts/Roboto-Bold.ttf",
      "./src/factory/pdf/resources/fonts/Roboto-Regular.ttf",
      "./src/factory/pdf/resources/fonts/Roboto-Italic.ttf",
      "./src/factory/pdf/resources/fonts/Roboto-BoldItalic.ttf",
    ],
  }),
});

https://docs.astro.build/en/guides/integrations-guide/vercel/#includefiles

muhammadkasimm commented 2 days ago

Hi All,

I am generating a PDF on the server side then sending back to client. In localhost it works fine, once i deploy to Vercel (nextjs) I get an error.

Server Code:

      const fonts = {
        Roboto: {
          // Path relative to process.cwd()
          normal: 'src/pages/api/fonts/Roboto-Regular.ttf',
          italics: 'src/pages/api/fonts/Roboto-Italic.ttf',
          bold: 'src/pages/api/fonts/Roboto-Bold.ttf'
        }
      };

      const printer = new PdfPrinter(fonts);
      const docDefinition: TDocumentDefinitions = await generateDocDefinition(patient);
      const pdfDoc = printer.createPdfKitDocument(docDefinition);
      pdfDoc.pipe(BlobStream());
      pdfDoc.end();
      res.send(pdfDoc);

On Client:

const generatePDF = async (id: string) => {
  await apiClient
    .get(`/patient/generatePDF/${id}`, {
      responseType: 'blob'
    })
    .then((response) => {
      const file = new Blob([response.data], {
        type: 'application/pdf'
      });
      //Build a URL from the file
      const fileURL = URL.createObjectURL(file);
      //Open the URL on new Window
      window.open(fileURL);
    })
    .catch((error) => {
      console.log('Error: ' + error);
    });
};

In deploy environment it is hitting the catch block.

Did you resolve the issue? I keep my fonts in /public directory. On localhost, I am able to load fonts using public/fonts/abc.ttf but this doesn't work in Vercel.

Vercel uploads static files to .vercel/output/static and are available from the root URL of your deployed app, so e.g your-app-name.vercel.app/fonts/abc.ttf. But now the problem is that pdfmake tries to load file from a local file URL when running server side so it fails to load fonts from a network URL.

ALSO, how were you able to run pdfkmake in a NextJS api route in the first place? I made a simple route in NextJS (app router) API like /api/preview. I send a dummy response to test, it works fine. I try generating pdf from the router handler, add multiple logs, call the endpoint - I get 404, no logs show up anywhere. I go back to dummy response, it works fine on the exact same endpoint. That doesn't make any sense to me at all. :|

So I made a monorepo with a small Express app just for the pdf generation. Finally was able to get server up and running with my NextJS app but now the fonts are not loading.

muhammadkasimm commented 2 days ago

I realized that I don't need and shouldn't be putting my fonts in the public directory for my apps' specific use case and ended up doing this to load fonts in my ExpressJS app hosted on Vercel.

{
    EBGaramond: {
      normal: path.join(__dirname, "/fonts/EBGaramond-Regular.ttf"),
      bold: path.join(__dirname, "/fonts/EBGaramond-Bold.ttf"),
    },
    SFPro: {
      normal: path.join(__dirname, "/fonts/SF-Pro-Text-Regular.otf"),
      bold: path.join(__dirname, "/fonts/SF-Pro-Text-Bold.otf"),
    }
} 

This works perfectly locally and in deployment.