8

I'm trying to create a Node server that generates a PDF on-the-fly using PDFKit. The PDF is generated based on parameters from a POST request (via Express). One of the parameters specifies an image URL, which the server downloads and injects into the PDF.

Right now, I have the following structure:

// Get dependencies var express = require('express'), http = require('http'), fs = require('fs'), pdfDocument = require('pdfkit'); // Get express started. var app = express(); // Use JSON in POST body app.use(express.json()); // Setup POST response app.post('/post_pdf', function(req, res) { // Get the PDF initialized var doc = new pdfDocument(); // Set some headers res.statusCode = 200; res.setHeader('Content-type', 'application/pdf'); res.setHeader('Access-Control-Allow-Origin', '*'); // Header to force download res.setHeader('Content-disposition', 'attachment; filename=Untitled.pdf'); // Pipe generated PDF into response doc.pipe(res); /** * Generate PDF contents */ // Prepare write stream for image var image = fs.createWriteStream('image.jpeg'); // Download image http.get("http://dummyimage.com/640.jpeg", function(response) { // Pipe response into image write stream // (because PDFKit needs to read from a saved file) response.pipe(image).on('close', function() { // Read data back, make sure there are no errors fs.readFile('image.jpeg', function(err, data) { if (err) throw err; /** * Use `data` to get image info (width, height, etc.) * ------------------ * Inject image */ // Close document and response doc.end(); res.end(); return; }) }); }); }); 

I have two questions:

  • Is there a less messy way to do this, perhaps with fewer nested callbacks? I'm totally open to adding another dependency to make life easier.
  • Right now, the code above does not work. It returns a PDF, but the PDF is corrupted (according to Preview). Any tips as to why this could be occurring are very welcome.

2 Answers 2

8

In debugging this issue, I discovered several things:

PDFKit does not need to read info from a file. It will also accept a Buffer

doc.image(myBuffer); // You don't have to use a path string 

When piping a file directly into the response, a manual call to response.end() will cause problems if the file has already been closed

doc.pipe(res); // Pipe document directly into the response doc.end(); // When called, this ends the file and the response // res.end(); <-- DON'T call res.end() // The response was already closed by doc.end() return; 

Request is a super-useful NodeJS library that can flatten the callback tree


Updated code:

var express = require('express'), request = require('request'), pdfDocument = require('pdfkit'); // Start Express var app = express(); // Use JSON in POST body app.use(express.json()); // Setup POST response app.post('/post_pdf', function(req, res) { // Create PDF var doc = new pdfDocument(); // Write headers res.writeHead(200, { 'Content-Type': 'application/pdf', 'Access-Control-Allow-Origin': '*', 'Content-Disposition': 'attachment; filename=Untitled.pdf' }); // Pipe generated PDF into response doc.pipe(res); // Process image request({ url: 'http://dummyimage.com/640.jpeg', encoding: null // Prevents Request from converting response to string }, function(err, response, body) { if (err) throw err; // Inject image doc.image(body); // `body` is a Buffer because we told Request // to not make it a string doc.end(); // Close document and, by extension, response return; }); }); 
Sign up to request clarification or add additional context in comments.

6 Comments

How have you handled the client side part to enable the user to download the generated pdf? I am trying to generate on the fly and don't want to save to the file system but simply initiate a download for the user. Thanks.
I believe changing the Content-Disposition header to something like inline; filename=" would work. Tell the browser to display the PDF.
How about if I want to convert page/url to pdf. Say www.google.com. Does this still applicable?
@ShenLance As far as I know, no, this answer doesn't apply to PDFs generated by snapshotting a webpage.
How do you handle the response in client side?
|
2
const fetch = require('node-fetch'); const PDFDocument = require('pdfkit'); const doc = new PDFDocument({}); url = AnyImageUrl; res = await fetch(url,{encoding: null }); imageBuffer = await res.buffer(); img = new Buffer(imageBuffer, 'base64'); doc.image(img,(doc.page.width - 525) /2, doc.y, {align: 'center', width: 125}); 

1 Comment

This worked great for me. I liked the simplicity of it. The only thing I ended up changing (other than the variable names and dimensions in the last line) was the "new Buffer(imageBuffer, 'base64');" to "new Buffer.from(imageBuffer, 'base64');" - use the .from method of Buffer since using Buffer is now deprecated.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.