Skip to content
Merged
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
32 changes: 17 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ A JavaScript PDF generation library for Node and the browser.

## Description

PDFKit is a PDF document generation library for Node and the browser that makes creating complex, multi-page, printable documents easy.
It's written in CoffeeScript, but you can choose to use the API in plain 'ol JavaScript if you like. The API embraces
chainability, and includes both low level functions as well as abstractions for higher level functionality. The PDFKit API
PDFKit is a PDF document generation library for Node and the browser that makes creating complex, multi-page, printable documents easy.
It's written in CoffeeScript, but you can choose to use the API in plain 'ol JavaScript if you like. The API embraces
chainability, and includes both low level functions as well as abstractions for higher level functionality. The PDFKit API
is designed to be simple, so generating complex documents is often as simple as a few function calls.

Check out some of the [documentation and examples](http://pdfkit.org/docs/getting_started.html) to see for yourself!
Expand Down Expand Up @@ -49,15 +49,17 @@ Installation uses the [npm](http://npmjs.org/) package manager. Just type the f
* Underlines
* etc.
* Outlines

* PDF security
* Encryption
* Access privileges (printing, copying, modifying, annotating, form filling, content accessibility, document assembly)

## Coming soon!

* Patterns fills
* PDF Security
* Higher level APIs for creating tables and laying out content
* More performance optimizations
* Even more awesomeness, perhaps written by you! Please fork this repository and send me pull requests.

## Example

```coffeescript
Expand Down Expand Up @@ -111,9 +113,9 @@ doc.addPage()
# Finalize PDF file
doc.end()
```
[The PDF output from this example](http://pdfkit.org/demo/out.pdf) (with a few additions) shows the power of PDFKit — producing
complex documents with a very small amount of code. For more, see the `demo` folder and the

[The PDF output from this example](http://pdfkit.org/demo/out.pdf) (with a few additions) shows the power of PDFKit — producing
complex documents with a very small amount of code. For more, see the `demo` folder and the
[PDFKit programming guide](http://pdfkit.org/docs/getting_started.html).

## Browser Usage
Expand All @@ -122,13 +124,13 @@ There are two ways to use PDFKit in the browser. The first is to use [Browserif
which is a Node module packager for the browser with the familiar `require` syntax. The second is to use
a prebuilt version of PDFKit, which you can [download from Github](https://github.com/devongovett/pdfkit/releases).

In addition to PDFKit, you'll need somewhere to stream the output to. HTML5 has a
In addition to PDFKit, you'll need somewhere to stream the output to. HTML5 has a
[Blob](https://developer.mozilla.org/en-US/docs/Web/API/Blob) object which can be used to store binary data, and
get URLs to this data in order to display PDF output inside an iframe, or upload to a server, etc. In order to
get URLs to this data in order to display PDF output inside an iframe, or upload to a server, etc. In order to
get a Blob from the output of PDFKit, you can use the [blob-stream](https://github.com/devongovett/blob-stream)
module.

The following example uses Browserify to load `PDFKit` and `blob-stream`, but if you're not using Browserify,
The following example uses Browserify to load `PDFKit` and `blob-stream`, but if you're not using Browserify,
you can load them in whatever way you'd like (e.g. script tags).

```coffeescript
Expand Down Expand Up @@ -157,9 +159,9 @@ stream.on 'finish', ->

You can see an interactive in-browser demo of PDFKit [here](http://pdfkit.org/demo/browser.html).

Note that in order to Browserify a project using PDFKit, you need to install the `brfs` module with npm,
which is used to load built-in font data into the package. It is listed as a `devDependency` in
PDFKit's `package.json`, so it isn't installed by default for Node users.
Note that in order to Browserify a project using PDFKit, you need to install the `brfs` module with npm,
which is used to load built-in font data into the package. It is listed as a `devDependency` in
PDFKit's `package.json`, so it isn't installed by default for Node users.
If you forget to install it, Browserify will print an error message.

## Documentation
Expand Down
65 changes: 65 additions & 0 deletions docs/getting_started.coffee.md
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,71 @@ capitalized.
* `CreationDate` - the date the document was created (added automatically by PDFKit)
* `ModDate` - the date the document was last modified

## Encryption and Access Privileges

PDF specification allow you to encrypt the PDF file and require a password when opening the file,
and/or set permissions of what users can do with the PDF file. PDFKit implements standard security
handler in PDF version 1.3 (40-bit RC4), version 1.4 (128-bit RC4), PDF version 1.7 (128-bit AES),
and PDF version 1.7 ExtensionLevel 3 (256-bit AES).

To enable encryption, provide a user password when creating the `PDFDocument` in `options` object.
The PDF file will be encrypted when a user password is provided, and users will be prompted to enter
the password to decrypt the file when opening it.

* `userPassword` - the user password (string value)

To set access privileges for the PDF file, you need to provide an owner password and permission
settings in the `option` object when creating `PDFDocument`. By default, all operations are disallowed.
You need to explicitly allow certain operations.

* `ownerPassword` - the owner password (string value)
* `permissions` - the object specifying PDF file permissions

Following settings are allowed in `permissions` object:

* `printing` - whether printing is allowed. Specify `"lowResolution"` to allow degraded printing, or `"highResolution"` to allow printing with high resolution
* `modifying` - whether modifying the file is allowed. Specify `true` to allow modifying document content
* `copying` - whether copying text or graphics is allowed. Specify `true` to allow copying
* `annotating` - whether annotating, form filling is allowed. Specify `true` to allow annotating and form filling
* `fillingForms` - whether form filling and signing is allowed. Specify `true` to allow filling in form fields and signing
* `contentAccessibility` - whether copying text for accessibility is allowed. Specify `true` to allow copying for accessibility
* `documentAssembly` - whether assembling document is allowed. Specify `true` to allow document assembly

You can specify either user password, owner password or both passwords.
Behavior differs according to passwords you provides:

* When only user password is provided,
users with user password are able to decrypt the file and have full access to the document.
* When only owner password is provided,
users are able to decrypt and open the document without providing any password,
but the access is limited to those operations explicitly permitted.
Users with owner password have full access to the document.
* When both passwords are provided,
users with user password are able to decrypt the file
but only have limited access to the file according to permission settings.
Users with owner password have full access to the document.

Note that PDF file itself cannot enforce access privileges.
When file is decrypted, PDF viewer applications have full access to the file content,
and it is up to viewer applications to respect permission settings.

To choose encryption method, you need to specify PDF version.
PDFKit will choose best encryption method available in the PDF version you specified.

* `pdfVersion` - a string value specifying PDF file version

Available options includes:

* `1.3` - PDF version 1.3 (default), 40-bit RC4 is used
* `1.4` - PDF version 1.4, 128-bit RC4 is used
* `1.5` - PDF version 1.5, 128-bit RC4 is used
* `1.6` - PDF version 1.6, 128-bit AES is used
* `1.7` - PDF version 1.7, 128-bit AES is used
* `1.7ext3` - PDF version 1.7 ExtensionLevel 3, 256-bit AES is used

When using PDF version 1.7 ExtensionLevel 3, password is truncated to 127 bytes of its UTF-8 representation.
In older versions, password is truncated to 32 bytes, and only Latin-1 characters are allowed.

### Adding content

Once you've created a `PDFDocument` instance, you can add content to the
Expand Down
54 changes: 45 additions & 9 deletions lib/document.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import fs from 'fs';
import PDFObject from './object';
import PDFReference from './reference';
import PDFPage from './page';
import PDFSecurity from './security';
import ColorMixin from './mixins/color';
import VectorMixin from './mixins/vector';
import FontsMixin from './mixins/fonts';
Expand All @@ -22,7 +23,24 @@ class PDFDocument extends stream.Readable {
this.options = options;

// PDF version
this.version = 1.3;
switch (options.pdfVersion) {
case '1.4':
this.version = 1.4;
break;
case '1.5':
this.version = 1.5;
break;
case '1.6':
this.version = 1.6;
break;
case '1.7':
case '1.7ext3':
this.version = 1.7;
break;
default:
this.version = 1.3;
break;
}

// Whether streams should be compressed
this.compress = this.options.compress != null ? this.options.compress : true;
Expand Down Expand Up @@ -82,6 +100,12 @@ class PDFDocument extends stream.Readable {
}
}

// Generate file ID
this._id = PDFSecurity.generateFileID(this.info);

// Initialize security settings
this._security = PDFSecurity.create(this, options);

// Write the header
// PDF version
this._write(`%PDF-${this.version}`);
Expand Down Expand Up @@ -213,7 +237,10 @@ Please pipe the document into a Node stream.\
val = new String(val);
}

this._info.data[key] = val;
let entry = this.ref(val);
entry.end();

this._info.data[key] = entry;
}

this._info.end();
Expand All @@ -224,10 +251,14 @@ Please pipe the document into a Node stream.\
}

this.endOutline();

this._root.end();
this._root.data.Pages.end();

if (this._security) {
this._security.end();
}

if (this._waiting === 0) {
return this._finalize();
} else {
Expand All @@ -248,13 +279,18 @@ Please pipe the document into a Node stream.\
}

// trailer
this._write('trailer');
this._write(PDFObject.convert({
const trailer = {
Size: this._offsets.length + 1,
Root: this._root,
Info: this._info
})
);
Info: this._info,
ID: [this._id, this._id]
};
if (this._security) {
trailer.Encrypt = this._security.dictionary;
}

this._write('trailer');
this._write(PDFObject.convert(trailer));

this._write('startxref');
this._write(`${xRefOffset}`);
Expand All @@ -270,7 +306,7 @@ Please pipe the document into a Node stream.\
};

const mixin = methods => {
Object.assign(PDFDocument.prototype, methods);
Object.assign(PDFDocument.prototype, methods);
};

mixin(ColorMixin);
Expand Down
43 changes: 31 additions & 12 deletions lib/object.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ By Devon Govett
import PDFAbstractReference from './abstract_reference';

const pad = (str, length) => (Array(length + 1).join('0') + str).slice(-length);

const escapableRe = /[\n\r\t\b\f\(\)\\]/g;
const escapable = {
'\n': '\\n',
Expand Down Expand Up @@ -36,7 +36,7 @@ const swapBytes = function(buff) {
};

class PDFObject {
static convert(object) {
static convert(object, encryptFn = null) {
// String literals are converted to the PDF name type
if (typeof object === 'string') {
return `/${object}`;
Expand All @@ -54,8 +54,18 @@ class PDFObject {
}

// If so, encode it as big endian UTF-16
let stringBuffer;
if (isUnicode) {
string = swapBytes(new Buffer(`\ufeff${string}`, 'utf16le')).toString('binary');
stringBuffer = swapBytes(new Buffer(`\ufeff${string}`, 'utf16le'));
} else {
stringBuffer = new Buffer(string, 'ascii');
}

// Encrypt the string when necessary
if (encryptFn) {
string = encryptFn(stringBuffer).toString('binary');
} else {
string = stringBuffer.toString('binary');
}

// Escape characters as required by the spec
Expand All @@ -71,23 +81,32 @@ class PDFObject {
return object.toString();

} else if (object instanceof Date) {
return `(D:${pad(object.getUTCFullYear(), 4)}` +
pad(object.getUTCMonth() + 1, 2) +
pad(object.getUTCDate(), 2) +
pad(object.getUTCHours(), 2) +
pad(object.getUTCMinutes(), 2) +
pad(object.getUTCSeconds(), 2) +
'Z)';
let string = `D:${pad(object.getUTCFullYear(), 4)}` +
pad(object.getUTCMonth() + 1, 2) +
pad(object.getUTCDate(), 2) +
pad(object.getUTCHours(), 2) +
pad(object.getUTCMinutes(), 2) +
pad(object.getUTCSeconds(), 2) + 'Z';

// Encrypt the string when necessary
if (encryptFn) {
string = encryptFn(new Buffer(string, 'ascii')).toString('binary');

// Escape characters as required by the spec
string = string.replace(escapableRe, c => escapable[c]);
}

return `(${string})`;

} else if (Array.isArray(object)) {
const items = (object.map((e) => PDFObject.convert(e))).join(' ');
const items = (object.map((e) => PDFObject.convert(e, encryptFn))).join(' ');
return `[${items}]`;

} else if ({}.toString.call(object) === '[object Object]') {
const out = ['<<'];
for (let key in object) {
const val = object[key];
out.push(`/${key} ${PDFObject.convert(val)}`);
out.push(`/${key} ${PDFObject.convert(val, encryptFn)}`);
}

out.push('>>');
Expand Down
18 changes: 14 additions & 4 deletions lib/reference.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import PDFObject from './object';

class PDFReference extends PDFAbstractReference {
constructor(document, id, data) {
super();
super();
this.document = document;
this.id = id;
if (data == null) { data = {}; }
Expand Down Expand Up @@ -45,15 +45,25 @@ class PDFReference extends PDFAbstractReference {
return setTimeout(() => {
this.offset = this.document._offset;

this.document._write(`${this.id} ${this.gen} obj`);
this.document._write(PDFObject.convert(this.data));
const encryptFn = this.document._security ? this.document._security.getEncryptFn(this.id, this.gen) : null;

if (this.buffer.length) {
this.buffer = Buffer.concat(this.buffer);
if (this.compress) {
this.buffer = zlib.deflateSync(this.buffer);
this.data.Length = this.buffer.length;
}

if (encryptFn) {
this.buffer = encryptFn(this.buffer);
}

this.data.Length = this.buffer.length;
}

this.document._write(`${this.id} ${this.gen} obj`);
this.document._write(PDFObject.convert(this.data, encryptFn));

if (this.buffer.length) {
this.document._write('stream');
this.document._write(this.buffer);

Expand Down
Loading