Send PDF from server to client

I did customize it a bit, here is the official page of the script so you see more options, you can even draw on it https://parall.ax/products/jspdf

I haven’t had a reason to try that, but if you’re going to create the zip file client side, why not create pdf there as well?

@jeffm That’s what I am thinking too. I’m looking into JSPDF to see if it will do what I need it to do.

@hodaraadam Did you add images to the html that you turned into a PDF? I’m trying to add an image to the pdf (by adding it to the html) and style it with inline styles but the styles don’t seem to be working. Did you try styling your html?

I did add images but also having a hard time with the styling, as of now it prints all the data with images but working also on the styling :frowning:, let me know if you come up with something

we wrapped and included JSPDF in the meteor:pdf package, but have discovered that styling is tough. You could try using meteorhacks:ssr as another approach. I’ve had some success with that. I hope that someone comes up with a foolproof package though - because most of the existing solutions are only partially viable.

1 Like

@ongoworks Yes, that would be very nice. I’ve built a huge app over the last month and the most difficult (and only unsolved) problem has been generating PDF reports and then storing them in a zip with photos from S3 for someone to download on the client… I’m getting pretty frustrated with it haha

1 Like

I too have built a hug application, part of which PDF form fill and generation in addition to S3 documents where to be apart. Alas, I could only get the PDF form fill and generation (server to client) to work. The saving to and distributing from S3 still alludes me (I can save the PDF to the local server, but choose to delete after I’ve rendered the PDF to the client for now).

Of course in my case, I use an existing PDF ‘template’, which I use as a ‘vessel’ (with fields, images, text, styling, etc. already in place) to form fill with data I gather from client side html forms (aka views).

If anyone knows how to save a PDF to S3 and then render said saved PDF from S3 to the client, with/in Meteor, I’d love to see the code.

@hodaraadam Alright, I figured out a solution that seems to be working really well. I have a private directory in the top level of my app with an html file called ‘case-report.html’. That file is an html page that I’m using to generate a PDF via webshot. It is styled with styles in that file. I actually just copied all the css from my main app into a style tag in the head section of that html page. I then created a route with the following code:

I guess I should also mention that I had to install "webshot": "0.16.0" via meteorhacks:npm, dfischer:phantomjs, and meteorhacks:ssr

Router.route('/cases/:_id/report/generatePDF', {
name: 'generatePDF',
where: 'server',
action: function() {
    var webshot = Meteor.npmRequire('webshot');
    var fs      = Npm.require('fs');
    var Future = Npm.require('fibers/future');
    var fut = new Future();
    var fileName = "case-report.pdf";
    var curCase = Cases.findOne(this.params._id);

    // Render the template on the server
    SSR.compileTemplate('caseReport', Assets.getText('case-report.html'));

    var html = SSR.render("caseReport", curCase);

    // Setup Webshot options
    var options = {
        "paperSize": {
            "format": "Letter",
            "orientation": "portrait",
            "margin": "1cm"
        },
        siteType: 'html'
    };



    webshot(html, fileName, options, function(err) {
        fs.readFile(fileName, function (err, data) {
            if (err) {
                return console.log(err);
            }

            fs.unlinkSync(fileName);
            fut.return(data);
        });
    });

    this.response.writeHead(200, {'Content-Type': 'application/pdf',"Content-Disposition": "attachment; filename=generated.pdf"});
    this.response.end(fut.wait());

}
});

I then have a link on the page that I want to generate the PDF from:

<a href="{{pathFor 'generatePDF'}}" class="waves-effect waves-light btn download">Download Case</a>

Because I am doing it this way, I can actually have another route with the exact same code as the case-report.html file and show users a preview of what the PDF will look like.

3 Likes

Just a few quick notes: On a non-Meteor project about half a year ago when I needed server-side PDF generation and was researching and implementing it I found that the most straightforward way would be to use webkit (wkhtmltopdf in my case, to be specific) to render an HTML page served up with all the regular styles (and some additional ones specific to displaying as PDF) and then just let it render that to PDF. Sending that to the client, or using it for emailing, is not a big deal after that, just a matter of understanding how Meteor/NodeJS allow you to do that.

I’m fairly sure most of that still applies. Webkit == phantomjs. Just wanted to state this, to let you know that this is a good path to pursue and that it can work really well. I don’t have time right now to write things up and port the solution to Meteor, knee-deep in work right now, but maybe if PDF generation still feels like a not super straightforward thing to accomplish with Meteor until I get around to it, I’ll write a short article and publish a package with a nice API for it. Even thinking of a SaaS for this, though I’m not sure if that wouldn’t be overkill. But could be nice, not having to install anything like (the right version of) phantomjs (or wkhtmltopdf) on your server and having it just work, be secure (noone should be able to call your routes that generate PDFs, other than the PDF webkit)…

And it does seem like Ryan figured it out, maybe there’s going to be a nice package for this until the time that I next need this functionality and would get to it. :smile:

1 Like

@seeekr I definitely think that generating a PDF server side is the way to go. I’ve done a bit more work on my project and now have a route that renders a PDF report, adds the file to a zip folder, grabs some images from Amazon S3, adds the images to the zip, and then sends the zip to the client as a download. I may look into wrapping the code up into a package… I think it would be super useful to have this functionality packaged up in the future. If I don’t get around to it, I’d love to see what you come up with!

1 Like

I have a project where I generate a PDF on the server, and send it to the client via a meteor method (as a data url). I describe it here https://github.com/pascoual/meteor-pdfkit/issues/11#issuecomment-74018064, but I’ll copy and paste what I wrote to make things easier:

I return a base64 encoded PDF from a meteor method for my own Meteor application. I started out using this package、but ended up using the npm package pdfkit directly, because I wanted access to the pipe() function. Maybe there is some way to get access to that in this Meteor package?

The advantage to doing this through a meteor method for myself is because I get access to authentication information (this.userId) within the server-side meteor method’s context.

//Code that I wrote as a custom meteor package 
var base64 = Npm.require('base64-stream');
var Future = Npm.require('fibers/future');
var PDFDocument = Npm.require('pdfkit');

somePDFGeneratingFunction(){
var doc = new PDFDocument({…});
//create the pdf with various commands (doc.text, doc.rect, etc)

var future = new Future();
var finalString = “”;
var stream = doc.pipe(base64.encode());
doc.end();
stream.on(‘data’, function(chunk){
finalString += chunk;
});

stream.on(‘end’, Meteor.bindEnvironment(function(){
future.return(finalString);
}));

return future.wait();
}

```javascript
//inside a meteor method
Meteor.methods({
  'reports.generatePDF': function(){
    return somePDFGeneratingFunction();
  }
});
//on the web browser
Meteor.call('reports.generatePDF', function(err, res){
  window.open("data:application/pdf;base64, " + res);
});

It was easier than creating a special route for the client to go to to download the PDF, and plus since it’s a meteor method I get this.userId so I know if they’re logged in or not, and what their access level is.

7 Likes

@wallslide That’s cool. I like that. I’ll play around with it and see if I can get it working in my app. Thanks for sharing!

Interesting. I’ve was needing something very alike. I’ve created a package that basically take a collection and draw PDF out of its records. It can take screenshot from displayed HTML fragments or insert images from CollectionFS or the public folder. Here’s my blog post on the reason why I did it this way and a basic example.

I love the CFS packages for this: https://atmospherejs.com/cfs

jeez. Is there nothing like Express static files middleware for Meteor/Iron Router? This is a good example of why I wish Meteor just provided some middleware I could plug into express or another general-purpose server-side routing framework. Then again, I guess ultimately I could run a separate Express server that proxies to the Meteor server…

Good idea! What I wound up doing worked, but was a lot more trouble than it should have been.

I found a much better example of how to do this in this Stack Overflow answer (though I should note that its usage of connect is very outdated, as connect.static has been moved to the serve-static package.

I’m using a pdf filler with the PDFtk server installed on the published server, but keep getting a “Error: spawn pdftk ENOENT” error, any clues as to why this is happening? I’ve made sure I’m getting the correct path, and it works just fine on a local machine.

I have a server-side route and I’m using pdfmake. When user visit the route, the PDF is generated.