const Logger = require('../lib/utils/logger'); const config = require('../config'); const fs = require('fs'); const convert = require('xml-js'); const { create } = require('xmlbuilder2', { encoding: 'utf-8' }); const { execSync } = require("child_process"); const { Worker, workerData, parentPort } = require('worker_threads'); const jobId = workerData; const MAGIC_MYSTERY_NUMBER = 2; const logger = new Logger('presAnn Process Worker'); logger.info("Processing PDF for job " + jobId); const kickOffNotifierWorker = (jobType) => { return new Promise((resolve, reject) => { const worker = new Worker('./workers/notifier.js', { workerData: [jobType, jobId] }); worker.on('message', resolve); worker.on('error', reject); worker.on('exit', (code) => { if (code !== 0) reject(new Error(`PresAnn Notifier Worker stopped with exit code ${code}`)); }) }) } function color_to_hex(color) { color = parseInt(color).toString(16) return '0'.repeat(6 - color.length) + color } function scale_shape(dimension, coord) { return (coord / 100.0 * dimension); } function render_HTMLTextBox(htmlFilePath, id, width, height) { commands = [ 'wkhtmltoimage', '--format', 'png', '--encoding', `${config.process.whiteboardTextEncoding}`, '--transparent', '--crop-w', width, '--crop-h', height, '--log-level', 'none', '--quality', '100', htmlFilePath, `${dropbox}/text${id}.png` ] execSync(commands.join(' '), (error, stderr) => { if (error) { logger.error(`Error when rendering text box for string "${string}" with wkhtmltoimage: ${error.message}`); return; } if (stderr) { logger.error(`stderr when rendering text box for string "${string}" with wkhtmltoimage: ${stderr}`); return; } }) } function overlay_ellipse(svg, annotation, w, h) { let shapeColor = color_to_hex(annotation.color); let fill = annotation.fill ? `#${shapeColor}` : 'none'; let x1 = scale_shape(w, annotation.points[0]) let y1 = scale_shape(h, annotation.points[1]) let x2 = scale_shape(w, annotation.points[2]) let y2 = scale_shape(h, annotation.points[3]) let width_r = Math.abs(x2 - x1) / 2 let height_r = Math.abs(y2 - y1) / 2 let hx = Math.abs(x1 + x2) / 2 let hy = Math.abs(y1 + y2) / 2 // Normalize the x,y coordinates if (x1 > x2) { [x1, x2] = [x2, x1] } if (y1 > y2) { [y1, y2] = [y2, y1] } path = `M${x1} ${hy} A${width_r} ${height_r} 0 0 1 ${hx} ${y1} A${width_r} ${height_r} 0 0 1 ${x2} ${hy} A${width_r} ${height_r} 0 0 1 ${hx} ${y2} A${width_r} ${height_r} 0 0 1 ${x1} ${hy} Z` svg.ele('g', { style: `stroke:#${shapeColor};stroke-width:${scale_shape(w, annotation.thickness)}; fill:${fill};stroke-linejoin:miter;stroke-miterlimit:8` }).ele('path', { d: path }).up() } function overlay_line(svg, annotation, w, h) { let shapeColor = color_to_hex(annotation.color); svg.ele('g', { style: `stroke:#${shapeColor};stroke-width:${scale_shape(w, annotation.thickness)};stroke-linecap:butt` }).ele('line', { x1: scale_shape(w, annotation.points[0]), y1: scale_shape(h, annotation.points[1]), x2: scale_shape(w, annotation.points[2]), y2: scale_shape(h, annotation.points[3]), }).up() } function overlay_pencil(svg, annotation, w, h) { let shapeColor = color_to_hex(annotation.color); if (annotation.points.length < 2) { logger.info("Pencil doesn't have enough points") return; } else if (annotation.points.length == 2) { svg.ele('g', { style: `stroke:none;fill:#${shapeColor}`, }).ele('circle', { cx: scale_shape(w, annotation.points[0]), cy: scale_shape(h, annotation.points[1]), r: scale_shape(w, annotation.thickness) / 2 }).up() } else { let path = "" let dataPoints = annotation.points for(let i = 0; i < annotation.commands.length; i++) { switch(annotation.commands[i]){ case 1: // MOVE TO var x = scale_shape(w, dataPoints.shift()) var y = scale_shape(h, dataPoints.shift()) path = `${path} M${x} ${y}` break; case 2: // LINE TO var x = scale_shape(w, dataPoints.shift()) var y = scale_shape(h, dataPoints.shift()) path = `${path} L${x} ${y}` break; case 4: // C_CURVE_TO var cx1 = scale_shape(w, dataPoints.shift()) var cy1 = scale_shape(h, dataPoints.shift()) var cx2 = scale_shape(w, dataPoints.shift()) var cy2 = scale_shape(h, dataPoints.shift()) var x = scale_shape(w, dataPoints.shift()) var y = scale_shape(h, dataPoints.shift()) path = `${path} C${cx1} ${cy1},${cx2} ${cy2},${x} ${y}` break; default: logger.error(`Unknown pencil command: ${annotation.commands[i]}`) } } svg.ele('g', { style: `stroke:#${shapeColor};stroke-linecap:round;stroke-linejoin:round; stroke-width:${scale_shape(w, annotation.thickness)};fill:none` }).ele('path', { d: path }).up() } } function overlay_poll(svg, annotation, w, h) { if (annotation.result.length == 0) { return; } let poll_x = scale_shape(w, annotation.points[0]); let poll_y = scale_shape(h, annotation.points[1]); let poll_width = Math.round(scale_shape(w, annotation.points[2])); let poll_height = Math.round(scale_shape(h, annotation.points[3])); let pollId = annotation.id.replace(/\//g, ''); let pollSVG = `${dropbox}/poll-${pollId}.svg` let pollJSON = `${dropbox}/poll-${pollId}.json` // Rename 'numVotes' key to 'num_votes' let pollJSONContent = annotation.result.map(result => { result.num_votes = result.numVotes; delete result.numVotes; return result; }); // Store the poll result in a JSON file fs.writeFileSync(pollJSON, JSON.stringify(pollJSONContent), function(err) { if(err) { return logger.error(err); } }); // Create empty SVG poll fs.writeFileSync(pollSVG, '', function(err) { if(err) { return logger.error(err); } }); // Render the poll SVG using gen_poll_svg script execSync(`${config.genPollSVG.path} -i ${pollJSON} -w ${poll_width} -h ${poll_height} -n ${annotation.numResponders} -o ${pollSVG}`, (error, stderr) => { if (error) { logger.error(`Poll generation failed with error: ${error.message}`); return; } if (stderr) { logger.error(`Poll generation failed with stderr: ${stderr}`); return; } }); // Add poll image element svg.ele('image', { 'xlink:href': `file://${pollSVG}`, x: poll_x, y: poll_y, width: poll_width, height: poll_height, }) } function overlay_rectangle(svg, annotation, w, h) { let shapeColor = color_to_hex(annotation.color); let fill = annotation.fill ? `#${shapeColor}` : 'none'; let x1 = scale_shape(w, annotation.points[0]) let y1 = scale_shape(h, annotation.points[1]) let x2 = scale_shape(w, annotation.points[2]) let y2 = scale_shape(h, annotation.points[3]) let path = `M${x1} ${y1} L${x2} ${y1} L${x2} ${y2} L${x1} ${y2} Z` svg.ele('g', { style: `stroke:#${shapeColor};stroke-width:${scale_shape(w, annotation.thickness)};fill:${fill};stroke-linejoin:miter` }).ele('path', { d: path }).up() } function overlay_triangle(svg, annotation, w, h) { let shapeColor = color_to_hex(annotation.color); let fill = annotation.fill ? `#${shapeColor}` : 'none'; let x1 = scale_shape(w, annotation.points[0]) let y1 = scale_shape(h, annotation.points[1]) let x2 = scale_shape(w, annotation.points[2]) let y2 = scale_shape(h, annotation.points[3]) let px = (x1 + x2) / 2 let path = `M${px} ${y1} L${x2} ${y2} L${x1} ${y2} Z` svg.ele('g', { style: `stroke:#${shapeColor};stroke-width:${scale_shape(w, annotation.thickness)};fill:${fill};stroke-linejoin:miter;stroke-miterlimit:8` }).ele('path', { d: path }).up() } function overlay_text(svg, annotation, w, h) { let fontColor = color_to_hex(annotation.fontColor); let textBoxWidth = Math.round(scale_shape(w, annotation.textBoxWidth)); let textBoxHeight = Math.round(scale_shape(h, annotation.textBoxHeight)); let textBox_x = Math.round(scale_shape(w, annotation.x)); let textBox_y = Math.round(scale_shape(h, annotation.y)); let fontSize = scale_shape(h, annotation.calcedFontSize) let style = [ `width:${textBoxWidth}px;`, `height:${textBoxHeight}px;`, `color:#${fontColor};`, "word-wrap:break-word;", "font-family:Arial;", `font-size:${fontSize}px` ] var html = `
${annotation.text.split('\n').join('
')}