Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for multiple input with options (from PR #119) #140

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
90 changes: 56 additions & 34 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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') {
Expand All @@ -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 = {};
Expand All @@ -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;
Expand Down Expand Up @@ -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 {
Expand Down
31 changes: 31 additions & 0 deletions spec/wkhtmltopdf.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down