bigbluebutton-Github/export-annotations/workers/process.js
Daniel Petri Rocha 6455a8e738 Implement panzooms
2022-02-15 18:11:13 +01:00

164 lines
6.1 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 })