From f3de36d3ce7d6e15a9ce8eea177e2499d002ed40 Mon Sep 17 00:00:00 2001 From: dobon Date: Sun, 16 Sep 2018 23:00:33 -0700 Subject: [PATCH 1/2] Add support multi-source input and update spec. --- index.js | 146 ++++++++++++++++++++++++--------------- spec/wkhtmltopdf.spec.js | 16 +++++ 2 files changed, 106 insertions(+), 56 deletions(-) diff --git a/index.js b/index.js index 1ed9371..97c343e 100644 --- a/index.js +++ b/index.js @@ -2,6 +2,11 @@ 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']; +var tocArgs = ['disableDottedLines', 'tocHeaderText', 'tocLevelIndentation', 'disableTocLinks', 'tocTextSizeShrink', 'xslStyleSheet']; + 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 +16,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 = {}; @@ -22,73 +58,71 @@ function wkhtmltopdf(input, options, callback) { var output = options.output; delete options.output; - // make sure the special keys are last - var extraKeys = []; - var keys = Object.keys(options).filter(function(key) { - if (key === 'toc' || key === 'cover' || key === 'page') { - extraKeys.push(key); - return false; - } - - return true; - }).concat(extraKeys); - - // make sure toc specific args appear after toc arg - if (keys.indexOf('toc') >= 0) { - var tocArgs = ['disableDottedLines', 'tocHeaderText', 'tocLevelIndentation', 'disableTocLinks', 'tocTextSizeShrink', 'xslStyleSheet']; - var myTocArgs = []; - keys = keys.filter(function(key){ - if (tocArgs.find(function(tkey){ return tkey === key })) { - myTocArgs.push(key); - return false; - } - return true; - }); - var spliceArgs = [keys.indexOf('toc')+1, 0].concat(myTocArgs); - Array.prototype.splice.apply(keys, spliceArgs); - } - var args = [wkhtmltopdf.command]; if (!options.debug) { args.push('--quiet'); } + var isArray = Array.isArray(input); + // handle multi-page input + if (isArray) { + // add global options + Object.keys(options).forEach(function(key) { + if (globalArgs.indexOf(key) >= 0) { + args = args.concat(generateArgument(key, options[key])); + } + }); - 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)); + // add pages/covers/toc and options + input.forEach(function(page) { + // add input for page + args = args.concat(generateArgument(page.type || 'page', quote(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 { // add normal args - if (val !== false) { - args.push(key); + }); + } else { + // make sure the special keys are last + var extraKeys = []; + var keys = Object.keys(options).filter(function(key) { + if (key === 'toc' || key === 'cover' || key === 'page') { + extraKeys.push(key); + return false; } - if (typeof val !== 'boolean') { - args.push(quote(val)); - } + return true; + }).concat(extraKeys); + + // make sure toc specific args appear after toc arg + if (keys.indexOf('toc') >= 0) { + var myTocArgs = []; + keys = keys.filter(function(key){ + if (tocArgs.find(function(tkey){ return tkey === key })) { + myTocArgs.push(key); + return false; + } + return true; + }); + var spliceArgs = [keys.indexOf('toc')+1, 0].concat(myTocArgs); + Array.prototype.splice.apply(keys, spliceArgs); } - }); - var isUrl = /^(https?|file):\/\//.test(input); - args.push(isUrl ? quote(input) : '-'); // stdin if HTML given directly - args.push(output ? quote(output) : '-'); // stdout if no output file + keys.forEach(function(key) { + if (key === 'ignore' || key === 'debug' || key === 'debugStdOut') { // skip adding the ignore/debug keys + return false; + } + args = args.concat(generateArgument(key, options[key])); + }); + + var isUrl = /^(https?|file):\/\//.test(input); + args.push(isUrl ? quote(input) : '-'); // stdin if HTML given directly + } + + args.push(output ? quote(output) : '-'); // stdout if no output file // show the command that is being run if debug opion is passed if (options.debug && !(options instanceof Function)) { console.log('[node-wkhtmltopdf] [debug] [command] ' + args.join(' ')); @@ -185,7 +219,7 @@ function wkhtmltopdf(input, options, callback) { } // write input to stdin if it isn't a url - if (!isUrl) { + if (!isUrl && !isArray) { // Handle errors on the input stream (happens when command cannot run) child.stdin.on('error', handleError); if (isStream(input)) { diff --git a/spec/wkhtmltopdf.spec.js b/spec/wkhtmltopdf.spec.js index 46a74b7..3bfb525 100644 --- a/spec/wkhtmltopdf.spec.js +++ b/spec/wkhtmltopdf.spec.js @@ -110,4 +110,20 @@ describe('wkhtmltopdf', function() { }); }); + describe('when input is an array', function() { + it('use the list of urls as inputs and concatenate the results into a single pdf', function(done) { + var output = Fs.createWriteStream(resultPath('arraySourceSpec.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('arraySourceSpec.pdf', 'validFile.pdf'); + done(); + }) + }); + }); + }); From 5afa4fd6e2e69952b2331eca6d724d0a9355bec5 Mon Sep 17 00:00:00 2001 From: dobon Date: Sun, 16 Sep 2018 23:08:22 -0700 Subject: [PATCH 2/2] Update README.md to include multi-source input. --- README.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index a7f9cec..fea322c 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 a URL, an inline HTML string or an Array of 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 objects as the first argument. Each element of the array represents a single source for the resulting PDF, and must be 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