Which package do you use to generate PDFs in Meteor?

I need to generate invoice and some others pdf’s/docs from my meteor app. On atmosphere there are a few libs. Which one you would recommend to use?

I already tried https://atmospherejs.com/ongoworks/pdf, but can’t find how to do styling (any thoughts?).

4 Likes

This is a very good question. ongoworks:pdf is ok, but there are problems with styling (probably because of jsPDF). I am afraid that it needs some server side rendering with phantomjs or something like that.

I also need a simple and good solution for this.

I have used pascoual:pdfkit. It is quite versatile but does only work server-side.

1 Like

Here is a little snippet I recently came up with when working with a client.

this.route('generatePDF', {
        path: '/api/generatePDF',
        where: 'server',
        action: function() {
            var webshot = Meteor.npmRequire('webshot');
            var fs      = Npm.require('fs');
            var fut = new Future();
            var fileName = "generated_"+Random.id()+".pdf";
            var url = Meteor.absoluteUrl(<path to a client side route with print css>);

            var options = {
              renderDelay:5000,
                "paperSize": {
                    "format": "Letter", 
                    "orientation": "portrait", 
                    "margin": "1cm"
                }
            };

            webshot(url, 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());
        }
    });

works well in practice for the use case i had (generating nicely formatted certificates) and it was pretty easy to style as desired using print css on the route we wanted to make the pdf from

7 Likes

I tried ongoworks:pdf, but don’t support khmer utf8 and table tag.

1 Like

I needed this ability as well and resorted to the following tools to do the job.

  • create a template on the server which creates an SVG. I achieved this by using the cmather:handlebars-server plugin
  • use inkscape to convert the SVG to PDF inkscape --without-gui --file=fileName.svg --export-pdf=fileName.pdf

If you need to have a synchronous exec function you can wrap it like so var execSync = Meteor.wrapAsync(exec);

It works very nicely and fast.

Nice, how do you call that from the client ?

that code declares a route which returns a pdf, so you can call it / execute it with something like:

<a href="{{pathFor 'generatePDF'}}" class="btn btn-primary" target="_blank">Download PDF</a>

this will have a pdf open/download in a new window - works pretty well for me

@natestrauser

What version of PhantomJS were you using?

I’ve been attempting to use your snippet with little luck. It gives me ENOENT errors, when I try to generate a PDF, or says [Error: PhantomJS exited with return value 2] when I try to use the example snippet (to take a screenshot of google.com) from the webshot readme. I’ve tried building PhantomJS from source, installing it from the Ubuntu Software Center, installing it with npm, and using dfiscers’s Meteor package.

Either I’m out to lunch — I’m fairly new to Meteor and NodeJS, so it’s not impossible — or there’s some issue with the webshot/phantom combination I’m using. Right now I’m using bryanmorgan’s webshot meteor package. Any suggestions?

Thanks!

I’m using the dfischer:phantomjs package and "webshot":"0.15.4" installed via the npm package

works well for me locally and on modulus

1 Like

Thanks very much @natestrauser . That fixed the problem I was having, and I’m now able to generate PDFs. Unfortunately, I have another (perhaps trivial) problem: the client side template renders fine, but when I run webshot on the path, it only gets what’s hardcoded into the HTML, and the handlebars data goes missing. Any suggestions about getting around this? Thanks!

I’m using dfischer:phantomjs package and "webshot":"0.15.4" installed via the npm package. However, still getting [Error: PhantomJS exited with return value 2] on Ubuntu, and [Error: PhantomJS exited with return value 1] on Windows. Steps followed:

  1. Added the NPM package: meteor add meteorhacks:npm. This added the packages.json file to the project
  2. Added the Webshot package: meteor add bryanmorgan:webshot
  3. Added the PhantomJS package: meteor add dfischer:phantomjs
  4. Added NPM webshot package: "webshot":"0.15.4" to packages.json

@natestrauser @omphalosskeptic, could you pls see the above steps, and let me know what have I missed? Will be sooper helpful… thx

@omphalosskeptic probably the 5000ms render delay is not long enough - i switched my own app to take the snapshot based on a callback

var options = {
              takeShotOnCallback: true,
                "paperSize": {
                    "format": "Letter", 
                    "orientation": "portrait", 
                    "margin": "1cm"
                }
            };

then trigger the shot from the rendered callback


Template.TEMPLATE.onRendered(function() {
  if (_.isFunction(window.callPhantom))
    Meteor.setTimeout(function() {
      window.callPhantom('takeShot');
    }, 500);
});

this seems to work well for me


@pranab maybe try removing the bryanmorgan:webshot package? i don’t think you should need this as the sample code above is directly using the webshot npm. the other steps you are describing look good to me.

2 Likes

@natestrauser Thanks. Is there any chance you could put the whole thing in a Gist? Now I’m getting Uncaught TypeError: window.callPhantom is not a function on the client side. Sorry for all the questions :confused:

Are you attempting to generate a pdf from scratch or do you have a template that you fill in with data? Are you looking for a solution client or server side?

1 Like

@aadams Thanks for your reply.

  1. I have a template with data.
  2. Server or client. Any solution that works, and can be styled with css, is fine. Client-side would reduce the load on my hosting provider, but server-side would probably give more control, so it’s a toss-up.

Do you have something in mind? Using Webshot + Phantom for a glorified Print to File does seem a bit overkill…

I helped built a package to do this within a server side route https://atmospherejs.com/aadams/pdftk

I’m just trying to take a screenshot of a webpage and store it in a png or a jpg file. No template/css etc. As @omphalosskeptic mentioned, @natestrauser, it would be sooper helpful if you could put the whole thing in a step-by-step tutorial in a single post :slight_smile: Would be sooper helpful to us, and to the entire community!

@natestrauser I need to create a pdf and then save it in a zip file along with some images. I’ve got this method (using your code)

createPDF: function() {
    if (Meteor.isServer) {
        var webshot = Meteor.npmRequire('webshot');
        var fs = Npm.require('fs');
        var Future = Npm.require('fibers/future');
        var fut = new Future();

        var fileName = "google.pdf";
        var url = "google.com";

        var options = {
            renderDelay:5000,
            "paperSize": {
                "format": "Letter",
                "orientation": "portrait",
                "margin": "1cm"
            }
        };

        console.log("About to webshot");
        webshot(url, fileName, options, function(err) {
            fs.readFile(fileName, function (err,data) {
                if (err) {
                    return console.log(err);
                }

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

       
        return fut.wait();
    }


}

I am calling the method on the client like so:

'click .download-case': function (e, tmpl) {
    e.preventDefault();

    var firm = Firms.findOne(this.firm_id);
    var zip = new JSZip();

    Meteor.call('createPDF', function (err, res) {
        if (err) {
            console.error(err);
        }
        console.log(res);
            zip.file("Hello.txt", "Hello World\n");
            var img = zip.folder("images");
            img.file("google.pdf", res, {base64: true});
            var content = zip.generate({type: "blob"});
            saveAs(content, "case.zip");
    });
}

I’m not actually wanting to save the home page of Google, but am instead trying to get the code to work before I implement taking a webshot of a client side template. However, I don’t have any idea how to send the new PDF from the server to the client… when I console.log the res object I just get a bunch of symbols in my console haha. I’m a noob when it comes to file system stuff. I’d appreciate any help you can provide.

@natestrauser
I’m getting this on my console.

/Users/sangyookim/.meteor/packages/meteor-tool/.1.1.3.1wysac9++os.osx.x86_64+web.browser+web.cordova/mt-os.osx.x86_64/dev_bundle/server-lib/node_modules/fibers/future.js:245
						throw(ex);
						      ^
TypeError: Object #<Object> has no method 'route'
    at app/modules/client_view/lib/savePDF.js:1:41
    at app/modules/client_view/lib/savePDF.js:36:3
    at /Users/sangyookim/Desktop/client-manager/.meteor/local/build/programs/server/boot.js:222:10
    at Array.forEach (native)
    at Function._.each._.forEach (/Users/sangyookim/.meteor/packages/meteor-tool/.1.1.3.1wysac9++os.osx.x86_64+web.browser+web.cordova/mt-os.osx.x86_64/dev_bundle/server-lib/node_modules/underscore/underscore.js:79:11)
    at /Users/sangyookim/Desktop/client-manager/.meteor/local/build/programs/server/boot.js:117:5
Exited with code: 8
Your application is crashing. Waiting for file change.

I did include webshot and phantomJS. I copied pasted your code into a separate savePDF.js in a lib folder.

this is the absolute link to my css
'Users/sangyookim/Desktop/client-manager/modules/client_view/style/client_view_print.css'

I do not know what is going wrong : (