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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.