Merge pull request #15326 from danielpetri1/tldrawpdfexport
This commit is contained in:
commit
b64032b954
@ -7,11 +7,11 @@
|
||||
"presAnnDropboxDir": "/tmp/pres-ann-dropbox"
|
||||
},
|
||||
"collector": {
|
||||
"backgroundSlideDPI": 300,
|
||||
"backgroundSlidePPI": 200
|
||||
"pngWidthRasterizedSlides": 2560
|
||||
},
|
||||
"process": {
|
||||
"whiteboardTextEncoding": "utf-8",
|
||||
"textScaleFactor": 4,
|
||||
"pointsPerInch": 72,
|
||||
"pixelsPerInch": 96
|
||||
},
|
||||
|
@ -4,7 +4,6 @@ const fs = require('fs');
|
||||
const redis = require('redis');
|
||||
const { Worker, workerData, parentPort } = require('worker_threads');
|
||||
const path = require('path');
|
||||
const probe = require('probe-image-size');
|
||||
const { execSync } = require("child_process");
|
||||
|
||||
const jobId = workerData;
|
||||
@ -66,27 +65,25 @@ let exportJob = JSON.parse(job);
|
||||
|
||||
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}`);
|
||||
|
||||
// CairoSVG doesn't handle transparent SVG and PNG embeds properly, e.g., in rasterized text.
|
||||
// So textboxes may get a black background when downloading/exporting repeatedly.
|
||||
// To avoid that, we take slides from the uploaded file, but later probe the dimensions from the SVG
|
||||
// so it matches what was shown in the browser -- Tldraw unfortunately uses absolute coordinates.
|
||||
// so it matches what was shown in the browser.
|
||||
|
||||
let extract_png_from_pdf = [
|
||||
'pdftocairo',
|
||||
'-png',
|
||||
'-f', pageNumber,
|
||||
'-l', pageNumber,
|
||||
'-r', config.collector.backgroundSlidePPI,
|
||||
'-scale-to', config.collector.pngWidthRasterizedSlides,
|
||||
'-singlefile',
|
||||
'-cropbox',
|
||||
pdfFile, outputFile,
|
||||
].join(' ')
|
||||
|
||||
execSync(extract_png_from_pdf);
|
||||
fs.copyFileSync(svgFile, path.join(dropbox, `slide${pageNumber}.svg`));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -77,4 +77,7 @@ if (jobType == 'PresentationWithAnnotationDownloadJob') {
|
||||
logger.error(`Notifier received unknown job type ${jobType}`);
|
||||
}
|
||||
|
||||
// Delete temporary files
|
||||
fs.rm(dropbox, { recursive: true }, (err) => { if (err) { throw err; } });
|
||||
|
||||
parentPort.postMessage({ message: workerData })
|
||||
|
@ -120,12 +120,26 @@ function to_px(pt) {
|
||||
return (pt / config.process.pointsPerInch) * config.process.pixelsPerInch
|
||||
}
|
||||
|
||||
// Escape shell metacharacters based on MDN's page on regular expressions,
|
||||
// the escape-string-regexp npm package, and Pango markup.
|
||||
function escapeText(string) {
|
||||
return string
|
||||
.replace(/&/g, '\\&')
|
||||
.replace(/'/g, '\\'')
|
||||
.replace(/>/g, '\\>')
|
||||
.replace(/</g, '\\<')
|
||||
.replace(/[~`!".*+?%^${}()|[\]\\\/]/g, '\\$&')
|
||||
.replace(/-/g, '\\-');
|
||||
}
|
||||
|
||||
function render_textbox(textColor, font, fontSize, textAlign, text, id, textBoxWidth = null) {
|
||||
|
||||
fontSize = to_pt(fontSize);
|
||||
fontSize = to_pt(fontSize) * config.process.textScaleFactor
|
||||
text = escapeText(text);
|
||||
|
||||
// Sticky notes need automatic line wrapping: take width into account
|
||||
let size = textBoxWidth ? `-size ${textBoxWidth}x` : ''
|
||||
// Texbox scaled by a constant factor to improve resolution at small scales
|
||||
let size = textBoxWidth ? `-size ${textBoxWidth * config.process.textScaleFactor}x` : ''
|
||||
|
||||
let pangoText = `pango:"<span font_family='${font}' font='${fontSize}' color='${textColor}'>${text}</span>"`
|
||||
|
||||
@ -560,8 +574,8 @@ function overlay_shape_label(svg, annotation) {
|
||||
render_textbox(fontColor, font, fontSize, textAlign, text, id);
|
||||
|
||||
let dimensions = probe.sync(fs.readFileSync(path.join(dropbox, `text${id}.png`)));
|
||||
let labelWidth = dimensions.width;
|
||||
let labelHeight = dimensions.height;
|
||||
let labelWidth = dimensions.width / config.process.textScaleFactor;
|
||||
let labelHeight = dimensions.height / config.process.textScaleFactor;
|
||||
|
||||
svg.ele('g', {
|
||||
transform: `rotate(${rotation} ${label_center_x} ${label_center_y})`
|
||||
@ -744,7 +758,8 @@ let ghostScriptInput = ""
|
||||
for (let currentSlide of pages) {
|
||||
|
||||
let backgroundImagePath = path.join(dropbox, `slide${currentSlide.page}`);
|
||||
let svgFileExists = fs.existsSync(`${backgroundImagePath}.svg`)
|
||||
let svgBackgroundSlide = path.join(exportJob.presLocation, 'svgs', `slide${currentSlide.page}.svg`);
|
||||
let svgBackgroundExists = fs.existsSync(svgBackgroundSlide);
|
||||
let backgroundFormat = fs.existsSync(`${backgroundImagePath}.png`) ? 'png' : 'jpeg'
|
||||
|
||||
// Output dimensions in pixels even if stated otherwise (pt)
|
||||
@ -752,8 +767,8 @@ for (let currentSlide of pages) {
|
||||
// that would prevent loading file in memory
|
||||
// Ideally, use dimensions provided by tldraw's background image asset
|
||||
// (this is not yet always provided)
|
||||
let dimensions = svgFileExists ?
|
||||
probe.sync(fs.readFileSync(`${backgroundImagePath}.svg`)) :
|
||||
let dimensions = svgBackgroundExists ?
|
||||
probe.sync(fs.readFileSync(svgBackgroundSlide)) :
|
||||
probe.sync(fs.readFileSync(`${backgroundImagePath}.${backgroundFormat}`));
|
||||
|
||||
let slideWidth = parseInt(dimensions.width, 10);
|
||||
@ -794,14 +809,12 @@ for (let currentSlide of pages) {
|
||||
if (err) { return logger.error(err); }
|
||||
});
|
||||
|
||||
// Dimensions converted back to a pixel size which,
|
||||
// Dimensions converted to a pixel size which,
|
||||
// when converted to points, will yield the desired
|
||||
// dimension in pixels when read without conversion
|
||||
|
||||
// e.g. say Tldraw's canvas is 1920x1080 px.
|
||||
// The background SVG dimensions are set to 1920x1080 pt (incorrect unit).
|
||||
// So we read it in ignoring the unit as 1920x1080 px, making the position of the drawings match.
|
||||
// Now we assume we had 1920x1080pt and resize to 2560x1440 px so that the SVG generates with the original "wrong" size.
|
||||
// e.g. say the background SVG dimensions are set to 1920x1080 pt
|
||||
// Resize output to 2560x1440 px so that the SVG generates with the original size in pt.
|
||||
|
||||
let convertAnnotatedSlide = [
|
||||
'cairosvg',
|
||||
|
Loading…
Reference in New Issue
Block a user