小Roy
小Roy5mo ago

Use pdfkit in actions

Hi all, I am trying to create an action to generate pdf and store that in files storage. I am using the pdfkit to generate the pdf. https://github.com/foliojs/pdfkit I ran into error saying it's something wrong with loading the fonts. However, when I try to do this in a node another node backend server, I don't see the same issue. Could somebody give me a hint how should I approach this?
"use node"

import PDFDocument from "pdfkit";

export const generatePDF = internalAction({
args: {},
handler: async (ctx, args) => {
const doc = new PDFDocument({size: [4*72, 6*72]});
}
})
"use node"

import PDFDocument from "pdfkit";

export const generatePDF = internalAction({
args: {},
handler: async (ctx, args) => {
const doc = new PDFDocument({size: [4*72, 6*72]});
}
})
GitHub
GitHub - foliojs/pdfkit: A JavaScript PDF generation library for No...
A JavaScript PDF generation library for Node and the browser - foliojs/pdfkit
No description
13 Replies
ballingt
ballingt5mo ago
Since this package appears to use files from the filesystem (so doesn't work with bundling) you may need to use external packages: https://docs.convex.dev/functions/bundling#external-packages
Bundling | Convex Developer Hub
Bundling is the process of gathering, optimizing and transpiling the JS/TS
ballingt
ballingt5mo ago
This will run an npm install for the package, instead of bundling it locally on your machine.
小Roy
小RoyOP5mo ago
@ballingt Thanks for getting back. I tried including the pdfkit in the external libraries and no luck. :\ I attached a screenshot what's included inside the pdfkit. I also attached a screenshot of the source map explorer. (The pdfkit is indeed included. I wonder if this is related to the fonts somehow as the error indicates.
No description
No description
ballingt
ballingt5mo ago
Did you try with both "use node" and external packages? What was the error you got? I just tried this and it's working for me, at least it's not crashing.
"use node";
import { action } from "./_generated/server";

export const foo = action(() => {
console.log(process.env.hello);
const PDFDocument = require("pdfkit");
const doc = new PDFDocument({ size: [4 * 72, 6 * 72] });
});
"use node";
import { action } from "./_generated/server";

export const foo = action(() => {
console.log(process.env.hello);
const PDFDocument = require("pdfkit");
const doc = new PDFDocument({ size: [4 * 72, 6 * 72] });
});
小Roy
小RoyOP5mo ago
Did you try clicking the run action? here is what I got after clicking the "Run action"
No description
ballingt
ballingt5mo ago
Yeah, it seemed to run fine! What does your convex.json look like?
小Roy
小RoyOP5mo ago
@ballingt Here is what it looks like. Also attached my convex version
No description
No description
小Roy
小RoyOP5mo ago
@ballingt I created a public repo to reproduce this issue. repo: https://github.com/roy-law/test-convex-pdfkit steps: 1. Setup - clone repo and run npx convex dev 2. Navigate into Convex dashboard > Functions > foo > Run Function > Run action 3. Clicking "Run action" > Should see the exact error as the screenshot If possible, please let me know whether it also fails on your end with the repo.
GitHub
GitHub - roy-law/test-convex-pdfkit
Contribute to roy-law/test-convex-pdfkit development by creating an account on GitHub.
No description
小Roy
小RoyOP5mo ago
GitHub
PDFKit doesn't work with ESM · Issue #1491 · foliojs/pdfkit
Bug Report Description of the problem In modern projects that rely on ES modules PDFKit cannot work due to missing __dirname global variable. During development of my SvelteKit app I could use PDFK...
ballingt
ballingt5mo ago
@小Roy Ah ok I was able to reproduce your error by tring to use fonts in the PDF Here's a non-Node.js approach that's I think works:
import { action } from "./_generated/server";

const PDFDocument = require("pdfkit/js/pdfkit.standalone");

export const foo = action(() => {
const doc = new PDFDocument({ size: [4 * 72, 6 * 72] });

let chunks: Uint8Array[] = [];

return new Promise((resolve, reject) => {
try {
// Add content to the PDF here
doc.text(
"Hello, this is a PDF created with pdfkit in a worker environment!"
);

doc.on("data", (chunk: Uint8Array) => {
chunks.push(chunk);
});

doc.on("end", () => {
const totalLength = chunks.reduce(
(acc, chunk) => acc + chunk.length,
0
);
const result = new Uint8Array(totalLength);
let offset = 0;
for (const chunk of chunks) {
result.set(chunk, offset);
offset += chunk.length;
}
resolve(result.buffer);
});

doc
.save()
.moveTo(100, 150)
.lineTo(100, 250)
.lineTo(200, 250)
.fill("#FF3300");
doc.end();
} catch (error) {
reject(error);
}
});
});
import { action } from "./_generated/server";

const PDFDocument = require("pdfkit/js/pdfkit.standalone");

export const foo = action(() => {
const doc = new PDFDocument({ size: [4 * 72, 6 * 72] });

let chunks: Uint8Array[] = [];

return new Promise((resolve, reject) => {
try {
// Add content to the PDF here
doc.text(
"Hello, this is a PDF created with pdfkit in a worker environment!"
);

doc.on("data", (chunk: Uint8Array) => {
chunks.push(chunk);
});

doc.on("end", () => {
const totalLength = chunks.reduce(
(acc, chunk) => acc + chunk.length,
0
);
const result = new Uint8Array(totalLength);
let offset = 0;
for (const chunk of chunks) {
result.set(chunk, offset);
offset += chunk.length;
}
resolve(result.buffer);
});

doc
.save()
.moveTo(100, 150)
.lineTo(100, 250)
.lineTo(200, 250)
.fill("#FF3300");
doc.end();
} catch (error) {
reject(error);
}
});
});
we should clean it up a bit, extract out the buffer stuff or stick this in an HTTP action where it could stream the body back as a response object
ballingt
ballingt5mo ago
Cool, it seems to work!
No description
小Roy
小RoyOP5mo ago
Thanks so much! I think this approach works much better than mine!
ballingt
ballingt5mo ago
Thanks for the repro! We need to document some of these solutions, I'll write this up

Did you find this page helpful?