Using PDFKit in a netlify function

Netlify Site (function only)

I’m trying to build some functions that return PDFs using PDFkit. I’ve found a few examples for implementing this directly with AWS lambdas but I’m getting the following error when running the functions server with both netlify dev & netlify functions:serve

Error: ENOENT: no such file or directory, open '/Users/blackcatstudio1/code/ts-pdfgen-lamda/.netlify/functions-serve/pdfgen/src/functions/pdfgen/data.trie'
  Object.openSync (fs.js:476:3)
  Object.readFileSync (fs.js:377:35)
  node_modules/unicode-properties/unicode-properties.cjs.js (/Users/blackcatstudio1/code/ts-pdfgen-lamda/node_modules/unicode-properties/node.js:6:33)
  __require (/Users/blackcatstudio1/code/ts-pdfgen-lamda/.netlify/functions-serve/pdfgen/src/functions/pdfgen/index.js:9:44)
  node_modules/fontkit/index.js (/Users/blackcatstudio1/code/ts-pdfgen-lamda/.netlify/functions-serve/pdfgen/src/functions/pdfgen/index.js:136732:35)
  __require (/Users/blackcatstudio1/code/ts-pdfgen-lamda/.netlify/functions-serve/pdfgen/src/functions/pdfgen/index.js:9:44)
  node_modules/pdfkit/js/pdfkit.js (/Users/blackcatstudio1/code/ts-pdfgen-lamda/.netlify/functions-serve/pdfgen/src/functions/pdfgen/index.js:148922:19)
  __require (/Users/blackcatstudio1/code/ts-pdfgen-lamda/.netlify/functions-serve/pdfgen/src/functions/pdfgen/index.js:9:44)
  Object.<anonymous> (/Users/blackcatstudio1/code/ts-pdfgen-lamda/functions/pdfgen/index.ts:2:25)
  Module._compile (internal/modules/cjs/loader.js:1063:30)

Any idea what might be causing this? I have the repo here - it’s the only function in the project currently. Thanks!

Hi @stordahl

The cause is related to UnicodeTrie of which there are two references in the shipped function

var trie = new UnicodeTrie(fs.readFileSync(__dirname + "/data.trie"));

// more ...

var trie = new UnicodeTrie(require("fs").readFileSync(__dirname + "/data.trie"));

There is an issue here though it is related specifically to Webpack, and another here which doesn’t have a solution per se. unicode-trie is dependency of both fontkit and linebreak which are in turn both dependencies of pdfkit. I did try the code from this thread (which the OP appears to have gotten working) with the same result as well as trying this code.

1 Like

This is an old post, but I just wrestled with this issue, so in the hope that it helps others. Sort of a nuclear option, but hey, it works:

  • Copy all the triefiles into your functions directory. For me, with a functions dir of src/functions (from my project root) this was:
cp ./node_modules/fontkit/*trie src/functions
cp ./node_modules/linebreak/src/*trie src/functions
  • Then, because pdfkit will still look for its fonts, you need to copy those too:
cp -r ./node_modules/pdfkit/js/data ./node_modules/pdfkit/js/font src/functions

Now, use the included_files prop on [functions] in netlify.toml to include all that:

[functions]
  included_files = ["src/functions/**/*"]
1 Like

Clever! I did some digging and it seems you can have npm doing the ‘copying’ for you, according to this post:

1 Like

Thank you @mfan and @tomrutgers for chiming in here! This will definitely be beneficial for future forums members who encounter something similar. Happy building :rocket:

I’m a beginner in netlify function. I got the same error when using pdfkit. please help me to solve this issue.
Error: ENOENT: no such file or directory, open ‘/Users/goutamsamal/Documents/workspace/resume-maker/.netlify/functions-serve/pdf-maker/src/functions/data/Helvetica.afm’
Object.openSync (node:fs:590:3)
Object.readFileSync (node:fs:458:35)
Object.Helvetica (/Users/goutamsamal/Documents/workspace/resume-maker/node_modules/pdfkit/lib/font/standard.js:20:15)
new StandardFont (/Users/goutamsamal/Documents/workspace/resume-maker/node_modules/pdfkit/lib/font/standard.js:60:49)
Function.open (/Users/goutamsamal/Documents/workspace/resume-maker/node_modules/pdfkit/lib/font_factory.js:11:16)
PDFDocument.font (/Users/goutamsamal/Documents/workspace/resume-maker/node_modules/pdfkit/lib/mixins/fonts.js:51:33)
PDFDocument.initFonts (/Users/goutamsamal/Documents/workspace/resume-maker/node_modules/pdfkit/lib/mixins/fonts.js:17:12)
new PDFDocument (/Users/goutamsamal/Documents/workspace/resume-maker/node_modules/pdfkit/lib/document.js:88:10)
/Users/goutamsamal/Documents/workspace/resume-maker/functions/helper/makePdf.js:6:17
new Promise ()

import pdfDocuments from 'pdfkit';

const makePDF = async () => {

  return new Promise(resolve => {
    const doc = new pdfDocuments({
      bufferPages: true,
      autoFirstPage: false
    })

    doc.fontSize(24)
    .text('Hello World!', 100, 100);

    doc.addPage({
      margins: {
        top: 50,
        bottom: 50,
        left: 62,
        right: 72
      },
      size: 'A4',
      layout: 'portrait'
    })

    
    const buffers = []
    doc.on('data', buffers.push.bind(buffers))
    doc.on('end', () => {
      const pdf = Buffer.concat(buffers)

      resolve(pdf)
    })
    doc.end()
  })
}
export default makePDF;
import makePDF from './helper/makePdf.js'

export async function handler (event, context, callback) {

  const stream = await makePDF()
  const response = {
    statusCode: 200,
    headers: {
      /* Required for CORS support to work "*.eval.team, eval.team, *localhost*" */
      'Access-Control-Allow-Origin': '*',
      'Access-Control-Allow-Methods': 'OPTIONS, POST, GET',
      /* Required for cookies, authorization headers with HTTPS */
      'Access-Control-Allow-Credentials': true,
      'Access-Control-Allow-Headers': '*',
      'Content-Type': 'application/pdf',
      'Content-Disposition': `attachment;filename=invoice.pdf`
    },
    body: stream.toString('base64'),
    isBase64Encoded: true
  }

  return response
}

Is it a manual process?

I don’t see this path referenced anywhere in your code, have you shared the complete example?

In any case, debugging custom code is out of our Scope of Support.