Nodejs and pdfKit and qr-image - node.js

I am working on a small project that works with generating pdf's in node and express, l am using the pdfkit npm module but to generate a pdf. l am also using the qr-image npm module to generate the QR code image but l am struggling to attach the generated the QR code to the pdf. This is the code that l am using to generate the QR code:
var file = "./"+certificationnumber+".png";
var qr_svg = qr.image(file, { type: 'png' });
qr_svg.pipe(require('fs').createWriteStream(file));
And this is how l am trying attache it to the pdf using pdfkit npm module:
doc.image(qr_svg.pipe(require('fs').createWriteStream(file)))
since every pdf has a unique QR code.
Thanks in advance

I exactly have the same problem where get the PDF in 64 bit encoded form and i have to attach qr image to it.
Below is the code snippet.
PDFDocument = require('pdfkit');
const base64 = require('base64-stream');
const doc = new PDFDocument();
doc.image('test.jpeg', {
fit: [250, 300],
align: 'center',
valign: 'center'
});
const finalString = pdf.response;
// contains the base64 string
//logic to append the qr image.
const stream = doc.pipe(base64.encode());
stream.on('data', chunk => finalString += chunk);
stream.on('end', () => {
// the stream is at its end, so push the resulting base64 string to the response
const backToPDF = new Buffer(finalString, 'base64');
read.writeFileSync('./Report.pdf', backToPDF);
});
doc.end();

Here is the solution I have come up with. Using pdf-lib, express and qrcode
const fs = require("fs");
const path = require("path");
const express = require("express");
const http = require("http");
const cors = require("cors");
const multer = require("multer");
const app = express();
const server = http.createServer(app);
const { PDFDocument } = require("pdf-lib");
const QRCode = require("qrcode");
const Joi = require("joi");
const { readFile, writeFile, unlink } = require("fs/promises");
app.use(cors());
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
const dir = "public";
const subDirectory = "public/uploads";
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir);
fs.mkdirSync(subDirectory);
}
const generateQRCodeImage = async function (filePath, text, color) {
return new Promise((resolve, reject) => {
QRCode.toFile(
filePath,
text,
{
color,
},
function (err) {
if (err) return reject(err);
resolve();
}
);
});
};
const run = async ({
width,
height,
x,
y,
pathToImage,
pathToPDF,
pathToOutputPDF,
qrCodeText,
qrDarkColor = "#000",
qrLightColor = "#0000",
}) => {
await generateQRCodeImage(pathToImage, qrCodeText, {
dark: qrDarkColor,
light: qrLightColor,
});
const pdfDoc = await PDFDocument.load(await readFile(pathToPDF));
const img = await pdfDoc.embedPng(await readFile(pathToImage));
Array.from({ length: pdfDoc.getPageCount() }).forEach((_, index) => {
let imagePage = pdfDoc.getPage(index);
imagePage.drawImage(img, {
x,
y,
width,
height,
});
});
const pdfBytes = await pdfDoc.save();
await writeFile(pathToOutputPDF, pdfBytes);
};
const pdfFileFilter = function (req, file, callback) {
const ext = path.extname(file.originalname);
if (ext !== ".pdf") {
return callback("This extension is not supported");
}
callback(null, true);
};
const storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, "public/uploads");
},
filename: function (req, file, cb) {
cb(
null,
file.fieldname + "-" + Date.now() + path.extname(file.originalname)
);
},
});
const filesToProcess = multer({ storage: storage, fileFilter: pdfFileFilter });
const schema = Joi.object({
width: Joi.string().regex(/^\d+$/).required(),
height: Joi.string().regex(/^\d+$/).required(),
x: Joi.string().regex(/^\d+$/).required(),
y: Joi.string().regex(/^\d+$/).required(),
qrCodeData: Joi.string().required(),
qrDarkColor: Joi.string(),
qrLightColor: Joi.string(),
});
app.post("/addQrToPdf", filesToProcess.array("file", 1), async (req, res) => {
const pathToImage = "public/uploads/" + Date.now() + "temp-qr.png";
const pathToOutputPDF = "public/uploads/" + Date.now() + "-output.pdf";
if (req.files) {
const [file] = req.files;
if (!file) {
res.send("No file detected on input");
}
const pathToPDF = file.path;
try {
const { width, height, x, y, qrCodeData, qrDarkColor, qrLightColor } =
await schema.validateAsync(req.body);
await run({
width: +width,
height: +height,
x: +x,
y: +y,
qrDarkColor,
qrLightColor,
qrCodeText: qrCodeData,
pathToImage,
pathToOutputPDF,
pathToPDF,
});
const pdfFile = await readFile(pathToOutputPDF);
res.contentType("application/pdf");
res.send(pdfFile);
await unlink(pathToImage);
await unlink(pathToPDF);
await unlink(pathToOutputPDF);
} catch (error) {
try {
await unlink(pathToPDF);
await unlink(pathToImage);
} catch (err) {
console.warn(err);
}
res.send(error);
}
}
});
server.listen(4000, () => console.log("listening on port *:4000"));

I had the same problem.
It looks like it is an async issue.
If you treat the generation of the QR code like a promise and then run the create pdf code, it works.
My code is below (using promise). It should also work using async await
const createPDF = (ticketID) => {
const doc = new pdf
doc.pipe(fs.createWriteStream(`${ticketID}.pdf`))
doc.text('Your Tickets').fontSize(25)
doc.image(`./qrCodes/${ticketID}.png`, {
fit: [250, 300],
align: 'center',
valign: 'center'
});
doc.end()
}
const createQRCode = (ticketID) => {
QR.toFile(`./qrCodes/${ticketID}.png`, String(ticket), {width: 250}).then(qr => {
createPDF(ticketID)
})
}

I use a version without writing to the disk, using the svg path output, extract the path from the qrcode data and render it into the pdf
let s = await toString('hallo2', {
type: 'svg'
})
// extract the 2nd path element
let path = Array.from(s.matchAll(/d="(.*?)"/gm))[1][1]
let doc = new PDFDocument({ size: 'A4' });
doc.scale(8)
.path(data)
.stroke()
doc.pipe(fs.createWriteStream('out2.pdf', {}));
doc.end()

Related

req is "undefined" in one middleware and not in another

I am trying to use sharp in my MERN application, I sent a request from my frontend and it is undefined in my sharp middleware but if I get rid of the sharp middleware the req is defined later on. If I log the request in createCountry, the body is defined, if I log it in convertToWebP, it is not.
the route is the one that says "/new" below:
const express = require("express");
const router = express.Router();
const { storage } = require("../imageupload/cloudinary.js");
const multer = require("multer");
const {
getCountry,
createCountry,
getCountries,
updateCountry,
deleteCountry,
getAllCountries,
} = require("../controllers/country.js");
const {convertToWebP} = require('../middlewares/toWebP')
const { isLoggedIn, authorizeCountry, validateCountry } = require("../middlewares/auth");
const catchAsync = require("../utils/catchAsync");
const ExpressError = require("../utils/ExpressError");
const upload = multer({ storage: storage });
router.get("/", getCountries);
router.get('/getAll', getAllCountries);
router.post("/new", isLoggedIn, converToWebP, upload.array("images"), createCountry);
router.get("/:countryId", getCountry);
router.patch("/:countryId", validateCountry, authorizeCountry, upload.array("images", 8), updateCountry);
router.delete("/:countryId", authorizeCountry, deleteCountry);
module.exports = router;
the code for create country is here:
exports.createCountry = async (req, res) => {
const { name, description, tags, location, cjLink } = req.body;
const creator = req.user._id;
const images = req.files.map((file) => {
return { image: file.path, publicId: file.filename };
});
try {
const geoData = await geocoder
.forwardGeocode({
query: req.body.location,
limit: 1,
})
.send();
const geometry = geoData.body.features[0].geometry;
const country = new Country({
name,
description,
tags,
creator,
location, //: //geometry
geometry,
url: '',
cjLink: cjLink,
});
const overall = new Overall({
name,
description,
tags,
creator,
location, //: //geometry
geometry,
url: '',
cjLink: cjLink,
});
country.images.push(...images);
country.headerImage.push(...images);
const data = await country.save();
overall.url = `/country/${data._id}`
data.url = `/country/${data._id}`
overall.save();
data.save();
return res.status(201).json(data);
} catch (error) {
return console.log("error during create country", error);
}
};
And lastly the code for the convertToWebP is here:
const sharp = require("sharp");
const { cloudinary } = require("../imageupload/cloudinary");
exports.convertToWebP = async (req, res, next) => {
try {
req.files = await Promise.all(req.files.map(async (file) => {
const buffer = await sharp(file.buffer)
.toFormat('webp')
.toBuffer();
return { ...file, buffer, originalname: `${file.originalname}.webp` };
}));
next();
} catch (error) {
res.status(500).json({ message: error.message });
}
};
Any help is appreciated! I tried console.log as described above, I tried to change the order of the middleware and that does not work either, and I tried logging the req.body directly from the route and it came up as an empty object
You cannot acces req.files before you use multer middleware
You have to reorder
router.post("/new", isLoggedIn, upload.array("images"), converToWebP, createCountry);

How to perform an HTTP post request using express on Cloud Functions for Firebase using busboy

Hi I am trying to insert data in the database using a POST Request but the data is not being inserted.
On further investigation I found that to upload form-data, busboy needs to be used for image upload in firebase functions but I am not able to find a solution for using busboy with post method.
Hence if someone can please help me resolve this issue.
Below is the code for reference.
app.js
const express = require('express')
//const router = true;
const router = new express.Router()
const userInfo = require('../models/userProfile')
const multer = require('multer');
var fs = require('fs');
var path = require('path');
var JSONStream = require('JSONStream');
const planCheck = require('../models/planDetails');
const login = require('../models/login_creditionals');
const {Storage} = require('#google-cloud/storage');
const {format} = require('util');
const busboy = require('busboy');
const storage = new Storage({
projectId: "biz-1",
keyFilename: "/Users/akm/pixNodes/functions/pixbiz-65a402.json"
});
const bucket = storage.bucket("gs://biz-1");
const upload = multer({
storage: multer.memoryStorage(),
limits: {
fileSize: 10 * 1024 * 1024
}
})
const uploadImageToStorage = (file) => {
return new Promise((resolve, reject) => {
if (!file) {
reject('No image file');
}
let newFileName = `${Date.now() + path.extname(file.originalname)}`;
let fileUpload = bucket.file(newFileName);
const blobStream = fileUpload.createWriteStream({
metadata: {
contentType: file.mimetype
}
});
blobStream.on('error', (error) => {
reject('Something is wrong! Unable to upload at the moment.');
});
blobStream.on('finish', () => {
// The public URL can be used to directly access the file via HTTP.
const url = format(`https://storage.googleapis.com/${bucket.name}/${fileUpload.name}`);
resolve(url);
});
blobStream.end(file.buffer);
});
}
router.post('/userprofile/check' ,upload.single('profile_pic'), async (req,res) => {
var reqFiles;
var reqFilesUrl;
reqFiles = req.file;
if(reqFiles) {
// const imagerUrl = await uploadImageToStorage(reqFiles)
reqFilesUrl = imagerUrl;
console.log(reqFilesUrl);
const notify = new userInfo({
userId: req.body.userId,
mobile_number : req.body.mobileNumber,
profile_pic: reqFilesUrl
})
try {
console.log('success insert data');
await notify.save((err,post) => {
if(err) {
console.log(err);
}
//console.log('data saved', post);
res.status(201).send(post);
});
// });
// res.status(201).send();
console.log('201');
} catch(e) {
//res.status(401);
return res.send(e);
}

How to get Dicom lossless image from google cloud?

This is my code to get a dicom image from Google Cloud. It works well, but the image is lossy.
router.get("/dicomWebRetrieveInstance/dcmfile", async (req, res, next) => {
const writeFile = util.promisify(fs.writeFile);
emphasized textconst fileName = 'rendered_image.jpeg';
const cloudRegion = "us";
const projectId = "neurocaredicom";
const datasetId = "System_1";
const dicomStoreId = "Site_1A";
const studyUid = "1.2.276.0.7230010.3.1.2.296485376.1.1521713579.1849134";
const seriesUid = "1.2.276.0.7230010.3.1.3.2`enter code here`96485376.1.1521713580.1849651";
const instanceUid = "1.2.276.0.7230010.3.1.4.296485376.1.1521713580.1849652";
const parent = `projects/${projectId}/locations/${cloudRegion}/datasets/${datasetId}/dicomStores/${dicomStoreId}`;
const dicomWebPath = `studies/${studyUid}/series/${seriesUid}/instances/${instanceUid}/rendered`;
const request = {parent, dicomWebPath};
const rendered =
await healthcare.projects.locations.datasets.dicomStores.studies.series.instances.retrieveRendered(
request,
{
headers: "application/octet-stream; transfer-syntax=* ",
responseType: 'arraybuffer',
}
);
const fileBytes = Buffer.from(rendered.data);
await writeFile(fileName, fileBytes);
var options = {
root: path.join(__dirname),
};
// read binary data
var bitmap = fs.readFileSync(fileName);
// convert binary data to base64 encoded string
res.status(200).sendFile(fileName, options, function (err) {
if (err) {
next(err);
} else {
console.log(
`Retrieved rendered image and saved to ${fileName} in current directory`
);
}
});
});
Any solution to that problem would be appreciated, so anyone can help.

Push img resolution from loop to array

I receive an array of images and want to get the resolution (width, height) and store them properly to the db. How can I push the resolution const resolution correctly into the array of the respective file?
const FilesStorage = require("../models/filesStorageModel");
const sizeOf = require('image-size');
const url = require('url')
const https = require('https')
exports.uploadFiles = async (req, res) => {
const filesArray = req.files
try {
for (const file of filesArray) {
const imgUrl = file.location
const options = url.parse(imgUrl)
https.get(options, (res) => {
const chunks = []
res.on('data', (chunk) => {
chunks.push(chunk)
}).on('end', () => {
const buffer = Buffer.concat(chunks)
const resolution = sizeOf(buffer)
console.log(resolution)
})
})
const uploadFiles = new FilesStorage({
name: file.originalname,
altTag: "alt",
format: file.mimetype,
filePath: file.location,
sizeKB: (file.size / 1000),
height: "resolution.height", // How?
width: "resolution.width", // How?
})
await uploadFiles.save()
}
return res.status(200).json({
success: true,
message: 'Files successfully uploaded',
})
} catch(err) {
return res.status(500).json({
success: false,
message: `Something wen't wrong`,
})
}
}
Here's the answer you're looking for:
First, we need to consider variable scopes. When we look at the resolution constant you have defined in your handler, we can see the scope makes it local to that handler and not the parent code block.
To make it accessible in other areas of the code block, we move it above the handler scope but keep it inside your for loop so it's file-specific. With this change, we can now insert the resolution into your database upload call.
Hope this helps!
const FilesStorage = require("../models/filesStorageModel");
const sizeOf = require('image-size');
const url = require('url')
const https = require('https')
exports.uploadFiles = async(req, res) => {
const filesArray = req.files
try {
for (const file of filesArray) {
const imgUrl = file.location
const options = url.parse(imgUrl)
//Added variable outside arrow function scope
var resolution;
https.get(options, (res) => {
const chunks = []
res.on('data', (chunk) => {
chunks.push(chunk)
}).on('end', () => {
const buffer = Buffer.concat(chunks)
//Set value to variable outside function scope
resolution = sizeOf(buffer)
console.log(resolution)
})
})
const uploadFiles = new FilesStorage({
name: file.originalname,
altTag: "alt",
format: file.mimetype,
filePath: file.location,
sizeKB: (file.size / 1000),
//Get value by accessing variable above
height: resolution.height,
width: resolution.width,
})
await uploadFiles.save()
}
return res.status(200).json({
success: true,
message: 'Files successfully uploaded',
})
} catch (err) {
return res.status(500).json({
success: false,
message: `Something wen't wrong`,
})
}
}

image resize on node.js

I want to save images after resize so used sharp package but dont small image size.Iadded await sharp(uploadImage.name).resize(600, 600); line after save folder What am I missing?
const path = require('path');
const fs = require('fs');
const AdminBro = require('admin-bro');
const sharp=require('sharp');
/** #type {AdminBro.After<AdminBro.ActionResponse>} */
const after = async (response, request, context) => {
const { record, uploadImage } = context;
if (record.isValid() && uploadImage) {
// console.log(uploadImage.name);
await sharp(uploadImage.name).resize(600, 600);
const filePath = path.join('uploads', record.id().toString(), uploadImage.name);
await fs.promises.mkdir(path.dirname(filePath), { recursive: true });
await fs.promises.rename(uploadImage.path, filePath);
await record.update({ imagePath: `/${filePath}` });
}
return response;
};
/** #type {AdminBro.Before} */
const before = async (request, context) => {
if (request.method === 'post') {
const { uploadImage, ...otherParams } = request.payload;
// eslint-disable-next-line no-param-reassign
context.uploadImage = uploadImage;
return {
...request,
payload: otherParams,
};
}
return request;
};
module.exports = { after, before };
const filePath = path.join('uploads', record.id().toString(), uploadImage.name);
await fs.promises.mkdir(path.dirname(filePath), { recursive: true });
await sharp(uploadImage.path).withMetadata().resize(600,600).toFile(filePath);//here added correct paths but now although it saved image, doesnt reduce imageSize
You are not writing the processed image to a file.
Let's say this is your storage path:
const outputPath = __dirname + `/../storage/myImage.jpeg`;
You can give that path to sharp
await sharp('path/of/originalImage').withMetadata().resize(600, 600).toFile(outputPath);
And I highly recommend you to use withMetaData() otherwise you lose your images metaData

Resources