How to make PDF from HTML

Hi,

Since I came across some limitations in node-red-contrib-pdf (for example not being able to change the paper size) and seeing that the node seems to be deprecated, I'm trying to switch over to html-pdf-node - npm.

I'm loading the module and it's dependencies in the module settings of the function code:
image

The function currently is:

const html_to_pdf = new htmlPdfNode();
//let generated_html = msg.generated_html;

let options = { format: 'A4', path: '/media/qsl/test.pdf' };
//let options = { width: '140m', height: '90mm' };
// Example of options with args //
// let options = { format: 'A4', args: ['--no-sandbox', '--disable-setuid-sandbox'] };

let file = { content: "<h1>Welcome to html-pdf-node</h1>" };

html_to_pdf.generatePdf(file, options).then(pdfBuffer => {
  console.log("PDF Buffer:-", pdfBuffer);
});
return msg

Unfortunately I get:

TypeError: htmlPdfNode is not a constructor

How can I address this issue?

Besides that, I'd like to use a msg value (like msg.id) to define the path. Example: let options = { format: 'A4', path: '/media/qsl/HEREGOESTHEMSGID.pdf' };
Also, I need to load html which is generated in the template node before the function node into let file = { content: "<h1>Welcome to html-pdf-node</h1>" } instead of

Welcome to html-pdf-node

I'd really appreciate your help on this.

Thank you very much

Sure you can. Just set it in the docDefinition

https://pdfmake.github.io/docs/0.1/document-definition-object/page/

EDIT:

I assume you mean the node-red-contrib-pdfmake node?

Currently I'm using node-red-contrib-pdf (node) - Node-RED

I also thought about using the node use sent but it does not support html code. There is an "extension" to it (GitHub - Aymkdn/html-to-pdfmake: This module permits to convert HTML to the PDFMake format) but I don't know if it'd be hard to combine a node-red module with a non-node-red node.

Not really. It is a client side node (needs a window and DOM) but that can be solved with other packages (jsdom).

Here is a demo...
Code_nV71JDWy9C

The output...

The function...

const { JSDOM } = jsdom;
const { window } = new JSDOM("");

const html = htmlToPdfmake(msg.payload, { window: window });

const docDefinition = {
    // a string or { width: number, height: number }
    pageSize: msg.pageSize || 'A4',

    // by default we use portrait, you can change it to landscape if you wish
    pageOrientation: msg.pageOrientation || 'portrait',

    // [left, top, right, bottom] or [horizontal, vertical] or just a number for equal margins
    pageMargins: [40, 60, 40, 60],

    content: [
        html
    ]
};
msg.payload = docDefinition
return msg

The Demo flow...

[{"id":"075ae101fed922d1","type":"pdfmake","z":"4c5ad8c7caa80822","name":"","outputType":"Buffer","inputProperty":"payload","options":"{}","outputProperty":"payload","x":1300,"y":980,"wires":[["4e692a74d7a1230c"]]},{"id":"11a2106c4a5c8d06","type":"function","z":"4c5ad8c7caa80822","name":"HTML to docDef","func":"const { JSDOM } = jsdom;\nconst { window } = new JSDOM(\"\");\n\nconst html = htmlToPdfmake(msg.payload, { window: window });\n\nconst docDefinition = {\n    // a string or { width: number, height: number }\n    pageSize: msg.pageSize || 'A4',\n\n    // by default we use portrait, you can change it to landscape if you wish\n    pageOrientation: msg.pageOrientation || 'portrait',\n\n    // [left, top, right, bottom] or [horizontal, vertical] or just a number for equal margins\n    pageMargins: [40, 60, 40, 60],\n\n    content: [\n        html\n    ]\n};\n\n\nmsg.payload = docDefinition\n\nreturn msg","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[{"var":"htmlToPdfmake","module":"html-to-pdfmake"},{"var":"jsdom","module":"jsdom"}],"x":1300,"y":920,"wires":[["075ae101fed922d1"]]},{"id":"d75fa487a7253c78","type":"inject","z":"4c5ad8c7caa80822","name":"HTML, A6, Landscape","props":[{"p":"payload"},{"p":"topic","vt":"str"},{"p":"filename","v":"c:/temp/b1.pdf","vt":"str"},{"p":"pageOrientation","v":"landscape","vt":"str"},{"p":"pageSize","v":"A6","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"<div>       <h1>Different HTML</h1>      <table>         <tr>             <td>col 1</td> <td>col 2</td>         </tr>         <tr>             <td>A</td> <td>1</td>         </tr>         <tr>             <td>B</td> <td>2</td>         </tr>         <tr>             <td>C</td> <td>3</td>         </tr>     </table>  </div>","payloadType":"str","x":1080,"y":940,"wires":[["11a2106c4a5c8d06"]]},{"id":"4e692a74d7a1230c","type":"file","z":"4c5ad8c7caa80822","name":"","filename":"filename","filenameType":"msg","appendNewline":false,"createDir":true,"overwriteFile":"true","encoding":"none","x":1320,"y":1040,"wires":[["9ba7ebda55b1d722"]]},{"id":"18e8442ae7da9bbc","type":"inject","z":"4c5ad8c7caa80822","name":"HTML, A5, Portait","props":[{"p":"payload"},{"p":"topic","vt":"str"},{"p":"filename","v":"c:/temp/a1.pdf","vt":"str"},{"p":"pageSize","v":"A5","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"<div> <h1>My title</h1> <p>     This is a sentence with a <strong>bold word</strong>, <em>one in italic</em>,     and <u>one with underline</u>. And finally <a href=\"https://www.google.com\">a link to google</a>. </p> </div>","payloadType":"str","x":1100,"y":900,"wires":[["11a2106c4a5c8d06"]]},{"id":"9ba7ebda55b1d722","type":"debug","z":"4c5ad8c7caa80822","name":"","active":true,"tosidebar":true,"console":false,"tostatus":true,"complete":"filename","targetType":"msg","statusVal":"filename","statusType":"auto","x":1310,"y":1100,"wires":[]}]
1 Like

Thank you very much,

Unfortunately I get TypeError: JSDOM is not a constructor

with the following flow:

[
    {
        "id": "ee71cb46e6084549",
        "type": "template",
        "z": "9a9055680f904d80",
        "name": "",
        "field": "generated_html",
        "fieldType": "msg",
        "format": "handlebars",
        "syntax": "mustache",
        "template": "\"<!doctype html>\n<html>\n<head>\n<style>\n    body {\n        background: url('https://cloudlog.online360.at/qslprinter/radio_qsl_sql_bg_withouttext.jpg');\n        background-repeat: no-repeat;\n        background-size: 140mm 90mm;\n        font-family: Arial, Helvetica, sans-serif;\n    }\n    html {\n        height: 100%;\n    }\n    #sql {\n        margin-left: 5mm;\n        margin-top: 15mm;\n        font-size: 12px;\n    }\n    #address {\n        margin-left: 95mm;\n        margin-top: -10mm;\n        font-size: 14px;\n        font-weight: bold;\n    }\n    table {\n        width: 120mm;\n        margin-top: 20mm;\n        margin-left: 5mm;\n    }\n    table, th, td {\n        font-size: 13px;\n        border: 1px solid;\n        text-align: center;\n        border-collapse: collapse;\n    }\n    th, td {\n        padding: 5px;\n    }\n</style>\n</head>\n<body>\n    <div id=\"sql\">SELECT *<br>FROM logbook<br>WHERE callsign='OE3BIO' AND partner='{{COL_CALL}}'<br>ORDER BY date DESC </div>\n    <div id=\"address\">To radio: {{COL_CALL}}\n        <br>\n        {{#COL_QSL_VIA}}\n        <b>via {{COL_QSL_VIA}}</b>\n        {{/COL_QSL_VIA}}\n    </div>\n    <table>\n        <tr>\n            <th>YEAR</th>\n            <th>MONTH</th>\n            <th>DAY</th>\n            <th>TIME (UTC)</th>\n            <th>BAND</th>\n            <th>MODE</th>\n            <th>RST</th>\n        </tr>\n        <tr>\n            <td>{{year}}</td>\n            <td>{{month}}</td>\n            <td>{{day}}</td>\n            <td>{{time}}</td>\n            <td>{{COL_BAND}}</td>\n            <td>{{COL_MODE}}</td>\n            <td>{{COL_RST_SENT}}</td>\n        </tr>\n    </table>\n</body>\n</html>\"",
        "output": "str",
        "x": 900,
        "y": 480,
        "wires": [
            [
                "834be93ce354c306"
            ]
        ]
    },
    {
        "id": "b80b61d89ced5bf4",
        "type": "inject",
        "z": "9a9055680f904d80",
        "name": "inject demo",
        "props": [
            {
                "p": "partner",
                "v": "ABC123",
                "vt": "str"
            },
            {
                "p": "filename",
                "v": "/media/qsl.pdf",
                "vt": "str"
            },
            {
                "p": "manager",
                "v": "CBA321",
                "vt": "str"
            },
            {
                "p": "COL_TIME_OFF",
                "v": "2022-09-05 20:33:00",
                "vt": "str"
            },
            {
                "p": "COL_MODE",
                "v": "FT8",
                "vt": "str"
            },
            {
                "p": "COL_BAND",
                "v": "20m",
                "vt": "str"
            },
            {
                "p": "COL_RST_SENT",
                "v": "-16",
                "vt": "str"
            },
            {
                "p": "COL_QSL_SENT",
                "v": "R",
                "vt": "str"
            },
            {
                "p": "COL_QSL_SENT",
                "v": "R",
                "vt": "str"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "",
        "x": 390,
        "y": 480,
        "wires": [
            [
                "ee71cb46e6084549"
            ]
        ]
    },
    {
        "id": "1f5a7fe3013edd67",
        "type": "mysql",
        "z": "9a9055680f904d80",
        "mydb": "67a5a532e5304528",
        "name": "",
        "x": 730,
        "y": 160,
        "wires": [
            [
                "9f0f9ab18c1b3975",
                "0c360a6d178ffda4"
            ]
        ]
    },
    {
        "id": "0340d4c330c4e0f6",
        "type": "function",
        "z": "9a9055680f904d80",
        "name": "Get QSOs",
        "func": "msg.payload = \"\"\nmsg.topic = 'SELECT COL_PRIMARY_KEY,COL_CALL,COL_QSL_VIA,COL_TIME_ON,COL_MODE,COL_BAND,COL_RST_SENT,COL_QSL_SENT,COL_QSL_SENT FROM TABLE_HRD_CONTACTS_V01 WHERE COL_QSL_SENT=\"R\" ';\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 530,
        "y": 160,
        "wires": [
            [
                "1f5a7fe3013edd67"
            ]
        ]
    },
    {
        "id": "8b3132695fdda741",
        "type": "inject",
        "z": "9a9055680f904d80",
        "name": "Trigger QSL card generation",
        "props": [
            {
                "p": "payload"
            },
            {
                "p": "filename",
                "v": "/media/qsl.pdf",
                "vt": "str"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "",
        "payload": "",
        "payloadType": "date",
        "x": 300,
        "y": 160,
        "wires": [
            [
                "e60da31cd797efdf"
            ]
        ]
    },
    {
        "id": "ea741ffad455ff94",
        "type": "change",
        "z": "9a9055680f904d80",
        "name": "",
        "rules": [
            {
                "t": "set",
                "p": "COL_CALL",
                "pt": "msg",
                "to": "payload.COL_CALL",
                "tot": "msg"
            },
            {
                "t": "set",
                "p": "COL_QSL_VIA",
                "pt": "msg",
                "to": "payload.COL_QSL_VIA",
                "tot": "msg"
            },
            {
                "t": "set",
                "p": "COL_MODE",
                "pt": "msg",
                "to": "payload.COL_MODE",
                "tot": "msg"
            },
            {
                "t": "set",
                "p": "COL_BAND",
                "pt": "msg",
                "to": "payload.COL_BAND",
                "tot": "msg"
            },
            {
                "t": "set",
                "p": "COL_RST_SENT",
                "pt": "msg",
                "to": "payload.COL_RST_SENT",
                "tot": "msg"
            },
            {
                "t": "set",
                "p": "COL_QSL_SENT",
                "pt": "msg",
                "to": "payload.COL_QSL_SENT",
                "tot": "msg"
            },
            {
                "t": "set",
                "p": "filename",
                "pt": "msg",
                "to": "\"/media/qsl/\" & $.payload.COL_PRIMARY_KEY & \".pdf\"",
                "tot": "jsonata"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 400,
        "y": 300,
        "wires": [
            [
                "a7bc6ca94c8649cb"
            ]
        ]
    },
    {
        "id": "a7bc6ca94c8649cb",
        "type": "moment",
        "z": "9a9055680f904d80",
        "name": "get year",
        "topic": "",
        "input": "COL_TIME_ON_formatted",
        "inputType": "msg",
        "inTz": "ETC/UTC",
        "adjAmount": 0,
        "adjType": "days",
        "adjDir": "add",
        "format": "YYYY",
        "locale": "C",
        "output": "year",
        "outputType": "msg",
        "outTz": "ETC/UTC",
        "x": 640,
        "y": 300,
        "wires": [
            [
                "6eb1f5ba2fddb727"
            ]
        ]
    },
    {
        "id": "6eb1f5ba2fddb727",
        "type": "moment",
        "z": "9a9055680f904d80",
        "name": "get month",
        "topic": "",
        "input": "COL_TIME_ON_formatted",
        "inputType": "msg",
        "inTz": "ETC/UTC",
        "adjAmount": 0,
        "adjType": "days",
        "adjDir": "add",
        "format": "MM",
        "locale": "C",
        "output": "month",
        "outputType": "msg",
        "outTz": "ETC/UTC",
        "x": 640,
        "y": 340,
        "wires": [
            [
                "7adeee6b4f9eebf3"
            ]
        ]
    },
    {
        "id": "7adeee6b4f9eebf3",
        "type": "moment",
        "z": "9a9055680f904d80",
        "name": "get day",
        "topic": "",
        "input": "COL_TIME_ON_formatted",
        "inputType": "msg",
        "inTz": "ETC/UTC",
        "adjAmount": 0,
        "adjType": "days",
        "adjDir": "add",
        "format": "DD",
        "locale": "C",
        "output": "day",
        "outputType": "msg",
        "outTz": "ETC/UTC",
        "x": 640,
        "y": 380,
        "wires": [
            [
                "58994e7e0483c7ac"
            ]
        ]
    },
    {
        "id": "58994e7e0483c7ac",
        "type": "moment",
        "z": "9a9055680f904d80",
        "name": "get time",
        "topic": "",
        "input": "COL_TIME_ON_formatted",
        "inputType": "msg",
        "inTz": "ETC/UTC",
        "adjAmount": 0,
        "adjType": "days",
        "adjDir": "add",
        "format": "HH:mm",
        "locale": "C",
        "output": "time",
        "outputType": "msg",
        "outTz": "ETC/UTC",
        "x": 640,
        "y": 420,
        "wires": [
            [
                "ee71cb46e6084549"
            ]
        ]
    },
    {
        "id": "6c1cd0a9d0a25243",
        "type": "string",
        "z": "9a9055680f904d80",
        "name": "",
        "methods": [
            {
                "name": "toString",
                "params": []
            },
            {
                "name": "replaceAll",
                "params": [
                    {
                        "type": "str",
                        "value": " GMT+0200 (Central European Summer Time)"
                    },
                    {
                        "type": "str",
                        "value": " UTC"
                    }
                ]
            }
        ],
        "prop": "payload.COL_TIME_ON",
        "propout": "COL_TIME_ON_formatted",
        "object": "msg",
        "objectout": "msg",
        "x": 370,
        "y": 240,
        "wires": [
            [
                "ea741ffad455ff94"
            ]
        ]
    },
    {
        "id": "9f0f9ab18c1b3975",
        "type": "split",
        "z": "9a9055680f904d80",
        "name": "",
        "splt": "\\n",
        "spltType": "str",
        "arraySplt": 1,
        "arraySpltType": "len",
        "stream": false,
        "addname": "",
        "x": 890,
        "y": 180,
        "wires": [
            [
                "6c1cd0a9d0a25243"
            ]
        ]
    },
    {
        "id": "7ab6f15f763da16a",
        "type": "function",
        "z": "9a9055680f904d80",
        "name": "function 52",
        "func": "const html_to_pdf = new htmlPdfNode();\n//let generated_html = msg.generated_html;\n\nlet options = { format: 'A4', path: '/media/qsl/test.pdf' };\n//let options = { width: '140m', height: '90mm' };\n// Example of options with args //\n// let options = { format: 'A4', args: ['--no-sandbox', '--disable-setuid-sandbox'] };\n\nlet file = { content: \"<h1>Welcome to html-pdf-node</h1>\" };\n\nhtml_to_pdf.generatePdf(file, options).then(pdfBuffer => {\n  console.log(\"PDF Buffer:-\", pdfBuffer);\n});\nreturn msg",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [
            {
                "var": "htmlPdfNode",
                "module": "html-pdf-node"
            },
            {
                "var": "bluebird",
                "module": "bluebird"
            },
            {
                "var": "handlebars",
                "module": "handlebars"
            },
            {
                "var": "inlineCss",
                "module": "inline-css"
            },
            {
                "var": "puppeteer",
                "module": "puppeteer"
            }
        ],
        "x": 1090,
        "y": 420,
        "wires": [
            [
                "041f05f7276be52e",
                "cd8e27f9212632c3"
            ]
        ]
    },
    {
        "id": "834be93ce354c306",
        "type": "change",
        "z": "9a9055680f904d80",
        "name": "",
        "rules": [
            {
                "t": "set",
                "p": "width",
                "pt": "msg",
                "to": "140",
                "tot": "num"
            },
            {
                "t": "set",
                "p": "height",
                "pt": "msg",
                "to": "90",
                "tot": "num"
            },
            {
                "t": "set",
                "p": "pageOrientation",
                "pt": "msg",
                "to": "landscape",
                "tot": "str"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 1060,
        "y": 480,
        "wires": [
            [
                "11a2106c4a5c8d06"
            ]
        ]
    },
    {
        "id": "0c360a6d178ffda4",
        "type": "debug",
        "z": "9a9055680f904d80",
        "name": "debug 47",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "false",
        "statusVal": "",
        "statusType": "auto",
        "x": 980,
        "y": 120,
        "wires": []
    },
    {
        "id": "e60da31cd797efdf",
        "type": "function",
        "z": "9a9055680f904d80",
        "name": "Get QSOs",
        "func": "msg.payload = \"\"\nmsg.topic = 'SELECT COL_PRIMARY_KEY,COL_CALL,COL_QSL_VIA,COL_TIME_ON,COL_MODE,COL_BAND,COL_RST_SENT,COL_QSL_SENT,COL_QSL_SENT FROM TABLE_HRD_CONTACTS_V01 WHERE COL_CALL=\"DK1PU\" ';\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 530,
        "y": 120,
        "wires": [
            [
                "1f5a7fe3013edd67"
            ]
        ]
    },
    {
        "id": "075ae101fed922d1",
        "type": "pdfmake",
        "z": "9a9055680f904d80",
        "name": "",
        "outputType": "Buffer",
        "inputProperty": "payload",
        "options": "{}",
        "outputProperty": "payload",
        "x": 1340,
        "y": 660,
        "wires": [
            [
                "4e692a74d7a1230c"
            ]
        ]
    },
    {
        "id": "11a2106c4a5c8d06",
        "type": "function",
        "z": "9a9055680f904d80",
        "name": "HTML to docDef",
        "func": "const { JSDOM } = jsdom;\nconst { window } = new JSDOM(\"\");\n\nconst html = htmlToPdfmake(msg.payload, { window: window });\n\nconst docDefinition = {\n    // a string or { width: number, height: number }\n    pageSize: {\n    width: msg.width ,\n    height: msg.height\n  },\n\n    // by default we use portrait, you can change it to landscape if you wish\n    pageOrientation: msg.pageOrientation || 'landscape',\n\n    // [left, top, right, bottom] or [horizontal, vertical] or just a number for equal margins\n    //pageMargins: [40, 60, 40, 60],\n\n    content: [\n        html\n    ]\n};\n\n\nmsg.payload = docDefinition\n\nreturn msg",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [
            {
                "var": "htmlToPdfmake",
                "module": "html-to-pdfmake"
            },
            {
                "var": "jsdom",
                "module": "jsdom"
            }
        ],
        "x": 1340,
        "y": 600,
        "wires": [
            [
                "075ae101fed922d1"
            ]
        ]
    },
    {
        "id": "4e692a74d7a1230c",
        "type": "file",
        "z": "9a9055680f904d80",
        "name": "",
        "filename": "filename",
        "filenameType": "msg",
        "appendNewline": false,
        "createDir": true,
        "overwriteFile": "true",
        "encoding": "none",
        "x": 1360,
        "y": 720,
        "wires": [
            [
                "9ba7ebda55b1d722"
            ]
        ]
    },
    {
        "id": "9ba7ebda55b1d722",
        "type": "debug",
        "z": "9a9055680f904d80",
        "name": "",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": true,
        "complete": "filename",
        "targetType": "msg",
        "statusVal": "filename",
        "statusType": "auto",
        "x": 1350,
        "y": 780,
        "wires": []
    },
    {
        "id": "67a5a532e5304528",
        "type": "MySQLdatabase",
        "name": "cloudlog db",
        "host": "0.0.0.0",
        "port": "3306",
        "db": "cloudlog",
        "tz": "",
        "charset": "UTF8"
    }
]

jsdom is set under modules as I copied the node from your side.

As you can see from my demo, it works for me.

  1. Does the server running node-red have internet access?
  2. Have you tried restarting node-red?
  3. What versions of NODE and node-red are you using? (I am using Node 16 and Node-red 3.0.2)
  4. Are you running node-red directly or in a container (e.g. docker/HA)?

Regarding each question:

yes, I'm actually accessing it via the Internet
2.
yes, also the server itself
3.
Node-Red 3.0.2
Node 16.17
4.
It's directly installed without a container

I can see jsdom under /.node-red/node_modules.

Ok so after running n latest and n prune, I'm now getting an output.
Node version is now v18.9.0

The pdf is currently only several empty pages some tables at the end (without content).

Do I have to put some " or ' at the beginning and end of the following template?:

<!doctype html>
<html>
<head>
<style>
    body {
        background: url('/radio_qsl_sql_bg_withouttext.jpg');
        background-repeat: no-repeat;
        background-size: 140mm 90mm;
        font-family: Arial, Helvetica, sans-serif;
    }
    html {
        height: 100%;
    }
    #sql {
        margin-left: 5mm;
        margin-top: 15mm;
        font-size: 12px;
    }
    #address {
        margin-left: 95mm;
        margin-top: -10mm;
        font-size: 14px;
        font-weight: bold;
    }
    table {
        width: 120mm;
        margin-top: 20mm;
        margin-left: 5mm;
    }
    table, th, td {
        font-size: 13px;
        border: 1px solid;
        text-align: center;
        border-collapse: collapse;
    }
    th, td {
        padding: 5px;
    }
</style>
</head>
<body>
    <div id="sql">SELECT *<br>FROM logbook<br>WHERE callsign='OE3BIO' AND partner='{{COL_CALL}}'<br>ORDER BY date DESC </div>
    <div id="address">To radio: {{COL_CALL}}
        <br>
        {{#COL_QSL_VIA}}
        <b>via {{COL_QSL_VIA}}</b>
        {{/COL_QSL_VIA}}
    </div>
    <table>
        <tr>
            <th>YEAR</th>
            <th>MONTH</th>
            <th>DAY</th>
            <th>TIME (UTC)</th>
            <th>BAND</th>
            <th>MODE</th>
            <th>RST</th>
        </tr>
        <tr>
            <td>{{year}}</td>
            <td>{{month}}</td>
            <td>{{day}}</td>
            <td>{{time}}</td>
            <td>{{COL_BAND}}</td>
            <td>{{COL_MODE}}</td>
            <td>{{COL_RST_SENT}}</td>
        </tr>
    </table>
</body>
</html>

Ok it seems to be some problem with page width and height as well as the node not accepting all css.

I will play a bit with them and post a solution, if I find one.

After hundreds of pdf generations, I'm now somehow happy with the result:

[
    {
        "id": "d3a42c6fb6abe1dc",
        "type": "template",
        "z": "9a9055680f904d80",
        "name": "",
        "field": "payload",
        "fieldType": "msg",
        "format": "handlebars",
        "syntax": "mustache",
        "template": "<!doctype html>\n<html>\n<head>\n</head>\n<body>\n    <div class=\"sql\">SELECT *<br>FROM logbook<br>WHERE callsign='OE3BIO' AND partner='{{COL_CALL}}'<br>ORDER BY date DESC </div>\n    <div class=\"address\">To radio: {{COL_CALL}}\n        <br>\n        {{#COL_QSL_VIA}}\n        <b>via {{COL_QSL_VIA}}</b>\n        {{/COL_QSL_VIA}}\n    </div>\n    <div class=\"qso\" style=\"font-size:13px; text-align:center;\">\n    <table style=\"border-collapse:collapse; border:1px solid;\">\n        <tr style=\"font-weight:bold;\">\n            <th width=\"50px\">YEAR</th>\n            <th width=\"50px\">MONTH</th>\n            <th width=\"50px\">DAY</th>\n            <th width=\"80px\">TIME (UTC)</th>\n            <th width=\"50px\">BAND</th>\n            <th width=\"50px\">MODE</th>\n            <th width=\"50px\">RST</th>\n        </tr>\n        <tr style=\"padding:5px\">\n            <td>{{year}}</td>\n            <td>{{month}}</td>\n            <td>{{day}}</td>\n            <td>{{time}}</td>\n            <td>{{COL_BAND}}</td>\n            <td>{{COL_MODE}}</td>\n            <td>{{COL_RST_SENT}}</td>\n        </tr>\n    </table>\n    </div>\n</body>\n</html>",
        "output": "str",
        "x": 880,
        "y": 480,
        "wires": [
            [
                "834be93ce354c306"
            ]
        ]
    },
    {
        "id": "834be93ce354c306",
        "type": "change",
        "z": "9a9055680f904d80",
        "name": "",
        "rules": [
            {
                "t": "set",
                "p": "pageOrientation",
                "pt": "msg",
                "to": "landscape",
                "tot": "str"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 1090,
        "y": 480,
        "wires": [
            [
                "11a2106c4a5c8d06"
            ]
        ]
    },
    {
        "id": "11a2106c4a5c8d06",
        "type": "function",
        "z": "9a9055680f904d80",
        "name": "HTML to docDef",
        "func": "const { JSDOM } = jsdom;\nconst { window } = new JSDOM(\"\");\n\nconst html = htmlToPdfmake(msg.payload, { window: window, tableAutoSize:true });\n\nconst docDefinition = {\n    // a string or { width: number, height: number }\npageSize: {\n    width: 396.85,\n    height: 255.118\n  },\n\n//pageSize: msg.pageSize || 'A4',\n\n    // by default we use portrait, you can change it to landscape if you wish\n    pageOrientation: msg.pageOrientation || 'landscape',\n    pageMargins: [ 20, 0, 0, 0 ],\n\n    // [left, top, right, bottom] or [horizontal, vertical] or just a number for equal margins\n    //pageMargins: [40, 60, 40, 60],\n\n    content: [\n        html\n    ],\n    styles:{\n        sql:{\n            //marginLeft: 14.1732,\n            marginTop: 40,\n            fontSize: 9,\n            marginBottom: 25\n        },\n        address:{\n            marginLeft: 270,\n            marginTop: -80,\n            fontSize: 10.5,\n            bold: true,\n            marginBottom: 40\n        },\n        //qso:{\n            //alignment: 'center'\n            //width: 100,\n            //width: 200,\n            //marginTop: 56.6929,\n            //paddingLeft: 14.1732,\n            //fontSize: 13\n        //}\n}};\n\n\nmsg.payload = docDefinition\n\nreturn msg",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [
            {
                "var": "htmlToPdfmake",
                "module": "html-to-pdfmake"
            },
            {
                "var": "jsdom",
                "module": "jsdom"
            }
        ],
        "x": 1340,
        "y": 600,
        "wires": [
            [
                "075ae101fed922d1"
            ]
        ]
    },
    {
        "id": "075ae101fed922d1",
        "type": "pdfmake",
        "z": "9a9055680f904d80",
        "name": "",
        "outputType": "Buffer",
        "inputProperty": "payload",
        "options": "{}",
        "outputProperty": "payload",
        "x": 1340,
        "y": 660,
        "wires": [
            [
                "4e692a74d7a1230c"
            ]
        ]
    },
    {
        "id": "4e692a74d7a1230c",
        "type": "file",
        "z": "9a9055680f904d80",
        "name": "",
        "filename": "filename",
        "filenameType": "msg",
        "appendNewline": false,
        "createDir": true,
        "overwriteFile": "true",
        "encoding": "none",
        "x": 1360,
        "y": 720,
        "wires": [
            [
                "9ba7ebda55b1d722"
            ]
        ]
    },
    {
        "id": "9ba7ebda55b1d722",
        "type": "debug",
        "z": "9a9055680f904d80",
        "name": "",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": true,
        "complete": "filename",
        "targetType": "msg",
        "statusVal": "filename",
        "statusType": "auto",
        "x": 1350,
        "y": 780,
        "wires": []
    }
]

Template:

<!doctype html>
<html>
<head>
</head>
<body>
    <div class="sql">SELECT *<br>FROM logbook<br>WHERE callsign='OE3BIO' AND partner='{{COL_CALL}}'<br>ORDER BY date DESC </div>
    <div class="address">To radio: {{COL_CALL}}
        <br>
        {{#COL_QSL_VIA}}
        <b>via {{COL_QSL_VIA}}</b>
        {{/COL_QSL_VIA}}
    </div>
    <div class="qso" style="font-size:13px; text-align:center;">
    <table style="border-collapse:collapse; border:1px solid;">
        <tr style="font-weight:bold;">
            <th width="50px">YEAR</th>
            <th width="50px">MONTH</th>
            <th width="50px">DAY</th>
            <th width="80px">TIME (UTC)</th>
            <th width="50px">BAND</th>
            <th width="50px">MODE</th>
            <th width="50px">RST</th>
        </tr>
        <tr style="padding:5px">
            <td>{{year}}</td>
            <td>{{month}}</td>
            <td>{{day}}</td>
            <td>{{time}}</td>
            <td>{{COL_BAND}}</td>
            <td>{{COL_MODE}}</td>
            <td>{{COL_RST_SENT}}</td>
        </tr>
    </table>
    </div>
</body>
</html>

Function:

const { JSDOM } = jsdom;
const { window } = new JSDOM("");

const html = htmlToPdfmake(msg.payload, { window: window, tableAutoSize:true });

const docDefinition = {
    // a string or { width: number, height: number }
pageSize: {
    width: 396.85,
    height: 255.118
  },

//pageSize: msg.pageSize || 'A4',

    // by default we use portrait, you can change it to landscape if you wish
    pageOrientation: msg.pageOrientation || 'landscape',
    pageMargins: [ 20, 0, 0, 0 ],

    // [left, top, right, bottom] or [horizontal, vertical] or just a number for equal margins
    //pageMargins: [40, 60, 40, 60],

    content: [
        html
    ],
    styles:{
        sql:{
            //marginLeft: 14.1732,
            marginTop: 40,
            fontSize: 9,
            marginBottom: 25
        },
        address:{
            marginLeft: 270,
            marginTop: -80,
            fontSize: 10.5,
            bold: true,
            marginBottom: 40
        },
        //qso:{
            //alignment: 'center'
            //width: 100,
            //width: 200,
            //marginTop: 56.6929,
            //paddingLeft: 14.1732,
            //fontSize: 13
        //}
}};


msg.payload = docDefinition

return msg

The thing is, html-to-pdfmake make and pdfmake both do not allow all css functionalities in html or in the function.
Especially tables are very hard to format and need inline css and so on.

There are definitely people who are able to clean this up but for me it's working now.

Thanks again to @Steve-Mcl

I'll now try to add another row to the table for other payloads with the same identifier (callsign).