UNPKG

prince

Version:

Node API for executing XML/HTML to PDF renderer PrinceXML via prince(1) CLI

358 lines (329 loc) 12.5 kB
/* ** node-prince -- Node API for executing PrinceXML via prince(1) CLI ** Copyright (c) 2014-2026 Dr. Ralf S. Engelschall <rse@engelschall.com> ** ** Permission is hereby granted, free of charge, to any person obtaining ** a copy of this software and associated documentation files (the ** "Software"), to deal in the Software without restriction, including ** without limitation the rights to use, copy, modify, merge, publish, ** distribute, sublicense, and/or sell copies of the Software, and to ** permit persons to whom the Software is furnished to do so, subject to ** the following conditions: ** ** The above copyright notice and this permission notice shall be included ** in all copies or substantial portions of the Software. ** ** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, ** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF ** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. ** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY ** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, ** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE ** SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /* * prince-api.js: Node run-time API */ /* core requirements */ const child_process = require("child_process") const fs = require("fs") const path = require("path") /* extra requirements */ const promise = require("promise") const _ = require("lodash") /* the officially support options of prince(1) */ const princeOptions = { "help": false, "version": false, "credits": false, "verbose": false, "debug": false, "log": true, "no-warn-css": false, "no-warn-css-unknown": false, "no-warn-css-unsupported": false, "structured-log": true, "input": true, "input-list": true, "baseurl": true, "remap": true, "fileroot": true, "xinclude": false, "xml-external-entities": false, "no-local-files": false, "no-network": false, "auth-user": true, "auth-password": true, "auth-server": true, "auth-scheme": true, "auth-method": true, "auth": true, "no-auth-preemptive": false, "http-proxy": true, "http-timeout": true, "cookie": true, "cookiejar": true, "ssl-cacert": true, "ssl-capath": true, "ssl-cert": true, "ssl-cert-type": true, "ssl-key": true, "ssl-key-type": true, "ssl-key-password": true, "ssl-version": true, "insecure": false, "no-parallel-downloads": false, "javascript": false, "script": true, "style": true, "media": true, "page-size": true, "page-margin": true, "no-author-style": false, "no-default-style": false, "output": true, "pdf-profile": true, "pdf-xmp": true, "pdf-output-intent": true, "pdf-lang": true, "attach": true, "tagged-pdf": false, "no-artificial-fonts": false, "no-embed-fonts": false, "no-subset-fonts": false, "force-identity-encoding": false, "no-compress": false, "no-object-streams": false, "convert-colors": false, "fallback-cmyk-profile": true, "pdf-title": true, "pdf-subject": true, "pdf-author": true, "pdf-keywords": true, "pdf-creator": true, "encrypt": false, "key-bits": true, "user-password": true, "owner-password": true, "disallow-print": false, "disallow-copy": false, "disallow-annotate": false, "disallow-modify": false, "raster-output": true, "raster-format": true, "raster-pages": true, "raster-dpi": true, "raster-background": true, "raster-threads": true, "scanfonts": false, "control": false } /* API constructor */ function Prince (options) { /* optionally on-the-fly generate an instance */ if (!(this instanceof Prince)) return new Prince(options) /* create default configuration */ this.config = { binary: "prince", prefix: "", license: "", timeout: 10 * 1000, maxbuffer: 10 * 1024 * 1024, cwd: ".", option: {}, inputs: [], cookies: [], output: "" } /* override defaults with more reasonable information about environment */ const install = [ { basedir: "prince/lib/prince", binary: "bin/prince" }, { basedir: "prince", binary: "bin\\prince.exe" } ] let basedir let binary for (let i = 0; i < install.length; i++) { basedir = path.resolve(path.join(__dirname, install[i].basedir)) binary = path.join(basedir, install[i].binary) if (fs.existsSync(binary)) { this.binary(binary) this.prefix(basedir) break } } /* allow caller to override defaults */ if (typeof options === "object") { if (typeof options.binary !== "undefined") this.binary(options.binary) if (typeof options.prefix !== "undefined") this.prefix(options.prefix) if (typeof options.inputs !== "undefined") this.inputs(options.inputs) if (typeof options.cookies !== "undefined") this.cookies(options.cookies) if (typeof options.output !== "undefined") this.output(options.output) } return this } /* set path to CLI binary */ Prince.prototype.binary = function (binary) { if (arguments.length !== 1) throw new Error("Prince#binary: invalid number of arguments") this.config.binary = binary this.config.prefix = "" return this } /* set path to installation tree */ Prince.prototype.prefix = function (prefix) { if (arguments.length !== 1) throw new Error("Prince#prefix: invalid number of arguments") this.config.prefix = prefix return this } /* set path to license file */ Prince.prototype.license = function (filename) { if (arguments.length !== 1) throw new Error("Prince#license: invalid number of arguments") this.config.license = filename return this } /* set timeout for CLI execution */ Prince.prototype.timeout = function (timeout) { if (arguments.length !== 1) throw new Error("Prince#timeout: invalid number of arguments") this.config.timeout = timeout return this } /* set maximum stdout/stderr buffer for CLI execution */ Prince.prototype.maxbuffer = function (maxbuffer) { if (arguments.length !== 1) throw new Error("Prince#maxbuffer: invalid number of arguments") this.config.maxbuffer = maxbuffer return this } /* set current working directory for CLI execution */ Prince.prototype.cwd = function (cwd) { if (arguments.length !== 1) throw new Error("Prince#cwd: invalid number of arguments") this.config.cwd = cwd return this } /* set input file(s) */ Prince.prototype.inputs = function (inputs) { if (arguments.length !== 1) throw new Error("Prince#inputs: invalid number of arguments") this.config.inputs = Array.isArray(inputs) ? inputs : [ inputs ] return this } /* set cookie(s) */ Prince.prototype.cookies = function (cookies) { if (arguments.length !== 1) throw new Error("Prince#cookies: invalid number of arguments") this.config.cookies = Array.isArray(cookies) ? cookies : [ cookies ] return this } /* set output file */ Prince.prototype.output = function (output) { if (arguments.length !== 1) throw new Error("Prince#output: invalid number of arguments") this.config.output = output return this } /* set CLI options */ Prince.prototype.option = function (name, value, forced) { if (arguments.length < 1 || arguments.length > 3) throw new Error("Prince#option: invalid number of arguments") if (arguments.length < 2) value = true if (arguments.length < 3) forced = false if (!forced && typeof princeOptions[name] === "undefined") throw new Error(`Prince#option: invalid prince(1) option: "${name}" (but can be forced)`) if (!forced && princeOptions[name] === true && arguments.length === 1) throw new Error(`Prince#option: prince(1) option "${name}" required argument`) this.config.option[name] = value return this } /* execute the CLI binary */ Prince.prototype._execute = function (method, args) { /* determine path to prince(1) binary */ let prog = this.config.binary if (!fs.existsSync(prog)) { const findInPath = function (name) { const p = process.env.PATH.split(path.delimiter).map(function(item) { return path.join(item, name) }) for (let i = 0, len = p.length; i < len; i++) if (fs.existsSync(p[i])) return p[i] return undefined } prog = findInPath(prog) if (typeof prog === "undefined") throw new Error(`Prince#${method}: cannot resolve binary "${this.config.binary}" to a filesystem path`) } /* return promise for executing CLI */ const self = this return new promise(function (resolve, reject) { try { const options = {} options.timeout = self.config.timeout options.maxBuffer = self.config.maxbuffer options.cwd = self.config.cwd options.encoding = "buffer" child_process.execFile(prog, args, options, function (error, stdout, stderr) { const m = stderr.toString().match(/prince:\s+error:\s+([^\n]+)/) if (error === null && m) reject({ error: m[1], stdout: stdout, stderr: stderr }) else if (error !== null) reject({ error: error, stdout: stdout, stderr: stderr }) else resolve({ stdout: stdout, stderr: stderr }) } ) } catch (exception) { reject({ error: exception, stdout: "", stderr: "" }) } }) } /* execute the CLI binary */ Prince.prototype.execute = function () { /* determine arguments to prince(1) binary */ const args = [] if (this.config.prefix !== "") { args.push("--prefix") args.push(this.config.prefix) } if (this.config.license !== "") { args.push("--license-file") args.push(this.config.license) } _.forOwn(this.config.option, function (value, name) { args.push(`--${name}`) if (value !== true) args.push(value) }) this.config.inputs.forEach(function (input) { args.push(input) }) /* supported since Prince 10 */ this.config.cookies.forEach(function (cookie) { args.push("--cookie") args.push(cookie) }) /* required from Prince 11 on, supported since Prince 7 */ if (this.config.output !== "") { args.push("--output") args.push(this.config.output) } else if (this.config.option["raster-output"] === undefined) throw new Error("Prince#execute: require either \"output\" or \"raster-output\" options") /* return promise for executing CLI */ return this._execute("execute", args) } /* export API constructor */ module.exports = Prince