bpampuch / pdfmake

Client/server side PDF printing in pure JavaScript
http://pdfmake.org
Other
11.66k stars 2.04k forks source link

NodeJS version does not work out of the box #1312

Open mLuby opened 6 years ago

mLuby commented 6 years ago

Clearly Node ("server-side") setup is a stumbling block, especially around fonts.

916

https://github.com/bpampuch/pdfmake/issues/1044#issuecomment-308321337

1062

I'd say libraries should just work out of the box. Specifically, this should work:

var PdfPrinter = require('pdfmake')
var fs = require('fs')

const doc = new PdfPrinter().createPdfKitDocument({content: 'my text'})
doc.pipe(fs.createWriteStream('myFile.pdf'))
doc.end()

Unfortunately, this simple example errors out with Error: Font 'Roboto' in style 'normal' is not defined in the font section of the document definition because PdfMake requires a font definition object. I think that's something that should be provided by default, and can be optionally overwritten by people who want custom fonts. Doing so would make using this library much simpler to use with Node. This is the minimal example that works:

var PdfPrinter = require('pdfmake');
var fs = require('fs');

const doc = new PdfPrinter({
    Roboto: {normal: new Buffer(require('pdfmake/build/vfs_fonts.js').pdfMake.vfs['Roboto-Regular.ttf'], 'base64')}
}).createPdfKitDocument({content: 'my text'})
doc.pipe(fs.createWriteStream('myFile.pdf'))
doc.end()

Perhaps it should be in the wiki.

mozfet commented 6 years ago

Its my first day with pdfmake trying to get it to work server side on Meteor; its been a long day, and this post was quite helpful. Using the minimal example proved here I was able to get the pdf generated and downloaded in the client, but now I get a pdf document (with my metadata) with adobe complaining "cannot extract the embedded font 'UIMONA+Roboto-Regular'. I'm stumped...

mozfet commented 6 years ago

After some digging, I leaned that PDFKit, which pdfmake is built on, includes support for 14 standard fonts that are built into the pdf standard. Using these fonts avoid embedding any fonts in the document, thus also use less bandwidth. Since I run meteor on production clusters, I stay away from using the file system and use a buffer stream for my example instead.

Thus this makes the minimum node (es6) example I can create:

import PdfPrinter from 'pdfmake';
import streamBuffers from 'stream-buffers';
const doc = new PdfPrinter({timesRoman:{normal: 'Times-Roman'})
    .createPdfKitDocument({defaultStyle:{font: 'timesRoman'}}, {content: 'my text'});
const writableBuffer = new streamBuffers.WritableStreamBuffer();
doc.pipe(writableBuffer);
doc.end();

I do not see any errors on the server console, and on the client Adobe Acrobat does not generate any warnings for docs created this way... but neither shows any text... for me at least...

mozfet commented 6 years ago

I concluded perhaps generating pdf server side is not such a good idea because I cannot get it to work, so I looked for a workaround. Now I generate the document definition on the server, and return it to the client to generate the pdf with pdfmake there. It works well so far.

mozfet commented 6 years ago

The workaround stopped working well... well because, because when I want a footer with a page number in it, it is defined as footer key in the document definition as a function to dynamically determine the content of the footer. Functions are not very portable and tricky to pass from server to client, and in my case using meteor methods to transfer the document definition does not work, so my reports cannot have a page number.

mozfet commented 6 years ago

The workaround for the workaround is to create the document definition, excluding the footer on the server, and to add the footer on the client.

FrancisCalizo commented 6 years ago

@mozfet Hey Mozfet, do you happen to have code for me to get an idea on how to get a pdf generated server-side? I've been stuck on this for a bit and just need some sort of idea. I am using Node and Express at the moment and am confused as to how to even get a pdf generated with these docs.

mLuby commented 6 years ago

@FrancisCalizo does my second code block above not work for you?

@mozfet I remember seeing mention of fonts when tracing this issue through pdfmake's dependencies but I don't remember actually ever finding the font definitions. You think one or more deps actually come packaged with font files?

officer-rosmarino commented 6 years ago

@mLuby for me it works, but I don't need to create a file. I need to create a Buffer in order to be able to send the generated pdf as an email attachment. Do you have any idea on how to do it?

FrancisCalizo commented 6 years ago

@mLuby thanks for the reply. I am sort somewhat unfamiliar with some backend aspects, I've been studying JS for about 3-4 months.

I created a file using Express Generator. Would that second block of code go into a file in the routes folder? I used the second block of code in the index.js file in the routes folder.

Sorry for the newbie question, still trying to get a hang of all this.

officer-rosmarino commented 6 years ago

@FrancisCalizo it doesn't matter where you put it. You could put it in a route handler in an Express application, in a standalone script or whatever else.

For instance, if you want to generate and save a PDF file when you call a certain API, you could do something like this:

[create express server]
var PdfPrinter = require('pdfmake');
var fs = require('fs');

app.get('/generatePDF', (req, res)=>{
  const doc = new PdfMake().createPdfKitDocument({content: 'my text'})
  doc.pipe(fs.createWriteStream('myFile.pdf'))
  doc.end()
})
mozfet commented 6 years ago

@mLuby I never got it working server side... I generate the document definition server side and the pdf client side...

mozfet commented 6 years ago

@honestserpent I would not expect that code to work server side because PdfMake is never imported, PdfPrinter is. I could not find a PdfMake function available for import server side (I logged the imported object to the console to check), and got an error when trying to use pdf printer without a fonts object.

As I understand it PdfMake is for use client side, PdfPrinter is used server side.

mozfet commented 6 years ago

@mLuby I think that you will not find these standard fonts in the pdfmake dependency chain because they are embedded in the pdf viewer binaries already installed as client-side software, they are thus built in and need not be embedded in pdf document and need not be known to pdfmake, are thus only referred to by name.

officer-rosmarino commented 6 years ago

@mozfet it doesn't work because I messed it up and didn't pass the font argument to the constructor. Here is a working gist

officer-rosmarino commented 6 years ago

@mozfet @mLuby I also made a first attempt to create the express version. Here is the gist.

mozfet commented 6 years ago

@honestserpent I got that far as well, which is basically your opening post. Then the pdf downloads on the client side, it complains about missing font. When I used the standard fonts, then there was no error in pdf viewer, but nothing was visible either. The meta information is however clearly readable inside adobe as file properties, and thus I do not expect it to be a transmission encoding issue...

officer-rosmarino commented 6 years ago

@mozfet the one I did is purely server side, and it does not give me any problems with the generated PDFs. Gotta say that the generated pdf is VERY simple (just a line of text). But hey, it works! :D

mozfet commented 6 years ago

@honestserpent I have created a reply gist of doing the same on Meteor. To me it looks like I am doing the same as you...

officer-rosmarino commented 6 years ago

And this doesn't work for you?

mozfet commented 6 years ago

Nooo...

When I download using Chrome and open the generated pdf on OSX using Adobe Acrobat I get an error. screen shot 2018-04-19 at 17 21 59

officer-rosmarino commented 6 years ago

Well, I don't know specifically. Have you tried my example? Try to implement it in meteor and let me know. I can try meteor but not now

mozfet commented 6 years ago

@honestserpent It is all based on your example, which is the same as mLuby's, just refactored, and I tested the meteor gist this afternoon, resulted in the snapshot above.

Anyhoo I do not need it now anymore, it is working ok with the workaround, and I'm starting to think it may even be a good thing doing it this way, less server processing means faster responses and it is quite fast enough on the client. Perhaps in a few weeks I give it another go.

officer-rosmarino commented 6 years ago

I am wondering if this is an issue with Mac OS. Do you have a chance to try it on a different OS?

mozfet commented 6 years ago

I do not have another OS instance read ATM... here is the file; can you open it and see any text? report.pdf

officer-rosmarino commented 6 years ago

No, I can open it but I don't see any text. But it makes sense. Would be interesting to test your code on a different platform. I'm gonna test it on mine.

dvruette commented 6 years ago

Thanks for the hint! This was the final piece in the puzzle of generating a PDF on the server. It was quite confusing that I had to load the fonts on my own when they were just specified as a path in every example as well as in the documentation.

mLuby commented 6 years ago

@bpampuch would you welcome a PR to fix this? I believe it's a non-breaking API change.

liborm85 commented 6 years ago

@mLuby, I do not see any bug to fix, because:

mLuby commented 6 years ago

It's not a bug exactly, just a frustrating user experience (and others agree 👍).

My goal here is to save others the time and frustration I went through to get this library working. Once it works, pdfMake is a super helpful piece of software, so thank you! 😄

It's your work, so I'll close this issue if you still disagree with me that the UX could be improved.

muffeeee commented 6 years ago

This module is a nightmare for server-side. You should really just remove it from npm so people don't have to waste their time on this until you can provide proper (and functional) documentation. It doesn't even seem like you really understand your own project..

Even after two hours of digging in GitHub issues and a couple stackoverflow questions, the library still wouldn't render a PDF document with my specified font. Tried doing it the way the docs said (if you can even call that docs, half the functionality of this project aren't even mentioned), tried converting the fonts to base64 using the vfs_fonts.js method (as described in the comments here).

I'll just find something else to generate my PDFs, I guess.

dvruette commented 6 years ago

You’re right, the features of this module are great, but getting it to work server side is a hassle and the documentation doesn’t help whatsoever, which is a shame..

mLuby commented 6 years ago

@muffeeee It's still a pretty darn useful piece of software, but I 💯agree with you that the documentation and dev experience could be significantly improved for server-side users.

Makes me wonder whether there's support for a server-focused fork? 👍👎

kaushikpathak commented 6 years ago

This module is a nightmare for server-side. You should really just remove it from npm so people don't have to waste their time on this until you can provide proper (and functional) documentation. It doesn't even seem like you really understand your own project..

Even after two hours of digging in GitHub issues and a couple stackoverflow questions, the library still wouldn't render a PDF document with my specified font. Tried doing it the way the docs said (if you can even call that docs, half the functionality of this project aren't even mentioned), tried converting the fonts to base64 using the vfs_fonts.js method (as described in the comments here).

I'll just find something else to generate my PDFs, I guess.

Hey, @muffeeee I am using pdfmake for server side and I have used multiple fonts, even I am using MICR for printing cheque. Just a few changes you need to do in code of pdfmake. I agree pdfmake is not that good but we can modify code as per our use.

YoannBuzenet commented 4 years ago

Hey ! So could anyone make a small recap on how to make it work serverside ? I'm stuck on the font problem and I still don't get how to proceed.

Thank you :)

YoannBuzenet commented 4 years ago

Got it working. Just to share with you guys What I did :

And I finally stopped having this message...

Credits to this SO answer : https://stackoverflow.com/questions/45196528/how-to-create-a-pdf-on-node-js-using-pdfmake-and-vfs-fonts

So it looks like that ;

let fonts = {
    Roboto: {
        normal: 'node_modules/roboto-font/fonts/Roboto/roboto-regular-webfont.ttf',
        bold: 'node_modules/roboto-font/fonts/Roboto/roboto-bold-webfont.ttf',
        italics: 'node_modules/roboto-font/fonts/Roboto/roboto-italic-webfont.ttf',
        bolditalics: 'node_modules/roboto-font/fonts/Roboto/roboto-bolditalic-webfont.ttf'
    }
};
let printer = new pdfMake(fonts);

EDIT : Here is my whole code that works server side :

var PdfPrinter = require("pdfmake");
var fs = require("fs");

var fonts = {
  Roboto: {
    normal: "node_modules/roboto-font/fonts/Roboto/roboto-regular-webfont.ttf",
    bold: "node_modules/roboto-font/fonts/Roboto/roboto-bold-webfont.ttf",
    italics: "node_modules/roboto-font/fonts/Roboto/roboto-italic-webfont.ttf",
    bolditalics:
      "node_modules/roboto-font/fonts/Roboto/roboto-bolditalic-webfont.ttf",
  },
};

var printer = new PdfPrinter(fonts);

function writePDF() {
  var docDefinition = {
    content: [
      {
        layout: "lightHorizontalLines", // optional
        table: {
          // headers are automatically repeated if the table spans over multiple pages
          // you can declare how many rows should be treated as headers
          headerRows: 1,
          widths: ["*", "auto", 100, "*"],

          body: [
            ["First", "Second", "Third", "The last one"],
            ["Value 1", "Value 2", "Value 3", "Value 4"],
            [{ text: "Bold value", bold: true }, "Val 2", "Val 3", "Val 4"],
          ],
        },
      },
    ],
  };
  var options;
  var pdfDoc = printer.createPdfKitDocument(docDefinition, options);
  pdfDoc.pipe(fs.createWriteStream("document.pdf"));
  pdfDoc.end();
}

module.exports = { writePDF };
paddotk commented 4 years ago

This answer seems to work. However I think pdfMake should be doing this by default, I almost gave up trying..

govnag commented 3 years ago

@mLuby Thank you!! Your buffer example is the only thing that worked for me; including adding the @YoannBuzenet fix for including the roboto font

I updated new Buffer() -> Buffer.from() and it still works