[solved] PDF generation - can't get "pascoual:pdfjs" to work?

Situation

  1. A user can create a product with text & images
  2. After creation it can be viewed or downloaded as pdf

I’m trying to get the pdf creation to work.

After looking at different packages pascoual:pdfjs seems to be a good choice for this.
With this I can wrap any template in a canvas and convert it to a pdf. At least thats my understanding of it.

Problem

I’m stumbling with the implementation.
The short “Quick Start”-Guide on the package-page is not very clear to me.

Steps taken

So far I’ve tried to implement it like this:

My template:

<template name="products_detail">
   <canvas id="pdfcanvas">
      [content of my template]
   </canvas>
</template>

My javascript:

Template.products_detail.onRendered(function() {
   // Set worker URL to package assets
   PDFJS.workerSrc = '/packages/pascoual_pdfjs/build/pdf.worker.js';
   // Create PDF
   PDFJS.getDocument(url).then(function getPdfHelloWorld(pdf) {
      // Fetch the first page
      pdf.getPage(1).then(function getPageHelloWorld(page) {
    	var scale = 1;
    	var viewport = page.getViewport(scale);

        // Prepare canvas using PDF page dimensions
        var canvas = document.getElementById('pdfcanvas');
        var context = canvas.getContext('2d');
        canvas.height = viewport.height;
        canvas.width = viewport.width;

        // Render PDF page into canvas context
        page.render({canvasContext: context, viewport: viewport}).promise.then(function () {

        });
      });
   });
});

I pretty much just copy and pasted the javascript from the “Quick Start”-Guide.
Is there more configuration requiered on my end?
Has anyone experience with this?

Any kind of help is much appreciated.

Apart from that

Overall I found that there is a complete lack of Meteor -> PDF tutorials. (At least I couldn’t find any)
So I decided to just write a beginner-friendly one, once I’ve got this to work. Hope this helps other new meteorites out in the future.

2 Likes

I think you have misunderstood the purpose of pdfjs. It only renders PDF documents in the browser, it does not create them.

You might take a look at this thread. There are a few solutions using various different packages, pascoual:pdfjs being one of them. Send PDF from server to client

1 Like

Hey @levid,

thanks a ton for the push in the right direction.

I tried jspdf:core and it was exactely what I needed.
I then reverse enineered the example at http://jspdf-example.meteor.com/21 (thanks to @hodaraadam).

Source: https://github.com/surfer77/JSPDF-Meteor6

Works :slight_smile:

HAHA cool, I deleted my post because after reading again the thread you were using a diff package so I thought I was way off in my answer.

here it is again in case it helps someone in the future:
This is how I got it to work but I used jspdf:jspdf package

1 Like

Hey @hodaraadam , thanks for the update. :slight_smile:

Yeah, I got it to work.
Unfortunately the styling options with jspdf are practically nonexistent.

I don’t expect my generated pdf to look like a photoshoped movie poster, but at least some margins or a simple layout without tables (or at least make the tables invisible) would be nice.

I did not expect that this is so difficult. :frowning:

Guess I’ll take a look at jgdovin:html-pdf next.

Sure thing Nils, glad to hear you got it working :smile:

What the package to generate PDF that you use?

Hey @theara,

I’m using the jspdf:core package.
https://atmospherejs.com/jspdf/core

This is an example by @hodaraadam on how to implement it:
Source: https://github.com/surfer77/JSPDF-Meteor
Live: http://jspdf-example.meteor.com/21

You can use it to see how the package works.

Issues:
The problems I currently have with it, are:

  1. you can apply no styling (only font-size, line-height and color seem to work)
  2. you can apply no layout to your pdfs (good luck even trying to get a block of text NEXT TO an image)

Especially the last thing is a real problem in my case.
I’m still not sure how to solve this.

Help?
If anyone has experience with a package that generates pdfs, wich you can (at least to some degree) style: Please let me know. I’m getting a bit desperate here :smiley:

Quick update:
I guess I’ll take a look at the solution by @ryanswapp next.
Forum Topic: Send PDF from server to client

He uses:
"webshot": "0.16.0" (via meteorhacks:npm), dfischer:phantomjs, and meteorhacks:ssr

With those he:

  • renders a html document server side (meteorhacks:ssr)
  • take a screenshot of it (phantomjs & webshot)
  • converts it to pdf and serves it to the client

I’ll see how this works.
I guess I’ll be able to get some styling with this approach.

@nilsdannemann I can confirm that the solution from @ryanswapp does work nicely for custom styling. I am using it in one of my projects. Another cool thing is that you can serve up the same code on the client side and provide a live preview of the PDF for users.

Here is an example of the PDF that webshot generates and serves up to the client for download: http://levid.com/scoresheet.pdf

And here is a screenshot of the live web preview (which also updates in real-time as Meteor picks up data changes).

1 Like

Hey @levid,
wow, thanks, thats definitely some layout there :smile:
Nice work!

The styling works now.
Really happy about that.

One question:
Are those images in the generated pdf (e.g. the APA-Logo) pulled from a collection?
(I’m currently trying to get the images from my collectionFS to display in the generated pdf)

Anyway, many thanks for the push in the right direction so far.

@nilsdannemann Haha yeah it is quite a crazy layout right? Believe it or not though, filling this out by hand with pencil and paper has been the norm for the past 30+ years… It’s time for a change, thanks to Meteor :smile:

As for your question about the images, the scoresheet background is simply an img tag in the HTML. However, the tiny signatures that you see near the bottom are indeed being pulled in from Collection FS using cfs:gridfs.

Here is how I have that part set up if you are interested:

  • /lib/config/gridfs-config.js:
var signatureStore = new FS.Store.GridFS("signatures");
Signatures = new FS.Collection("signatures", {
  stores: [signatureStore]
});
  • /client/views/scoresheet/scoresheet.js:

I am passing a LeagueMatch object into the template to set the data context.

Template.scoresheet.onCreated(function () {
  var self = this;
  self.autorun(function () {
    self.subscribe("signatures", self._id);
  });
});
Template.scoresheet.helpers({
  homeTeamSignature: function(){
    return Signatures.findOne({'metadata.matchId': this._id, 'metadata.teamId': this.homeTeamId}, {sort: {uploadedAt: -1}}); // Where Images is an FS.Collection instance
  }
});
  • /client/views/scoresheet/scoresheet.html:

This is where I display the stored image from Collection FS.

<div class="data-field captain-signature">
            {{#with homeTeamSignature}}
              <a href="{{this.url}}" target="_blank"><img src="{{this.url store='signatures' uploading='/images/uploading.gif' storing='/images/storing.gif'}}" alt="" class="thumbnail" /></a>
            {{/with}}
          </div>
  • /server/method.js

I am using Meteor.call to send a base64 encoded string to the server-side and then wrap it in an FS.File object (along with some metadata to query against) before inserting.

Meteor.methods({
  '/signature/save': function(payload) {
    if (payload) {
      var fsFile = new FS.File(payload.dataURI);
      fsFile.metadata = {
        owner: payload.owner,
        teamId: payload.teamId,
        matchId: payload.matchId
      };
      Signatures.insert(fsFile, function (err, fileObj) {
        // Inserted new doc with ID fileObj._id
      });
    }
  }
});
  • /server/publish.js

Basic publication

Meteor.publish('signatures', function(id) {
  check(id, String);
  return Signatures.find({'metadata.matchId': id});
});

That’s it !

1 Like

Hey @levid,

Thanks for your detailed explanation.
That was definitely more than I expected. Many thanks!

I took a closer look at your code and fortunately I can follow along. :smile:
Unfortunately adapting your solution to fit my project resulted in a lot of these moments so far:
Tableflip

Why?
I think I don’t fully understand your method and insert event.

Is '/signature/save' a route?
Or can you name a method like this?

When I try to adapt my event to your solution it looks like this:

Template.myTemplate.events({
  'change .myFileinput': function(event, template) {
	FS.Utility.eachFile(event, function(file) {
		var fsFile = new FS.File(file.dataURI);
		fsFile.myString = "This is my cool Text";
		fsFile.myDate = new Date();
		
		Images.insert(fsFile, function (err, fileObj) {
                });
	});
  }
});

Shouldn’t this work?
When I run this, there is no error message.
But the insert isn’t happening as well. (at least according to Mongol).

Anyway, thanks for your help so far.
I hope I get this figured out.

Hello,
thx for the detailed description on how to create the pdf. I have one question regarding the background picture and hope you have some hint for me.

How you call this from within the html code? You said its a normal image tag. I try like this at the moment but the image does not show up.

img src="/private/images/test.png"

I also tried with

img src=“images/test.png”

Where the html file takes the images from? Do I have to use a relative URL or an absolute URL?

Is it also possible to use the image as a background image in the CSS? If yes how the syntax for this looks like? Relative or absolute path?

Thanks for any help.

Daniel

Hello,
I am using jspdf:core as per dicussion.
I am able to print pdf but I not able to get input value from html .
its coming blank…
any solution to get input value in pdf.

Hi @nilsdannemann,

I’m in need of doing something very similar to what was discussed in this topic, and I was happy to find some good hints in here.

As this is an old post, just would like to check - have you got the final solution to work? Did you achieve it with the tools discussed here, or do you have perhaps something newer?

Any help would be appreciated!