diff --git a/README.md b/README.md index a7f9cec..3016c74 100644 --- a/README.md +++ b/README.md @@ -66,9 +66,21 @@ wkhtmltopdf('http://apple.com/', { }); ``` -`wkhtmltopdf` is just a function, which you call with either a URL or an inline HTML string, and it returns +`wkhtmltopdf` is just a function, which you call with either a URL, an inline HTML string or an Array of URL/objects (see [Multi-Source-Input](#multi-source-input)), and it returns a stream that you can read from or pipe to wherever you like (e.g. a file, or an HTTP response). +### Multi-Source-input + +`wkhtmltopdf` supports the ability to construct a PDF from several source documents, and can even generate a table-of-contents based on an outline inferred from the source HTML structure. To combine several documents into a single PDF, pass an Array of URL or objects as the first argument. Each element of the array represents a single source for the resulting PDF, and must be a valid URL or an object conforming to the following structure: + +``` + { + source: STRING, // URL to source. Omit for type 'toc'. + type: STRING, // 'page', 'toc' or 'cover'. Default: 'page' + options: {...} // Page-specific options using same format as global options. Default: {} + } +``` + ## Options There are [many options](http://wkhtmltopdf.org/docs.html) available to diff --git a/index.js b/index.js index e1471be..95b9a3f 100644 --- a/index.js +++ b/index.js @@ -2,6 +2,10 @@ var spawn = require('child_process').spawn; var slang = require('slang'); var isStream = require('is-stream'); +var globalArgs = ['collate', 'noCollate', 'cookieJar', 'copies', 'dpi', 'extendedHelp', 'grayscale', 'help', 'htmldoc', 'imageDpi', 'imageQuality', 'license', 'logLevel', 'lowquality', + 'manpage', 'marginBottom', 'marginLeft', 'marginRight', 'marginTop', 'orientation', 'pageHeight', 'pageSize', 'pageWidth', 'noPdfCompression', 'quiet', 'readArgsFromStdin', 'readme', + 'title', 'useXserver', 'version']; + function quote(val) { // escape and quote the value if it is a string and this isn't windows if (typeof val === 'string' && process.platform !== 'win32') { @@ -11,6 +15,37 @@ function quote(val) { return val; } +function generateArgument(key, val) { + var args = []; + + if (key !== 'toc' && key !== 'cover' && key !== 'page') { + key = key.length === 1 ? '-' + key : '--' + slang.dasherize(key); + } + + if (Array.isArray(val)) { // add repeatable args + val.forEach(function(valueStr) { + args.push(key); + if (Array.isArray(valueStr)) { // if repeatable args has key/value pair + valueStr.forEach(function(keyOrValueStr) { + args.push(quote(keyOrValueStr)); + }); + } else { + args.push(quote(valueStr)); + } + }); + } else { // add normal args + if (val !== false) { + args.push(key); + } + + if (typeof val !== 'boolean') { + args.push(quote(val)); + } + } + + return args; +} + function wkhtmltopdf(input, options, callback) { if (!options) { options = {}; @@ -27,6 +62,10 @@ function wkhtmltopdf(input, options, callback) { // make sure the special keys are last var extraKeys = []; var keys = Object.keys(options).filter(function(key) { + if (key === 'ignore' || key === 'debug' || key === 'debugStdOut') { // skip adding the ignore/debug keys + return false; + } + if (key === 'toc' || key === 'cover' || key === 'page') { extraKeys.push(key); return false; @@ -56,46 +95,29 @@ function wkhtmltopdf(input, options, callback) { } keys.forEach(function(key) { - var val = options[key]; - if (key === 'ignore' || key === 'debug' || key === 'debugStdOut') { // skip adding the ignore/debug keys - return false; - } - - if (key !== 'toc' && key !== 'cover' && key !== 'page') { - key = key.length === 1 ? '-' + key : '--' + slang.dasherize(key); - } - - if (Array.isArray(val)) { // add repeatable args - val.forEach(function(valueStr) { - args.push(key); - if (Array.isArray(valueStr)) { // if repeatable args has key/value pair - valueStr.forEach(function(keyOrValueStr) { - args.push(quote(keyOrValueStr)); - }); - } else { - args.push(quote(valueStr)); - } - }); - } else { // add normal args - if (val !== false) { - args.push(key); - } - - if (typeof val !== 'boolean') { - args.push(quote(val)); - } - } + args = args.concat(generateArgument(key, options[key])); }); // Input var isArray = Array.isArray(input); if (isArray) { - input.forEach(function(element) { - var isUrl = /^(https?|file):\/\//.test(element); - if (isUrl) { - args.push(quote(element)); + input.forEach(function(page) { + if (typeof page === 'object') { + args = args.concat(generateArgument(page.type || 'page', page.source || true)); + // add per-page options + var opts = page.options || {}; + Object.keys(opts).forEach(function(key) { + if (globalArgs.indexOf(key) < 0) { + args = args.concat(generateArgument(key, opts[key])); + } + }); } else { - console.log('[node-wkhtmltopdf] [warn] Multi PDF only supported for URL files (http[s]:// or file://)') + var isUrl = /^(https?|file):\/\//.test(page); + if (isUrl) { + args.push(quote(page)); + } else { + console.log('[node-wkhtmltopdf] [warn] Multi PDF only supported for URL files (http[s]:// or file://)') + } } }) } else { diff --git a/spec/wkhtmltopdf.spec.js b/spec/wkhtmltopdf.spec.js index 4e3656b..368e5f8 100644 --- a/spec/wkhtmltopdf.spec.js +++ b/spec/wkhtmltopdf.spec.js @@ -96,6 +96,37 @@ describe('wkhtmltopdf', function() { }); }); + describe('when input is an array of strings', function() { + it('should use the list of URL as inputs and concatenate the results into a single pdf', function(done) { + var output = Fs.createWriteStream(resultPath('stringArraySourceSpec.pdf')); + Wkhtmltopdf([ + fixtureFileUri('validFile.html'), + fixtureFileUri('validFile.html'), + fixtureFileUri('validFile.html') + ]).pipe(output); + output.on('finish', function() { + checkResults('stringArraySourceSpec.pdf', 'validFile.pdf'); + done(); + }) + }); + }); + + describe('when input is an array of objects', function() { + it('should use the list of object source as inputs and concatenate the results into a single pdf', function(done) { + var output = Fs.createWriteStream(resultPath('objectArraySourceSpec.pdf')); + Wkhtmltopdf([ + {source: fixtureFileUri('validFile.html'), options: {defaultHeader: true}}, + {source: fixtureFileUri('validFile.html'), type: 'page'}, + {source: fixtureFileUri('validFile.html'), type: 'cover'}, + {type: 'toc', options: {defaultHeader: true}} + ], {orientation: 'Landscape'}).pipe(output); + output.on('finish', function() { + checkResults('objectArraySourceSpec.pdf', 'validFile.pdf'); + done(); + }) + }); + }); + describe('when callback is used', function() { it('should return a readable stream', function(done) { Wkhtmltopdf(Fs.createReadStream(fixturePath('validFile.html')), function(err, stream) {