html-pdf-lts
Version:
HTML to PDF converter that uses phantomjs
188 lines (162 loc) • 5.48 kB
JavaScript
var fs = require("fs");
var childprocess = require("child_process");
var path = require("path");
var assert = require("assert");
try {
var phantomjs = require("phantomjs-prebuilt");
} catch (err) {
// console.log('html-pdf: Failed to load PhantomJS module.', err)
}
/*
* phantomjs version 1.8.1 and later should work.
*
* Create a PDF file out of an html string.
*
* Regions for the PDF page are:
*
* - Page Header -> document.getElementById('pageHeader')
* - Page Content -> document.getElementById('pageContent')
* - Page Footer -> document.getElementById('pageFooter')
*
* When no #pageContent is available, phantomjs will use document.body as pdf content
*/
module.exports = PDF;
function PDF(html, options) {
this.html = html;
this.options = options || {};
if (this.options.script) {
this.script = path.normalize(this.options.script);
} else {
this.script = path.join(__dirname, "scripts", "pdf_a4_portrait.js");
}
if (this.options.filename)
this.options.filename = path.resolve(this.options.filename);
if (!this.options.phantomPath)
this.options.phantomPath = phantomjs && phantomjs.path;
this.options.phantomArgs = this.options.phantomArgs || [];
assert(
this.options.phantomPath,
"html-pdf: Failed to load PhantomJS module. You have to set the path to the PhantomJS binary using 'options.phantomPath'"
);
assert(
typeof this.html === "string" && this.html.length,
"html-pdf: Can't create a pdf without an html string"
);
this.options.timeout = parseInt(this.options.timeout, 10) || 30000;
}
PDF.prototype.toBuffer = function PdfToBuffer(callback) {
this.exec(function execPdfToBuffer(err, res) {
if (err) return callback(err);
fs.readFile(res.filename, function readCallback(err, buffer) {
if (err) return callback(err);
fs.unlink(res.filename, function unlinkPdfFile(err) {
if (err) return callback(err);
callback(null, buffer);
});
});
});
};
PDF.prototype.toStream = function PdfToStream(callback) {
this.exec(function (err, res) {
if (err) return callback(err);
try {
var stream = fs.createReadStream(res.filename);
} catch (err) {
return callback(err);
}
stream.on("end", function () {
fs.unlink(res.filename, function unlinkPdfFile(err) {
if (err) console.log("html-pdf:", err);
});
});
var dimensions = { height: res.height, width: res.width };
callback(null, stream, dimensions);
});
};
PDF.prototype.toFile = function PdfToFile(filename, callback) {
assert(
arguments.length > 0,
"html-pdf: The method .toFile([filename, ]callback) requires a callback."
);
if (filename instanceof Function) {
callback = filename;
filename = undefined;
} else {
this.options.filename = path.resolve(filename);
}
this.exec(callback);
};
PDF.prototype.exec = function PdfExec(callback) {
var child = childprocess.spawn(
this.options.phantomPath,
[].concat(this.options.phantomArgs, [this.script]),
this.options.childProcessOptions
);
var stderr = [];
var timeout = setTimeout(function execTimeout() {
respond(
null,
new Error(
"html-pdf: PDF generation timeout. Phantom.js script did not exit."
)
);
}, this.options.timeout);
function onError(buffer) {
stderr.push(buffer);
}
function onData(buffer) {
var result;
try {
var json = buffer.toString().trim();
if (json) result = JSON.parse(json);
} catch (err) {
// Proxy for debugging purposes
process.stdout.write(buffer);
}
if (result) respond(null, null, result);
}
var callbacked = false;
function respond(code, err, data) {
if (callbacked) return;
callbacked = true;
clearTimeout(timeout);
// If we don't have an exit code, we kill the process, ignore stderr after this point
if (code === null) kill(child, onData, onError);
// Since code has a truthy/falsy value of either 0 or 1, check for existence first.
// Ignore if code has a value of 0 since that means PhantomJS has executed and exited successfully.
// Also, as per your script and standards, having a code value of 1 means one can always assume that
// an error occured.
if ((typeof code !== "undefined" && code !== null && code !== 0) || err) {
var error = null;
if (err) {
// Rudimentary checking if err is an instance of the Error class
error = err instanceof Error ? err : new Error(err);
} else {
// This is to catch the edge case of having a exit code value of 1 but having no error
error = new Error("html-pdf: Unknown Error");
}
// Append anything caught from the stderr
var postfix = stderr.length
? "\n" + Buffer.concat(stderr).toString()
: "";
if (postfix) error.message += postfix;
return callback(error);
}
callback(null, data);
}
child.stdout.on("data", onData);
child.stderr.on("data", onError);
child.on("error", function onError(err) {
respond(null, err);
});
// An exit event is most likely an error because we didn't get any data at this point
child.on("close", respond);
child.on("exit", respond);
var config = JSON.stringify({ html: this.html, options: this.options });
child.stdin.write(config + "\n", "utf8");
child.stdin.end();
};
function kill(child, onData, onError) {
child.stdin.end();
child.kill();
}