javascriptnode.jsexpressqr-codepdfkit

Parse base64 encoded image Data to pdfkit module


I am coding an app, which takes numbers to generate a QR Code with the qrcode module, takes the generated URL and parses it to the pdfkit doc.image method.

//  Skript zur Erstellung von PDFs aus den Daten der mainApp

const PDFDocument = require("pdfkit");
const fs = require("fs");
const QRCode = require("qrcode");
const { resolve } = require("path");

const testList = "664584";

// Create a document

const doc = new PDFDocument({
  size: "A4",
  layout: "landscape",
});
doc.pipe(fs.createWriteStream("QRCode.pdf"));

//  Actual Content
//  Shapes

doc.rect(220.945, 50, 400, 400).stroke();
doc.rect(841.89 - 30 - 160, 50, 160, 50).stroke();
doc.rect(30, 50, 160, 50).stroke();
doc.rect(841.89 - 30 - 160, 595.28 - 250, 160, 50).stroke();

//  QR Code

function getDataURL(str){
  QRCode.toDataURL(str)
  .then(base64 => {
    doc.image(base64,245.945,75,{width:350});
  })
  .catch(err => {
    console.error(`Debugger: Catched Error from QRCode.toDataURL promise: ${err}`)
  })
  .catch(err => {
    console.error(err);
  })
}

getDataURL(testList);

//  Text
doc.fontSize(20).text("Field1", 30, 97.64 + 35);
doc.fontSize(20).text("Field2", 841.89 - (160 + 30), 97.64 + 35);
doc.fontSize(20).text("Field3", 841.89 - (160 + 30), 400 + 97.64 - 75);
doc.fontSize(25).text("Description", 40, 490, {
  align: "center",
  lineBreak: "false",
});

doc.end();

Here is the problem: The method to generate a QR Code is QRCode.toDataURL, which returns a promise. I am able to log the resulting data in the console, and if I parse it to doc.image method, the process is done without issues. Except that pdfkit inserts an empty image.

Generating the PDF with QRCode.toDataURL on the fly

If i take the generated base64 string, declare it as a value and passes it to doc.image, an image is generated just fine.

(...)
//  QR Code

const testListtoURL = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHQAAAB0CAYAAABUmhYnAAAAAklEQVR4AewaftIAAALRSURBVO3BQa7jSAwFwXyE7n/lnL/kqgBBsqdNMCL+YY1RrFGKNUqxRinWKMUapVijFGuUYo1SrFGKNUqxRinWKMUapVijFGuUi4eS8E0qJ0noVLoknKh0SfgmlSeKNUqxRinWKBcvU3lTEk6S0KmcqDyh8qYkvKlYoxRrlGKNcvFhSbhD5Q6VLgknKm9Kwh0qn1SsUYo1SrFGuRhGpUtCl4QTlV9WrFGKNUqxRrn4cUnoVDqVkyRMUqxRijVKsUa5+DCVT1LpktCpfJLKv6RYoxRrlGKNcvGyJHxTEjqVLgmdyhNJ+JcVa5RijVKsUeIfBklCp9Il4UTllxVrlGKNUqxRLh5KQqfSJaFT6ZLQqXRJ6FROVLoknKg8kYRO5SQJncqbijVKsUYp1igXD6mcqDyhckcSOpUnktCpdCpdEv5PxRqlWKMUa5T4hweS0Kl0SehUTpLwhEqXhE6lS0KnckcS7lD5pGKNUqxRijVK/MMXJeFE5YkknKjckYRO5SQJnco3FWuUYo1SrFEuPiwJJypdEk5UTlTuSEKnckcSOpX/U7FGKdYoxRol/uGHJeFEpUtCp9IloVPpktCpdEnoVLoknKg8UaxRijVKsUa5eCgJ36TSqXxSEu5Q6ZLQqXxSsUYp1ijFGuXiZSpvSsIdSbgjCScqXRK6JJyofFOxRinWKMUa5eLDknCHyh1J6FROktCpdEnoknCicpKETqVLQqfyRLFGKdYoxRrl4sepdEk4UTlR6ZLQqTyRhE7lTcUapVijFGuUix+XhE7ljiR0Kp3KSRI6lU7lJAmdyhPFGqVYoxRrlIsPU/kklS4JncqJykkSOpVO5SQJnUqn8qZijVKsUYo1ysXLkvBNSehU7khCp3JHEjqVO5LQqTxRrFGKNUqxRol/WGMUa5RijVKsUYo1SrFGKdYoxRqlWKMUa5RijVKsUYo1SrFGKdYoxRrlP6NMNdKR1U/EAAAAAElFTkSuQmCC";
doc.image(testListtoURL,245.945,75,{width:350});

//  Text
(...)

PDF document generated with the data as a string variable

I first thought about a promise/async issue, but could show, that the pdfkit.js from the module receives the base64 data and returns without errors. I also declared the same variable globally and passed the resolved data to it, but without effect. As you see, I already tried with a simple variable declaration, which works just fine. I only got 3 months worth of js experience and do not know where to further look for the issue. Thank you.


Solution

  • The problem is that getDataURL is async, so doc.end() runs before image parsing is done, and the pdf is empty.

    A quick-and-dirty solution would be to move doc.end part inside promise callback:

      QRCode.toDataURL(str)
      .then(base64 => {
    
            doc.image(base64,245.945,75,{width:350});
    
            // now finish the doc
            doc.fontSize(20).text("Field1", 30, 97.64 + 35);
            doc.fontSize(20).text("Field2", 841.89 - (160 + 30), 97.64 + 35);
            doc.fontSize(20).text("Field3", 841.89 - (160 + 30), 400 + 97.64 - 75);
            doc.fontSize(25).text("Description", 40, 490, {
              align: "center",
              lineBreak: "false",
            });
    
            doc.end();    
    })
    

    or you could refactor the code, and use async/await, which would take more coding.

    For example, make getDataURL return a promise, and wait for it to finish, and then cal doc.end part:

    //  QR Code
    async function getDataURL(str){
    
      try {
        return QRCode.toDataURL(str);
      } catch(err) {
        console.error(`Debugger: Catched Error from QRCode.toDataURL promise: ${err}`)
        throw err;
      }
    
    }
    
    
    
    getDataURL(testList).then((base64)=>{
    
      doc.image(base64,245.945,75,{width:350});
    
      //  Text
      doc.fontSize(20).text("Field1", 30, 97.64 + 35);
      doc.fontSize(20).text("Field2", 841.89 - (160 + 30), 97.64 + 35);
      doc.fontSize(20).text("Field3", 841.89 - (160 + 30), 400 + 97.64 - 75);
      doc.fontSize(25).text("Description", 40, 490, {
        align: "center",
        lineBreak: "false",
      });
    
      doc.end();
    
    });