scissors
Version:
PDF manipulation in Node.js, based on PDFTK.
120 lines (107 loc) • 3.24 kB
JavaScript
var fs = require('fs');
var spawn = require('child_process').spawn;
var rimraf = require('rimraf').sync;
var temp = require('temp');
require('bufferjs/indexOf');
// take stdin, write to random access file
// strip cropbox tthen reapply cropbox and write to stdout
// doesn't work with all PDFs yet, see
// https://github.com/tcr/scissors/issues/21
// http://stackoverflow.com/questions/6183479/cropping-a-pdf-using-ghostscript-9-01?rq=1
function debug () {
//console.error.apply(console, arguments);
}
function repairPDF (outs) {
var pdftk = spawn('pdftk', ['-', 'output', '-']);
pdftk.stderr.on('data', function (data) {
throw new Error('pdftk encountered an error:\n', String(data));
});
pdftk.on('exit', function (code) {
if (code) {
throw new Error('pdftk exited with failure code:', code);
}
});
pdftk.stdout.pipe(outs);
return pdftk.stdin;
}
function stripCropbox (ins, outs, next) {
var cropbox = [0, 0, 0, 0];
var repair = repairPDF(outs);
ins.on('data', function (data) {
var i;
if ((i = data.indexOf('/CropBox')) != -1) {
repair.write(data.slice(0, i));
for (var j = i; data[j] != '\n'.charCodeAt(0); ) {
j++;
}
debug('Scissors: found cropbox', String(data.slice(i, j)));
cropbox = String(data.slice(i, j))
.match(/\/CropBox\s+\[([\d.]+)\s+([\d.]+)\s+([\d.]+)\s+([\d.]+)\]/)
.slice(1, 5).map(Number);
repair.write(data.slice(j + 1));
} else {
repair.write(data);
}
});
ins.on('end', function () {
debug('Scissors: Finished stripping cropbox.');
repair.end();
})
outs.on('close', function (err) {
next(err, cropbox);
});
}
function combineCropboxes (a, b) {
return [a[0] + b[0], a[1] + b[1], Math.min(a[0] + b[2], a[2]), Math.min(a[1] + b[3], a[3])];
}
function writeCropbox (ins, cropbox) {
var gs = spawn('gs', [
'-sDEVICE=pdfwrite',
'-sOutputFile=-',
'-q',
//'-sstdout=/dev/null',
'-dNOPAUSE', '-dBATCH',
'-c', '[/CropBox [' + cropbox.join(' ') + '] /PAGES pdfmark',
'-f', '-']);
ins.pipe(gs.stdin);
gs.stderr.on('data', function (data) {
throw new Error('gs encountered an error:\n', String(data));
});
gs.on('exit', function (code) {
if (code) {
throw new Error('gs exited with failure code:', code);
}
debug('Scissors: Finished writing cropbox.');
});
return gs.stdout;
}
//stripCropbox(process.stdin, fs)
if (process.argv.length < 6) {
throw new Error('Invalid number of arguments.');
process.exit(1);
}
var modcropbox = process.argv.slice(2, 6).map(Number);
debug('Scissors: opening temp file');
temp.open('stripCropbox', function (err, info) {
debug('Scissors: closing temp file', info.path);
fs.close(info.fd, function () {
debug('Scissors: closed.');
stripCropbox(process.stdin, fs.createWriteStream(info.path), function (err, cropbox) {
if (err) {
rimraf(info.path);
throw new Error(err);
}
var stream = fs.createReadStream(info.path);
stream.on('close', function () {
rimraf(info.path);
}).on('error', function () {
rimraf(info.path);
});
//fs.createReadStream(info.path).pipe(process.stdout);
writeCropbox(stream, combineCropboxes(cropbox, modcropbox))
.pipe(process.stdout);
});
process.stdin.resume();
});
});