2022-02-13 04:03:07 +08:00
|
|
|
|
const Logger = require('../lib/utils/logger');
|
|
|
|
|
const config = require('../config');
|
|
|
|
|
const fs = require('fs');
|
2022-04-19 22:35:03 +08:00
|
|
|
|
const sizeOf = require('image-size');
|
2022-02-14 01:10:37 +08:00
|
|
|
|
const { create } = require('xmlbuilder2', { encoding: 'utf-8' });
|
2022-02-23 00:02:54 +08:00
|
|
|
|
const { execSync } = require("child_process");
|
2022-02-27 00:52:05 +08:00
|
|
|
|
const { Worker, workerData, parentPort } = require('worker_threads');
|
2022-04-29 19:50:42 +08:00
|
|
|
|
const path = require('path');
|
|
|
|
|
const sanitize = require("sanitize-filename");
|
2022-05-26 00:35:43 +08:00
|
|
|
|
const twemoji = require("twemoji");
|
|
|
|
|
const { getStroke, getStrokePoints } = require('perfect-freehand');
|
2022-02-13 04:03:07 +08:00
|
|
|
|
|
|
|
|
|
const jobId = workerData;
|
|
|
|
|
|
|
|
|
|
const logger = new Logger('presAnn Process Worker');
|
|
|
|
|
logger.info("Processing PDF for job " + jobId);
|
|
|
|
|
|
2022-05-01 05:28:11 +08:00
|
|
|
|
const kickOffNotifierWorker = (jobType, filename) => {
|
2022-02-24 01:20:58 +08:00
|
|
|
|
return new Promise((resolve, reject) => {
|
2022-05-01 05:28:11 +08:00
|
|
|
|
const worker = new Worker('./workers/notifier.js', { workerData: [jobType, jobId, filename] });
|
2022-02-24 01:20:58 +08:00
|
|
|
|
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}`));
|
|
|
|
|
})
|
|
|
|
|
})
|
2022-02-23 00:02:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
2022-05-26 00:35:43 +08:00
|
|
|
|
function align_to_css_property(alignment) {
|
|
|
|
|
switch (alignment) {
|
|
|
|
|
case 'start': return 'left'
|
|
|
|
|
case 'middle': return 'center'
|
|
|
|
|
case 'end': return 'right'
|
|
|
|
|
default: return alignment
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-01 01:23:24 +08:00
|
|
|
|
function color_to_hex(color, isStickyNote = false, isFilled = false) {
|
2022-05-26 00:35:43 +08:00
|
|
|
|
if (isStickyNote) { color = `sticky-${color}` }
|
2022-06-01 01:23:24 +08:00
|
|
|
|
if (isFilled) { color = `fill-${color}` }
|
2022-05-25 00:35:08 +08:00
|
|
|
|
|
2022-05-26 00:35:43 +08:00
|
|
|
|
switch (color) {
|
2022-05-18 02:00:16 +08:00
|
|
|
|
case 'white': return '#1d1d1d'
|
2022-06-01 01:23:24 +08:00
|
|
|
|
case 'fill-white': return '#fefefe'
|
2022-05-25 00:35:08 +08:00
|
|
|
|
case 'sticky-white': return '#fddf8e'
|
2022-05-18 02:00:16 +08:00
|
|
|
|
case 'lightGray': return '#c6cbd1'
|
2022-06-01 01:23:24 +08:00
|
|
|
|
case 'fill-lightGray': return '#f1f2f3'
|
2022-05-25 00:35:08 +08:00
|
|
|
|
case 'sticky-lightGray': return '#dde0e3'
|
2022-05-18 02:00:16 +08:00
|
|
|
|
case 'gray': return '#788492'
|
2022-06-01 01:23:24 +08:00
|
|
|
|
case 'fill-gray': return '#e3e5e7'
|
2022-05-25 00:35:08 +08:00
|
|
|
|
case 'sticky-gray': return '#b3b9c1'
|
2022-05-18 02:00:16 +08:00
|
|
|
|
case 'black': return '#1d1d1d'
|
2022-06-01 01:23:24 +08:00
|
|
|
|
case 'fill-black': return '#d2d2d2'
|
2022-05-25 00:35:08 +08:00
|
|
|
|
case 'sticky-black': return '#fddf8e'
|
2022-05-18 02:00:16 +08:00
|
|
|
|
case 'green': return '#36b24d'
|
2022-06-01 01:23:24 +08:00
|
|
|
|
case 'fill-green': return '#d7eddb'
|
2022-05-25 00:35:08 +08:00
|
|
|
|
case 'sticky-green': return '#8ed29b'
|
2022-05-18 02:00:16 +08:00
|
|
|
|
case 'cyan': return '#0e98ad'
|
2022-06-01 01:23:24 +08:00
|
|
|
|
case 'fill-cyan': return '#d0e8ec'
|
2022-05-25 00:35:08 +08:00
|
|
|
|
case 'sticky-cyan': return '#78c4d0'
|
2022-05-18 02:00:16 +08:00
|
|
|
|
case 'blue': return '#1c7ed6'
|
2022-06-01 01:23:24 +08:00
|
|
|
|
case 'fill-blue': return '#d2e4f4'
|
2022-05-25 00:35:08 +08:00
|
|
|
|
case 'sticky-blue': return '#80b6e6'
|
2022-05-18 02:00:16 +08:00
|
|
|
|
case 'indigo': return '#4263eb'
|
2022-06-01 01:23:24 +08:00
|
|
|
|
case 'fill-indigo': return '#d9dff7'
|
2022-05-25 00:35:08 +08:00
|
|
|
|
case 'sticky-indigo': return '#95a7f2'
|
2022-05-18 02:00:16 +08:00
|
|
|
|
case 'violet': return '#7746f1'
|
2022-06-01 01:23:24 +08:00
|
|
|
|
case 'fill-violet': return '#e2daf8'
|
2022-05-25 00:35:08 +08:00
|
|
|
|
case 'sticky-violet': return '#b297f5'
|
2022-05-18 02:00:16 +08:00
|
|
|
|
case 'red': return '#ff2133'
|
2022-06-01 01:23:24 +08:00
|
|
|
|
case 'fill-red': return '#fbd3d6'
|
2022-05-25 00:35:08 +08:00
|
|
|
|
case 'sticky-red': return '#fd838d'
|
2022-05-18 02:00:16 +08:00
|
|
|
|
case 'orange': return '#ff9433'
|
2022-06-01 01:23:24 +08:00
|
|
|
|
case 'fill-orange': return '#fbe8d6'
|
2022-05-25 00:35:08 +08:00
|
|
|
|
case 'sticky-orange': return '#fdc28d'
|
2022-05-18 02:00:16 +08:00
|
|
|
|
case 'yellow': return '#ffc936'
|
2022-06-01 01:23:24 +08:00
|
|
|
|
case 'fill-yellow': return '#fbf1d7'
|
2022-05-25 00:35:08 +08:00
|
|
|
|
case 'sticky-yellow': return '#fddf8e'
|
2022-05-18 02:00:16 +08:00
|
|
|
|
|
|
|
|
|
default: return color
|
|
|
|
|
}
|
2022-02-24 01:20:58 +08:00
|
|
|
|
}
|
|
|
|
|
|
2022-05-26 00:35:43 +08:00
|
|
|
|
function determine_dasharray(dash, gap = 0) {
|
2022-05-25 00:35:08 +08:00
|
|
|
|
|
2022-05-26 00:35:43 +08:00
|
|
|
|
switch (dash) {
|
|
|
|
|
case 'dashed': return `stroke-linecap:butt;stroke-dasharray:${gap};`
|
|
|
|
|
case 'dotted': return `stroke-linecap:round;stroke-dasharray:${gap};`
|
2022-05-18 02:00:16 +08:00
|
|
|
|
|
2022-05-26 00:35:43 +08:00
|
|
|
|
default: return 'stroke-linejoin:round;stroke-linecap:round;'
|
2022-05-18 02:00:16 +08:00
|
|
|
|
}
|
2022-02-24 01:20:58 +08:00
|
|
|
|
}
|
2022-02-23 00:02:54 +08:00
|
|
|
|
|
2022-05-18 02:00:16 +08:00
|
|
|
|
function determine_font_from_family(family) {
|
2022-05-26 00:35:43 +08:00
|
|
|
|
switch (family) {
|
2022-05-18 02:00:16 +08:00
|
|
|
|
case 'script': return 'Caveat Brush'
|
2022-06-01 01:23:24 +08:00
|
|
|
|
case 'sans': return 'Source Sans Pro'
|
|
|
|
|
case 'serif': return 'Crimson Pro'
|
|
|
|
|
case 'mono': return 'Source Code Pro'
|
2022-05-18 02:00:16 +08:00
|
|
|
|
|
|
|
|
|
default: return family
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-05-26 00:35:43 +08:00
|
|
|
|
function rad_to_degree(angle) {
|
2022-06-01 01:23:24 +08:00
|
|
|
|
return angle * (180 / Math.PI);
|
2022-02-15 23:48:58 +08:00
|
|
|
|
}
|
|
|
|
|
|
2022-05-25 00:35:08 +08:00
|
|
|
|
function render_HTMLTextBox(htmlFilePath, id, width, height) {
|
|
|
|
|
let commands = [
|
|
|
|
|
'wkhtmltoimage',
|
|
|
|
|
'--format', 'png',
|
|
|
|
|
'--encoding', `${config.process.whiteboardTextEncoding}`,
|
|
|
|
|
'--transparent',
|
|
|
|
|
'--crop-w', width,
|
|
|
|
|
'--crop-h', height,
|
|
|
|
|
'--log-level', 'none',
|
|
|
|
|
'--quality', '100',
|
|
|
|
|
htmlFilePath, path.join(dropbox, `text${id}.png`)
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
execSync(commands.join(' '));
|
|
|
|
|
}
|
|
|
|
|
|
2022-05-26 00:35:43 +08:00
|
|
|
|
function get_gap(dash, size) {
|
|
|
|
|
switch (dash) {
|
2022-05-31 17:09:38 +08:00
|
|
|
|
case 'dashed':
|
2022-05-26 00:35:43 +08:00
|
|
|
|
if (size == 'small') { return '8 8' }
|
|
|
|
|
else if (size == 'medium') { return '14 14' }
|
|
|
|
|
else { return '20 20' }
|
|
|
|
|
case 'dotted':
|
|
|
|
|
if (size == 'small') { return '0.1 8' }
|
|
|
|
|
else if (size == 'medium') { return '0.1 14' }
|
|
|
|
|
else { return '0.1 20' }
|
|
|
|
|
|
|
|
|
|
default: return '0'
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function get_stroke_width(dash, size) {
|
|
|
|
|
switch (size) {
|
|
|
|
|
case 'small': if (dash === 'draw') { return 1 } else { return 4 };
|
|
|
|
|
case 'medium': if (dash === 'draw') { return 1.75 } else { return 6.25 };
|
|
|
|
|
case 'large': if (dash === 'draw') { return 2.5 } else { return 8.5 }
|
|
|
|
|
|
|
|
|
|
default: return 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-01 01:23:24 +08:00
|
|
|
|
function text_size_to_px(size, scale = 1, isStickyNote = false) {
|
2022-05-26 00:35:43 +08:00
|
|
|
|
if (isStickyNote) { size = `sticky-${size}` }
|
|
|
|
|
|
|
|
|
|
switch (size) {
|
|
|
|
|
case 'sticky-small': return 24
|
2022-06-01 01:23:24 +08:00
|
|
|
|
case 'small': return 28 * scale
|
2022-05-26 00:35:43 +08:00
|
|
|
|
case 'sticky-medium': return 36
|
2022-06-01 01:23:24 +08:00
|
|
|
|
case 'medium': return 48 * scale
|
2022-05-26 00:35:43 +08:00
|
|
|
|
case 'sticky-large': return 48
|
2022-06-01 01:23:24 +08:00
|
|
|
|
case 'large': return 96 * scale
|
2022-05-26 00:35:43 +08:00
|
|
|
|
|
2022-06-01 01:23:24 +08:00
|
|
|
|
default: return 28 * scale
|
2022-05-26 00:35:43 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-05-31 17:09:38 +08:00
|
|
|
|
function getPath(annotationPoints) {
|
|
|
|
|
// Gets inner path of a stroke outline
|
|
|
|
|
// For solid, dashed, and dotted types
|
|
|
|
|
let stroke = getStrokePoints(annotationPoints).map((strokePoint) => strokePoint.point);
|
2022-05-26 00:35:43 +08:00
|
|
|
|
|
2022-05-31 17:09:38 +08:00
|
|
|
|
let [max_x, max_y] = [0, 0];
|
|
|
|
|
let path = stroke.reduce(
|
|
|
|
|
(acc, [x0, y0], i, arr) => {
|
2022-06-01 01:23:24 +08:00
|
|
|
|
if (!arr[i + 1]) return acc
|
|
|
|
|
let [x1, y1] = arr[i + 1]
|
|
|
|
|
if (x1 >= max_x) { max_x = x1 }
|
|
|
|
|
if (y1 >= max_y) { max_y = y1 }
|
|
|
|
|
acc.push(x0, y0, (x0 + x1) / 2, (y0 + y1) / 2)
|
|
|
|
|
return acc
|
2022-05-31 17:09:38 +08:00
|
|
|
|
},
|
2022-02-16 19:06:12 +08:00
|
|
|
|
|
2022-05-31 17:09:38 +08:00
|
|
|
|
['M', ...stroke[0], 'Q']
|
2022-06-01 01:23:24 +08:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
path.join(' ');
|
|
|
|
|
return [path, max_x, max_y];
|
2022-02-16 19:06:12 +08:00
|
|
|
|
}
|
|
|
|
|
|
2022-05-31 17:09:38 +08:00
|
|
|
|
function getOutlinePath(annotationPoints) {
|
|
|
|
|
// From steveruizok/perfect-freehand
|
|
|
|
|
// Gets outline of a hand-drawn input, with pressure
|
|
|
|
|
let stroke = getStroke(annotationPoints, {
|
|
|
|
|
simulatePressure: true,
|
|
|
|
|
size: 8,
|
|
|
|
|
});
|
2022-05-26 00:35:43 +08:00
|
|
|
|
|
2022-05-31 17:09:38 +08:00
|
|
|
|
let [max_x, max_y] = [0, 0];
|
|
|
|
|
let path = stroke.reduce(
|
|
|
|
|
(acc, [x0, y0], i, arr) => {
|
|
|
|
|
let [x1, y1] = arr[(i + 1) % arr.length]
|
|
|
|
|
if (x1 >= max_x) { max_x = x1 }
|
|
|
|
|
if (y1 >= max_y) { max_y = y1 }
|
|
|
|
|
acc.push(x0, y0, (x0 + x1) / 2, (y0 + y1) / 2)
|
|
|
|
|
return acc;
|
|
|
|
|
},
|
2022-02-16 21:49:41 +08:00
|
|
|
|
|
2022-05-31 17:09:38 +08:00
|
|
|
|
['M', ...stroke[0], 'Q']
|
|
|
|
|
);
|
2022-02-16 21:49:41 +08:00
|
|
|
|
|
2022-05-31 17:09:38 +08:00
|
|
|
|
path.push('Z');
|
|
|
|
|
path.join(' ');
|
2022-02-16 21:49:41 +08:00
|
|
|
|
|
2022-05-31 17:09:38 +08:00
|
|
|
|
return [path, max_x, max_y];
|
2022-02-16 21:49:41 +08:00
|
|
|
|
}
|
|
|
|
|
|
2022-05-26 00:35:43 +08:00
|
|
|
|
function overlay_draw(svg, annotation) {
|
|
|
|
|
let dash = annotation.style.dash;
|
2022-05-25 00:35:08 +08:00
|
|
|
|
|
2022-05-31 17:09:38 +08:00
|
|
|
|
let [path, max_x, max_y] = (dash == 'draw') ? getOutlinePath(annotation.points) : getPath(annotation.points);
|
2022-05-26 00:35:43 +08:00
|
|
|
|
|
2022-05-31 17:09:38 +08:00
|
|
|
|
if (!path.length) return;
|
2022-05-26 00:35:43 +08:00
|
|
|
|
|
|
|
|
|
let shapeColor = color_to_hex(annotation.style.color);
|
2022-05-18 02:00:16 +08:00
|
|
|
|
let rotation = rad_to_degree(annotation.rotation);
|
2022-05-26 00:35:43 +08:00
|
|
|
|
let thickness = get_stroke_width(dash, annotation.style.size);
|
|
|
|
|
let gap = get_gap(dash, annotation.style.size);
|
2022-05-18 02:00:16 +08:00
|
|
|
|
|
2022-05-26 00:35:43 +08:00
|
|
|
|
let [x, y] = annotation.point;
|
2022-02-24 01:20:58 +08:00
|
|
|
|
|
2022-05-26 00:35:43 +08:00
|
|
|
|
let stroke_dasharray = determine_dasharray(dash, gap);
|
2022-05-31 17:09:38 +08:00
|
|
|
|
let fill = (dash === 'draw') ? shapeColor : 'none';
|
2022-05-26 00:35:43 +08:00
|
|
|
|
|
|
|
|
|
svg.ele('g', {
|
|
|
|
|
style: `stroke:${shapeColor};stroke-width:${thickness};fill:${fill};${stroke_dasharray}`,
|
|
|
|
|
}).ele('path', {
|
|
|
|
|
d: path,
|
|
|
|
|
transform: `translate(${x} ${y}), rotate(${rotation} ${max_x / 2} ${max_y / 2})`
|
|
|
|
|
}).up()
|
2022-02-17 02:42:46 +08:00
|
|
|
|
}
|
|
|
|
|
|
2022-06-01 01:23:24 +08:00
|
|
|
|
function overlay_rectangle(svg, annotation) {
|
|
|
|
|
|
|
|
|
|
let dash = annotation.style.dash;
|
|
|
|
|
let rect_dash = (dash == 'draw') ? 'solid' : dash // Use 'solid' thickness for draw type
|
|
|
|
|
|
|
|
|
|
let [x, y] = annotation.point;
|
|
|
|
|
let [w, h] = annotation.size;
|
|
|
|
|
let isFilled = annotation.style.isFilled;
|
|
|
|
|
|
|
|
|
|
let shapeColor = color_to_hex(annotation.style.color);
|
|
|
|
|
let fillColor = isFilled ? color_to_hex(annotation.style.color, false, isFilled) : 'none';
|
|
|
|
|
|
|
|
|
|
let rotation = rad_to_degree(annotation.rotation);
|
|
|
|
|
let sw = get_stroke_width(rect_dash, annotation.style.size);
|
|
|
|
|
let gap = get_gap(dash, annotation.style.size);
|
|
|
|
|
|
|
|
|
|
let stroke_dasharray = determine_dasharray(dash, gap);
|
|
|
|
|
|
|
|
|
|
let rx = (dash == 'draw') ? Math.min(w / 4, sw * 2) : 0;
|
|
|
|
|
let ry = (dash == 'draw') ? Math.min(h / 4, sw * 2) : 0;
|
|
|
|
|
|
|
|
|
|
svg.ele('g', {
|
|
|
|
|
style: `stroke:${shapeColor};stroke-width:${sw};fill:${fillColor};${stroke_dasharray}`,
|
|
|
|
|
}).ele('rect', {
|
|
|
|
|
width: w,
|
|
|
|
|
height: h,
|
|
|
|
|
'rx': rx,
|
|
|
|
|
'ry': ry,
|
|
|
|
|
transform: `translate(${x} ${y}), rotate(${rotation} ${(x + w) / 2} ${(y + h) / 2})`
|
|
|
|
|
}).up()
|
|
|
|
|
}
|
|
|
|
|
|
2022-05-25 00:35:08 +08:00
|
|
|
|
function overlay_sticky(svg, annotation) {
|
|
|
|
|
|
|
|
|
|
let backgroundColor = color_to_hex(annotation.style.color, true);
|
2022-06-01 01:23:24 +08:00
|
|
|
|
let fontSize = text_size_to_px(annotation.style.size, annotation.style.scale, true);
|
2022-05-25 00:35:08 +08:00
|
|
|
|
let rotation = rad_to_degree(annotation.rotation);
|
|
|
|
|
let font = determine_font_from_family(annotation.style.font);
|
|
|
|
|
let textAlign = align_to_css_property(annotation.style.textAlign);
|
|
|
|
|
|
|
|
|
|
let [textBoxWidth, textBoxHeight] = annotation.size;
|
|
|
|
|
let [textBox_x, textBox_y] = annotation.point;
|
|
|
|
|
|
|
|
|
|
var html = twemoji.parse(
|
|
|
|
|
`<!DOCTYPE html>
|
|
|
|
|
<style>
|
|
|
|
|
img.emoji { height: 1em; width: 1em; }
|
|
|
|
|
p {
|
|
|
|
|
width:${textBoxWidth}px;
|
|
|
|
|
height:${textBoxHeight}px;
|
|
|
|
|
color:#0d0d0d;
|
|
|
|
|
word-wrap:break-word;
|
|
|
|
|
font-family:${font};
|
|
|
|
|
font-size:${fontSize}px;
|
|
|
|
|
text-align:${textAlign};
|
|
|
|
|
background-color:${backgroundColor};
|
|
|
|
|
}
|
|
|
|
|
</style>
|
|
|
|
|
<html>
|
|
|
|
|
<p>${annotation.text.split('\n').join('<br>')}</p>
|
|
|
|
|
</html>`);
|
|
|
|
|
|
|
|
|
|
var htmlFilePath = path.join(dropbox, `text${annotation.id}.html`)
|
|
|
|
|
|
|
|
|
|
fs.writeFileSync(htmlFilePath, html, function (err) {
|
|
|
|
|
if (err) logger.error(err);
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
render_HTMLTextBox(htmlFilePath, annotation.id, textBoxWidth, textBoxHeight)
|
|
|
|
|
|
|
|
|
|
svg.ele('image', {
|
|
|
|
|
'xlink:href': `file://${dropbox}/text${annotation.id}.png`,
|
|
|
|
|
x: textBox_x,
|
|
|
|
|
y: textBox_y,
|
|
|
|
|
width: textBoxWidth,
|
|
|
|
|
height: textBoxHeight,
|
|
|
|
|
transform: `rotate(${rotation}, ${textBox_x + (textBoxWidth / 2)}, ${textBox_y + (textBoxHeight / 2)})`
|
|
|
|
|
}).up();
|
|
|
|
|
}
|
|
|
|
|
|
2022-05-26 00:35:43 +08:00
|
|
|
|
function overlay_text(svg, annotation) {
|
|
|
|
|
|
|
|
|
|
let fontColor = color_to_hex(annotation.style.color);
|
2022-06-01 01:23:24 +08:00
|
|
|
|
let fontSize = text_size_to_px(annotation.style.size, annotation.style.scale);
|
2022-05-26 00:35:43 +08:00
|
|
|
|
// let rotation = rad_to_degree(annotation.rotation);
|
|
|
|
|
let font = determine_font_from_family(annotation.style.font);
|
|
|
|
|
|
|
|
|
|
let [textBox_x, textBox_y] = annotation.point;
|
|
|
|
|
let textNode = svg.ele('text', {
|
|
|
|
|
'x': textBox_x,
|
|
|
|
|
'y': textBox_y,
|
|
|
|
|
'font-size': fontSize,
|
|
|
|
|
'font-family': font,
|
|
|
|
|
'fill': fontColor,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
for (let line of annotation.text.split('\n')) {
|
|
|
|
|
if (line === '\n') { line = '' }
|
|
|
|
|
textNode.ele('tspan', { x: textBox_x, dy: '1em' }).txt(line).up()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-04-05 21:01:26 +08:00
|
|
|
|
function overlay_annotations(svg, currentSlideAnnotations, w, h) {
|
2022-05-25 00:35:08 +08:00
|
|
|
|
|
2022-05-26 00:35:43 +08:00
|
|
|
|
for (let annotation of currentSlideAnnotations) {
|
2022-06-01 01:23:24 +08:00
|
|
|
|
console.log(annotation.annotationInfo);
|
2022-05-18 02:00:16 +08:00
|
|
|
|
switch (annotation.annotationInfo.type) {
|
2022-05-26 00:35:43 +08:00
|
|
|
|
case 'draw':
|
|
|
|
|
overlay_draw(svg, annotation.annotationInfo);
|
|
|
|
|
break;
|
2022-06-01 01:23:24 +08:00
|
|
|
|
case 'rectangle':
|
|
|
|
|
overlay_rectangle(svg, annotation.annotationInfo);
|
|
|
|
|
break;
|
2022-05-25 00:35:08 +08:00
|
|
|
|
case 'sticky':
|
|
|
|
|
overlay_sticky(svg, annotation.annotationInfo);
|
|
|
|
|
break;
|
2022-02-16 21:28:35 +08:00
|
|
|
|
case 'text':
|
2022-05-18 02:00:16 +08:00
|
|
|
|
overlay_text(svg, annotation.annotationInfo);
|
2022-02-16 21:28:35 +08:00
|
|
|
|
break;
|
2022-02-15 23:48:58 +08:00
|
|
|
|
default:
|
2022-05-18 02:00:16 +08:00
|
|
|
|
logger.error(`Unknown annotation type ${annotation.annotationInfo.type}.`);
|
2022-02-15 23:48:58 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-13 04:03:07 +08:00
|
|
|
|
// Process the presentation pages and annotations into a PDF file
|
|
|
|
|
|
|
|
|
|
// 1. Get the job
|
2022-04-29 19:50:42 +08:00
|
|
|
|
const dropbox = path.join(config.shared.presAnnDropboxDir, jobId);
|
|
|
|
|
let job = fs.readFileSync(path.join(dropbox, 'job'));
|
2022-02-14 01:10:37 +08:00
|
|
|
|
let exportJob = JSON.parse(job);
|
|
|
|
|
|
2022-02-13 04:03:07 +08:00
|
|
|
|
// 2. Get the annotations
|
2022-04-29 19:50:42 +08:00
|
|
|
|
let annotations = fs.readFileSync(path.join(dropbox, 'whiteboard'));
|
2022-02-14 01:10:37 +08:00
|
|
|
|
let whiteboard = JSON.parse(annotations);
|
|
|
|
|
let pages = JSON.parse(whiteboard.pages);
|
2022-04-19 22:35:03 +08:00
|
|
|
|
let ghostScriptInput = ""
|
2022-02-14 01:10:37 +08:00
|
|
|
|
|
2022-02-13 04:03:07 +08:00
|
|
|
|
// 3. Convert annotations to SVG
|
2022-04-05 21:01:26 +08:00
|
|
|
|
for (let currentSlide of pages) {
|
2022-04-29 19:50:42 +08:00
|
|
|
|
var dimensions = sizeOf(path.join(dropbox, `slide${currentSlide.page}.png`));
|
2022-04-19 22:35:03 +08:00
|
|
|
|
var slideWidth = dimensions.width;
|
|
|
|
|
var slideHeight = dimensions.height;
|
2022-02-14 01:10:37 +08:00
|
|
|
|
|
|
|
|
|
// Create the SVG slide with the background image
|
2022-02-15 20:40:08 +08:00
|
|
|
|
let svg = create({ version: '1.0', encoding: 'UTF-8' })
|
2022-05-26 00:35:43 +08:00
|
|
|
|
.ele('svg', {
|
|
|
|
|
xmlns: 'http://www.w3.org/2000/svg',
|
|
|
|
|
'xmlns:xlink': 'http://www.w3.org/1999/xlink',
|
|
|
|
|
width: slideWidth,
|
|
|
|
|
height: slideHeight,
|
|
|
|
|
})
|
|
|
|
|
.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${currentSlide.page}.png`,
|
|
|
|
|
width: slideWidth,
|
|
|
|
|
height: slideHeight,
|
|
|
|
|
})
|
|
|
|
|
.up()
|
|
|
|
|
.ele('g', {
|
|
|
|
|
class: 'canvas'
|
|
|
|
|
});
|
2022-02-14 01:10:37 +08:00
|
|
|
|
|
2022-02-15 23:48:58 +08:00
|
|
|
|
// 4. Overlay annotations onto slides
|
2022-02-16 21:28:35 +08:00
|
|
|
|
// Based on /record-and-playback/presentation/scripts/publish/presentation.rb
|
2022-04-05 21:01:26 +08:00
|
|
|
|
overlay_annotations(svg, currentSlide.annotations, slideWidth, slideHeight)
|
2022-02-15 23:48:58 +08:00
|
|
|
|
|
2022-02-15 20:40:08 +08:00
|
|
|
|
svg = svg.end({ prettyPrint: true });
|
2022-02-14 01:10:37 +08:00
|
|
|
|
// Write annotated SVG file
|
2022-04-29 19:50:42 +08:00
|
|
|
|
let SVGfile = path.join(dropbox, `annotated-slide${currentSlide.page}.svg`)
|
|
|
|
|
let PDFfile = path.join(dropbox, `annotated-slide${currentSlide.page}.pdf`)
|
2022-04-19 22:35:03 +08:00
|
|
|
|
|
2022-05-26 00:35:43 +08:00
|
|
|
|
fs.writeFileSync(SVGfile, svg, function (err) {
|
|
|
|
|
if (err) { return logger.error(err); }
|
2022-02-14 01:10:37 +08:00
|
|
|
|
});
|
2022-02-16 01:11:13 +08:00
|
|
|
|
|
2022-04-19 22:35:03 +08:00
|
|
|
|
let convertAnnotatedSlide = [
|
|
|
|
|
'cairosvg',
|
|
|
|
|
SVGfile,
|
|
|
|
|
'-o', PDFfile
|
|
|
|
|
].join(' ');
|
|
|
|
|
|
2022-05-31 17:09:38 +08:00
|
|
|
|
execSync(convertAnnotatedSlide);
|
2022-04-19 22:35:03 +08:00
|
|
|
|
|
|
|
|
|
ghostScriptInput += `${PDFfile} `
|
2022-02-14 01:10:37 +08:00
|
|
|
|
}
|
|
|
|
|
|
2022-03-03 00:02:08 +08:00
|
|
|
|
// Create PDF output directory if it doesn't exist
|
2022-04-29 19:50:42 +08:00
|
|
|
|
let output_dir = path.join(exportJob.presLocation, 'pdfs', jobId);
|
|
|
|
|
|
|
|
|
|
if (!fs.existsSync(output_dir)) { fs.mkdirSync(output_dir, { recursive: true }); }
|
2022-03-03 00:02:08 +08:00
|
|
|
|
|
2022-05-01 05:28:11 +08:00
|
|
|
|
let filename = sanitize(exportJob.filename.replace(/\s/g, '_'));
|
|
|
|
|
|
2022-04-19 22:35:03 +08:00
|
|
|
|
let mergePDFs = [
|
|
|
|
|
'gs',
|
|
|
|
|
'-dNOPAUSE',
|
|
|
|
|
'-sDEVICE=pdfwrite',
|
2022-04-29 19:50:42 +08:00
|
|
|
|
`-sOUTPUTFILE="${path.join(output_dir, `${filename}.pdf`)}"`,
|
2022-04-19 22:35:03 +08:00
|
|
|
|
`-dBATCH`,
|
|
|
|
|
ghostScriptInput,
|
2022-05-26 00:35:43 +08:00
|
|
|
|
].join(' ');
|
2022-03-03 00:02:08 +08:00
|
|
|
|
|
2022-02-13 04:03:07 +08:00
|
|
|
|
// Resulting PDF file is stored in the presentation dir
|
2022-05-31 17:09:38 +08:00
|
|
|
|
execSync(mergePDFs);
|
2022-02-13 04:03:07 +08:00
|
|
|
|
|
|
|
|
|
// Launch Notifier Worker depending on job type
|
2022-04-29 19:50:42 +08:00
|
|
|
|
logger.info(`Saved PDF at ${output_dir}/${jobId}/${filename}.pdf`);
|
2022-02-24 01:20:58 +08:00
|
|
|
|
|
2022-04-29 19:50:42 +08:00
|
|
|
|
kickOffNotifierWorker(exportJob.jobType, filename);
|
2022-02-13 04:03:07 +08:00
|
|
|
|
|
2022-05-31 17:09:38 +08:00
|
|
|
|
parentPort.postMessage({ message: workerData });
|