Has anyone used the 'pdfmake' node to embed an image in a document?

I'm trying to use this node to create a PDF from a 'document description' as a project for my IoT students to automatically create a newsletter using text and images from external sources. So far text insertion works fine, the problem is inserting an image as per the supplied example with the node.

With help from @zenofmud I have this flow which uses http-request to insert an external image (from a remote server or a server like NGINX on a local RPi) successfully in to a document.

[{"id":"00613c000e438d73","type":"file","z":"d089cbcf16e105d6","name":"","filename":"zzz.pdf","appendNewline":true,"createDir":false,"overwriteFile":"true","encoding":"none","x":1130,"y":1660,"wires":[[]]},{"id":"8a372253633bb4fc","type":"pdfmake","z":"d089cbcf16e105d6","name":"","outputType":"Buffer","inputProperty":"payload","options":"{}","outputProperty":"payload","x":980,"y":1660,"wires":[["00613c000e438d73"]]},{"id":"ece6ac22c59f46f6","type":"function","z":"d089cbcf16e105d6","name":"","func":"let picture = msg.payload;\n\nlet dd= {\n    \"pageSize\": \"A5\",\n    \"pageOrientation\": \"portrait\",\n    \"pageMargins\": [\n        40,\n        60,\n        40,\n        60\n    ],\n    \"info\": {\n        \"title\": \"My awesome document\",\n        \"author\": \"David Dempster\",\n        \"subject\": \"Just testing PDFmaker\",\n        \"keywords\": \"Node-RED, Raspberry Pi, RPi\"\n    },\n    \"content\": [\n        \"Sample Image from file\",\n        {\n            \"image\": \"data:image/jpeg;base64,\" + picture,\n            \"width\": 200\n        },\n        \"Sample SVG\",\n        {\n            \"svg\": \"<svg width=\\\"300\\\" height=\\\"200\\\" viewBox=\\\"0 0 300 200\\\"><polygon points=\\\"100,10 40,198 190,78 10,78 160,198\\\" style=\\\"fill:lime;stroke:purple;stroke-width:5;fill-rule:evenodd;stroke-linecap:round;\\\"/></svg>\",\n            \"width\": 150\n        }\n\n    ]\n}\n\nmsg.payload = dd;\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":820,"y":1660,"wires":[["8a372253633bb4fc"]]},{"id":"2be58c956b005552","type":"http request","z":"d089cbcf16e105d6","name":"","method":"GET","ret":"bin","paytoqs":"ignore","url":"","tls":"","persist":false,"proxy":"","authType":"","senderr":false,"x":470,"y":1660,"wires":[["05c80df79fda374f","98851d9bd00d1d05","1c90cdd62290c3fe"]]},{"id":"bb8a456f3b3e783d","type":"inject","z":"d089cbcf16e105d6","name":"castleton_ducks.jpg remote access","props":[{"p":"url","v":"http://resources-area.co.uk/pics_of_places/castleton_ducks.jpg","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":200,"y":1600,"wires":[["2be58c956b005552"]]},{"id":"05c80df79fda374f","type":"image","z":"d089cbcf16e105d6","name":"Latest detections","width":"320","data":"payload","dataType":"msg","thumbnail":false,"active":true,"pass":false,"outputs":0,"x":490,"y":1780,"wires":[]},{"id":"98851d9bd00d1d05","type":"debug","z":"d089cbcf16e105d6","name":"debug 12","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":460,"y":1720,"wires":[]},{"id":"1c90cdd62290c3fe","type":"base64","z":"d089cbcf16e105d6","name":"","action":"str","property":"payload","x":660,"y":1660,"wires":[["aadffb260219948d","ece6ac22c59f46f6"]]},{"id":"aadffb260219948d","type":"debug","z":"d089cbcf16e105d6","name":"debug 15","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":660,"y":1720,"wires":[]},{"id":"a5887fe74433f4ba","type":"inject","z":"d089cbcf16e105d6","name":"san_francisco on remote server","props":[{"p":"url","v":"http://resources-area.co.uk/pics_of_places/san_francisco.jpg","vt":"str"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":190,"y":1660,"wires":[["2be58c956b005552"]]},{"id":"3b2a62bf1f2b029e","type":"inject","z":"d089cbcf16e105d6","name":"mini_tower on my server","props":[{"p":"url","v":"http://192.168.1.152/pics_of_places/mini_tower.jpg","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":170,"y":1720,"wires":[["2be58c956b005552"]]},{"id":"bc103ac244be3b1c","type":"comment","z":"d089cbcf16e105d6","name":"This works with the http request node and base64","info":"","x":590,"y":1600,"wires":[]}]

I was hoping I could reference the images from within the 'document description (dd)' rather than having to perform an http request, as my students would like to embed more than one image.

My objective was for the students to be able create a newsletter automatically for our weather stations and send it out to a distribution list or make it available via a Telegram request.
e.g. Collect temp/humidity readings and insert this as text, together with a set of related images.

Would appreciate hearing from anyone who has managed to insert multiple images with the example supplied with the 'pdfmake' node, or have found another method of achieving this task..

There are a few ways to skin this.

You could do several http requests for all the images you wish - in series with a change node to move the payload image data to a spare msg prop e.g. move msg.payload to msg.image1)

A better more encapsulated way would be to move the http-req and base64 to a separate flow and access it via a link call

inject > change (set URL) > link-call > change (move payload to image1 & set new url) > link-call > change (move payload to image2 & set new url) > link-call > change (move payload to image3 & set new url) > build PDF

You could make an array of URLs, pass that through a split node, then call the link call & then to a join node.

Many options.

1 Like

Thanks Steve I'll take a look at your suggestions.

I think your first suggestion might be the easiest for my IoT students to tackle (at the moment).

here is a demo

[{"id":"00613c000e438d73","type":"file","z":"6061813d087d6df4","name":"","filename":"zzz.pdf","appendNewline":true,"createDir":false,"overwriteFile":"true","encoding":"none","x":1100,"y":280,"wires":[[]]},{"id":"ece6ac22c59f46f6","type":"function","z":"6061813d087d6df4","name":"","func":"let picture = msg.payload;\nlet dd= {\n    \"pageSize\": \"A5\",\n    \"pageOrientation\": \"portrait\",\n    \"pageMargins\": [\n        40,\n        60,\n        40,\n        60\n    ],\n    \"info\": {\n        \"title\": \"My awesome document\",\n        \"author\": \"David Dempster\",\n        \"subject\": \"Just testing PDFmaker\",\n        \"keywords\": \"Node-RED, Raspberry Pi, RPi\"\n    },\n    \"content\": [\n        \"Sample SVG\",\n        {\n            \"svg\": \"<svg width=\\\"300\\\" height=\\\"200\\\" viewBox=\\\"0 0 300 200\\\"><polygon points=\\\"100,10 40,198 190,78 10,78 160,198\\\" style=\\\"fill:lime;stroke:purple;stroke-width:5;fill-rule:evenodd;stroke-linecap:round;\\\"/></svg>\",\n            \"width\": 150\n        }\n\n    ]\n}\n\nfunction insertImage(docDef, title, imageData, width) {\n    dd.content.unshift({\n        \"image\": \"data:image/jpeg;base64,\" + imageData,\n        \"width\": width || 200\n    })\n    dd.content.unshift(title)\n}\n\nif (msg.images?.ducks) {\n    insertImage(dd, \"a picture of ducks\", msg.images.ducks)\n}\nif (msg.images?.san_francisco) {\n    insertImage(dd, \"a picture of san francisco\", msg.images.san_francisco)\n}\nmsg.payload = dd;\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":790,"y":280,"wires":[["ff77bb8b2f12c908"]]},{"id":"a5887fe74433f4ba","type":"inject","z":"6061813d087d6df4","name":"go","props":[{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":390,"y":340,"wires":[["253cd7aa27765043"]]},{"id":"8a372253633bb4fc","type":"pdfmake","z":"6061813d087d6df4","name":"","outputType":"Buffer","inputProperty":"payload","options":"{}","outputProperty":"payload","x":950,"y":280,"wires":[["00613c000e438d73"]]},{"id":"05c80df79fda374f","type":"image","z":"6061813d087d6df4","name":"Latest detections","width":"320","data":"images[msg.name]","dataType":"msg","thumbnail":false,"active":true,"pass":false,"outputs":0,"x":470,"y":440,"wires":[]},{"id":"78bcaccc2f2f2d9c","type":"link call","z":"6061813d087d6df4","name":"","links":["716c2f69c882fdd0"],"linkType":"static","timeout":"30","x":670,"y":340,"wires":[["759ee64d2b86d2df","05c80df79fda374f"]]},{"id":"253cd7aa27765043","type":"change","z":"6061813d087d6df4","name":"ducks","rules":[{"t":"set","p":"url","pt":"msg","to":"http://resources-area.co.uk/pics_of_places/castleton_ducks.jpg","tot":"str"},{"t":"set","p":"name","pt":"msg","to":"ducks","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":510,"y":340,"wires":[["78bcaccc2f2f2d9c"]]},{"id":"759ee64d2b86d2df","type":"change","z":"6061813d087d6df4","name":"san_francisco","rules":[{"t":"set","p":"url","pt":"msg","to":"http://resources-area.co.uk/pics_of_places/san_francisco.jpg","tot":"str"},{"t":"set","p":"name","pt":"msg","to":"san_francisco","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":880,"y":340,"wires":[["5a8a8189d926fe1c"]]},{"id":"5a8a8189d926fe1c","type":"link call","z":"6061813d087d6df4","name":"","links":["716c2f69c882fdd0"],"linkType":"static","timeout":"30","x":1070,"y":340,"wires":[["0e81b0b85f585afe","ece6ac22c59f46f6","e4a524280fbc8674"]]},{"id":"0e81b0b85f585afe","type":"image","z":"6061813d087d6df4","name":"Latest detections","width":"320","data":"images[msg.name]","dataType":"msg","thumbnail":false,"active":true,"pass":false,"outputs":0,"x":890,"y":440,"wires":[]},{"id":"ff77bb8b2f12c908","type":"debug","z":"6061813d087d6df4","name":"debug 155","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":910,"y":220,"wires":[]},{"id":"e4a524280fbc8674","type":"debug","z":"6061813d087d6df4","name":"debug 157","active":true,"tosidebar":true,"console":false,"tostatus":true,"complete":"images","targetType":"msg","statusVal":"images","statusType":"auto","x":1210,"y":440,"wires":[]},{"id":"bc48871f0e737688","type":"group","z":"6061813d087d6df4","style":{"stroke":"#999999","stroke-opacity":"1","fill":"none","fill-opacity":"1","label":true,"label-position":"nw","color":"#a4a4a4"},"nodes":["7dd6cc2e0f77196b","fbb05a506e732f28","716c2f69c882fdd0","844654de8f435af6","17eba72aeb6a84d6"],"x":384,"y":799,"w":732,"h":82},{"id":"7dd6cc2e0f77196b","type":"http request","z":"6061813d087d6df4","g":"bc48871f0e737688","name":"","method":"GET","ret":"bin","paytoqs":"ignore","url":"","tls":"","persist":false,"proxy":"","authType":"","senderr":false,"x":550,"y":840,"wires":[["fbb05a506e732f28"]]},{"id":"fbb05a506e732f28","type":"base64","z":"6061813d087d6df4","g":"bc48871f0e737688","name":"","action":"str","property":"payload","x":700,"y":840,"wires":[["17eba72aeb6a84d6"]]},{"id":"716c2f69c882fdd0","type":"link in","z":"6061813d087d6df4","g":"bc48871f0e737688","name":"get image (base64)","links":[],"x":425,"y":840,"wires":[["7dd6cc2e0f77196b"]]},{"id":"844654de8f435af6","type":"link out","z":"6061813d087d6df4","g":"bc48871f0e737688","name":"link out 2","mode":"return","links":[],"x":1075,"y":840,"wires":[]},{"id":"17eba72aeb6a84d6","type":"change","z":"6061813d087d6df4","g":"bc48871f0e737688","name":"payload -> images[msg.name]","rules":[{"t":"move","p":"payload","pt":"msg","to":"images[msg.name]","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":910,"y":840,"wires":[["844654de8f435af6"]]}]
1 Like

Brilliant - thanks Steve for taking the time to put this example together.
Here's the result I get by using your flow.


My IoT students will be so excited and pleased as they can now start to automate their newsletter.

1 Like

Just used ChatGPT to have a go at creating the 'dd' code for an A5 page with two equal columns with an image at the top of the first column. Quite easy for a first go. I need to sort out a page header and probably a small quote at the foot of the page to make it look better and more balanced. Thank-you ChatGPT.

Second go. Looking better now.

??? What is that?

I believe in pdfmake ‘dd’ stands for ‘document definition’

Ah, I see. Multi-column only really good for printed pages - are you still advocating those?

uibuilder is gaining the ability to turn data into web page layouts - hoping to get to a position where you can turn all manner of input data into HTML reports and pages. I'd really like to reach a point where PDF is redundant for most things. PDF isn't very accessible so it would be good not to need it. Not sure if you have any students with accessibility issues.

Thanks for your feedback. I'll suggest to the students to consider a single column format and maybe scale it so it fits on their mobile phone. ChatGPT told me this...

In Pdfmake, the pageSize property is specified in points, not pixels. A point is a unit of measurement commonly used in printing and is equivalent to 1/72 of an inch.

However, if you have a specific size in pixels that you want to use, you can convert it to points by dividing it by the desired resolution in pixels per inch. For example, if you want to specify a page size of 800 pixels by 600 pixels at a resolution of 72 pixels per inch, you can convert it to points like this:

let pageWidth = 800 / 72 * 25.4;
let pageHeight = 600 / 72 * 25.4;

let dd = {
  pageSize: {width: pageWidth, height: pageHeight },
  pageOrientation: 'portrait',
  pageMargins: [ 5, 10, 5, 10 ],
  background: '#f0f0f0',
  content: [
    'Page Content...'
  ]
};

msg.payload = dd;
return msg;

Might be a useful introduction to accessibility for them. Be good to teach a new generation accessibility from the start. :grin:

You can specify points as a measurement for CSS as well. CSS values and units - Learn web development | MDN (mozilla.org). It assumes that a point is "1/72nd of 1in" - most print drivers should take care of the actual scaling. I've not tried points with different monitors - would be interesting to see whether the OS takes care of the scaling - it didn't used to but that may well have changed with the advent of big monitors.

CSS allows you to specify separate styles for printing of course should you need to. For web styling, the students should learn a little about vision so that they understand not to make a web-page's content too wide on a wide monitor. That will lead them to the max-width CSS setting and the idea that all of the page content should be wrapped in a container that has a max-width (which could simply be the body tag). Height can also be set of course on a a web page but if you are getting the students to document things, height is generally not set and paging isn't necessary or desirable until the page gets so long that it becomes impractical or is using too much memory.

Just had another go at making a layout that fits on a mobile phone and also Telegram.

1 Like

Nice. :slight_smile: (argh - annoying Discourse wants long responses!)

This topic was automatically closed 60 days after the last reply. New replies are no longer allowed.