Use probe-image-size for dimensions; embed SVGs in place of PNGs

This commit is contained in:
Daniel Petri Rocha 2022-06-01 23:22:08 +02:00
parent 5c6818c48a
commit f23e462cb1
4 changed files with 186 additions and 99 deletions

View File

@ -10,8 +10,8 @@
"dependencies": {
"axios": "^0.26.0",
"form-data": "^4.0.0",
"image-size": "^1.0.1",
"perfect-freehand": "^1.0.16",
"probe-image-size": "^7.2.3",
"redis": "^4.0.3",
"sanitize-filename": "^1.6.3",
"twemoji": "^14.0.2",
@ -153,6 +153,14 @@
"node": ">= 0.8"
}
},
"node_modules/debug": {
"version": "3.2.7",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
"integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
"dependencies": {
"ms": "^2.1.1"
}
},
"node_modules/delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
@ -239,25 +247,17 @@
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz",
"integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA=="
},
"node_modules/image-size": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/image-size/-/image-size-1.0.1.tgz",
"integrity": "sha512-VAwkvNSNGClRw9mDHhc5Efax8PLlsOGcUTh0T/LIriC8vPA3U5PdqXWqkz406MoYHMKW8Uf9gWr05T/rYB44kQ==",
"node_modules/iconv-lite": {
"version": "0.4.24",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
"dependencies": {
"queue": "6.0.2"
},
"bin": {
"image-size": "bin/image-size.js"
"safer-buffer": ">= 2.1.2 < 3"
},
"engines": {
"node": ">=12.0.0"
"node": ">=0.10.0"
}
},
"node_modules/inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
},
"node_modules/jsonfile": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-5.0.0.tgz",
@ -269,6 +269,11 @@
"graceful-fs": "^4.1.6"
}
},
"node_modules/lodash.merge": {
"version": "4.6.2",
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="
},
"node_modules/mime-db": {
"version": "1.51.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz",
@ -288,17 +293,40 @@
"node": ">= 0.6"
}
},
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
},
"node_modules/needle": {
"version": "2.9.1",
"resolved": "https://registry.npmjs.org/needle/-/needle-2.9.1.tgz",
"integrity": "sha512-6R9fqJ5Zcmf+uYaFgdIHmLwNldn5HbK8L5ybn7Uz+ylX/rnOsSp1AHcvQSrCaFN+qNM1wpymHqD7mVasEOlHGQ==",
"dependencies": {
"debug": "^3.2.6",
"iconv-lite": "^0.4.4",
"sax": "^1.2.4"
},
"bin": {
"needle": "bin/needle"
},
"engines": {
"node": ">= 4.4.x"
}
},
"node_modules/perfect-freehand": {
"version": "1.0.16",
"resolved": "https://registry.npmjs.org/perfect-freehand/-/perfect-freehand-1.0.16.tgz",
"integrity": "sha512-D4+avUeR8CHSl2vaPbPYX/dNpSMRYO3VOFp7qSSc+LRkSgzQbLATVnXosy7VxtsSHEh1C5t8K8sfmo0zCVnfWQ=="
},
"node_modules/queue": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/queue/-/queue-6.0.2.tgz",
"integrity": "sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==",
"node_modules/probe-image-size": {
"version": "7.2.3",
"resolved": "https://registry.npmjs.org/probe-image-size/-/probe-image-size-7.2.3.tgz",
"integrity": "sha512-HubhG4Rb2UH8YtV4ba0Vp5bQ7L78RTONYu/ujmCu5nBI8wGv24s4E9xSKBi0N1MowRpxk76pFCpJtW0KPzOK0w==",
"dependencies": {
"inherits": "~2.0.3"
"lodash.merge": "^4.6.2",
"needle": "^2.5.2",
"stream-parser": "~0.3.1"
}
},
"node_modules/redis": {
@ -333,6 +361,11 @@
"node": ">=4"
}
},
"node_modules/safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
},
"node_modules/sanitize-filename": {
"version": "1.6.3",
"resolved": "https://registry.npmjs.org/sanitize-filename/-/sanitize-filename-1.6.3.tgz",
@ -341,11 +374,37 @@
"truncate-utf8-bytes": "^1.0.0"
}
},
"node_modules/sax": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
"integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw=="
},
"node_modules/sprintf-js": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
"integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw="
},
"node_modules/stream-parser": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/stream-parser/-/stream-parser-0.3.1.tgz",
"integrity": "sha1-FhhUhpRCACGhGC/wrxkRwSl2F3M=",
"dependencies": {
"debug": "2"
}
},
"node_modules/stream-parser/node_modules/debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"dependencies": {
"ms": "2.0.0"
}
},
"node_modules/stream-parser/node_modules/ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
},
"node_modules/truncate-utf8-bytes": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz",
@ -529,6 +588,14 @@
"delayed-stream": "~1.0.0"
}
},
"debug": {
"version": "3.2.7",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
"integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
"requires": {
"ms": "^2.1.1"
}
},
"delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
@ -584,19 +651,14 @@
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz",
"integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA=="
},
"image-size": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/image-size/-/image-size-1.0.1.tgz",
"integrity": "sha512-VAwkvNSNGClRw9mDHhc5Efax8PLlsOGcUTh0T/LIriC8vPA3U5PdqXWqkz406MoYHMKW8Uf9gWr05T/rYB44kQ==",
"iconv-lite": {
"version": "0.4.24",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
"requires": {
"queue": "6.0.2"
"safer-buffer": ">= 2.1.2 < 3"
}
},
"inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
},
"jsonfile": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-5.0.0.tgz",
@ -606,6 +668,11 @@
"universalify": "^0.1.2"
}
},
"lodash.merge": {
"version": "4.6.2",
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="
},
"mime-db": {
"version": "1.51.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz",
@ -619,17 +686,34 @@
"mime-db": "1.51.0"
}
},
"ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
},
"needle": {
"version": "2.9.1",
"resolved": "https://registry.npmjs.org/needle/-/needle-2.9.1.tgz",
"integrity": "sha512-6R9fqJ5Zcmf+uYaFgdIHmLwNldn5HbK8L5ybn7Uz+ylX/rnOsSp1AHcvQSrCaFN+qNM1wpymHqD7mVasEOlHGQ==",
"requires": {
"debug": "^3.2.6",
"iconv-lite": "^0.4.4",
"sax": "^1.2.4"
}
},
"perfect-freehand": {
"version": "1.0.16",
"resolved": "https://registry.npmjs.org/perfect-freehand/-/perfect-freehand-1.0.16.tgz",
"integrity": "sha512-D4+avUeR8CHSl2vaPbPYX/dNpSMRYO3VOFp7qSSc+LRkSgzQbLATVnXosy7VxtsSHEh1C5t8K8sfmo0zCVnfWQ=="
},
"queue": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/queue/-/queue-6.0.2.tgz",
"integrity": "sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==",
"probe-image-size": {
"version": "7.2.3",
"resolved": "https://registry.npmjs.org/probe-image-size/-/probe-image-size-7.2.3.tgz",
"integrity": "sha512-HubhG4Rb2UH8YtV4ba0Vp5bQ7L78RTONYu/ujmCu5nBI8wGv24s4E9xSKBi0N1MowRpxk76pFCpJtW0KPzOK0w==",
"requires": {
"inherits": "~2.0.3"
"lodash.merge": "^4.6.2",
"needle": "^2.5.2",
"stream-parser": "~0.3.1"
}
},
"redis": {
@ -658,6 +742,11 @@
"redis-errors": "^1.0.0"
}
},
"safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
},
"sanitize-filename": {
"version": "1.6.3",
"resolved": "https://registry.npmjs.org/sanitize-filename/-/sanitize-filename-1.6.3.tgz",
@ -666,11 +755,39 @@
"truncate-utf8-bytes": "^1.0.0"
}
},
"sax": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
"integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw=="
},
"sprintf-js": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
"integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw="
},
"stream-parser": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/stream-parser/-/stream-parser-0.3.1.tgz",
"integrity": "sha1-FhhUhpRCACGhGC/wrxkRwSl2F3M=",
"requires": {
"debug": "2"
},
"dependencies": {
"debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"requires": {
"ms": "2.0.0"
}
},
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
}
}
},
"truncate-utf8-bytes": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz",

View File

@ -8,8 +8,8 @@
"dependencies": {
"axios": "^0.26.0",
"form-data": "^4.0.0",
"image-size": "^1.0.1",
"perfect-freehand": "^1.0.16",
"probe-image-size": "^7.2.3",
"redis": "^4.0.3",
"sanitize-filename": "^1.6.3",
"twemoji": "^14.0.2",

View File

@ -2,8 +2,7 @@ const Logger = require('../lib/utils/logger');
const config = require('../config');
const fs = require('fs');
const redis = require('redis');
const { execSync } = require("child_process");
const { Worker, workerData, parentPort } = require('worker_threads')
const { Worker, workerData, parentPort } = require('worker_threads');
const path = require('path');
const jobId = workerData;
@ -46,6 +45,8 @@ let exportJob = JSON.parse(job);
// Remove annotations from Redis
await client.DEL(jobId);
client.disconnect();
let annotations = JSON.stringify(presAnn);
let whiteboard = JSON.parse(annotations);
@ -56,60 +57,28 @@ let exportJob = JSON.parse(job);
});
// Collect the Presentation Page files from the presentation directory
// PDF / PNG / JPEG file
let presentationFile = path.join(exportJob.presLocation, exportJob.presId);
let pdfFileExists = fs.existsSync(`${presentationFile}.pdf`);
// Use the SVG files as shown in the browser in order to avoid incorrect dimensions
// Tldraw uses absolute coordinates
for (let p of pages) {
let pageNumber = p.page;
let svgFile = path.join(exportJob.presLocation, 'svgs', `slide${pageNumber}.svg`)
let outputFile = path.join(dropbox, `slide${pageNumber}`);
let svgFileExists = fs.existsSync(svgFile);
// CairoSVG doesn't handle transparent SVG embeds properly, e.g., for transparent pictures.
// In these cases we reference the PNG image
if(pdfFileExists) {
let extactSlideAsPDFCommands = [
'pdftocairo',
'-png',
'-scale-to', '1920',
'-f', pageNumber,
'-l', pageNumber,
'-singlefile',
`${presentationFile}.pdf`,
outputFile
].join(' ');
execSync(extactSlideAsPDFCommands, (error, stderr) => {
if (error) {
return logger.error(`PDFtoCairo failed with error: ${error.message}`);
}
if (stderr) {
return logger.error(`PDFtoCairo failed with stderr: ${stderr}`);
}
})
}
else if (fs.existsSync(`${presentationFile}.png`)) {
if (fs.existsSync(`${presentationFile}.png`)) {
fs.copyFileSync(`${presentationFile}.png`, `${outputFile}.png`);
}
else if (fs.existsSync(`${presentationFile}.jpeg`)) {
let convertImageToPngCommands = [
'convert',
`${presentationFile}.jpeg`,
'-background', 'white',
'-resize', '1920x1920',
'-auto-orient',
'-flatten',
`${outputFile}.png`
].join(' ');
execSync(convertImageToPngCommands, (error, stderr) => {
if (error) {
return logger.error(`Image conversion to PNG failed with error: ${error.message}`);
}
if (stderr) {
return logger.error(`Image conversion to PNG failed with stderr: ${stderr}`);
}
})
else if (svgFileExists) {
fs.copyFileSync(svgFile, `${outputFile}.svg`);
}
else {
@ -118,7 +87,6 @@ let exportJob = JSON.parse(job);
}
kickOffProcessWorker(exportJob.jobId);
client.disconnect();
})()
parentPort.postMessage({ message: workerData })

View File

@ -1,7 +1,6 @@
const Logger = require('../lib/utils/logger');
const config = require('../config');
const fs = require('fs');
const sizeOf = require('image-size');
const { create } = require('xmlbuilder2', { encoding: 'utf-8' });
const { execSync } = require("child_process");
const { Worker, workerData, parentPort } = require('worker_threads');
@ -9,6 +8,7 @@ const path = require('path');
const sanitize = require("sanitize-filename");
const twemoji = require("twemoji");
const { getStroke, getStrokePoints } = require('perfect-freehand');
const probe = require('probe-image-size');
const jobId = workerData;
@ -395,20 +395,14 @@ function overlay_text(svg, annotation) {
}
}
// function sortByKey(array, key, pos) {
// return array.sort(function(a, b) {
// let [x, y] = [a[key][pos], b[key][pos]]
// return ((x < y) ? -1 : ((x > y) ? 1 : 0));
// });
// }
function overlay_annotations(svg, currentSlideAnnotations, w, h) {
// currentSlideAnnotations = sortByKey(currentSlideAnnotations, 'annotationInfo', 'childIndex');
for (let annotation of currentSlideAnnotations) {
switch (annotation.annotationInfo.type) {
// case 'arrow':
// overlay_arrow(svg, annotation.annotationInfo);
// break;
case 'draw':
overlay_draw(svg, annotation.annotationInfo);
break;
@ -448,9 +442,17 @@ let ghostScriptInput = ""
// 3. Convert annotations to SVG
for (let currentSlide of pages) {
var dimensions = sizeOf(path.join(dropbox, `slide${currentSlide.page}.png`));
var slideWidth = dimensions.width;
var slideHeight = dimensions.height;
let backgroundImagePath = path.join(dropbox, `slide${currentSlide.page}`);
let backgroundFormat = fs.existsSync(`${backgroundImagePath}.png`) ? 'png' : 'svg'
// Output dimensions in pixels even if stated otherwise (pt)
// CairoSVG didn't like attempts to read the dimensions from a stream
// to prevent loading file in memory
let dimensions = probe.sync(fs.readFileSync(`${backgroundImagePath}.${backgroundFormat}`));
let slideWidth = dimensions.width;
let slideHeight = dimensions.height;
// Create the SVG slide with the background image
let svg = create({ version: '1.0', encoding: 'UTF-8' })
@ -465,7 +467,7 @@ for (let currentSlide of pages) {
sysID: 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'
})
.ele('image', {
'xlink:href': `file://${dropbox}/slide${currentSlide.page}.png`,
'xlink:href': `file://${dropbox}/slide${currentSlide.page}.${backgroundFormat}`,
width: slideWidth,
height: slideHeight,
})