Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
Hopding committed Sep 4, 2017
0 parents commit fd7459e
Show file tree
Hide file tree
Showing 15 changed files with 1,897 additions and 0 deletions.
4 changes: 4 additions & 0 deletions .babelrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"presets": ["es2015"],
"plugins": ["transform-class-properties"]
}
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Dependencies
node_modules

# Error logs
yarn-error.log
103 changes: 103 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import {
PDFArrayObject,
PDFDictionaryObject,
PDFNameObject,
PDFIndirectObject,
PDFStreamObject,
} from './src/PDFObjects';

const outlinesObj = new PDFIndirectObject(2, 0, {
'Type': new PDFNameObject('Outlines'),
'Count': 0,
});

const pagesObj = new PDFIndirectObject(3, 0);

const contentsObj = new PDFStreamObject(5, 0, {
'Length': 73,
},
`
BT
/F1 24 Tf
100 100 Td
(Hello World) Tj
ET
`);

const procSetObj = new PDFIndirectObject(6, 0, [
new PDFNameObject('PDF'),
new PDFNameObject('Text'),
]);

const fontObj = new PDFIndirectObject(7, 0, {
'Type': new PDFNameObject('Font'),
'Subtype': new PDFNameObject('Type1'),
'Name': new PDFNameObject('F1'),
'BaseFont': new PDFNameObject('Helvetica'),
'Encoding': new PDFNameObject('MacRomanEncoding'),
});

const pageObj = new PDFIndirectObject(4, 0, {
'Type': new PDFNameObject('Page'),
'Parent': pagesObj,
'MediaBox': new PDFArrayObject([0, 0, 612, 792]),
'Contents': contentsObj,
'Resources': new PDFDictionaryObject({
'ProcSet': procSetObj,
'Font': new PDFDictionaryObject({ 'F1': fontObj }),
}),
});

pagesObj.setContent({
'Type': new PDFNameObject('Pages'),
'Kids': new PDFArrayObject([pageObj.toIndirectRef()]),
'Count': 1,
});

const catalogObj = new PDFIndirectObject(1, 0, {
'Type': new PDFNameObject('Catalog'),
'Outlines': outlinesObj,
'Pages': pagesObj,
});

const pdfStr =
`
%PDF-1.7
${catalogObj}
${outlinesObj}
${pagesObj}
${pageObj}
${contentsObj}
${procSetObj}
${fontObj}
xref
0 7
0000000000 65535 f
0000000009 00000 n
0000000074 00000 n
0000000120 00000 n
0000000179 00000 n
0000000364 00000 n
0000000466 00000 n
0000000496 00000 n
trailer
${new PDFDictionaryObject({
'Size': 8,
'Root': catalogObj,
})}
startxref
625
%%EOF
`;

console.log(pdfStr);
26 changes: 26 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"name": "pdf-lib",
"version": "0.0.0",
"description": "Library for creating and editing PDF files in JavaScript",
"main": "index.js",
"scripts": {
"test": "babel-node index.js",
"build": "babel . -d dist --ignore=\"node_modules\""
},
"repository": {
"type": "git",
"url": "git+https://github.com/Hopding/pdf-lib.git"
},
"author": "Andrew Dillon",
"license": "MIT",
"bugs": {
"url": "https://github.com/Hopding/pdf-lib/issues"
},
"homepage": "https://github.com/Hopding/pdf-lib#readme",
"devDependencies": {
"babel-cli": "^6.26.0",
"babel-core": "^6.26.0",
"babel-plugin-transform-class-properties": "^6.24.1",
"babel-preset-es2015": "^6.24.1"
}
}
Empty file added src/PDFDocument.js
Empty file.
24 changes: 24 additions & 0 deletions src/PDFObjects/PDFArrayObject.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { charCode, isString, PDFString } from '../utils';

/*
Represents a PDF Array Object.
From PDF 1.7 Specification, "7.3.6 Array Objects"
(http://www.adobe.com/content/dam/Adobe/en/devnet/acrobat/pdfs/PDF32000_2008.pdf):
An array object is a one-dimensional collection of objects arranged sequentially. Unlike arrays in many other computer languages, PDF arrays may be heterogeneous; that is, an array’s elements may be any combination of numbers, strings, dictionaries, or any other objects, including other arrays. An array may have zero elements.
An array shall be written as a sequence of objects enclosed in SQUARE BRACKETS (using LEFT SQUARE BRACKET (5Bh) and RIGHT SQUARE BRACKET (5Dh)).
EXAMPLE:
[549 3.14 false (Ralph) /SomeName]
PDF directly supports only one-dimensional arrays. Arrays of higher dimension can be constructed by using arrays as elements of arrays, nested to any depth.
*/
export default class PDFArrayObject {
constructor(array) {
this.array = array;
}

toString = () =>
`[${this.array.map(String).join(' ')}]`;
}
57 changes: 57 additions & 0 deletions src/PDFObjects/PDFDictionaryObject.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { charCode, isString, PDFString } from '../utils';
import PDFNameObject from './PDFNameObject';

/*
Represents a PDF Dictionary Object.
From PDF 1.7 Specification, "7.3.7 Dictionary Objects"
(http://www.adobe.com/content/dam/Adobe/en/devnet/acrobat/pdfs/PDF32000_2008.pdf):
A dictionary object is an associative table containing pairs of objects, known as the dictionary’s entries. The first element of each entry is the key and the second element is the value. The key shall be a name (unlike dictionary keys in PostScript, which may be objects of any type). The value may be any kind of object, including another dictionary. A dictionary entry whose value is null (see 7.3.9, "Null Object") shall be treated the same as if the entry does not exist. (This differs from PostScript, where null behaves like any other object as the value of a dictionary entry.) The number of entries in a dictionary shall be subject to an implementation limit; see Annex C. A dictionary may have zero entries.
The entries in a dictionary represent an associative table and as such shall be unordered even though an arbitrary order may be imposed upon them when written in a file. That ordering shall be ignored.
Multiple entries in the same dictionary shall not have the same key.
A dictionary shall be written as a sequence of key-value pairs enclosed in double angle brackets (<<...>>) (using LESS-THAN SIGNs (3Ch) and GREATER-THAN SIGNs (3Eh)).
EXAMPLE:
<< /Type /Example
/Subtype /DictionaryExample
/Version 0.01
/IntegerItem 12
/StringItem (a string)
/Subdictionary << /Item1 0.4
/Item2 true
/LastItem (not!)
/VeryLastItem (OK)
>>
>>
*/
export default class PDFDictionaryObject {
/**
* `object` should be an object literal whose keys are strings, and
* whose values are one of:
* PDFDictionaryObject
* | PDFArrayObject
* | PDFNameObject
* | PDFIndirectObject
* | PDFStreamObject
* The string keys should not contain leading slashes. The keys will be
* converted to valid PDFNameObjects when the `toString()` method is called.
*/
constructor(object) {
this.object = object;
}

toString = () =>
Object.keys(this.object).reduce(
(dict, key) =>
dict.concat(new PDFNameObject(key))
.concat(' ')
.concat(
isString(this.object[key]) ? PDFString(this.object[key])
: this.object[key].isPDFIndirectObject ? this.object[key].toIndirectRef()
: this.object[key])
.concat('\n'),
'<<\n',
) + '>>';
}
56 changes: 56 additions & 0 deletions src/PDFObjects/PDFIndirectObject.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { isInt, isString, isObject } from '../utils';
import PDFDictionaryObject from './PDFDictionaryObject';
import PDFArrayObject from './PDFArrayObject';

/*
Represents a PDF Indirect Object.
From PDF 1.7 Specification, "7.3.10 Indirect Objects"
(http://www.adobe.com/content/dam/Adobe/en/devnet/acrobat/pdfs/PDF32000_2008.pdf):
Any object in a PDF file may be labelled as an indirect object. This gives the object a unique object identifier by which other objects can refer to it (for example, as an element of an array or as the value of a dictionary entry). The object identifier shall consist of two parts:
• A positive integer object number. Indirect objects may be numbered sequentially within a PDF file, but this is not required; object numbers may be assigned in any arbitrary order.
• A non-negative integer generation number. In a newly created file, all indirect objects shall have generation numbers of 0. Nonzero generation numbers may be introduced when the file is later updated; see sub- clauses 7.5.4, "Cross-Reference Table" and 7.5.6, "Incremental Updates."
Together, the combination of an object number and a generation number shall uniquely identify an indirect object.
The definition of an indirect object in a PDF file shall consist of its object number and generation number (separated by white space), followed by the value of the object bracketed between the keywords obj and endobj.
EXAMPLE Indirect object definition:
12 0 obj
( Brillig )
endobj
Defines an indirect string object with an object number of 12, a generation number of 0, and the value Brillig.
The object may be referred to from elsewhere in the file by an indirect reference. Such indirect references shall consist of the object number, the generation number, and the keyword R (with white space separating each part):
12 0 R
*/
export default class PDFIndirectObject {
isPDFIndirectObject = true;

constructor(objectNum, generationNum, content) {
if (!isInt(objectNum) && objectNum >= 0) throw new Error(
'PDF Indirect Objects must have integer object numbers >= 0'
);
if (!isInt(generationNum) && generationNum >= 0) throw new Error(
'PDF Indirect Objects must have integer generation numbers >= 0'
);
this.objectNum = objectNum;
this.generationNum = generationNum;
this.content = content;
console.log(Object.prototype.toString.call(this.content));
}

setContent = (content) => { this.content = content; }

toIndirectRef = () =>
`${this.objectNum} ${this.generationNum} R`;

toString = () =>
`${this.objectNum} ${this.generationNum} obj\n` +
`${isObject(this.content) ? new PDFDictionaryObject(this.content)
: Array.isArray(this.content) ? new PDFArrayObject(this.content)
: this.content
}\n` +
`endobj`;
}
36 changes: 36 additions & 0 deletions src/PDFObjects/PDFNameObject.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { charCode } from '../utils';
/*
Represents a PDF Name Object.
From PDF 1.7 Specification, "7.3.5 Name Objects"
(http://www.adobe.com/content/dam/Adobe/en/devnet/acrobat/pdfs/PDF32000_2008.pdf):
When writing a name in a PDF file, a SOLIDUS (2Fh) (/) shall be used to introduce a name. The SOLIDUS is not part of the name but is a prefix indicating that what follows is a sequence of characters representing the name in the PDF file and shall follow these rules:
a) A NUMBER SIGN (23h) (#) in a name shall be written by using its 2-digit hexadecimal code (23), preceded by the NUMBER SIGN.
b) Any character in a name that is a regular character (other than NUMBER SIGN) shall be written as itself or by using its 2-digit hexadecimal code, preceded by the NUMBER SIGN.
c) Any character that is not a regular character shall be written using its 2-digit hexadecimal code, preceded by the NUMBER SIGN only.
White space used as part of a name shall always be coded using the 2-digit hexadecimal notation and no white space may intervene between the SOLIDUS and the encoded name.
Regular characters that are outside the range EXCLAMATION MARK(21h) (!) to TILDE (7Eh) (~) should be written using the hexadecimal notation.
*/
export default class PDFNameObject {
static isRegularChar = (char) => (
charCode(char) >= charCode('!') && charCode(char) <= charCode('~')
);

constructor(keyStr) {
if (keyStr.charAt(0) === ' ') throw new Error(
'PDF Name objects may not begin with a space character.'
);
this.key = ('/' + keyStr)
.replace('#', '#23')
.split('')
.map(char =>
PDFNameObject.isRegularChar(char) ? char : `#${charCode(char).toString(16)}`
)
.join('');
}

toString = () => this.key;
}
30 changes: 30 additions & 0 deletions src/PDFObjects/PDFStreamObject.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import PDFIndirectObject from './PDFIndirectObject';
import PDFDictionaryObject from './PDFDictionaryObject';

/*
Represents a PDF Stream Object.
From PDF 1.7 Specification, "7.3.8 Stream Objects"
(http://www.adobe.com/content/dam/Adobe/en/devnet/acrobat/pdfs/PDF32000_2008.pdf):
A stream object, like a string object, is a sequence of bytes. Furthermore, a stream may be of unlimited length, whereas a string shall be subject to an implementation limit. For this reason, objects with potentially large amounts of data, such as images and page descriptions, shall be represented as streams.
A stream shall consist of a dictionary followed by zero or more bytes bracketed between the keywords stream (followed by newline) and endstream:
EXAMPLE:
dictionary
stream
...Zero or more bytes...
endstream
All streams shall be indirect objects (see 7.3.10, "Indirect Objects") and the stream dictionary shall be a direct object. The keyword stream that follows the stream dictionary shall be followed by an end-of-line marker consisting of either a CARRIAGE RETURN and a LINE FEED or just a LINE FEED, and not by a CARRIAGE RETURN alone. The sequence of bytes that make up a stream lie between the end-of-line marker following the stream keyword and the endstream keyword; the stream dictionary specifies the exact number of bytes. There should be an end-of-line marker after the data and before endstream; this marker shall not be included in the stream length. There shall not be any extra bytes, other than white space, between endstream and endobj.
*/
export default class PDFStreamObject extends PDFIndirectObject {
isPDFStreamObject = true;

constructor(objectNum, generationNum, dictionary, streamContent) {
super(
objectNum,
generationNum,
`${new PDFDictionaryObject(dictionary)}\nstream\n${streamContent}\nendstream`,
);
}
}
12 changes: 12 additions & 0 deletions src/PDFObjects/PDFString.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/*
Creates a valid PDF String Object.
From PDF 1.7 Specification, "7.3.4 String Objects"
(http://www.adobe.com/content/dam/Adobe/en/devnet/acrobat/pdfs/PDF32000_2008.pdf):
A string object shall consist of a series of zero or more bytes. String objects are not integer objects, but are stored in a more compact format. The length of a string may be subject to implementation limits; see Annex C.
String objects shall be written in one of the following two ways:
• As a sequence of literal characters enclosed in parentheses ( ) (using LEFT PARENTHESIS (28h) and RIGHT PARENThESIS (29h)); see 7.3.4.2, "Literal Strings."
• As hexadecimal data enclosed in angle brackets < > (using LESS-THAN SIGN (3Ch) and GREATER- THAN SIGN (3Eh)); see 7.3.4.3, "Hexadecimal Strings."
*/
export const PDFString = (str) => '(' + str + ')';
5 changes: 5 additions & 0 deletions src/PDFObjects/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export { default as PDFArrayObject } from './PDFArrayObject';
export { default as PDFDictionaryObject } from './PDFDictionaryObject';
export { default as PDFNameObject } from './PDFNameObject';
export { default as PDFIndirectObject } from './PDFIndirectObject';
export { default as PDFStreamObject } from './PDFStreamObject';
Empty file added src/PDFPage.js
Empty file.
11 changes: 11 additions & 0 deletions src/utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export const isInt = (num) => (num % 1 === 0);

export const charCode = (char) => {
if (char.length !== 1) throw new Error('"char" must be exactly one character long');
return char.charCodeAt(0);
}

export const isString = (val) => typeof(val) === 'string';

export const isObject = (val) =>
Object.prototype.toString.call(val) === '[object Object]';
Loading

0 comments on commit fd7459e

Please sign in to comment.