164 lines
6.1 KiB
JavaScript
164 lines
6.1 KiB
JavaScript
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 { 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);
|
||
|
||
function shape_scale(dimension, coord){
|
||
return (coord / 100.0 * dimension)
|
||
}
|
||
|
||
function overlay_pencil(svg, annotation, w, h) {
|
||
const shapeColor = Number(annotation.color).toString(16)
|
||
|
||
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: shape_scale(w, annotation.points[0]),
|
||
cy: shape_scale(h, annotation.points[1]),
|
||
r: shape_scale(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 = shape_scale(w, dataPoints.shift())
|
||
var y = shape_scale(h, dataPoints.shift())
|
||
path = `${path} M${x} ${y}`
|
||
break;
|
||
case 2: // LINE TO
|
||
var x = shape_scale(w, dataPoints.shift())
|
||
var y = shape_scale(h, dataPoints.shift())
|
||
path = `${path} L${x} ${y}`
|
||
break;
|
||
case 4: // C_CURVE_TO
|
||
var cx1 = shape_scale(w, dataPoints.shift())
|
||
var cy1 = shape_scale(h, dataPoints.shift())
|
||
var cx2 = shape_scale(w, dataPoints.shift())
|
||
var cy2 = shape_scale(h, dataPoints.shift())
|
||
var x = shape_scale(w, dataPoints.shift())
|
||
var y = shape_scale(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:${shape_scale(w, annotation.thickness)};fill:none`
|
||
}).ele('path', {
|
||
d: path
|
||
}).up()
|
||
}
|
||
}
|
||
|
||
function overlay_annotations(svg, annotations, w, h) {
|
||
for(let i = 0; i < annotations.length; i++){
|
||
switch (annotations[i].annotationType) {
|
||
case 'pencil':
|
||
overlay_pencil(svg, annotations[i].annotationInfo, w, h)
|
||
break;
|
||
default:
|
||
logger.error(`Unknown annotation type ${annotations[i].annotationType}.`);
|
||
}
|
||
}
|
||
}
|
||
|
||
// Process the presentation pages and annotations into a PDF file
|
||
|
||
// 1. Get the job
|
||
let dropbox = `${config.shared.presAnnDropboxDir}/${jobId}`
|
||
let job = fs.readFileSync(`${dropbox}/job`);
|
||
let exportJob = JSON.parse(job);
|
||
|
||
// 2. Get the annotations
|
||
let annotations = fs.readFileSync(`${dropbox}/whiteboard`);
|
||
let whiteboard = JSON.parse(annotations);
|
||
let pages = JSON.parse(whiteboard.pages);
|
||
|
||
// 3. Convert annotations to SVG
|
||
for (let i = 0; i < pages.length; i++) {
|
||
|
||
// Get the current slide (without annotations)
|
||
let currentSlide = pages[i]
|
||
var backgroundSlide = fs.readFileSync(`${dropbox}/slide${pages[i].page}.svg`).toString();
|
||
|
||
// Read background slide in as JSON to determine dimensions
|
||
// TODO: find a better way to get width and height of slide (e.g. as part of message)
|
||
backgroundSlide = JSON.parse(convert.xml2json(backgroundSlide));
|
||
|
||
// There's a bug with older versions of rsvg which defaults SVG output to pixels.
|
||
// See: https://gitlab.gnome.org/GNOME/librsvg/-/issues/766
|
||
var slideWidth = Number(backgroundSlide.elements[0].attributes.width.replace(/\D/g, ""))
|
||
var slideHeight = Number(backgroundSlide.elements[0].attributes.height.replace(/\D/g, ""))
|
||
|
||
var panzoom_x = -currentSlide.xOffset * MAGIC_MYSTERY_NUMBER / 100.0 * slideWidth
|
||
var panzoom_y = -currentSlide.yOffset * MAGIC_MYSTERY_NUMBER / 100.0 * slideHeight
|
||
var panzoom_w = shape_scale(slideWidth, currentSlide.widthRatio)
|
||
var panzoom_h = shape_scale(slideHeight, currentSlide.heightRatio)
|
||
|
||
// Create the SVG slide with the background image
|
||
let svg = create({ version: '1.0', encoding: 'UTF-8' })
|
||
.ele('svg', {
|
||
xmlns: 'http://www.w3.org/2000/svg',
|
||
'xmlns:xlink': 'http://www.w3.org/1999/xlink',
|
||
width: slideWidth,
|
||
height: slideHeight,
|
||
viewBox: `${panzoom_x} ${panzoom_y} ${panzoom_w} ${panzoom_h}`
|
||
})
|
||
.dtd({
|
||
pubID: '-//W3C//DTD SVG 1.1//EN',
|
||
sysID: 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'
|
||
})
|
||
.ele('image', {
|
||
'xlink:href': `file://${dropbox}/slide${pages[i].page}.svg`,
|
||
width: slideWidth,
|
||
height: slideHeight,
|
||
})
|
||
.up()
|
||
.ele('g', {
|
||
class: 'canvas'
|
||
});
|
||
|
||
// 4. Overlay annotations onto slides
|
||
overlay_annotations(svg, pages[i].annotations, slideWidth, slideHeight)
|
||
|
||
svg = svg.end({ prettyPrint: true });
|
||
console.log (svg)
|
||
|
||
// Write annotated SVG file
|
||
fs.writeFile(`${dropbox}/annotated-slide${pages[i].page}.svg`, svg, function(err) {
|
||
if(err) { return logger.error(err); }
|
||
});
|
||
|
||
// rsvg-convert annotated-slide2.svg -f pdf -o out.pdf
|
||
}
|
||
|
||
// Resulting PDF file is stored in the presentation dir
|
||
// rsvg-convert annotated-slide2.svg annotated-slide3.svg ... -f pdf -o out.pdf
|
||
|
||
// Launch Notifier Worker depending on job type
|
||
|
||
parentPort.postMessage({ message: workerData })
|