UNPKG

@nzz/q-server

Version:

**Maintainer**: [Franco Gervasi](https://github.com/fgervasi)

181 lines (154 loc) 5.86 kB
const Joi = require("../../helper/custom-joi.js"); const Boom = require("@hapi/boom"); const querystring = require("querystring"); const fs = require("fs").promises; const util = require("util"); const exec = util.promisify(require("child_process").exec); const crypto = require("crypto"); const { getCmykTiffBufferFromPng, promoteTiffBufferToBlack, } = require("./conversions.js"); // this seems to be the standard chrome points per pixel unit const chromePPI = 96; function mmToInch(mm) { return mm / 25.4; } module.exports = { method: "POST", path: "/print/rendering-info.{format}", options: { validate: { options: { allowUnknown: true, }, params: { format: Joi.string().valid("png", "pdf", "tiff", "tif"), }, payload: { item: Joi.object().required(), toolRuntimeConfig: Joi.object() .required() .keys({ displayOptions: Joi.object().required().keys({ columns: Joi.number().required(), }), }), }, query: { _id: Joi.string().required(), }, }, cache: { expiresIn: 1000 * 60, // 60 seconds }, tags: ["api"], }, handler: async function (request, h) { try { const displayOptions = request.payload.toolRuntimeConfig.displayOptions; const screenshotRequestQuery = {}; if (displayOptions && displayOptions.printTitle) { request.payload.item.title = displayOptions.printTitle; } if (displayOptions && displayOptions.printSubtitle) { request.payload.item.subtitle = displayOptions.printSubtitle; } if (displayOptions && displayOptions.printNotes) { request.payload.item.notes = displayOptions.printNotes; } screenshotRequestToolRuntimeConfig = JSON.parse( JSON.stringify(request.payload.toolRuntimeConfig) ); // pass all the displayOptions to the tool // set hideTitle to true if the titleStyle is 'hide' screenshotRequestToolRuntimeConfig.displayOptions = Object.assign( displayOptions, { hideTitle: displayOptions.titleStyle === "hide", } ); const screenshotRequestPayload = { toolRuntimeConfig: screenshotRequestToolRuntimeConfig, item: request.payload.item, }; const dpi = request.payload.toolRuntimeConfig.dpi || 300; screenshotRequestQuery.dpr = dpi / chromePPI; // the screenshot width is the width in inch * target dpi const mm = await request.server.methods.plugins.q.print.colsToMm( displayOptions.columnsProfile, displayOptions.columns ); screenshotRequestQuery.width = Math.round( (mmToInch(mm) * dpi) / screenshotRequestQuery.dpr ); screenshotRequestQuery.background = screenshotRequestQuery.background || "white"; screenshotRequestQuery.wait = request.payload.toolRuntimeConfig.wait || 2000; screenshotRequestQuery.target = await request.server.settings.app.print .target; const screenshotImageResponse = await request.server.inject({ method: "POST", url: `/screenshot.png?${querystring.stringify(screenshotRequestQuery)}`, payload: screenshotRequestPayload, }); // fail early if there is an error to generate the screenshot if (screenshotImageResponse.statusCode !== 200) { request.server.log(["error"], screenshotImageResponse.payload); return screenshotImageResponse; } if (request.params.format === "png") { return h.response(screenshotImageResponse.rawPayload).type("image/png"); } return await new Promise(async (resolve, reject) => { const pngBuffer = screenshotImageResponse.rawPayload; const profiles = await request.server.settings.app.print.profiles; const tiffBuffer = await getCmykTiffBufferFromPng( pngBuffer, dpi, profiles ); const finalTiffBuffer = await promoteTiffBufferToBlack(tiffBuffer); // if a TIFF is requested we return it here if ( request.params.format === "tiff" || request.params.format === "tif" ) { return resolve(finalTiffBuffer); } // if the format is not pdf here, we have a problem and return this if (request.params.format !== "pdf") { throw Boom.badRequest(); } const requestId = crypto .createHash("sha1") .update(request.info.id) .digest("hex"); // the following could all be optimised maybe by implementing it using streams and buffers // instead of writing and reading files // but we do it easy for now... const fileNameBase = `${__dirname}/${requestId}`; // write the tiff buffer to disk await fs.writeFile(`${fileNameBase}orig.tiff`, tiffBuffer); // remove the alpha channel for tiff2pdf to work const { stdoutA, stderrA } = await exec( `convert ${fileNameBase}orig.tiff -alpha off -compress lzw ${fileNameBase}-no-alpha.tiff` ); // we need to use tiff2pdf instead of imagemagick since this produces pdf v1.3 compatible PDFs where imagemagick does not const { stdoutP, stderrP } = await exec( `tiff2pdf -z -o ${fileNameBase}.pdf ${fileNameBase}-no-alpha.tiff` ); const pdfBuffer = await fs.readFile(`${fileNameBase}.pdf`); resolve(h.response(pdfBuffer).type("application/pdf")); // remove all the intermediate files fs.unlink(`${fileNameBase}orig.tiff`); fs.unlink(`${fileNameBase}-no-alpha.tiff`); fs.unlink(`${fileNameBase}.pdf`); }); } catch (e) { request.server.log(["error"], e); throw e; } }, };