2017-11-05 00:59:48 +08:00
|
|
|
/* global window */
|
2018-05-06 08:46:33 +08:00
|
|
|
const { spawn } = require('child_process');
|
2017-11-05 00:59:48 +08:00
|
|
|
const fs = require('fs');
|
|
|
|
const tmp = require('tmp');
|
2019-06-30 00:33:21 +08:00
|
|
|
const conf = require('./conf');
|
2017-11-05 00:59:48 +08:00
|
|
|
|
|
|
|
const outputFormats = {
|
|
|
|
asciidoc: 'text/plain',
|
|
|
|
context: 'application/x-latex',
|
|
|
|
epub: 'application/epub+zip',
|
|
|
|
epub3: 'application/epub+zip',
|
|
|
|
latex: 'application/x-latex',
|
|
|
|
odt: 'application/vnd.oasis.opendocument.text',
|
|
|
|
pdf: 'application/pdf',
|
|
|
|
rst: 'text/plain',
|
|
|
|
rtf: 'application/rtf',
|
|
|
|
textile: 'text/plain',
|
|
|
|
docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
|
|
};
|
|
|
|
|
|
|
|
const highlightStyles = [
|
|
|
|
'pygments',
|
|
|
|
'kate',
|
|
|
|
'monochrome',
|
|
|
|
'espresso',
|
|
|
|
'zenburn',
|
|
|
|
'haddock',
|
|
|
|
'tango',
|
|
|
|
];
|
|
|
|
|
|
|
|
const readJson = (str) => {
|
|
|
|
try {
|
|
|
|
return JSON.parse(str);
|
|
|
|
} catch (e) {
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
exports.generate = (req, res) => {
|
|
|
|
let pandocError = '';
|
|
|
|
const outputFormat = Object.prototype.hasOwnProperty.call(outputFormats, req.query.format)
|
|
|
|
? req.query.format
|
|
|
|
: 'pdf';
|
2022-06-04 03:07:10 +08:00
|
|
|
new Promise((resolve, reject) => {
|
|
|
|
tmp.file({
|
|
|
|
postfix: `.${outputFormat}`,
|
|
|
|
}, (err, filePath, fd, cleanupCallback) => {
|
|
|
|
if (err) {
|
|
|
|
reject(err);
|
|
|
|
} else {
|
|
|
|
resolve({
|
|
|
|
filePath,
|
|
|
|
cleanupCallback,
|
2017-11-05 00:59:48 +08:00
|
|
|
});
|
|
|
|
}
|
2022-06-04 03:07:10 +08:00
|
|
|
});
|
|
|
|
}).then(({ filePath, cleanupCallback }) => new Promise((resolve, reject) => {
|
|
|
|
const options = readJson(req.query.options);
|
|
|
|
const metadata = readJson(req.query.metadata);
|
|
|
|
const params = [];
|
2017-11-05 00:59:48 +08:00
|
|
|
|
2023-10-18 09:09:27 +08:00
|
|
|
params.push('--pdf-engine=xelatex');
|
2022-06-04 03:07:10 +08:00
|
|
|
params.push('--webtex=http://chart.apis.google.com/chart?cht=tx&chf=bg,s,FFFFFF00&chco=000000&chl=');
|
|
|
|
if (options.toc) {
|
|
|
|
params.push('--toc');
|
|
|
|
}
|
|
|
|
options.tocDepth = parseInt(options.tocDepth, 10);
|
|
|
|
if (!Number.isNaN(options.tocDepth)) {
|
|
|
|
params.push('--toc-depth', options.tocDepth);
|
|
|
|
}
|
|
|
|
options.highlightStyle = highlightStyles.includes(options.highlightStyle) ? options.highlightStyle : 'kate';
|
|
|
|
params.push('--highlight-style', options.highlightStyle);
|
|
|
|
Object.keys(metadata).forEach((key) => {
|
|
|
|
params.push('-M', `${key}=${metadata[key]}`);
|
|
|
|
});
|
2017-11-05 00:59:48 +08:00
|
|
|
|
2022-06-04 03:07:10 +08:00
|
|
|
let finished = false;
|
2017-11-05 00:59:48 +08:00
|
|
|
|
2022-06-04 03:07:10 +08:00
|
|
|
function onError(error) {
|
|
|
|
finished = true;
|
|
|
|
cleanupCallback();
|
|
|
|
reject(error);
|
|
|
|
}
|
|
|
|
|
|
|
|
const format = outputFormat === 'pdf' ? 'latex' : outputFormat;
|
|
|
|
params.push('-f', 'json', '-t', format, '-o', filePath);
|
|
|
|
const pandoc = spawn(conf.values.pandocPath, params, {
|
|
|
|
stdio: [
|
|
|
|
'pipe',
|
|
|
|
'ignore',
|
|
|
|
'pipe',
|
|
|
|
],
|
|
|
|
});
|
|
|
|
let timeoutId = setTimeout(() => {
|
|
|
|
timeoutId = null;
|
|
|
|
pandoc.kill();
|
|
|
|
}, 50000);
|
|
|
|
pandoc.on('error', onError);
|
|
|
|
pandoc.stdin.on('error', onError);
|
|
|
|
pandoc.stderr.on('data', (data) => {
|
|
|
|
pandocError += `${data}`;
|
|
|
|
});
|
|
|
|
pandoc.on('close', (code) => {
|
|
|
|
if (!finished) {
|
|
|
|
clearTimeout(timeoutId);
|
|
|
|
if (!timeoutId) {
|
|
|
|
res.statusCode = 408;
|
|
|
|
cleanupCallback();
|
|
|
|
reject(new Error('timeout'));
|
|
|
|
} else if (code) {
|
|
|
|
cleanupCallback();
|
|
|
|
reject();
|
|
|
|
} else {
|
|
|
|
res.set('Content-Type', outputFormats[outputFormat]);
|
|
|
|
const readStream = fs.createReadStream(filePath);
|
|
|
|
readStream.on('open', () => readStream.pipe(res));
|
|
|
|
readStream.on('close', () => cleanupCallback());
|
|
|
|
readStream.on('error', () => {
|
2017-11-05 00:59:48 +08:00
|
|
|
cleanupCallback();
|
|
|
|
reject();
|
2022-06-04 03:07:10 +08:00
|
|
|
});
|
2017-11-05 00:59:48 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
2022-06-04 03:07:10 +08:00
|
|
|
req.pipe(pandoc.stdin);
|
|
|
|
}))
|
|
|
|
.catch((err) => {
|
|
|
|
console.error(err);
|
|
|
|
const message = err && err.message;
|
|
|
|
if (message === 'unauthorized') {
|
|
|
|
res.statusCode = 401;
|
|
|
|
res.end('Unauthorized.');
|
|
|
|
} else if (message === 'timeout') {
|
|
|
|
res.statusCode = 408;
|
|
|
|
res.end('Request timeout.');
|
|
|
|
} else {
|
|
|
|
res.statusCode = 400;
|
|
|
|
res.end(pandocError || 'Unknown error.');
|
|
|
|
}
|
|
|
|
});
|
2017-11-05 00:59:48 +08:00
|
|
|
};
|